package org.commons.jconfig.internal; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.apache.log4j.Logger; import org.commons.jconfig.config.ConfigRuntimeException; public class ScanClassPath<T extends Annotation> { private final Class<T> annoClazz; /** * package excludeFilter to filter common classes, like jdk and apache jars. this * dramatically reduces look up time. */ private final Set<String> excludeFilter; private final Set<String> allowFilter = new HashSet<String>(); /** * Set of all the resource paths which will be scanned. This set is build as * and when paths are scanned and will block scanning the same path again * thereby avoiding recursion. */ private final Set<String> dirLookupSet = new HashSet<String>(); private ScanClassPath(final Class<T> annoClazz) { this.annoClazz = annoClazz; String[] packageFilter = { "java.", "javax.", "org.ietf.jgss", "org.omg.", "org.w3c.dom.", "org.xml.sax.", "sun.tools.", "sun.jvmstat.", "com.sun.", "org.junit.", "org.testng.", "bsh.", "org.relaxng.", "mockit.", "com.beust.", "org.apache.log4j." }; excludeFilter = new HashSet<String>(Arrays.asList(packageFilter)); } public ScanClassPath(final Class<T> annoClazz, List<String> allowFilter) { this(annoClazz); if(allowFilter == null) { throw new IllegalArgumentException("Allow filter cannot be null"); } this.allowFilter.addAll(allowFilter); } private boolean isAllowed(final String clazzName) { // use allow filters if (!allowFilter.isEmpty()) { for (String allowPrefix : allowFilter) { if (clazzName.startsWith(allowPrefix)) { return true; } } return false; } // if allowfilters are empty use default exclude filters String[] tokens = clazzName.split("\\."); String path = ""; for (String token : tokens) { path += token + "."; if (excludeFilter.contains(path)) { return false; } } return true; } private final Logger logger = Logger.getLogger(this.getClass()); /** * Helper class to filter all the files in a directory with a particular * extension */ private class FileListFilter implements FilenameFilter { private final String name; private final String extension; public FileListFilter(final String name, final String extension) { this.name = name; this.extension = extension; } @Override public boolean accept(final File directory, final String filename) { boolean fileOK = true; if (name != null) { fileOK &= filename.startsWith(name); } if (extension != null) { fileOK &= filename.endsWith('.' + extension); } return fileOK; } } /** * Return all the classes in the jars in a given path * * @param path * @return Set of classes in given jar * @throws IOException * @throws ClassNotFoundException */ public Set<Class<?>> scanJarAnnotatedClasses(final JarFile jar) throws IOException { Set<Class<?>> clazzez = new HashSet<Class<?>>(); Enumeration<JarEntry> it = jar.entries(); while (it.hasMoreElements()) { JarEntry jarEntry = it.nextElement(); if (jarEntry.getName().endsWith(".class")) { String className = jarEntry.getName().replaceAll("/", "\\."); Class<?> clazz = applyFilter(className.substring(0, className.length() - 6)); if (clazz != null) { clazzez.add(clazz); } } } return clazzez; } /** * Returns all the classes in the classpath that are annotated. */ public Set<Class<?>> scanAnnotatedClasses() { Set<Class<?>> clazzez = new HashSet<Class<?>>(); clazzez.addAll(scanPackagesAnnotatedClasses()); clazzez.addAll(scanURLClassLoaderAnnotatedClasses()); return clazzez; } private Set<Class<?>> scanPackagesAnnotatedClasses() { Set<Class<?>> clazzez = new HashSet<Class<?>>(); Package[] packages = Package.getPackages(); for (Package lPackage : packages) { try { clazzez.addAll(scanPackageAnnotatedClasses(lPackage)); } catch (IOException e) { logger.warn("ScanConfigClasses failed for package: " + lPackage.getName(), e); } } return clazzez; } private Set<Class<?>> scanURLClassLoaderAnnotatedClasses() { final ClassLoader classLoader = this.getClass().getClassLoader(); if (!(classLoader instanceof URLClassLoader)) { throw new IllegalArgumentException("Classloader is not a URL classloader"); } final URLClassLoader urlClassLoader = (URLClassLoader) classLoader; URL[] urls = urlClassLoader.getURLs(); Set<Class<?>> clazzez = new HashSet<Class<?>>(); for (URL url : urls) { try { clazzez.addAll(scanURLAnnotatedClasses(url, "")); } catch (IOException e) { // Ignore IOException and continue to iterate thru the array // elements. } } return clazzez; } private Class<?> applyFilter(final String clazzName) { if (isAllowed(clazzName)) { try { Class<?> clazz = Class.forName(clazzName, false, this.getClass().getClassLoader()); T annotation = clazz.getAnnotation(annoClazz); if (annotation != null) { return clazz; } } catch (ClassNotFoundException e) { logger.trace("Unable to search classes for annotations: " + clazzName); // Ignore class not found } catch (NoClassDefFoundError e) { logger.trace("Unable to search classes for annotations: " + clazzName); // Ignore class not found } catch (UnsatisfiedLinkError e) { logger.trace("Unable to search classes for annotations: " + clazzName); // Ignore classes that required jni libraries that are not // present to be loaded } catch (UnsupportedClassVersionError e) { logger.trace("Unable to search classes for annotations: " + clazzName); // Ignore unsupported classes } } return null; } /** * Returns all classes with the annotation which belong to the given package * and sub-packages. * * @param package The base package * @return The classes * @throws IOException */ public Set<Class<?>> scanPackageAnnotatedClasses(final Package pPackage) throws IOException { String path = pPackage.getName().replace('.', '/'); Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(path); Set<Class<?>> clazzes = new HashSet<Class<?>>(); while (resources.hasMoreElements()) { clazzes.addAll(scanURLAnnotatedClasses(resources.nextElement(), pPackage.getName())); } return clazzes; } /** * Helper function to recursively traverse the directory to find annotated * classes * * @param directory * @param packageName * @return * @throws IOException * @throws Exception */ private Set<Class<?>> scanURLAnnotatedClasses(final URL directoryUrl, final String packageName) throws IOException { Set<Class<?>> clazzez = new HashSet<Class<?>>(); String directoryName = directoryUrl.toExternalForm(); if (directoryName.startsWith("jar:file:") && directoryName.contains("!")) { String[] split = directoryName.split("!"); split = split[0].split(":"); clazzez.addAll(scanJarAnnotatedClasses(new JarFile(split[2]))); return clazzez; } else if (directoryName.startsWith("file:")) { directoryName = directoryName.substring(5); File directory = new File(directoryName); if (!directory.exists()) { return clazzez; } if(directory.isFile() && directory.getPath().endsWith(".jar")) { clazzez.addAll(scanJarAnnotatedClasses(directory)); return clazzez; } File[] files = directory.listFiles(); if (files == null) { return clazzez; } for (File file : files) { if (file.isDirectory()) { if (!dirLookupSet.contains(file.getCanonicalPath())) { dirLookupSet.add(file.getCanonicalPath()); String prefix = packageName + "."; if (packageName.isEmpty()) { prefix = ""; } clazzez.addAll(scanURLAnnotatedClasses(new URL("file:" + file.getAbsolutePath()), prefix + file.getName())); } } else if (file.getName().endsWith(".class")) { String className = packageName + '.' + file.getName(); Class<?> clazz = applyFilter(className.substring(0, className.length() - 6)); if (clazz != null) { clazzez.add(clazz); } } } return clazzez; } else { logger.error("code should never reach here"); return clazzez; } } /** * Return all the classes with annotation in the given path * * @param dir * path to a given directory * @return */ public Set<Class<?>> scanDirAnnotatedClasses(final File dir) { Set<Class<?>> clazzez = new HashSet<Class<?>>(); FilenameFilter filter = new FileListFilter("", "class"); if (dir.isDirectory()) { for (String file : dir.list(filter)) { Class<?> clazz = applyFilter(file.substring(0, file.length() - 6)); if (clazz != null) { clazzez.add(clazz); } } } return clazzez; } private void addURL(final URL url) { final ClassLoader classLoader = this.getClass().getClassLoader(); if (!(classLoader instanceof URLClassLoader)) { throw new IllegalArgumentException("Classloader is not a URL classloader, failed to add '" + url + "'"); } final URLClassLoader urlClassLoader = (URLClassLoader) classLoader; List<URL> urls = Arrays.asList(urlClassLoader.getURLs()); if (!urls.contains(url)) { try { logger.debug("Adding URL to classpath " + url); Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class }); addURL.setAccessible(true); addURL.invoke(urlClassLoader, new Object[] { url }); } catch (SecurityException e) { throw new ConfigRuntimeException("Classloader failed to load URL '" + url + "'", e); } catch (NoSuchMethodException e) { throw new ConfigRuntimeException("Classloader failed to load URL '" + url + "'", e); } catch (IllegalArgumentException e) { throw new ConfigRuntimeException("Classloader failed to load URL '" + url + "'", e); } catch (IllegalAccessException e) { throw new ConfigRuntimeException("Classloader failed to load URL '" + url + "'", e); } catch (InvocationTargetException e) { throw new ConfigRuntimeException("Classloader failed to load URL '" + url + "'", e); } } } public void addFileToClassPath(final String absolutePath) { String urlPath = "jar:file://" + absolutePath + "!/"; try { addURL(new URL(urlPath)); } catch (MalformedURLException e) { throw new ConfigRuntimeException("Classloader failed to load URL '" + urlPath + "'", e); } } public void addFileToClassPath(final File path) { addFileToClassPath(path.getAbsolutePath()); } public void addAllFilesToClassPath(final File[] paths) { for (File path : paths) { addFileToClassPath(path); } } public Set<Class<?>> scanJarAnnotatedClasses(final File[] paths) { Set<Class<?>> clazzes = new HashSet<Class<?>>(); for (File path : paths) { try { clazzes.addAll(scanJarAnnotatedClasses(path)); } catch (IOException e) { // Ignore IO exception and continue to loop on the rest of the // elements of the array } } return clazzes; } public Set<Class<?>> scanJarAnnotatedClasses(final File path) throws IOException { return scanJarAnnotatedClasses(new JarFile(path)); } public Set<String> scanPackageToStringSet(String packageName) throws IOException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Set<String> names = new HashSet<String>(); // i "." vanno sostitutiti con "/" packageName = packageName.replace(".", File.separator) + File.separator; URL packageURL = classLoader.getResource(packageName); if (packageURL.getProtocol().equals("jar")) { String jarFileName = packageURL.getFile(); jarFileName = jarFileName.substring(5, jarFileName.indexOf("!")); JarFile jf = new JarFile(jarFileName); Enumeration<JarEntry> jarEntries = jf.entries(); while (jarEntries.hasMoreElements()) { String entryName = jarEntries.nextElement().getName(); if (entryName.startsWith(packageName) && entryName.length() > packageName.length() + 5) { entryName = entryName.substring(packageName.length(), entryName.lastIndexOf('.')); names.add(entryName); } } } else { File folder = new File(packageURL.getFile()); File[] files = folder.listFiles(); for (File actual : files) { String entryName = actual.getName(); entryName = entryName.substring(0, entryName.lastIndexOf('.')); names.add(entryName); } } return names; } public Set<File> scanPackageToFileSet(final String packageName) throws IOException { Set<File> clazzez = new HashSet<File>(); Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageName); while (urls.hasMoreElements()) { URL url = urls.nextElement(); File dir = new File(url.getFile()); clazzez.addAll(Arrays.asList(dir.listFiles())); } return clazzez; } }