/* ReflectionUtils, helper methods to load classes dynamically * Copyright (C) 2007 Andriy Rysin, Marcin Milkowski, Daniel Naber * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 * USA */ package de.danielnaber.languagetool.tools; import java.io.File; import java.io.IOException; import java.lang.reflect.Modifier; import java.net.JarURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; public final class ReflectionUtils { private ReflectionUtils() { // a static singleton class } /** * @param classLoader * Classloader to use for loading classes * @param packageName * Package name to check classes in * @param classNameRegEx * If not null limit class names to this regexp. This parameter is * checked before class is loaded so use it to improve performance by * skipping loading extra classes * @param subdirLevel * If more than 0 all subdirectories/subpackages up to * dirLevel will be traversed This parameter is checked * before class is loaded - use it to improve performance by skipping * loading extra classes * @param classExtends * If not null return only classes which extend this class * @param interfaceImplements * If not null return only classes which implement this interface * @return Returns all classes inside given package * @throws ClassNotFoundException */ public static Class[] findClasses(final ClassLoader classLoader, final String packageName, final String classNameRegEx, final int subdirLevel, final Class classExtends, final Class interfaceImplements) throws ClassNotFoundException { final Map foundClasses = new HashMap(); try { final String packagePath = packageName.replace('.', '/'); final Enumeration resources_ = classLoader.getResources(packagePath); final Set uniqResources = new HashSet(); while (resources_.hasMoreElements()) { final URI resource = resources_.nextElement().toURI(); uniqResources.add(resource); } for (final URI res : uniqResources) { final URL resource = res.toURL(); // System.err.println("trying resource: " + resource); // jars and directories are treated differently if (resource.getProtocol().startsWith("jar")) { findClassesInJar(packageName, classNameRegEx, subdirLevel, classExtends, interfaceImplements, foundClasses, resource); } else { findClassesInDirectory(classLoader, packageName, classNameRegEx, subdirLevel, classExtends, interfaceImplements, foundClasses, resource); } } } catch (final Exception ex) { throw new ClassNotFoundException("Loading rules failed: " + ex.getMessage(), ex); } return foundClasses.keySet().toArray(new Class[foundClasses.size()]); } private static void findClassesInDirectory(final ClassLoader classLoader, final String packageName, final String classNameRegEx, final int subdirLevel, final Class classExtends, final Class interfaceImplements, final Map foundClasses, final URL resource) throws Exception { final File directory = new File(resource.toURI()); if (!directory.exists() && !directory.isDirectory()) { throw new Exception("directory does not exist: " + directory.getAbsolutePath()); } // read classes for (final File file : directory.listFiles()) { if (file.isFile() && file.getName().endsWith(".class")) { final String classShortNm = file.getName().substring(0, file.getName().lastIndexOf('.')); if (classNameRegEx == null || classShortNm.matches(classNameRegEx)) { final Class clazz = Class.forName(packageName + "." + classShortNm); if (!isMaterial(clazz)) { continue; } if (classExtends == null || isExtending(clazz, classExtends.getName()) && interfaceImplements == null || isImplementing(clazz, interfaceImplements)) { foundClasses.put(clazz, file.getAbsolutePath()); // System.err.println("Added rule from dir: " + classShortNm); } } } } // then subdirectories if we're traversing if (subdirLevel > 0) { for (final File dir : directory.listFiles()) { if (dir.isDirectory()) { final Class[] subLevelClasses = findClasses(classLoader, packageName + "." + dir.getName(), classNameRegEx, subdirLevel - 1, classExtends, interfaceImplements); for (Class tmpClass : subLevelClasses) { foundClasses.put(tmpClass, "dir:" + dir.getAbsolutePath()); } } } } } private static void findClassesInJar(final String packageName, final String classNameRegEx, final int subdirLevel, final Class classExtends, final Class interfaceImplements, final Map foundClasses, final URL resource) throws IOException, URISyntaxException, ClassNotFoundException { final JarURLConnection conn = (JarURLConnection) resource.openConnection(); final JarFile currentFile = conn.getJarFile(); // new JarFile(new // File(resource.toURI())); // jars are flat containers: for (final Enumeration e = currentFile.entries(); e .hasMoreElements();) { final JarEntry current = e.nextElement(); final String name = current.getName(); // System.err.println("jar entry: " + name); if (name.endsWith(".class")) { final String classNm = name.replaceAll("/", ".").replace(".class", ""); final int pointIdx = classNm.lastIndexOf('.'); final String classShortNm = pointIdx == -1 ? classNm : classNm .substring(pointIdx + 1); if (classNm.startsWith(packageName) && (classNameRegEx == null || classShortNm.matches(classNameRegEx))) { final String subName = classNm.substring(packageName.length() + 1); if (countOccurrences(subName, '.') > subdirLevel) { continue; } final Class clazz = Class.forName(classNm); if (foundClasses.containsKey(clazz)) { throw new RuntimeException("Duplicate class definition:\n" + clazz.getName() + ", found in\n" + currentFile.getName() + " and\n" + foundClasses.get(clazz)); } if (!isMaterial(clazz)) { continue; } if (classExtends == null || isExtending(clazz, classExtends.getName()) && interfaceImplements == null || isImplementing(clazz, interfaceImplements)) { foundClasses.put(clazz, currentFile.getName()); // System.err.println("Added class from jar: " + name); } } } } } private static int countOccurrences(final String str, final char ch) { int i = 0; int pos = str.indexOf(ch, 0); while (pos != -1) { i++; pos = str.indexOf(ch, pos + 1); } return i; } private static boolean isMaterial(final Class clazz) { final int mod = clazz.getModifiers(); return !Modifier.isAbstract(mod) && !Modifier.isInterface(mod); } /** * @return Returns true if clazz extends superClassName */ private static boolean isExtending(final Class clazz, final String superClassName) { Class tmpSuperClass = clazz.getSuperclass(); while (tmpSuperClass != null) { if (superClassName.equals(tmpSuperClass.getName())) { return true; } tmpSuperClass = tmpSuperClass.getSuperclass(); } return false; } private static boolean isImplementing(final Class clazz, final Class interfaze) { return Arrays.asList(clazz.getInterfaces()).contains(interfaze); } }