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