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