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 static org.junit.Assert.assertArrayEquals;
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertTrue;
25
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.PrintStream;
31 import java.lang.reflect.Method;
32 import java.net.URL;
33 import java.net.URLClassLoader;
34 import java.util.HashSet;
35 import java.util.Set;
36 import java.util.concurrent.atomic.AtomicLong;
37 import java.util.jar.Attributes;
38 import java.util.jar.JarEntry;
39 import java.util.jar.JarOutputStream;
40 import java.util.jar.Manifest;
41
42 import javax.tools.JavaCompiler;
43 import javax.tools.ToolProvider;
44
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47 import org.junit.AfterClass;
48 import org.junit.BeforeClass;
49 import org.junit.Test;
50 import org.junit.experimental.categories.Category;
51
52 @Category(SmallTests.class)
53 public class TestClassFinder {
54 private static final Log LOG = LogFactory.getLog(TestClassFinder.class);
55 private static final HBaseCommonTestingUtility testUtil = new HBaseCommonTestingUtility();
56 private static final String BASEPKG = "tfcpkg";
57
58
59
60
61 private static AtomicLong testCounter = new AtomicLong(0);
62 private static AtomicLong jarCounter = new AtomicLong(0);
63
64 private static String basePath = null;
65
66 @BeforeClass
67 public static void createTestDir() throws IOException {
68 basePath = testUtil.getDataTestDir(TestClassFinder.class.getSimpleName()).toString();
69 if (!basePath.endsWith("/")) {
70 basePath += "/";
71 }
72
73 File testDir = new File(basePath);
74 if (testDir.exists()) {
75 deleteTestDir();
76 }
77 assertTrue(testDir.mkdirs());
78 }
79
80 @AfterClass
81 public static void deleteTestDir() throws IOException {
82 testUtil.cleanupTestDir(TestClassFinder.class.getSimpleName());
83 }
84
85 @Test
86 public void testClassFinderCanFindClassesInJars() throws Exception {
87 long counter = testCounter.incrementAndGet();
88 FileAndPath c1 = compileTestClass(counter, "", "c1");
89 FileAndPath c2 = compileTestClass(counter, ".nested", "c2");
90 FileAndPath c3 = compileTestClass(counter, "", "c3");
91 packageAndLoadJar(c1, c3);
92 packageAndLoadJar(c2);
93
94 ClassFinder allClassesFinder = new ClassFinder();
95 Set<Class<?>> allClasses = allClassesFinder.findClasses(
96 makePackageName("", counter), false);
97 assertEquals(3, allClasses.size());
98 }
99
100 @Test
101 public void testClassFinderHandlesConflicts() throws Exception {
102 long counter = testCounter.incrementAndGet();
103 FileAndPath c1 = compileTestClass(counter, "", "c1");
104 FileAndPath c2 = compileTestClass(counter, "", "c2");
105 packageAndLoadJar(c1, c2);
106 packageAndLoadJar(c1);
107
108 ClassFinder allClassesFinder = new ClassFinder();
109 Set<Class<?>> allClasses = allClassesFinder.findClasses(
110 makePackageName("", counter), false);
111 assertEquals(2, allClasses.size());
112 }
113
114 @Test
115 public void testClassFinderHandlesNestedPackages() throws Exception {
116 final String NESTED = ".nested";
117 final String CLASSNAME1 = "c2";
118 final String CLASSNAME2 = "c3";
119 long counter = testCounter.incrementAndGet();
120 FileAndPath c1 = compileTestClass(counter, "", "c1");
121 FileAndPath c2 = compileTestClass(counter, NESTED, CLASSNAME1);
122 FileAndPath c3 = compileTestClass(counter, NESTED, CLASSNAME2);
123 packageAndLoadJar(c1, c2);
124 packageAndLoadJar(c3);
125
126 ClassFinder allClassesFinder = new ClassFinder();
127 Set<Class<?>> nestedClasses = allClassesFinder.findClasses(
128 makePackageName(NESTED, counter), false);
129 assertEquals(2, nestedClasses.size());
130 Class<?> nestedClass1 = makeClass(NESTED, CLASSNAME1, counter);
131 assertTrue(nestedClasses.contains(nestedClass1));
132 Class<?> nestedClass2 = makeClass(NESTED, CLASSNAME2, counter);
133 assertTrue(nestedClasses.contains(nestedClass2));
134 }
135
136 @Test
137 public void testClassFinderFiltersByNameInJar() throws Exception {
138 final String CLASSNAME = "c1";
139 final String CLASSNAMEEXCPREFIX = "c2";
140 long counter = testCounter.incrementAndGet();
141 FileAndPath c1 = compileTestClass(counter, "", CLASSNAME);
142 FileAndPath c2 = compileTestClass(counter, "", CLASSNAMEEXCPREFIX + "1");
143 FileAndPath c3 = compileTestClass(counter, "", CLASSNAMEEXCPREFIX + "2");
144 packageAndLoadJar(c1, c2, c3);
145
146 ClassFinder.FileNameFilter notExcNameFilter = new ClassFinder.FileNameFilter() {
147 @Override
148 public boolean isCandidateFile(String fileName, String absFilePath) {
149 return !fileName.startsWith(CLASSNAMEEXCPREFIX);
150 }
151 };
152 ClassFinder incClassesFinder = new ClassFinder(null, notExcNameFilter, null);
153 Set<Class<?>> incClasses = incClassesFinder.findClasses(
154 makePackageName("", counter), false);
155 assertEquals(1, incClasses.size());
156 Class<?> incClass = makeClass("", CLASSNAME, counter);
157 assertTrue(incClasses.contains(incClass));
158 }
159
160 @Test
161 public void testClassFinderFiltersByClassInJar() throws Exception {
162 final String CLASSNAME = "c1";
163 final String CLASSNAMEEXCPREFIX = "c2";
164 long counter = testCounter.incrementAndGet();
165 FileAndPath c1 = compileTestClass(counter, "", CLASSNAME);
166 FileAndPath c2 = compileTestClass(counter, "", CLASSNAMEEXCPREFIX + "1");
167 FileAndPath c3 = compileTestClass(counter, "", CLASSNAMEEXCPREFIX + "2");
168 packageAndLoadJar(c1, c2, c3);
169
170 final ClassFinder.ClassFilter notExcClassFilter = new ClassFinder.ClassFilter() {
171 @Override
172 public boolean isCandidateClass(Class<?> c) {
173 return !c.getSimpleName().startsWith(CLASSNAMEEXCPREFIX);
174 }
175 };
176 ClassFinder incClassesFinder = new ClassFinder(null, null, notExcClassFilter);
177 Set<Class<?>> incClasses = incClassesFinder.findClasses(
178 makePackageName("", counter), false);
179 assertEquals(1, incClasses.size());
180 Class<?> incClass = makeClass("", CLASSNAME, counter);
181 assertTrue(incClasses.contains(incClass));
182 }
183
184 @Test
185 public void testClassFinderFiltersByPathInJar() throws Exception {
186 final String CLASSNAME = "c1";
187 long counter = testCounter.incrementAndGet();
188 FileAndPath c1 = compileTestClass(counter, "", CLASSNAME);
189 FileAndPath c2 = compileTestClass(counter, "", "c2");
190 packageAndLoadJar(c1);
191 final String excludedJar = packageAndLoadJar(c2);
192
193
194
195
196 final String excludedJarResource =
197 new File(excludedJar).toURI().getRawSchemeSpecificPart();
198
199 final ClassFinder.ResourcePathFilter notExcJarFilter =
200 new ClassFinder.ResourcePathFilter() {
201 @Override
202 public boolean isCandidatePath(String resourcePath, boolean isJar) {
203 return !isJar || !resourcePath.equals(excludedJarResource);
204 }
205 };
206 ClassFinder incClassesFinder = new ClassFinder(notExcJarFilter, null, null);
207 Set<Class<?>> incClasses = incClassesFinder.findClasses(
208 makePackageName("", counter), false);
209 assertEquals(1, incClasses.size());
210 Class<?> incClass = makeClass("", CLASSNAME, counter);
211 assertTrue(incClasses.contains(incClass));
212 }
213
214 @Test
215 public void testClassFinderCanFindClassesInDirs() throws Exception {
216
217
218 ClassFinder allClassesFinder = new ClassFinder();
219 Set<Class<?>> allClasses = allClassesFinder.findClasses(
220 this.getClass().getPackage().getName(), false);
221 assertTrue(allClasses.contains(this.getClass()));
222 assertTrue(allClasses.contains(ClassFinder.class));
223 }
224
225 @Test
226 public void testClassFinderFiltersByNameInDirs() throws Exception {
227 final String thisName = this.getClass().getSimpleName();
228 final ClassFinder.FileNameFilter notThisFilter = new ClassFinder.FileNameFilter() {
229 @Override
230 public boolean isCandidateFile(String fileName, String absFilePath) {
231 return !fileName.equals(thisName + ".class");
232 }
233 };
234 String thisPackage = this.getClass().getPackage().getName();
235 ClassFinder allClassesFinder = new ClassFinder();
236 Set<Class<?>> allClasses = allClassesFinder.findClasses(thisPackage, false);
237 ClassFinder notThisClassFinder = new ClassFinder(null, notThisFilter, null);
238 Set<Class<?>> notAllClasses = notThisClassFinder.findClasses(thisPackage, false);
239 assertFalse(notAllClasses.contains(this.getClass()));
240 assertEquals(allClasses.size() - 1, notAllClasses.size());
241 }
242
243 @Test
244 public void testClassFinderFiltersByClassInDirs() throws Exception {
245 final ClassFinder.ClassFilter notThisFilter = new ClassFinder.ClassFilter() {
246 @Override
247 public boolean isCandidateClass(Class<?> c) {
248 return c != TestClassFinder.class;
249 }
250 };
251 String thisPackage = this.getClass().getPackage().getName();
252 ClassFinder allClassesFinder = new ClassFinder();
253 Set<Class<?>> allClasses = allClassesFinder.findClasses(thisPackage, false);
254 ClassFinder notThisClassFinder = new ClassFinder(null, null, notThisFilter);
255 Set<Class<?>> notAllClasses = notThisClassFinder.findClasses(thisPackage, false);
256 assertFalse(notAllClasses.contains(this.getClass()));
257 assertEquals(allClasses.size() - 1, notAllClasses.size());
258 }
259
260 @Test
261 public void testClassFinderFiltersByPathInDirs() throws Exception {
262 final String hardcodedThisSubdir = "hbase-common";
263 final ClassFinder.ResourcePathFilter notExcJarFilter =
264 new ClassFinder.ResourcePathFilter() {
265 @Override
266 public boolean isCandidatePath(String resourcePath, boolean isJar) {
267 return isJar || !resourcePath.contains(hardcodedThisSubdir);
268 }
269 };
270 String thisPackage = this.getClass().getPackage().getName();
271 ClassFinder notThisClassFinder = new ClassFinder(notExcJarFilter, null, null);
272 Set<Class<?>> notAllClasses = notThisClassFinder.findClasses(thisPackage, false);
273 assertFalse(notAllClasses.contains(this.getClass()));
274 }
275
276 @Test
277 public void testClassFinderDefaultsToOwnPackage() throws Exception {
278
279
280 ClassFinder allClassesFinder = new ClassFinder();
281 Set<Class<?>> pkgClasses = allClassesFinder.findClasses(
282 ClassFinder.class.getPackage().getName(), false);
283 Set<Class<?>> defaultClasses = allClassesFinder.findClasses(false);
284 assertArrayEquals(pkgClasses.toArray(), defaultClasses.toArray());
285 }
286
287 private static class FileAndPath {
288 String path;
289 File file;
290 public FileAndPath(String path, File file) {
291 this.file = file;
292 this.path = path;
293 }
294 }
295
296 private static Class<?> makeClass(String nestedPkgSuffix,
297 String className, long counter) throws ClassNotFoundException {
298 return Class.forName(
299 makePackageName(nestedPkgSuffix, counter) + "." + className + counter);
300 }
301
302 private static String makePackageName(String nestedSuffix, long counter) {
303 return BASEPKG + counter + nestedSuffix;
304 }
305
306
307
308
309
310
311
312
313 private static FileAndPath compileTestClass(long counter,
314 String packageNameSuffix, String classNamePrefix) throws Exception {
315 classNamePrefix = classNamePrefix + counter;
316 String packageName = makePackageName(packageNameSuffix, counter);
317 String javaPath = basePath + classNamePrefix + ".java";
318 String classPath = basePath + classNamePrefix + ".class";
319 PrintStream source = new PrintStream(javaPath);
320 source.println("package " + packageName + ";");
321 source.println("public class " + classNamePrefix
322 + " { public static void main(String[] args) { } };");
323 source.close();
324 JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
325 int result = jc.run(null, null, null, javaPath);
326 assertEquals(0, result);
327 File classFile = new File(classPath);
328 assertTrue(classFile.exists());
329 return new FileAndPath(packageName.replace('.', '/') + '/', classFile);
330 }
331
332
333
334
335
336
337 private static String packageAndLoadJar(FileAndPath... filesInJar) throws Exception {
338
339 String path = basePath + "jar" + jarCounter.incrementAndGet() + ".jar";
340 Manifest manifest = new Manifest();
341 manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
342 FileOutputStream fos = new FileOutputStream(path);
343 JarOutputStream jarOutputStream = new JarOutputStream(fos, manifest);
344
345
346
347 Set<String> pathsInJar = new HashSet<String>();
348 for (FileAndPath fileAndPath : filesInJar) {
349 String pathToAdd = fileAndPath.path;
350 while (pathsInJar.add(pathToAdd)) {
351 int ix = pathToAdd.lastIndexOf('/', pathToAdd.length() - 2);
352 if (ix < 0) {
353 break;
354 }
355 pathToAdd = pathToAdd.substring(0, ix);
356 }
357 }
358 for (String pathInJar : pathsInJar) {
359 jarOutputStream.putNextEntry(new JarEntry(pathInJar));
360 jarOutputStream.closeEntry();
361 }
362 for (FileAndPath fileAndPath : filesInJar) {
363 File file = fileAndPath.file;
364 jarOutputStream.putNextEntry(
365 new JarEntry(fileAndPath.path + file.getName()));
366 byte[] allBytes = new byte[(int)file.length()];
367 FileInputStream fis = new FileInputStream(file);
368 fis.read(allBytes);
369 fis.close();
370 jarOutputStream.write(allBytes);
371 jarOutputStream.closeEntry();
372 }
373 jarOutputStream.close();
374 fos.close();
375
376
377 File jarFile = new File(path);
378 assertTrue(jarFile.exists());
379 URLClassLoader urlClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
380 Method method = URLClassLoader.class
381 .getDeclaredMethod("addURL", new Class[] { URL.class });
382 method.setAccessible(true);
383 method.invoke(urlClassLoader, new Object[] { jarFile.toURI().toURL() });
384 return jarFile.getAbsolutePath();
385 }
386 };