blob: 8ab78afaa888876b27a21222ad66b57a7bcc9b66 [file] [log] [blame]
/*
* 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(){}
}