| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.any23.util; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.net.URL; |
| import java.net.URLDecoder; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| |
| /** |
| * This class provides utility methods |
| * for discovering classes in packages. |
| * |
| * @author Michele Mostarda (mostarda@fbk.eu) |
| */ |
| public class DiscoveryUtils { |
| |
| private static final String FILE_PREFIX = "file:"; |
| private static final String CLASS_SUFFIX = ".class"; |
| |
| |
| /** |
| * Scans all classes accessible from the context class loader |
| * which belong to the given package and sub-packages. |
| * |
| * @param packageName the root package. |
| * @return list of matching classes. |
| */ |
| public static List<Class> getClassesInPackage(String packageName) { |
| final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); |
| assert classLoader != null; |
| final String path = packageName.replace('.', '/'); |
| final Enumeration<URL> resources; |
| try { |
| resources = classLoader.getResources(path); |
| } catch (IOException ioe) { |
| throw new IllegalStateException("Error while retrieving internal resource path.", ioe); |
| } |
| final List<File> dirs = new ArrayList<File>(); |
| while (resources.hasMoreElements()) { |
| final URL resource = resources.nextElement(); |
| final String fileName = resource.getFile(); |
| final String fileNameDecoded; |
| try { |
| fileNameDecoded = URLDecoder.decode(fileName, "UTF-8"); |
| } catch (UnsupportedEncodingException uee) { |
| throw new IllegalStateException("Error while decoding class file name.", uee); |
| } |
| dirs.add( new File(fileNameDecoded) ); |
| } |
| @SuppressWarnings("rawtypes") |
| final ArrayList<Class> classes = new ArrayList<Class>(); |
| for (File directory : dirs) { |
| classes.addAll(findClasses(directory, packageName) ); |
| } |
| return classes; |
| } |
| |
| /** |
| * Scans all classes accessible from the context class loader |
| * which belong to the given package and sub-packages and filter |
| * them by ones implementing the specified interface <code>iface</code>. |
| * |
| * @param packageName the root package. |
| * @param filter the interface/class filter. |
| * @return list of matching classes. |
| */ |
| public static List<Class> getClassesInPackage(String packageName, Class<?> filter) { |
| final List<Class> classesInPackage = getClassesInPackage(packageName); |
| @SuppressWarnings("rawtypes") |
| final List<Class> result = new ArrayList<Class>(); |
| Class<?> superClazz; |
| for(Class<?> clazz : classesInPackage) { |
| if(clazz.equals(filter)) { |
| continue; |
| } |
| superClazz = clazz.getSuperclass(); |
| if( ( superClazz != null && superClazz.equals(filter) ) || contains(clazz.getInterfaces(), filter) ) { |
| result.add(clazz); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Find all classes within the specified location by package name. |
| * |
| * @param location class location. |
| * @param packageName package name. |
| * @return list of detected classes. |
| */ |
| private static List<Class> findClasses(File location, String packageName) { |
| final String locationPath = location.getPath(); |
| if( locationPath.indexOf(FILE_PREFIX) == 0 ) { |
| return findClassesInJAR(locationPath); |
| } |
| return findClassesInDir(location, packageName); |
| } |
| |
| /** |
| * Find all classes within a JAR in a given prefix addressed with syntax |
| * <code>file:<path/to.jar>!<path/to/package>. |
| * |
| * @param location package location. |
| * @return list of detected classes. |
| */ |
| private static List<Class> findClassesInJAR(String location) { |
| final String[] sections = location.split("!"); |
| if(sections.length != 2) { |
| throw new IllegalArgumentException("Invalid JAR location."); |
| } |
| final String jarLocation = sections[0].substring(FILE_PREFIX.length()); |
| final String packagePath = sections[1].substring(1); |
| |
| try { |
| @SuppressWarnings("resource") |
| final JarFile jarFile = new JarFile(jarLocation); |
| final Enumeration<JarEntry> entries = jarFile.entries(); |
| @SuppressWarnings("rawtypes") |
| final List<Class> result = new ArrayList<Class>(); |
| JarEntry current; |
| String entryName; |
| String clazzName; |
| Class<?> clazz; |
| while(entries.hasMoreElements()) { |
| current = entries.nextElement(); |
| entryName = current.getName(); |
| if( |
| StringUtils.isPrefix(packagePath, entryName) |
| && |
| StringUtils.isSuffix(CLASS_SUFFIX, entryName) |
| && |
| ! entryName.contains("$") |
| ) { |
| try { |
| clazzName = entryName.substring( |
| 0, entryName.length() - CLASS_SUFFIX.length() |
| ).replaceAll("/","."); |
| clazz = Class.forName(clazzName); |
| } catch (ClassNotFoundException cnfe) { |
| throw new IllegalStateException("Error while loading detected class.", cnfe); |
| } |
| result.add(clazz); |
| } |
| } |
| return result; |
| } catch (IOException ioe) { |
| throw new RuntimeException("Error while opening JAR file.", ioe); |
| } |
| } |
| |
| /** |
| * Recursive method used to find all classes in a given directory and sub-dirs. |
| * |
| * @param directory The base directory |
| * @param packageName The package name for classes found inside the base directory |
| * @return The classes |
| */ |
| private static List<Class> findClassesInDir(File directory, String packageName) { |
| if (!directory.exists()) { |
| return Collections.emptyList(); |
| } |
| @SuppressWarnings("rawtypes") |
| final List<Class> classes = new ArrayList<Class>(); |
| File[] files = directory.listFiles(); |
| for (File file : files) { |
| String fileName = file.getName(); |
| if (file.isDirectory()) { |
| assert !fileName.contains("."); |
| classes.addAll(findClassesInDir(file, packageName + "." + fileName)); |
| } else if (fileName.endsWith(".class") && !fileName.contains("$")) { |
| try { |
| Class<?> clazz; |
| try { |
| clazz = Class.forName(packageName + '.' + fileName.substring(0, fileName.length() - 6)); |
| } catch (ExceptionInInitializerError e) { |
| /* |
| happen, for example, in classes, which depend on Spring to inject some beans, |
| and which fail, if dependency is not fulfilled |
| */ |
| clazz = Class.forName( |
| packageName + '.' + fileName.substring(0, fileName.length() - 6), |
| false, |
| Thread.currentThread().getContextClassLoader() |
| ); |
| } |
| classes.add(clazz); |
| } catch (ClassNotFoundException cnfe) { |
| throw new IllegalStateException("Error while loading detected class.", cnfe); |
| } |
| } |
| } |
| return classes; |
| } |
| |
| private static boolean contains(Object[] list, Object t) { |
| for(Object o : list) { |
| if( o.equals(t) ) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private DiscoveryUtils(){} |
| |
| } |