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 if (!proceedOnExceptions) {
137 throw ioEx;
138 }
139 LOG.warn("Failed to look for classes in " + jarFileName + ": " + ioEx);
140 }
141
142 Set<Class<?>> classes = new HashSet<Class<?>>();
143 JarEntry entry = null;
144 while (true) {
145 try {
146 entry = jarFile.getNextJarEntry();
147 } catch (IOException ioEx) {
148 if (!proceedOnExceptions) {
149 throw ioEx;
150 }
151 LOG.warn("Failed to get next entry from " + jarFileName + ": " + ioEx);
152 break;
153 }
154 if (entry == null) {
155 break;
156 }
157
158 String className = entry.getName();
159 if (!className.endsWith(CLASS_EXT)) {
160 continue;
161 }
162 int ix = className.lastIndexOf('/');
163 String fileName = (ix >= 0) ? className.substring(ix + 1) : className;
164 if (null != this.fileNameFilter
165 && !this.fileNameFilter.isCandidateFile(fileName, className)) {
166 continue;
167 }
168 className = className
169 .substring(0, className.length() - CLASS_EXT.length()).replace('/', '.');
170 if (!className.startsWith(packageName)) {
171 continue;
172 }
173 Class<?> c = makeClass(className, proceedOnExceptions);
174 if (c != null) {
175 if (!classes.add(c)) {
176 LOG.warn("Ignoring duplicate class " + className);
177 }
178 }
179 }
180 return classes;
181 }
182
183 private Set<Class<?>> findClassesFromFiles(File baseDirectory, String packageName,
184 boolean proceedOnExceptions) throws ClassNotFoundException, LinkageError {
185 Set<Class<?>> classes = new HashSet<Class<?>>();
186 if (!baseDirectory.exists()) {
187 LOG.warn("Failed to find " + baseDirectory.getAbsolutePath());
188 return classes;
189 }
190
191 File[] files = baseDirectory.listFiles(this.fileFilter);
192 if (files == null) {
193 LOG.warn("Failed to get files from " + baseDirectory.getAbsolutePath());
194 return classes;
195 }
196
197 for (File file : files) {
198 final String fileName = file.getName();
199 if (file.isDirectory()) {
200 classes.addAll(findClassesFromFiles(file, packageName + "." + fileName,
201 proceedOnExceptions));
202 } else {
203 String className = packageName + '.'
204 + fileName.substring(0, fileName.length() - CLASS_EXT.length());
205 Class<?> c = makeClass(className, proceedOnExceptions);
206 if (c != null) {
207 if (!classes.add(c)) {
208 LOG.warn("Ignoring duplicate class " + className);
209 }
210 }
211 }
212 }
213 return classes;
214 }
215
216 private Class<?> makeClass(String className, boolean proceedOnExceptions)
217 throws ClassNotFoundException, LinkageError {
218 try {
219 Class<?> c = Class.forName(className, false, this.getClass().getClassLoader());
220 boolean isCandidateClass = null == classFilter || classFilter.isCandidateClass(c);
221 return isCandidateClass ? c : null;
222 } catch (ClassNotFoundException classNotFoundEx) {
223 if (!proceedOnExceptions) {
224 throw classNotFoundEx;
225 }
226 LOG.debug("Failed to instantiate or check " + className + ": " + classNotFoundEx);
227 } catch (LinkageError linkageEx) {
228 if (!proceedOnExceptions) {
229 throw linkageEx;
230 }
231 LOG.debug("Failed to instantiate or check " + className + ": " + linkageEx);
232 }
233 return null;
234 }
235
236 private class FileFilterWithName implements FileFilter {
237 private FileNameFilter nameFilter;
238
239 public FileFilterWithName(FileNameFilter nameFilter) {
240 this.nameFilter = nameFilter;
241 }
242
243 @Override
244 public boolean accept(File file) {
245 return file.isDirectory()
246 || (file.getName().endsWith(CLASS_EXT)
247 && (null == nameFilter
248 || nameFilter.isCandidateFile(file.getName(), file.getAbsolutePath())));
249 }
250 };
251 };