package com.laytonsmith.PureUtilities.ClassLoading; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassReferenceMirror; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.FieldMirror; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.MethodMirror; import com.laytonsmith.PureUtilities.Common.ClassUtils; import com.laytonsmith.PureUtilities.Common.FileUtil; import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.ProgressIterator; import com.laytonsmith.PureUtilities.ZipIterator; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; /** * This class contains methods for dynamically determining things about Classes, * without loading them into PermGen. Search criteria is provided, (most notably * annotations, however also subclasses) and Class/Field/Method mirrors are * returned, eliminating the PermGen requirements, even though all known classes * are scanned against. It is then up to the calling method to actually * determine if the classes need to be loaded, thereby deferring all logic to * actually take up more PermGen space to the calling code, instead of this * class. */ public class ClassDiscovery { /** * The default instance. */ private static ClassDiscovery defaultInstance = null; /** * Returns the default, shared instance. This is usually how you want to * gain a reference to this class, as caching can often times be shared * among multiple tasks, though if you need a private instance, you can use * the constructor to create a new one. Using this instance automatically checks * for the jarInfo.ser file in this jar, and if present, adds it to the cache. * * @return */ public static synchronized ClassDiscovery getDefaultInstance() { if (defaultInstance == null) { defaultInstance = new ClassDiscovery(); //defaultInstance.setDebugMode(true); } return defaultInstance; } /** * Can be used to set the default ClassDiscovery instance returned by * getDefaultInstance. Setting it to null is acceptable, and then a new, * default ClassDiscovery instance will be generated. * * @param cd */ public static void setDefaultInstance(ClassDiscovery cd) { defaultInstance = cd; } /** * Creates a new instance of the ClassDiscovery class. Normally, you should * probably just use the default instance, as caching across the board is a * good thing, however, it may be the case that you need a standalone * instance, in which case, you can create a new one. */ public ClassDiscovery() { // } /** * Stores the mapping of class name to ClassMirror object. At any given * time, after doDiscovery is called, this will be up to date with all known * classes. */ private final Map<URL, Set<ClassMirror<?>>> classCache = new HashMap<>(); /** * This cache maps jvm name to the associated ClassMirror object, to speed * up lookups. */ private final Map<String, ClassMirror<?>> jvmNameToMirror = new HashMap<>(); /** * Maps the fuzzy class name to actual Class object. */ private final Map<String, ClassMirror<?>> fuzzyClassCache = new HashMap<>(); /** * Maps the forName cache. */ private final Map<String, ClassMirror<?>> forNameCache = new HashMap<>(); /** * List of all URLs from which to pull classes. */ private final Set<URL> urlCache = new HashSet<>(); /** * When a URL is added to urlCache, it is also initially added here. If * there are any URLs in this set, they must be resolved first. */ private final Set<URL> dirtyURLs = new HashSet<>(); /** * Cache for class subtypes. Whenever a new URL is added to the URL cache, * this is cleared. */ private final Map<Class<?>, Set<ClassMirror<?>>> classSubtypeCache = new HashMap<>(); /** * Cache for class annotations. Whenever a new URL is added to the URL * cache, this is cleared. */ private final Map<Class<? extends Annotation>, Set<ClassMirror<?>>> classAnnotationCache = new HashMap<>(); /** * Cache for field annotations. Whenever a new URL is added to the URL * cache, this is cleared. */ private final Map<Class<? extends Annotation>, Set<FieldMirror>> fieldAnnotationCache = new HashMap<>(); /** * Cache for method annotations. Whenever a new URL is added to the URL * cache, this is cleared. */ private final Map<Class<? extends Annotation>, Set<MethodMirror>> methodAnnotationCache = new HashMap<>(); /** * By default null, but this can be set per instance. */ private ProgressIterator progressIterator = null; /** * External cache. If added before discovery happens for a URL, this will * cause the discovery process to be skipped entirely for a given URL. */ private final Map<URL, ClassDiscoveryURLCache> preCaches = new HashMap<>(); /** * If true, debug information will be printed out. */ private boolean debug; /** * May be null, but if set, is the cache retriever. */ private ClassDiscoveryCache classDiscoveryCache; /** * Turns debug mode on. If true, data about what is happening is printed out, * as well as timing information. * @param on */ public void setDebugMode(boolean on){ debug = on; } /** * Removes the cache for this URL. After calling this, it is ensured that * the discovery methods won't be pulling from a cache. This is used during * initial cache creation. * * @param url */ public void removePreCache(URL url) { if (url == null) { throw new NullPointerException("url cannot be null"); } preCaches.remove(url); } /** * Adds a pre cache for a given URL. * * @param url * @param cache */ public void addPreCache(URL url, ClassDiscoveryURLCache cache) { if (url == null) { throw new NullPointerException("url cannot be null"); } if(debug){ System.out.println("Adding precache for " + url); } preCaches.put(url, cache); } /** * Sets the class discovery cache. This is optional, but if set * is used to potentially speed up caching. * @param cache */ public void setClassDiscoveryCache(ClassDiscoveryCache cache){ this.classDiscoveryCache = cache; } /** * Sets the progress iterator for when this class starts up. This is an * optional operation. * * @param progressIterator */ public void setProgressIterator(ProgressIterator progressIterator) { this.progressIterator = progressIterator; } /** * Looks through all the URLs and pulls out all known classes, and caches * them in the classCache object. */ private synchronized void doDiscovery() { if (!dirtyURLs.isEmpty()) { Iterator<URL> it = dirtyURLs.iterator(); while (it.hasNext()) { discover(it.next()); it.remove(); } } } /** * Does the class discovery for this particular URL. This should only be * called by doDiscovery. Other internal methods should call doDiscovery, * which handles looking through the dirtyURLs. */ private synchronized void discover(URL rootLocation) { long start = System.currentTimeMillis(); if(debug){ System.out.println("Beginning discovery of " + rootLocation); } try { //If the ClassDiscoveryCache is set, just use this. if(classDiscoveryCache != null){ ClassDiscoveryURLCache cduc = classDiscoveryCache.getURLCache(rootLocation); preCaches.put(rootLocation, cduc); } String url; try { url = URLDecoder.decode(rootLocation.toString(), "UTF8"); } catch (UnsupportedEncodingException ex) { url = URLDecoder.decode(rootLocation.toString()); } if (url == null) { url = GetClassContainer(ClassDiscovery.class).toString(); } final File rootLocationFile; if (!classCache.containsKey(rootLocation)) { classCache.put(rootLocation, Collections.synchronizedSet(new HashSet<ClassMirror<?>>())); } else { classCache.get(rootLocation).clear(); } final Set<ClassMirror<?>> mirrors = classCache.get(rootLocation); if (preCaches.containsKey(rootLocation)) { if(debug){ System.out.println("Precache already contains this URL, so using it"); } //No need, already got a cache for this url mirrors.addAll(preCaches.get(rootLocation).getClasses()); return; } if(debug){ System.out.println("Precache does not contain data for this URL, so scanning now."); } url = url.replaceFirst("^jar:", ""); if (url.endsWith("!/")) { url = StringUtils.replaceLast(url, "!/", ""); } if (url.startsWith("file:") && !url.endsWith(".jar")) { final AtomicInteger id = new AtomicInteger(0); // ExecutorService service = Executors.newFixedThreadPool(10, new ThreadFactory() { // @Override // public Thread newThread(Runnable r) { // return new Thread(r, "ClassDiscovery-Async-" + id.incrementAndGet()); // } // }); //Remove file: from the front String root = url.substring(5); rootLocationFile = new File(root); List<File> fileList = new ArrayList<>(); descend(new File(root), fileList); //Now, we have all the class files in the package. But, it's the absolute path //to all of them. We have to first remove the "front" part for (File f : fileList) { String file = f.toString(); if (!file.matches(".*\\$(?:\\d)*\\.class") && file.endsWith(".class")) { InputStream stream = null; try { stream = FileUtil.readAsStream(new File(rootLocationFile, f.getAbsolutePath().replaceFirst(Pattern.quote(new File(root).getAbsolutePath() + File.separator), ""))); ClassMirror cm = new ClassMirror(stream, new URL(url)); mirrors.add(cm); } catch (IOException ex) { Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex); } finally { if (stream != null) { try { stream.close(); } catch (IOException ex) { Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex); } } } } } // service.shutdown(); // try { // //Doesn't look like 0 is an option, so we'll just wait a day. // service.awaitTermination(1, TimeUnit.DAYS); // } catch (InterruptedException ex) { // Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex); // } } else if (url.startsWith("file:") && url.endsWith(".jar")) { //We are running from a jar url = url.replaceFirst("file:", ""); rootLocationFile = new File(url); ZipIterator zi = new ZipIterator(rootLocationFile); try { zi.iterate(new ZipIterator.ZipIteratorCallback() { @Override public void handle(String filename, InputStream in) { if (!filename.matches(".*\\$(?:\\d)*\\.class") && filename.endsWith(".class")) { try { ClassMirror cm = new ClassMirror(in, rootLocationFile.toURI().toURL()); mirrors.add(cm); } catch (IOException ex) { Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex); } } } }, progressIterator); } catch (IOException ex) { Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex); } } else { throw new RuntimeException("Unknown url type: " + rootLocation); } } finally { if(debug){ System.out.println("Scans finished for " + rootLocation + ", taking " + (System.currentTimeMillis() - start) + " ms."); } } } private ClassLoader defaultClassLoader = null; /** * Sets the default class loader for the various load methods that are * called without a ClassLoader. This is optional, and if not set, the class * loader of this class is used. * * @param cl */ public void setDefaultClassLoader(ClassLoader cl) { defaultClassLoader = cl; } /** * Gets the classloader set with {@link #setDefaultClassLoader(java.lang.ClassLoader)}, or the * builtin default if none was specified ever. Regardless, never returns null. * @return */ public ClassLoader getDefaultClassLoader() { if (defaultClassLoader == null) { return ClassDiscovery.class.getClassLoader(); } else { return defaultClassLoader; } } /** * Adds a new discovery URL. This makes the URL eligible to be included when * finding classes/methods/fields with the various other methods. If the URL * already has been added, this has no effect. * * @param url */ public synchronized void addDiscoveryLocation(URL url) { if (url == null) { throw new NullPointerException("url cannot be null"); } if (urlCache.contains(url)) { //Already here, so just return. return; } urlCache.add(url); dirtyURLs.add(url); classCache.put(url, new HashSet<ClassMirror<?>>()); } /** * Searches one deep, finding all jar files, and adds them, using * addDiscoveryLocation. If folder doesn't exist, is null, * doesn't contain any jars, or otherwise can't be read, nothing happens. * @param folder */ public void addAllJarsInFolder(File folder){ if(folder != null && folder.exists() && folder.isDirectory()){ for(File f : folder.listFiles()){ if(f.getName().endsWith(".jar")){ try { addDiscoveryLocation(f.toURI().toURL()); } catch (MalformedURLException ex) { // } } } } } /** * Remove a discovery URL. Will invalidate caches. * * @param url */ public synchronized void removeDiscoveryLocation(URL url) { if (url == null) { throw new NullPointerException("url cannot be null"); } if (!urlCache.contains(url)) { //Not here, so just return. return; } urlCache.remove(url); dirtyURLs.remove(url); preCaches.remove(url); invalidateCaches(); } /** * Clears the internal caches. This is called automatically when a new * discovery location is added with addDiscoveryLocation, but this should be * called if the caches could have become invalidated since the last load, * as well as if the reference to any of the class loaders that loaded any * classes during the course of using this instance need to be garbage * collected. */ public void invalidateCaches() { classCache.clear(); forNameCache.clear(); jvmNameToMirror.clear(); fuzzyClassCache.clear(); classAnnotationCache.clear(); fieldAnnotationCache.clear(); methodAnnotationCache.clear(); dirtyURLs.addAll(urlCache); } /** * Returns a list of all known classes. The ClassMirror for each class is * returned, and further examination can be done on each class, or loadClass * can be called on the ClassMirror to get the actual Class object. No * ClassLoaders are involved directly in this operation. * * @return A list of ClassMirror objects for all known classes */ public Set<ClassMirror<?>> getKnownClasses() { doDiscovery(); Set<ClassMirror<?>> ret = new HashSet<>(); for (URL url : urlCache) { ret.addAll(getKnownClasses(url)); } return ret; } /** * Gets all known classes, only within this URL. If this url isn't in the * list of discovery locations, it is automatically added, via * {@link #addDiscoveryLocation(java.net.URL)}. * * @param url * @return */ public List<ClassMirror<?>> getKnownClasses(URL url) { if (url == null) { throw new NullPointerException("url cannot be null"); } if (!classCache.containsKey(url)) { addDiscoveryLocation(url); } doDiscovery(); return new ArrayList<>(classCache.get(url)); } /** * Returns a list of known classes that extend the given superclass, or * implement the given interface. * * @param <T> * @param superType * @return */ public <T> Set<ClassMirror<T>> getClassesThatExtend(Class<T> superType) { if (superType == java.lang.Object.class) { //To avoid complication down the road, if this is the case, //just return all known classes here. return (Set) getKnownClasses(); } if (classSubtypeCache.containsKey(superType)) { return new HashSet<>((Set) classSubtypeCache.get(superType)); } doDiscovery(); Set<ClassMirror<?>> mirrors = new HashSet<>(); Set<ClassMirror<T>> knownClasses = (Set) getKnownClasses(); outer: for (ClassMirror m : knownClasses) { if(doesClassExtend(m, superType)){ mirrors.add(m); } } classSubtypeCache.put(superType, mirrors); return (Set) mirrors; } /** * Returns true if subClass extends, implements, or is superClass. * This searches the entire known class ecosystem, including the known ClassMirrors * for this information. * @param subClass * @param superClass * @return */ public boolean doesClassExtend(ClassMirror subClass, Class superClass){ if (subClass.directlyExtendsFrom(superClass)) { //Trivial case, so just add this now, then continue. return true; } //Well, crap, more complicated. Ok, so, the list of supers //can probably be walked up even further, so we need to find //the supers of these (and make sure it's not in the ClassMirror //cache, to avoid loading classes unneccessarily) and then load //the actual Class object for them. Essentially, this falls back //to loading the class when it //can't be found in the mirrors pool. Set<ClassReferenceMirror> supers = new HashSet<>(); //Get the superclass. If it's java.lang.Object, we're done. ClassReferenceMirror su = subClass.getSuperClass(); while (!su.getJVMName().equals("Ljava/lang/Object;")) { supers.add(su); ClassMirror find = getClassMirrorFromJVMName(su.getJVMName()); if (find == null) { try { //Ok, have to Class.forName this one Class clazz = ClassUtils.forCanonicalName(su.toString(), false, defaultClassLoader); //We can just use isAssignableFrom now if (superClass.isAssignableFrom(clazz)) { return true; } else { //We need to add change the reference to su su = new ClassReferenceMirror("L" + clazz.getSuperclass().getName().replace(".", "/") + ";"); } } catch (ClassNotFoundException ex) { //Hmm, ok? I guess something bad happened, so let's break //the loop and give up on this class. return false; } } else { su = find.getSuperClass(); } } for(ClassReferenceMirror r : supers){ // Look through the supers. If any of them equal the search class, return true if(r.getJVMName().equals(ClassUtils.getJVMName(superClass))){ return true; } } //Same thing now, but for interfaces Deque<ClassReferenceMirror> interfaces = new ArrayDeque<>(); Set<ClassReferenceMirror> handled = new HashSet<>(); interfaces.addAll(subClass.getInterfaces()); //Also have to add all the supers' interfaces too for (ClassReferenceMirror r : supers) { ClassMirror find = getClassMirrorFromJVMName(r.getJVMName()); if (find == null) { try { Class clazz = Class.forName(r.toString()); for (Class c : clazz.getInterfaces()) { interfaces.add(new ClassReferenceMirror("L" + c.getName().replace(".", "/") + ";")); } } catch (ClassNotFoundException ex) { return false; } } else { interfaces.addAll(find.getInterfaces()); } } while (!interfaces.isEmpty()) { ClassReferenceMirror in = interfaces.pop(); if(ClassUtils.getJVMName(superClass).equals(in.getJVMName())){ //Early short circuit. We know it's in the the list already. return true; } if (handled.contains(in)) { continue; } handled.add(in); supers.add(in); ClassMirror find = getClassMirrorFromJVMName(in.getJVMName()); if (find != null) { interfaces.addAll(find.getInterfaces()); } else { try { //Again, have to check Class.forName Class clazz = ClassUtils.forCanonicalName(in.toString(), false, getDefaultClassLoader()); if (superClass.isAssignableFrom(clazz)) { return true; } } catch (ClassNotFoundException ex) { return false; } } } //Nope. return false; } /** * Unlike {@link #getClassesThatExtend(java.lang.Class)}, this actually * loads the matching classes into PermGen, and returns a Set of these * classes. This is useful if you are for sure going to use these classes * immediately, and don't want to have to lazy load them individually. * * @param <T> * @param superType * @return */ public <T> Set<Class<T>> loadClassesThatExtend(Class<T> superType) { return loadClassesThatExtend(superType, getDefaultClassLoader(), true); } /** * Unlike {@link #getClassesThatExtend(java.lang.Class)}, this actually * loads the matching classes into PermGen, and returns a Set of these * classes. This is useful if you are for sure going to use these classes * immediately, and don't want to have to lazy load them individually. * * @param <T> * @param superType * @param loader * @param initialize * @return */ public <T> Set<Class<T>> loadClassesThatExtend(Class<T> superType, ClassLoader loader, boolean initialize) { Set<Class<T>> set = new HashSet<>(); for (ClassMirror<T> cm : getClassesThatExtend(superType)) { set.add(cm.loadClass(loader, initialize)); } return set; } private ClassMirror getClassMirrorFromJVMName(String className) { if (jvmNameToMirror.containsKey(className)) { return jvmNameToMirror.get(className); } for (ClassMirror c : getKnownClasses()) { if (c.getJVMClassName().equals(className)) { jvmNameToMirror.put(c.getJVMClassName(), c); return c; } } //Still not found? Return null then. jvmNameToMirror.put(className, null); return null; } /** * Returns a list of classes that have been annotated with the specified * annotation. This will work with annotations that have been declared with * the {@link RetentionPolicy#CLASS} property. * * @param annotation * @return */ public Set<ClassMirror<?>> getClassesWithAnnotation(Class<? extends Annotation> annotation) { if (classAnnotationCache.containsKey(annotation)) { return new HashSet<>(classAnnotationCache.get(annotation)); } doDiscovery(); Set<ClassMirror<?>> mirrors = new HashSet<>(); for (ClassMirror m : getKnownClasses()) { if (m.hasAnnotation(annotation)) { mirrors.add(m); } } classAnnotationCache.put(annotation, mirrors); return mirrors; } /** * Combines finding classes with a specified annotation, and classes that extend a certain type. * @param <T> The type that will be returned, based on superClass * @param annotation The annotation that the classes should be tagged with * @param superClass The super class that the classes should extend * @return A set of class mirrors that match the criteria */ public <T> Set<ClassMirror<T>> getClassesWithAnnotationThatExtend(Class<? extends Annotation> annotation, Class<T> superClass){ Set<ClassMirror<T>> mirrors = new HashSet<>(); for(ClassMirror<?> c : getClassesWithAnnotation(annotation)){ if(doesClassExtend(c, superClass)){ mirrors.add((ClassMirror<T>)c); } } return mirrors; } /** * Unlike {@link #getClassesWithAnnotationThatExtend(java.lang.Class, java.lang.Class)}, this actually * loads the matching classes into PermGen, and returns a Set of these classes. * This is useful if you are for sure going to use these classes immediately, and don't want to have * to lazy load them individually. * @param <T> The type that will be returned, based on superClass * @param annotation The annotation that the classes should be tagged with * @param superClass The super class that the classes should extend * @return A set of classes that match the criteria */ public <T> Set<Class<T>> loadClassesWithAnnotationThatExtend(Class<? extends Annotation> annotation, Class<T> superClass){ return loadClassesWithAnnotationThatExtend(annotation, superClass, getDefaultClassLoader(), true); } /** * Unlike {@link #getClassesWithAnnotationThatExtend(java.lang.Class, java.lang.Class)}, this actually * loads the matching classes into PermGen, and returns a Set of these classes. * This is useful if you are for sure going to use these classes immediately, and don't want to have * to lazy load them individually. * @param <T> The type that will be returned, based on superClass * @param annotation The annotation that the classes should be tagged with * @param superClass The super class that the classes should extend * @param loader * @param initialize * @return A set of classes that match the criteria */ public <T> Set<Class<T>> loadClassesWithAnnotationThatExtend(Class<? extends Annotation> annotation, Class<T> superClass, ClassLoader loader, boolean initialize){ Set<Class<T>> set = new HashSet<>(); for (ClassMirror<T> cm : getClassesWithAnnotationThatExtend(annotation, superClass)) { try { set.add(cm.loadClass(loader, initialize)); } catch (NoClassDefFoundError e) { //Ignore this for now? //throw new Error("While trying to process " + cm.toString() + ", an error occurred.", e); } } return set; } /** * Unlike {@link #getClassesWithAnnotation(java.lang.Class)}, this actually * loads the matching classes into PermGen, and returns a Set of these * classes. This is useful if you are for sure going to use these classes * immediately, and don't want to have to lazy load them individually. * * @param annotation * @return */ public Set<Class> loadClassesWithAnnotation(Class<? extends Annotation> annotation) { return loadClassesWithAnnotation(annotation, getDefaultClassLoader(), true); } /** * Unlike {@link #getClassesWithAnnotation(java.lang.Class)}, this actually * loads the matching classes into PermGen, and returns a Set of these * classes. This is useful if you are for sure going to use these classes * immediately, and don't want to have to lazy load them individually. * * @param annotation * @param loader * @param initialize * @return */ public Set<Class> loadClassesWithAnnotation(Class<? extends Annotation> annotation, ClassLoader loader, boolean initialize) { Set<Class> set = new HashSet<>(); for (ClassMirror<?> cm : getClassesWithAnnotation(annotation)) { try { set.add(cm.loadClass(loader, initialize)); } catch (NoClassDefFoundError e) { //Ignore this for now? //throw new Error("While trying to process " + cm.toString() + ", an error occurred.", e); } } return set; } /** * Returns a list of fields that have been annotated with the specified * annotation. This will work with annotations that have been declared with * the {@link RetentionPolicy#CLASS} property. * * @param annotation * @return */ public Set<FieldMirror> getFieldsWithAnnotation(Class<? extends Annotation> annotation) { if (fieldAnnotationCache.containsKey(annotation)) { return new HashSet<>(fieldAnnotationCache.get(annotation)); } doDiscovery(); Set<FieldMirror> mirrors = new HashSet<>(); for (ClassMirror m : getKnownClasses()) { for (FieldMirror f : m.getFields()) { if (f.hasAnnotation(annotation)) { mirrors.add(f); } } } fieldAnnotationCache.put(annotation, mirrors); return mirrors; } /** * Returns a list of methods that have been annotated with the specified * annotation. This will work with annotations that have been declared with * the {@link RetentionPolicy#CLASS} property. * * @param annotation * @return */ public Set<MethodMirror> getMethodsWithAnnotation(Class<? extends Annotation> annotation) { if (methodAnnotationCache.containsKey(annotation)) { return new HashSet<>(methodAnnotationCache.get(annotation)); } doDiscovery(); Set<MethodMirror> mirrors = new HashSet<>(); for (ClassMirror m : getKnownClasses()) { for (MethodMirror mm : m.getMethods()) { if (mm.hasAnnotation(annotation)) { mirrors.add(mm); } } } methodAnnotationCache.put(annotation, mirrors); return mirrors; } /** * Unlike {@link #getMethodsWithAnnotation(java.lang.Class)}, this actually * loads the matching method's containing classes into PermGen, and returns * a Set of Method objects. This is useful if you are for sure going to use * these methods immediately, and don't want to have to lazy load them * individually. * * @param annotation * @return */ public Set<Method> loadMethodsWithAnnotation(Class<? extends Annotation> annotation) { return loadMethodsWithAnnotation(annotation, getDefaultClassLoader(), true); } /** * Unlike {@link #getMethodsWithAnnotation(java.lang.Class)}, this actually * loads the matching method's containing classes into PermGen, and returns * a Set of Method objects. This is useful if you are for sure going to use * these methods immediately, and don't want to have to lazy load them * individually. * * @param annotation * @param loader * @param initialize * @return */ public Set<Method> loadMethodsWithAnnotation(Class<? extends Annotation> annotation, ClassLoader loader, boolean initialize) { try { Set<Method> set = new HashSet<>(); for (MethodMirror mm : getMethodsWithAnnotation(annotation)) { set.add(mm.loadMethod(loader, initialize)); } return set; } catch (ClassNotFoundException ex) { throw new NoClassDefFoundError(); } } /** * Returns the ClassMirror object for a given class name. Either the JVM * name, or canonical name works. * * @param className * @return * @throws java.lang.ClassNotFoundException */ public ClassMirror forName(String className) throws ClassNotFoundException { if (forNameCache.containsKey(className)) { return forNameCache.get(className); } for (ClassMirror<?> c : getKnownClasses()) { if (c.getClassName().equals(className) || c.getJVMClassName().equals(className)) { forNameCache.put(className, c); return c; } } throw new ClassNotFoundException(className); } /** * Calls forFuzzyName with initialize true, and the class loader used to * load this class. * * @param packageRegex * @param className * @return */ public ClassMirror forFuzzyName(String packageRegex, String className) { return forFuzzyName(packageRegex, className, true, getDefaultClassLoader()); } /** * Returns a class given a "fuzzy" package name, that is, the package name * provided is a regex. The class name must match exactly, but the package * name will be the closest match, or undefined if there is no clear * candidate. If no matches are found, null is returned. * * @param packageRegex * @param className * @param initialize * @param classLoader * @return */ public ClassMirror forFuzzyName(String packageRegex, String className, boolean initialize, ClassLoader classLoader) { String index = packageRegex + className; if (fuzzyClassCache.containsKey(index)) { return fuzzyClassCache.get(index); } Set<ClassMirror> found = new HashSet<>(); Set<ClassMirror<?>> searchSpace = getKnownClasses(); for (ClassMirror c : searchSpace) { if (c.getPackage().getName().matches(packageRegex) && c.getSimpleName().equals(className)) { found.add(c); } } ClassMirror find; if (found.size() == 1) { find = found.iterator().next(); } else if (found.isEmpty()) { find = null; } else { ClassMirror candidate = null; int max = Integer.MAX_VALUE; for (ClassMirror f : found) { int distance = StringUtils.LevenshteinDistance(f.getPackage().getName(), packageRegex); if (distance < max) { candidate = f; max = distance; } } find = candidate; } fuzzyClassCache.put(index, find); return find; } private static void descend(File start, List<File> fileList) { if (start.isFile()) { if (start.getName().endsWith(".class")) { fileList.add(start); } } else { File [] list = start.listFiles(); if(list == null){ System.out.println("Could not list files in " + start); return; } for (File child : start.listFiles()) { descend(child, fileList); } } } /** * Returns the container url for this class. This varies based on whether or * not the class files are in a zip/jar or not, so this method standardizes * that. The method may return null, if the class is a dynamically generated * class (perhaps with asm, or a proxy class) * * @param c * @return */ public static URL GetClassContainer(Class c) { if (c == null) { throw new NullPointerException("The Class passed to this method may not be null"); } while (c.isMemberClass() || c.isAnonymousClass()) { c = c.getEnclosingClass(); //Get the actual enclosing file } if (c.getProtectionDomain().getCodeSource() == null) { //This is a proxy or other dynamically generated class, and has no physical container, //so just return null. return null; } String packageRoot = null; //This is the full path to THIS file, but we need to get the package root below. String thisClass = c.getResource(c.getSimpleName() + ".class").toString(); try { try { packageRoot = StringUtils.replaceLast(thisClass, Pattern.quote(c.getName().replaceAll("\\.", "/") + ".class"), ""); } catch (Exception e) { //Hmm, ok, try this then packageRoot = c.getProtectionDomain().getCodeSource().getLocation().toString(); } packageRoot = URLDecoder.decode(packageRoot, "UTF-8"); if (packageRoot.matches("jar:file:.*!/")) { packageRoot = StringUtils.replaceLast(packageRoot, "!/", ""); packageRoot = packageRoot.replaceFirst("jar:", ""); } return new URL(packageRoot); } catch (UnsupportedEncodingException e) { throw new RuntimeException("While interrogating " + c.getName() + ", an unexpected exception was thrown.", e); } catch (MalformedURLException e) { throw new RuntimeException("While interrogating " + c.getName() + ", an unexpected exception was thrown for potential URL: \"" + packageRoot + "\"", e); } } }