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.net.URL;
26 import java.util.ArrayList;
27 import java.util.Enumeration;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Set;
31 import java.util.jar.JarEntry;
32 import java.util.jar.JarInputStream;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38
39
40
41
42
43
44 public class ClassFinder {
45 private static final Log LOG = LogFactory.getLog(ClassFinder.class);
46 private static String CLASS_EXT = ".class";
47
48 private ResourcePathFilter resourcePathFilter;
49 private FileNameFilter fileNameFilter;
50 private ClassFilter classFilter;
51 private FileFilter fileFilter;
52
53 public interface ResourcePathFilter {
54 boolean isCandidatePath(String resourcePath, boolean isJar);
55 };
56
57 public interface FileNameFilter {
58 boolean isCandidateFile(String fileName, String absFilePath);
59 };
60
61 public interface ClassFilter {
62 boolean isCandidateClass(Class<?> c);
63 };
64
65 public static class Not implements ResourcePathFilter, FileNameFilter, ClassFilter {
66 private ResourcePathFilter resourcePathFilter;
67 private FileNameFilter fileNameFilter;
68 private ClassFilter classFilter;
69
70 public Not(ResourcePathFilter resourcePathFilter){this.resourcePathFilter = resourcePathFilter;}
71 public Not(FileNameFilter fileNameFilter){this.fileNameFilter = fileNameFilter;}
72 public Not(ClassFilter classFilter){this.classFilter = classFilter;}
73
74 @Override
75 public boolean isCandidatePath(String resourcePath, boolean isJar) {
76 return !resourcePathFilter.isCandidatePath(resourcePath, isJar);
77 }
78 @Override
79 public boolean isCandidateFile(String fileName, String absFilePath) {
80 return !fileNameFilter.isCandidateFile(fileName, absFilePath);
81 }
82 @Override
83 public boolean isCandidateClass(Class<?> c) {
84 return !classFilter.isCandidateClass(c);
85 }
86 }
87
88 public static class And implements ClassFilter, ResourcePathFilter {
89 ClassFilter[] classFilters;
90 ResourcePathFilter[] resourcePathFilters;
91
92 public And(ClassFilter...classFilters) { this.classFilters = classFilters; }
93 public And(ResourcePathFilter... resourcePathFilters) {
94 this.resourcePathFilters = resourcePathFilters;
95 }
96
97 @Override
98 public boolean isCandidateClass(Class<?> c) {
99 for (ClassFilter filter : classFilters) {
100 if (!filter.isCandidateClass(c)) {
101 return false;
102 }
103 }
104 return true;
105 }
106
107 @Override public boolean isCandidatePath(String resourcePath, boolean isJar) {
108 for (ResourcePathFilter filter : resourcePathFilters) {
109 if (!filter.isCandidatePath(resourcePath, isJar)) {
110 return false;
111 }
112 }
113 return true;
114 }
115 }
116
117 public ClassFinder() {
118 this(null, null, null);
119 }
120
121 public ClassFinder(ResourcePathFilter resourcePathFilter,
122 FileNameFilter fileNameFilter, ClassFilter classFilter) {
123 this.resourcePathFilter = resourcePathFilter;
124 this.classFilter = classFilter;
125 this.fileNameFilter = fileNameFilter;
126 this.fileFilter = new FileFilterWithName(fileNameFilter);
127 }
128
129
130
131
132
133
134 public Set<Class<?>> findClasses(boolean proceedOnExceptions)
135 throws ClassNotFoundException, IOException, LinkageError {
136 return findClasses(this.getClass().getPackage().getName(), proceedOnExceptions);
137 }
138
139
140
141
142
143
144
145 public Set<Class<?>> findClasses(String packageName, boolean proceedOnExceptions)
146 throws ClassNotFoundException, IOException, LinkageError {
147 final String path = packageName.replace('.', '/');
148 final Pattern jarResourceRe = Pattern.compile("^file:(.+\\.jar)!/" + path + "$");
149
150 Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(path);
151 List<File> dirs = new ArrayList<File>();
152 List<String> jars = new ArrayList<String>();
153
154 while (resources.hasMoreElements()) {
155 URL resource = resources.nextElement();
156 String resourcePath = resource.getFile();
157 Matcher matcher = jarResourceRe.matcher(resourcePath);
158 boolean isJar = matcher.find();
159 resourcePath = isJar ? matcher.group(1) : resourcePath;
160 if (null == this.resourcePathFilter
161 || this.resourcePathFilter.isCandidatePath(resourcePath, isJar)) {
162 LOG.debug("Will look for classes in " + resourcePath);
163 if (isJar) {
164 jars.add(resourcePath);
165 } else {
166 dirs.add(new File(resourcePath));
167 }
168 }
169 }
170
171 Set<Class<?>> classes = new HashSet<Class<?>>();
172 for (File directory : dirs) {
173 classes.addAll(findClassesFromFiles(directory, packageName, proceedOnExceptions));
174 }
175 for (String jarFileName : jars) {
176 classes.addAll(findClassesFromJar(jarFileName, packageName, proceedOnExceptions));
177 }
178 return classes;
179 }
180
181 private Set<Class<?>> findClassesFromJar(String jarFileName,
182 String packageName, boolean proceedOnExceptions)
183 throws IOException, ClassNotFoundException, LinkageError {
184 JarInputStream jarFile = null;
185 try {
186 jarFile = new JarInputStream(new FileInputStream(jarFileName));
187 } catch (IOException ioEx) {
188 LOG.warn("Failed to look for classes in " + jarFileName + ": " + ioEx);
189 throw ioEx;
190 }
191
192 Set<Class<?>> classes = new HashSet<Class<?>>();
193 JarEntry entry = null;
194 try {
195 while (true) {
196 try {
197 entry = jarFile.getNextJarEntry();
198 } catch (IOException ioEx) {
199 if (!proceedOnExceptions) {
200 throw ioEx;
201 }
202 LOG.warn("Failed to get next entry from " + jarFileName + ": " + ioEx);
203 break;
204 }
205 if (entry == null) {
206 break;
207 }
208
209 String className = entry.getName();
210 if (!className.endsWith(CLASS_EXT)) {
211 continue;
212 }
213 int ix = className.lastIndexOf('/');
214 String fileName = (ix >= 0) ? className.substring(ix + 1) : className;
215 if (null != this.fileNameFilter
216 && !this.fileNameFilter.isCandidateFile(fileName, className)) {
217 continue;
218 }
219 className =
220 className.substring(0, className.length() - CLASS_EXT.length()).replace('/', '.');
221 if (!className.startsWith(packageName)) {
222 continue;
223 }
224 Class<?> c = makeClass(className, proceedOnExceptions);
225 if (c != null) {
226 if (!classes.add(c)) {
227 LOG.warn("Ignoring duplicate class " + className);
228 }
229 }
230 }
231 return classes;
232 } finally {
233 jarFile.close();
234 }
235 }
236
237 private Set<Class<?>> findClassesFromFiles(File baseDirectory, String packageName,
238 boolean proceedOnExceptions) throws ClassNotFoundException, LinkageError {
239 Set<Class<?>> classes = new HashSet<Class<?>>();
240 if (!baseDirectory.exists()) {
241 LOG.warn("Failed to find " + baseDirectory.getAbsolutePath());
242 return classes;
243 }
244
245 File[] files = baseDirectory.listFiles(this.fileFilter);
246 if (files == null) {
247 LOG.warn("Failed to get files from " + baseDirectory.getAbsolutePath());
248 return classes;
249 }
250
251 for (File file : files) {
252 final String fileName = file.getName();
253 if (file.isDirectory()) {
254 classes.addAll(findClassesFromFiles(file, packageName + "." + fileName,
255 proceedOnExceptions));
256 } else {
257 String className = packageName + '.'
258 + fileName.substring(0, fileName.length() - CLASS_EXT.length());
259 Class<?> c = makeClass(className, proceedOnExceptions);
260 if (c != null) {
261 if (!classes.add(c)) {
262 LOG.warn("Ignoring duplicate class " + className);
263 }
264 }
265 }
266 }
267 return classes;
268 }
269
270 private Class<?> makeClass(String className, boolean proceedOnExceptions)
271 throws ClassNotFoundException, LinkageError {
272 try {
273 Class<?> c = Class.forName(className, false, this.getClass().getClassLoader());
274 boolean isCandidateClass = null == classFilter || classFilter.isCandidateClass(c);
275 return isCandidateClass ? c : null;
276 } catch (ClassNotFoundException classNotFoundEx) {
277 if (!proceedOnExceptions) {
278 throw classNotFoundEx;
279 }
280 LOG.debug("Failed to instantiate or check " + className + ": " + classNotFoundEx);
281 } catch (LinkageError linkageEx) {
282 if (!proceedOnExceptions) {
283 throw linkageEx;
284 }
285 LOG.debug("Failed to instantiate or check " + className + ": " + linkageEx);
286 }
287 return null;
288 }
289
290 private class FileFilterWithName implements FileFilter {
291 private FileNameFilter nameFilter;
292
293 public FileFilterWithName(FileNameFilter nameFilter) {
294 this.nameFilter = nameFilter;
295 }
296
297 @Override
298 public boolean accept(File file) {
299 return file.isDirectory()
300 || (file.getName().endsWith(CLASS_EXT)
301 && (null == nameFilter
302 || nameFilter.isCandidateFile(file.getName(), file.getAbsolutePath())));
303 }
304 };
305 };