1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase;
20
21 import java.io.File;
22 import java.io.FileFilter;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.lang.reflect.Method;
26 import java.lang.reflect.Modifier;
27 import java.net.URL;
28 import java.util.ArrayList;
29 import java.util.Enumeration;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Set;
33 import java.util.jar.*;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39
40
41
42
43
44
45 public class ClassFinder {
46 private static final Log LOG = LogFactory.getLog(ClassFinder.class);
47 private static String CLASS_EXT = ".class";
48
49 private FileNameFilter fileNameFilter;
50 private ClassFilter classFilter;
51 private FileFilter fileFilter;
52
53 public static interface FileNameFilter {
54 public boolean isCandidateFile(String fileName, String absFilePath);
55 };
56
57 public static interface ClassFilter {
58 public boolean isCandidateClass(Class<?> c);
59 };
60
61 public ClassFinder(FileNameFilter fileNameFilter, ClassFilter classFilter) {
62 this.classFilter = classFilter;
63 this.fileNameFilter = fileNameFilter;
64 this.fileFilter = new FileFilterWithName(fileNameFilter);
65 }
66
67
68
69
70
71
72 public Set<Class<?>> findClasses(boolean proceedOnExceptions)
73 throws ClassNotFoundException, IOException, LinkageError {
74 return findClasses(this.getClass().getPackage().getName(), proceedOnExceptions);
75 }
76
77
78
79
80
81
82
83 public Set<Class<?>> findClasses(String packageName, boolean proceedOnExceptions)
84 throws ClassNotFoundException, IOException, LinkageError {
85 final String path = packageName.replace('.', '/');
86 final Pattern jarResourceRe = Pattern.compile("^file:(.+\\.jar)!/" + path + "$");
87
88 Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(path);
89 List<File> dirs = new ArrayList<File>();
90 List<String> jars = new ArrayList<String>();
91
92 while (resources.hasMoreElements()) {
93 URL resource = resources.nextElement();
94 String resourcePath = resource.getFile();
95 Matcher matcher = jarResourceRe.matcher(resourcePath);
96 if (matcher.find()) {
97 jars.add(matcher.group(1));
98 } else {
99 dirs.add(new File(resource.getFile()));
100 }
101 }
102
103 Set<Class<?>> classes = new HashSet<Class<?>>();
104 for (File directory : dirs) {
105 classes.addAll(findClassesFromFiles(directory, packageName, proceedOnExceptions));
106 }
107 for (String jarFileName : jars) {
108 classes.addAll(findClassesFromJar(jarFileName, packageName, proceedOnExceptions));
109 }
110 return classes;
111 }
112
113 private Set<Class<?>> findClassesFromJar(String jarFileName,
114 String packageName, boolean proceedOnExceptions)
115 throws IOException, ClassNotFoundException, LinkageError {
116 JarInputStream jarFile = null;
117 try {
118 jarFile = new JarInputStream(new FileInputStream(jarFileName));
119 } catch (IOException ioEx) {
120 if (!proceedOnExceptions) {
121 throw ioEx;
122 }
123 LOG.warn("Failed to look for classes in " + jarFileName + ": " + ioEx);
124 }
125
126 Set<Class<?>> classes = new HashSet<Class<?>>();
127 JarEntry entry = null;
128 while (true) {
129 try {
130 entry = jarFile.getNextJarEntry();
131 } catch (IOException ioEx) {
132 if (!proceedOnExceptions) {
133 throw ioEx;
134 }
135 LOG.warn("Failed to get next entry from " + jarFileName + ": " + ioEx);
136 break;
137 }
138 if (entry == null) {
139 break;
140 }
141
142 String className = entry.getName();
143 if (!className.endsWith(CLASS_EXT)) {
144 continue;
145 }
146 int ix = className.lastIndexOf('/');
147 String fileName = (ix >= 0) ? className.substring(ix + 1) : className;
148 if (!this.fileNameFilter.isCandidateFile(fileName, className)) {
149 continue;
150 }
151 className = className
152 .substring(0, className.length() - CLASS_EXT.length()).replace('/', '.');
153 if (!className.startsWith(packageName)) {
154 continue;
155 }
156 Class<?> c = makeClass(className, proceedOnExceptions);
157 if (c != null) {
158 if (!classes.add(c)) {
159 LOG.warn("Ignoring duplicate class " + className);
160 }
161 }
162 }
163 return classes;
164 }
165
166 private Set<Class<?>> findClassesFromFiles(File baseDirectory, String packageName,
167 boolean proceedOnExceptions) throws ClassNotFoundException, LinkageError {
168 Set<Class<?>> classes = new HashSet<Class<?>>();
169 if (!baseDirectory.exists()) {
170 LOG.warn("Failed to find " + baseDirectory.getAbsolutePath());
171 return classes;
172 }
173
174 File[] files = baseDirectory.listFiles(this.fileFilter);
175 if (files == null) {
176 LOG.warn("Failed to get files from " + baseDirectory.getAbsolutePath());
177 return classes;
178 }
179
180 for (File file : files) {
181 final String fileName = file.getName();
182 if (file.isDirectory()) {
183 classes.addAll(findClassesFromFiles(file, packageName + "." + fileName,
184 proceedOnExceptions));
185 } else {
186 String className = packageName + '.'
187 + fileName.substring(0, fileName.length() - CLASS_EXT.length());
188 Class<?> c = makeClass(className, proceedOnExceptions);
189 if (c != null) {
190 if (!classes.add(c)) {
191 LOG.warn("Ignoring duplicate class " + className);
192 }
193 }
194 }
195 }
196 return classes;
197 }
198
199 private Class<?> makeClass(String className, boolean proceedOnExceptions)
200 throws ClassNotFoundException, LinkageError {
201 try {
202 Class<?> c = Class.forName(className, false, this.getClass().getClassLoader());
203 return classFilter.isCandidateClass(c) ? c : null;
204 } catch (ClassNotFoundException classNotFoundEx) {
205 if (!proceedOnExceptions) {
206 throw classNotFoundEx;
207 }
208 LOG.info("Failed to instantiate or check " + className + ": " + classNotFoundEx);
209 } catch (LinkageError linkageEx) {
210 if (!proceedOnExceptions) {
211 throw linkageEx;
212 }
213 LOG.info("Failed to instantiate or check " + className + ": " + linkageEx);
214 }
215 return null;
216 }
217
218 private class FileFilterWithName implements FileFilter {
219 private FileNameFilter nameFilter;
220
221 public FileFilterWithName(FileNameFilter nameFilter) {
222 this.nameFilter = nameFilter;
223 }
224
225 @Override
226 public boolean accept(File file) {
227 return file.isDirectory()
228 || (file.getName().endsWith(CLASS_EXT)
229 && nameFilter.isCandidateFile(file.getName(), file.getAbsolutePath()));
230 }
231 };
232 };