1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package com.opensymphony.xwork2.util;
20
21 import org.apache.logging.log4j.Logger;
22 import org.apache.logging.log4j.LogManager;
23
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.lang.annotation.Annotation;
28 import java.net.URL;
29 import java.net.URLDecoder;
30 import java.util.Enumeration;
31 import java.util.HashSet;
32 import java.util.Set;
33 import java.util.jar.JarEntry;
34 import java.util.jar.JarInputStream;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public class ResolverUtil<T> {
71
72 private static final Logger LOG = LogManager.getLogger(ResolverUtil.class);
73
74
75
76
77
78 public static interface Test {
79
80
81
82
83
84
85 boolean matches(Class type);
86
87 boolean matches(URL resource);
88
89 boolean doesMatchClass();
90 boolean doesMatchResource();
91 }
92
93 public static abstract class ClassTest implements Test {
94 public boolean matches(URL resource) {
95 throw new UnsupportedOperationException();
96 }
97
98 public boolean doesMatchClass() {
99 return true;
100 }
101 public boolean doesMatchResource() {
102 return false;
103 }
104 }
105
106 public static abstract class ResourceTest implements Test {
107 public boolean matches(Class cls) {
108 throw new UnsupportedOperationException();
109 }
110
111 public boolean doesMatchClass() {
112 return false;
113 }
114 public boolean doesMatchResource() {
115 return true;
116 }
117 }
118
119
120
121
122
123 public static class IsA extends ClassTest {
124 private Class parent;
125
126
127
128
129 public IsA(Class parentType) { this.parent = parentType; }
130
131
132
133
134
135 public boolean matches(Class type) {
136 return type != null && parent.isAssignableFrom(type);
137 }
138
139 @Override public String toString() {
140 return "is assignable to " + parent.getSimpleName();
141 }
142 }
143
144
145
146
147 public static class NameEndsWith extends ClassTest {
148 private String suffix;
149
150
151
152
153
154 public NameEndsWith(String suffix) { this.suffix = suffix; }
155
156
157
158
159
160 public boolean matches(Class type) {
161 return type != null && type.getName().endsWith(suffix);
162 }
163
164 @Override public String toString() {
165 return "ends with the suffix " + suffix;
166 }
167 }
168
169
170
171
172
173 public static class AnnotatedWith extends ClassTest {
174 private Class<? extends Annotation> annotation;
175
176
177
178
179
180
181 public AnnotatedWith(Class<? extends Annotation> annotation) { this.annotation = annotation; }
182
183
184
185
186
187 public boolean matches(Class type) {
188 return type != null && type.isAnnotationPresent(annotation);
189 }
190
191 @Override public String toString() {
192 return "annotated with @" + annotation.getSimpleName();
193 }
194 }
195
196 public static class NameIs extends ResourceTest {
197 private String name;
198
199 public NameIs(String name) { this.name = "/" + name; }
200
201 public boolean matches(URL resource) {
202 return (resource.getPath().endsWith(name));
203 }
204
205 @Override public String toString() {
206 return "named " + name;
207 }
208 }
209
210
211 private Set<Class<? extends T>> classMatches = new HashSet<Class<?extends T>>();
212
213
214 private Set<URL> resourceMatches = new HashSet<>();
215
216
217
218
219
220 private ClassLoader classloader;
221
222
223
224
225
226
227
228 public Set<Class<? extends T>> getClasses() {
229 return classMatches;
230 }
231
232 public Set<URL> getResources() {
233 return resourceMatches;
234 }
235
236
237
238
239
240
241
242
243 public ClassLoader getClassLoader() {
244 return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader;
245 }
246
247
248
249
250
251
252
253 public void setClassLoader(ClassLoader classloader) { this.classloader = classloader; }
254
255
256
257
258
259
260
261
262
263
264 public void findImplementations(Class parent, String... packageNames) {
265 if (packageNames == null) return;
266
267 Test test = new IsA(parent);
268 for (String pkg : packageNames) {
269 findInPackage(test, pkg);
270 }
271 }
272
273
274
275
276
277
278
279
280 public void findSuffix(String suffix, String... packageNames) {
281 if (packageNames == null) return;
282
283 Test test = new NameEndsWith(suffix);
284 for (String pkg : packageNames) {
285 findInPackage(test, pkg);
286 }
287 }
288
289
290
291
292
293
294
295
296 public void findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
297 if (packageNames == null) return;
298
299 Test test = new AnnotatedWith(annotation);
300 for (String pkg : packageNames) {
301 findInPackage(test, pkg);
302 }
303 }
304
305 public void findNamedResource(String name, String... pathNames) {
306 if (pathNames == null) return;
307
308 Test test = new NameIs(name);
309 for (String pkg : pathNames) {
310 findInPackage(test, pkg);
311 }
312 }
313
314
315
316
317
318
319
320
321 public void find(Test test, String... packageNames) {
322 if (packageNames == null) return;
323
324 for (String pkg : packageNames) {
325 findInPackage(test, pkg);
326 }
327 }
328
329
330
331
332
333
334
335
336
337
338
339 public void findInPackage(Test test, String packageName) {
340 packageName = packageName.replace('.', '/');
341 ClassLoader loader = getClassLoader();
342 Enumeration<URL> urls;
343
344 try {
345 urls = loader.getResources(packageName);
346 }
347 catch (IOException ioe) {
348 if (LOG.isWarnEnabled()) {
349 LOG.warn("Could not read package: " + packageName, ioe);
350 }
351 return;
352 }
353
354 while (urls.hasMoreElements()) {
355 try {
356 String urlPath = urls.nextElement().getFile();
357 urlPath = URLDecoder.decode(urlPath, "UTF-8");
358
359
360 if ( urlPath.startsWith("file:") ) {
361 urlPath = urlPath.substring(5);
362 }
363
364
365 if (urlPath.indexOf('!') > 0) {
366 urlPath = urlPath.substring(0, urlPath.indexOf('!'));
367 }
368
369 if (LOG.isInfoEnabled()) {
370 LOG.info("Scanning for classes in [" + urlPath + "] matching criteria: " + test);
371 }
372 File file = new File(urlPath);
373 if ( file.isDirectory() ) {
374 loadImplementationsInDirectory(test, packageName, file);
375 }
376 else {
377 loadImplementationsInJar(test, packageName, file);
378 }
379 }
380 catch (IOException ioe) {
381 if (LOG.isWarnEnabled()) {
382 LOG.warn("could not read entries", ioe);
383 }
384 }
385 }
386 }
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401 private void loadImplementationsInDirectory(Test test, String parent, File location) {
402 File[] files = location.listFiles();
403 StringBuilder builder = null;
404
405 for (File file : files) {
406 builder = new StringBuilder(100);
407 builder.append(parent).append("/").append(file.getName());
408 String packageOrClass = ( parent == null ? file.getName() : builder.toString() );
409
410 if (file.isDirectory()) {
411 loadImplementationsInDirectory(test, packageOrClass, file);
412 }
413 else if (isTestApplicable(test, file.getName())) {
414 addIfMatching(test, packageOrClass);
415 }
416 }
417 }
418
419 private boolean isTestApplicable(Test test, String path) {
420 return test.doesMatchResource() || path.endsWith(".class") && test.doesMatchClass();
421 }
422
423
424
425
426
427
428
429
430
431
432 private void loadImplementationsInJar(Test test, String parent, File jarfile) {
433 try(JarInputStream jarStream = new JarInputStream(new FileInputStream(jarfile))) {
434 JarEntry entry;
435 while ((entry = jarStream.getNextJarEntry() ) != null) {
436 String name = entry.getName();
437 if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) {
438 addIfMatching(test, name);
439 }
440 }
441 } catch (IOException ioe) {
442 LOG.error("Could not search jar file '" + jarfile + "' for classes matching criteria: " +
443 test + " due to an IOException", ioe);
444 }
445 }
446
447
448
449
450
451
452
453
454 protected void addIfMatching(Test test, String fqn) {
455 try {
456 ClassLoader loader = getClassLoader();
457 if (test.doesMatchClass()) {
458 String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
459 if (LOG.isDebugEnabled()) {
460 LOG.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
461 }
462
463 Class type = loader.loadClass(externalName);
464 if (test.matches(type) ) {
465 classMatches.add( (Class<T>) type);
466 }
467 }
468 if (test.doesMatchResource()) {
469 URL url = loader.getResource(fqn);
470 if (url == null) {
471 url = loader.getResource(fqn.substring(1));
472 }
473 if (url != null && test.matches(url)) {
474 resourceMatches.add(url);
475 }
476 }
477 }
478 catch (Throwable t) {
479 if (LOG.isWarnEnabled()) {
480 LOG.warn("Could not examine class '" + fqn + "' due to a " +
481 t.getClass().getName() + " with message: " + t.getMessage());
482 }
483 }
484 }
485 }