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 ClassFinder() {
66 this(null, null, null);
67 }
68
69 public ClassFinder(ResourcePathFilter resourcePathFilter,
70 FileNameFilter fileNameFilter, ClassFilter classFilter) {
71 this.resourcePathFilter = resourcePathFilter;
72 this.classFilter = classFilter;
73 this.fileNameFilter = fileNameFilter;
74 this.fileFilter = new FileFilterWithName(fileNameFilter);
75 }
76
77
78
79
80
81
82 public Set<Class<?>> findClasses(boolean proceedOnExceptions)
83 throws ClassNotFoundException, IOException, LinkageError {
84 return findClasses(this.getClass().getPackage().getName(), proceedOnExceptions);
85 }
86
87
88
89
90
91
92
93 public Set<Class<?>> findClasses(String packageName, boolean proceedOnExceptions)
94 throws ClassNotFoundException, IOException, LinkageError {
95 final String path = packageName.replace('.', '/');
96 final Pattern jarResourceRe = Pattern.compile("^file:(.+\\.jar)!/" + path + "$");
97
98 Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(path);
99 List<File> dirs = new ArrayList<File>();
100 List<String> jars = new ArrayList<String>();
101
102 while (resources.hasMoreElements()) {
103 URL resource = resources.nextElement();
104 String resourcePath = resource.getFile();
105 Matcher matcher = jarResourceRe.matcher(resourcePath);
106 boolean isJar = matcher.find();
107 resourcePath = isJar ? matcher.group(1) : resourcePath;
108 if (null == this.resourcePathFilter
109 || this.resourcePathFilter.isCandidatePath(resourcePath, isJar)) {
110 LOG.debug("Will look for classes in " + resourcePath);
111 if (isJar) {
112 jars.add(resourcePath);
113 } else {
114 dirs.add(new File(resourcePath));
115 }
116 }
117 }
118
119 Set<Class<?>> classes = new HashSet<Class<?>>();
120 for (File directory : dirs) {
121 classes.addAll(findClassesFromFiles(directory, packageName, proceedOnExceptions));
122 }
123 for (String jarFileName : jars) {
124 classes.addAll(findClassesFromJar(jarFileName, packageName, proceedOnExceptions));
125 }
126 return classes;
127 }
128
129 private Set<Class<?>> findClassesFromJar(String jarFileName,
130 String packageName, boolean proceedOnExceptions)
131 throws IOException, ClassNotFoundException, LinkageError {
132 JarInputStream jarFile = null;
133 try {
134 jarFile = new JarInputStream(new FileInputStream(jarFileName));
135 } catch (IOException ioEx) {
136 LOG.warn("Failed to look for classes in " + jarFileName + ": " + ioEx);
137 throw ioEx;
138 }
139
140 Set<Class<?>> classes = new HashSet<Class<?>>();
141 JarEntry entry = null;
142 try {
143 while (true) {
144 try {
145 entry = jarFile.getNextJarEntry();
146 } catch (IOException ioEx) {
147 if (!proceedOnExceptions) {
148 throw ioEx;
149 }
150 LOG.warn("Failed to get next entry from " + jarFileName + ": " + ioEx);
151 break;
152 }
153 if (entry == null) {
154 break;
155 }
156
157 String className = entry.getName();
158 if (!className.endsWith(CLASS_EXT)) {
159 continue;
160 }
161 int ix = className.lastIndexOf('/');
162 String fileName = (ix >= 0) ? className.substring(ix + 1) : className;
163 if (null != this.fileNameFilter
164 && !this.fileNameFilter.isCandidateFile(fileName, className)) {
165 continue;
166 }
167 className =
168 className.substring(0, className.length() - CLASS_EXT.length()).replace('/', '.');
169 if (!className.startsWith(packageName)) {
170 continue;
171 }
172 Class<?> c = makeClass(className, proceedOnExceptions);
173 if (c != null) {
174 if (!classes.add(c)) {
175 LOG.warn("Ignoring duplicate class " + className);
176 }
177 }
178 }
179 return classes;
180 } finally {
181 jarFile.close();
182 }
183 }
184
185 private Set<Class<?>> findClassesFromFiles(File baseDirectory, String packageName,
186 boolean proceedOnExceptions) throws ClassNotFoundException, LinkageError {
187 Set<Class<?>> classes = new HashSet<Class<?>>();
188 if (!baseDirectory.exists()) {
189 LOG.warn("Failed to find " + baseDirectory.getAbsolutePath());
190 return classes;
191 }
192
193 File[] files = baseDirectory.listFiles(this.fileFilter);
194 if (files == null) {
195 LOG.warn("Failed to get files from " + baseDirectory.getAbsolutePath());
196 return classes;
197 }
198
199 for (File file : files) {
200 final String fileName = file.getName();
201 if (file.isDirectory()) {
202 classes.addAll(findClassesFromFiles(file, packageName + "." + fileName,
203 proceedOnExceptions));
204 } else {
205 String className = packageName + '.'
206 + fileName.substring(0, fileName.length() - CLASS_EXT.length());
207 Class<?> c = makeClass(className, proceedOnExceptions);
208 if (c != null) {
209 if (!classes.add(c)) {
210 LOG.warn("Ignoring duplicate class " + className);
211 }
212 }
213 }
214 }
215 return classes;
216 }
217
218 private Class<?> makeClass(String className, boolean proceedOnExceptions)
219 throws ClassNotFoundException, LinkageError {
220 try {
221 Class<?> c = Class.forName(className, false, this.getClass().getClassLoader());
222 boolean isCandidateClass = null == classFilter || classFilter.isCandidateClass(c);
223 return isCandidateClass ? c : null;
224 } catch (ClassNotFoundException classNotFoundEx) {
225 if (!proceedOnExceptions) {
226 throw classNotFoundEx;
227 }
228 LOG.debug("Failed to instantiate or check " + className + ": " + classNotFoundEx);
229 } catch (LinkageError linkageEx) {
230 if (!proceedOnExceptions) {
231 throw linkageEx;
232 }
233 LOG.debug("Failed to instantiate or check " + className + ": " + linkageEx);
234 }
235 return null;
236 }
237
238 private class FileFilterWithName implements FileFilter {
239 private FileNameFilter nameFilter;
240
241 public FileFilterWithName(FileNameFilter nameFilter) {
242 this.nameFilter = nameFilter;
243 }
244
245 @Override
246 public boolean accept(File file) {
247 return file.isDirectory()
248 || (file.getName().endsWith(CLASS_EXT)
249 && (null == nameFilter
250 || nameFilter.isCandidateFile(file.getName(), file.getAbsolutePath())));
251 }
252 };
253 };