/**
* 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.hadoop.hbase;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A class that finds a set of classes that are locally accessible
* (from .class or .jar files), and satisfy the conditions that are
* imposed by name and class filters provided by the user.
*/
public class ClassFinder {
private static final Log LOG = LogFactory.getLog(ClassFinder.class);
private static String CLASS_EXT = ".class";
private ResourcePathFilter resourcePathFilter;
private FileNameFilter fileNameFilter;
private ClassFilter classFilter;
private FileFilter fileFilter;
public static interface ResourcePathFilter {
public boolean isCandidatePath(String resourcePath, boolean isJar);
};
public static interface FileNameFilter {
public boolean isCandidateFile(String fileName, String absFilePath);
};
public static interface ClassFilter {
public boolean isCandidateClass(Class<?> c);
};
public ClassFinder() {
this(null, null, null);
}
public ClassFinder(ResourcePathFilter resourcePathFilter,
FileNameFilter fileNameFilter, ClassFilter classFilter) {
this.resourcePathFilter = resourcePathFilter;
this.classFilter = classFilter;
this.fileNameFilter = fileNameFilter;
this.fileFilter = new FileFilterWithName(fileNameFilter);
}
/**
* Finds the classes in current package (of ClassFinder) and nested packages.
* @param proceedOnExceptions whether to ignore exceptions encountered for
* individual jars/files/classes, and proceed looking for others.
*/
public Set<Class<?>> findClasses(boolean proceedOnExceptions)
throws ClassNotFoundException, IOException, LinkageError {
return findClasses(this.getClass().getPackage().getName(), proceedOnExceptions);
}
/**
* Finds the classes in a package and nested packages.
* @param packageName package names
* @param proceedOnExceptions whether to ignore exceptions encountered for
* individual jars/files/classes, and proceed looking for others.
*/
public Set<Class<?>> findClasses(String packageName, boolean proceedOnExceptions)
throws ClassNotFoundException, IOException, LinkageError {
final String path = packageName.replace('.', '/');
final Pattern jarResourceRe = Pattern.compile("^file:(.+\\.jar)!/" + path + "$");
Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(path);
List<File> dirs = new ArrayList<File>();
List<String> jars = new ArrayList<String>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
String resourcePath = resource.getFile();
Matcher matcher = jarResourceRe.matcher(resourcePath);
boolean isJar = matcher.find();
resourcePath = isJar ? matcher.group(1) : resourcePath;
if (null == this.resourcePathFilter
|| this.resourcePathFilter.isCandidatePath(resourcePath, isJar)) {
LOG.debug("Will look for classes in " + resourcePath);
if (isJar) {
jars.add(resourcePath);
} else {
dirs.add(new File(resourcePath));
}
}
}
Set<Class<?>> classes = new HashSet<Class<?>>();
for (File directory : dirs) {
classes.addAll(findClassesFromFiles(directory, packageName, proceedOnExceptions));
}
for (String jarFileName : jars) {
classes.addAll(findClassesFromJar(jarFileName, packageName, proceedOnExceptions));
}
return classes;
}
private Set<Class<?>> findClassesFromJar(String jarFileName,
String packageName, boolean proceedOnExceptions)
throws IOException, ClassNotFoundException, LinkageError {
JarInputStream jarFile = null;
try {
jarFile = new JarInputStream(new FileInputStream(jarFileName));
} catch (IOException ioEx) {
if (!proceedOnExceptions) {
throw ioEx;
}
LOG.error("Failed to look for classes in " + jarFileName + ": " + ioEx);
}
Set<Class<?>> classes = new HashSet<Class<?>>();
JarEntry entry = null;
while (true) {
try {
entry = jarFile.getNextJarEntry();
} catch (IOException ioEx) {
if (!proceedOnExceptions) {
throw ioEx;
}
LOG.error("Failed to get next entry from " + jarFileName + ": " + ioEx);
break;
}
if (entry == null) {
break; // loop termination condition
}
String className = entry.getName();
if (!className.endsWith(CLASS_EXT)) {
continue;
}
int ix = className.lastIndexOf('/');
String fileName = (ix >= 0) ? className.substring(ix + 1) : className;
if (null != this.fileNameFilter
&& !this.fileNameFilter.isCandidateFile(fileName, className)) {
continue;
}
className = className
.substring(0, className.length() - CLASS_EXT.length()).replace('/', '.');
if (!className.startsWith(packageName)) {
continue;
}
Class<?> c = makeClass(className, proceedOnExceptions);
if (c != null) {
if (!classes.add(c)) {
LOG.error("Ignoring duplicate class " + className);
}
}
}
return classes;
}
private Set<Class<?>> findClassesFromFiles(File baseDirectory, String packageName,
boolean proceedOnExceptions) throws ClassNotFoundException, LinkageError {
Set<Class<?>> classes = new HashSet<Class<?>>();
if (!baseDirectory.exists()) {
LOG.error("Failed to find " + baseDirectory.getAbsolutePath());
return classes;
}
File[] files = baseDirectory.listFiles(this.fileFilter);
if (files == null) {
LOG.error("Failed to get files from " + baseDirectory.getAbsolutePath());
return classes;
}
for (File file : files) {
final String fileName = file.getName();
if (file.isDirectory()) {
classes.addAll(findClassesFromFiles(file, packageName + "." + fileName,
proceedOnExceptions));
} else {
String className = packageName + '.'
+ fileName.substring(0, fileName.length() - CLASS_EXT.length());
Class<?> c = makeClass(className, proceedOnExceptions);
if (c != null) {
if (!classes.add(c)) {
LOG.error("Ignoring duplicate class " + className);
}
}
}
}
return classes;
}
private Class<?> makeClass(String className, boolean proceedOnExceptions)
throws ClassNotFoundException, LinkageError {
try {
Class<?> c = Class.forName(className, false, this.getClass().getClassLoader());
boolean isCandidateClass = null == classFilter || classFilter.isCandidateClass(c);
return isCandidateClass ? c : null;
} catch (ClassNotFoundException classNotFoundEx) {
if (!proceedOnExceptions) {
throw classNotFoundEx;
}
LOG.error("Failed to instantiate or check " + className + ": " + classNotFoundEx);
} catch (LinkageError linkageEx) {
if (!proceedOnExceptions) {
throw linkageEx;
}
LOG.error("Failed to instantiate or check " + className + ": " + linkageEx);
}
return null;
}
private class FileFilterWithName implements FileFilter {
private FileNameFilter nameFilter;
public FileFilterWithName(FileNameFilter nameFilter) {
this.nameFilter = nameFilter;
}
@Override
public boolean accept(File file) {
return file.isDirectory()
|| (file.getName().endsWith(CLASS_EXT)
&& (null == nameFilter
|| nameFilter.isCandidateFile(file.getName(), file.getAbsolutePath())));
}
};
};