package net.sourceforge.javautil.classloader.source;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import net.sourceforge.javautil.classloader.impl.ClassInfo;
import net.sourceforge.javautil.classloader.impl.ClassSearchInfo;
import net.sourceforge.javautil.classloader.impl.PackageSearchInfo;
import net.sourceforge.javautil.common.Refreshable;
import net.sourceforge.javautil.common.io.IVirtualArtifact;
/**
* A composite of class sources so they function and can be passed around as one.
*
* @author ponder
* @author $Author: ponderator $
* @version $Id: CompositeClassSource.java 2472 2010-10-24 11:49:51Z ponderator $
*/
public class CompositeClassSource extends ClassSource implements Refreshable {
private boolean searchedAll = false;
private boolean sourceAdded = false;
protected final List<ClassSource> sources = new CopyOnWriteArrayList<ClassSource>();
private Map<String, CompositePackageSearchInfo> pkgMap = new HashMap<String, CompositePackageSearchInfo>();
public CompositeClassSource() { this("Composite Source"); }
public CompositeClassSource(String name) { super(name); }
public CompositeClassSource(ClassSource... sources) { this(); this.add(sources); }
public CompositeClassSource(List<ClassSource> srcs) { this(); this.add(srcs); }
public void initialize (ClassLoader scl) {
int size = sources.size();
for (int s=0; s<size; s++) sources.get(s).initialize(scl);
}
/**
* @param <T> The type of class source
* @param type The type of class sources found in this composite that should be returned
* @return A collection of class sources of the type specified (may be empty, never null)
*/
public <T extends ClassSource> List<T> getClassSources (Class<T> type) {
List<T> sources = new ArrayList<T>();
Iterator<ClassSource> list = this.sources.iterator();
while (list.hasNext()) {
ClassSource src = list.next();
if (type.isAssignableFrom(src.getClass())) sources.add( (T) src );
}
return sources;
}
/**
* Add a collection of class sources to this composite.
*
* @param srcs The sources to add
*
* @see #add(ClassSource...)
*/
public void add(List<ClassSource> srcs) {
this.sources.addAll( srcs );
this.sourceAdded = true;
this.searchedAll = false;
this.pkgMap.clear();
}
@Override public IVirtualArtifact getVirtualArtifact() { return null; }
/**
* One or more class sources to add to this composite. This
* will reset the {@link #searchedAll} flag.
*
* @param sources The sources to add
*/
public void add(ClassSource... sources) {
this.add(Arrays.asList(sources));
}
/**
* Remove a previously {@link #add(ClassSource...)}'ed class source.
* This will also remove any package information related to the class
* source.
*
* @param src The source to remove
*/
public void remove(ClassSource src) {
sources.remove(src);
Iterator<String> names = pkgMap.keySet().iterator();
while (names.hasNext()) {
String key = names.next();
List<ClassSource> srclist = pkgMap.get(key).sources;
if (srclist != null && srclist.contains(src)) {
srclist.remove(src);
if (srclist.size() == 0) pkgMap.put(key, null);
}
}
}
/**
* @return The collection of class sources that currently make up this composite
*/
public List<ClassSource> getSources() {
return sources;
}
/**
* @return Return all the class sources (including the children of children composite's) that are not {@link CompositeClassSource}'s.
*/
public List<ClassSource> getAllNonCompositeSources () {
List<ClassSource> sources = new ArrayList<ClassSource>();
for (int s=0; s<this.sources.size(); s++) {
ClassSource src = this.sources.get(s);
if (src instanceof CompositeClassSource) sources.addAll( ((CompositeClassSource)src).getAllNonCompositeSources() );
else sources.add(src);
}
return sources;
}
@Override public ClassInfo getClassInfo(ClassSearchInfo info) throws ClassNotFoundException {
String path = info.getClassPath();
List<ClassSource> srcs = this.checkForPackage(info);
if (srcs != null) {
int size = srcs.size();
for (int s=0; s<size; s++) {
ClassSource src = srcs.get(s);
if (src.hasResource(path)) return src.getClassInfo(info);
}
}
throw new ClassNotFoundException(info.getFullClassName());
}
@Override public long getLastModified() {
long lastmodified = -1;
for (int s=0; s<this.sources.size(); s++) {
ClassSource src = this.sources.get(s);
if (src.getLastModified() > lastmodified) lastmodified = src.getLastModified();
}
return lastmodified;
}
@Override public long getLastModifiedClass() {
long lastmodified = -1;
for (int s=0; s<this.sources.size(); s++) {
ClassSource src = this.sources.get(s);
long lmc = src.getLastModifiedClass();
if (lmc > lastmodified) lastmodified = lmc;
}
return lastmodified;
}
@Override public boolean hasClass(ClassSearchInfo info) {
List<ClassSource> srcs = this.checkForPackage(info);
for (int s=0; s<srcs.size(); s++)
if (srcs.get(s).hasClass(info)) return true;
return false;
}
@Override public synchronized boolean hasPackage(PackageSearchInfo info) {
return this.checkForPackage(info) != null;
}
@Override public boolean hasParentPackage(String packageName) {
Iterator<String> names = pkgMap.keySet().iterator();
while (names.hasNext())
if (names.next().startsWith(packageName)) return true;
return false;
}
@Override public Collection<String> getPackages() {
if (!this.searchedAll) {
for (int s=0; s<sources.size(); s++) {
ClassSource src = sources.get(s);
Iterator<String> pkgs = src.getPackages().iterator();
while (pkgs.hasNext()) {
String name = pkgs.next();
if (!pkgMap.containsKey(name)) pkgMap.put(name, new CompositePackageSearchInfo());
CompositePackageSearchInfo info = pkgMap.get(name);
if (info != null) {
info.searchedFormally = true;
if (info.sources == null) info.sources = new ArrayList<ClassSource>();
if (!info.sources.contains(src)) info.sources.add(src);
}
}
}
}
return pkgMap.keySet();
}
@Override public URL getResource(String resourceName) {
int size = sources.size();
for (int s=0; s<size; s++) {
ClassSource src = sources.get(s);
if (src.hasResource(resourceName)) return src.getResource(resourceName);
}
return null;
}
/**
* @param resourceName The resource name to look for in all the sub-sources in this composite
* @return A list, possibly empty, of resources found that match the name
* @throws MalformedURLException
*/
public List<URL> getResources (String resourceName) throws MalformedURLException {
List<URL> urls = new ArrayList<URL>();
int size = sources.size();
for (int s=0; s<size; s++) {
ClassSource src = sources.get(s);
if (src instanceof CompositeClassSource) urls.addAll( ((CompositeClassSource)src).getResources(resourceName) );
else if (src.hasResource(resourceName)) urls.add( src.getResource(resourceName) );
}
return urls;
}
@Override public InputStream getResourceAsStream(String resourceName) {
for (int s=0; s<sources.size(); s++) {
if (sources.get(s).hasResource(resourceName)) return sources.get(s).getResourceAsStream(resourceName);
}
return null;
}
@Override public boolean hasResource(String resourceName) {
int size = sources.size();
for (int s=0; s<size; s++)
if (sources.get(s).hasResource(resourceName)) return true;
return false;
}
@Override public byte[] loadInternal(ClassSearchInfo info) throws ClassNotFoundException {
List<ClassSource> possible = this.checkForPackage(info);
if (possible != null) for (int p=0; p<possible.size(); p++) {
ClassSource src = possible.get(p);
if (src.hasResource(info.getClassPath())) return src.loadInternal(info);
}
throw new ClassNotFoundException(info.getFullClassName());
}
@Override public boolean hasSearchedAll() {
if (searchedAll) return true;
int size = sources.size();
for (int s=0; s<size; s++)
if (!sources.get(s).hasSearchedAll()) return false;
return searchedAll = true;
}
public void refresh () {
this.searchedAll = false;
this.pkgMap.clear();
int size = sources.size();
for (int s=0; s<size; s++) {
ClassSource src = sources.get(s);
if (src instanceof Refreshable) ((Refreshable)src).refresh();
}
}
@Override public void cleanup() {
int size = sources.size();
for (int s=0; s<size; s++) sources.get(s).cleanup();
}
@Override public URL getURL() { return null; }
public List<URL> getUrls () {
List<URL> urls = new ArrayList<URL>();
int size = sources.size();
for (int s=0; s<size; s++) {
ClassSource src = sources.get(s);
if (src instanceof CompositeClassSource) urls.addAll( ((CompositeClassSource)src).getUrls() );
else urls.add(src.getURL());
}
return urls;
}
@Override public Collection<String> getResourceNames() {
List<String> names = new ArrayList<String>();
int size = sources.size();
for (int s=0; s<size; s++)
names.addAll( sources.get(s).getResourceNames() );
return names;
}
@Override public Collection<String> getClassNames() {
List<String> names = new ArrayList<String>();
int size = sources.size();
for (int s=0; s<size; s++)
names.addAll( sources.get(s).getClassNames() );
return names;
}
@Override public boolean hasDirectories() {
int size = sources.size();
for (int s=0; s<size; s++) if (sources.get(s).hasDirectories()) return true;
return false;
}
@Override public List<String> getClassNamesForPackage(PackageSearchInfo info) {
List<String> classNames = new ArrayList<String>();
List<ClassSource> srcs = this.getPackageSources(info);
int size = srcs.size();
for (int s=0; s<size; s++)
classNames.addAll(srcs.get(s).getClassNamesForPackage(info));
return classNames;
}
/**
* @param info Package search info
* @return All the class sources in this composite that have contain the package provided
*/
public List<ClassSource> getPackageSources (PackageSearchInfo info) {
this.checkForPackage(info);
Set<ClassSource> srcs = new LinkedHashSet<ClassSource>();
List<ClassSource> psrc = pkgMap.get(info.getPackageName()).sources;
if (psrc != null) {
int size = psrc.size();
for (int s=0; s<size; s++) {
ClassSource src = psrc.get(s);
if (src instanceof CompositeClassSource) srcs.addAll( ((CompositeClassSource)src).getPackageSources(info) );
else srcs.add(src);
}
}
return new ArrayList<ClassSource>(srcs);
}
@Override public boolean isHasBeenModified() {
int size = sources.size();
for (int s=0; s<size; s++)
if (sources.get(s).isHasBeenModified()) { return true; }
return false;
}
@Override public boolean isHasClassesBeenModified() {
int size = sources.size();
for (int s=0; s<size; s++)
if (sources.get(s).isHasClassesBeenModified()) { return true; }
return false;
}
@Override public ClassSource clone() throws CloneNotSupportedException {
List<ClassSource> sources = new ArrayList<ClassSource>();
for (ClassSource source : this.sources) {
sources.add( source.clone() );
}
return new CompositeClassSource(sources);
}
@Override public synchronized void reload() {
int size = sources.size();
for (int s=0; s<size; s++) {
ClassSource src = sources.get(s);
if (src.isHasBeenModified()) {
this.removeFromPackageInfo(src);
src.reload();
}
}
this.searchedAll = false;
}
/**
* @param source The source to look for
* @return True if any source or sub source in this composite returns true when calling {@link #equals(Object)} on it for the source passed, otherwise false
*/
public boolean contains (ClassSource source) {
for (ClassSource src : this.sources) {
if (src instanceof CompositeClassSource) {
if (((CompositeClassSource)src).contains(source)) return true;
} else {
if (source.equals( src )) return true;
}
}
return false;
}
/**
* Facility method, remove all cached package information related
* to a particular class source.
*
* @param src The source for which package information should be removed
*/
private void removeFromPackageInfo (ClassSource src) {
List<String> pkgs = new ArrayList<String>(this.pkgMap.keySet());
int size = pkgs.size();
for (int k=0; k<size; k++) {
CompositePackageSearchInfo info = pkgMap.get(pkgs.get(k));
if (info != null && info.sources != null) {
info.sources.remove(src);
info.searchedFormally = false;
}
}
}
@Override public boolean hasParentResource(String parentResource) {
int size = sources.size();
for (int s=0; s<size; s++)
if (sources.get(s).hasParentResource(parentResource)) return true;
return false;
}
@Override public ClassInfo getIfHasClass(ClassSearchInfo info) throws ClassNotFoundException {
List<ClassSource> srcs = this.getPackageSources(info);
int size = srcs.size();
for (int i=0; i<size; i++) {
ClassSource src = srcs.get(i);
ClassInfo cinfo = src.getIfHasClass(info);
if (cinfo != null) return cinfo;
}
return null;
}
/**
* This will perform progressive scans (on each invocation) in relation to direct package
* requests, only searching what is necessary till finding what is requested and registering
* what is found on the way, thus striking a balance between doing a full pre-scan (which could
* take a long time) and caching what is found while searching for other things.
*
* @param pinfo The package search information
* @return A list of class sources that contain the package provided
*/
protected List<ClassSource> checkForPackage (PackageSearchInfo pinfo) {
CompositePackageSearchInfo info = pkgMap.get(pinfo.getPackageName());
if (info == null && "".equals(pinfo.getPackageName())) {
pkgMap.put("", info = new CompositePackageSearchInfo());
info.searchedFormally = true;
info.sources = new ArrayList<ClassSource>();
info.sources.addAll(sources);
} else if (info == null || (info != null && !info.searchedFormally)) {
String path = pinfo.getPackagePath();
pkgMap.put(pinfo.getPackageName(), info = new CompositePackageSearchInfo());
info.searchedFormally = true;
int size = sources.size();
for (int s=0; s<size; s++) {
ClassSource src = sources.get(s);
if (src.hasParentResource(path)) {
if (info.sources == null) info.sources = new ArrayList<ClassSource>();
if (!info.sources.contains(src)) info.sources.add(src);
}
}
if (info.sources != null) {
for (int p=0; p<pinfo.getPackages().size(); p++) {
String pkg = pinfo.getPackages().get(p);
CompositePackageSearchInfo ppinfo = pkgMap.get(pkg);
if (ppinfo == null) {
pkgMap.put(pkg, ppinfo = new CompositePackageSearchInfo());
ppinfo.sources = new ArrayList<ClassSource>();
}
if (ppinfo.sources == null) ppinfo.sources = new ArrayList<ClassSource>();
ppinfo.sources.addAll(info.sources);
}
}
}
return pkgMap.get(pinfo.getPackageName()).sources;
}
/**
* A composite specifically for holding state related to packages and
* a collection of class sources.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: CompositeClassSource.java 2472 2010-10-24 11:49:51Z ponderator $
*/
private class CompositePackageSearchInfo {
/**
* Whether this package has been searched formally, due to a direct
* request involving the specific package, not parent packages in an
* unrelated request.
*/
boolean searchedFormally = false;
/**
* The class sources that have been previously related to this package after a scan.
*/
List<ClassSource> sources = null;
}
}