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