1 /*
2 * Copyright (c) 2002-2003 by OpenSymphony
3 * All rights reserved.
4 */
5 package com.opensymphony.xwork2.util.finder;
6
7 import com.opensymphony.xwork2.util.logging.Logger;
8 import com.opensymphony.xwork2.util.logging.LoggerFactory;
9 import com.opensymphony.xwork2.util.URLUtil;
10 import com.opensymphony.xwork2.XWorkException;
11 import org.objectweb.asm.AnnotationVisitor;
12 import org.objectweb.asm.ClassReader;
13 import org.objectweb.asm.FieldVisitor;
14 import org.objectweb.asm.MethodVisitor;
15 import org.objectweb.asm.commons.EmptyVisitor;
16 import org.apache.commons.lang.StringUtils;
17
18 import java.io.File;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.lang.annotation.Annotation;
22 import java.lang.reflect.AnnotatedElement;
23 import java.lang.reflect.Constructor;
24 import java.lang.reflect.Field;
25 import java.lang.reflect.Method;
26 import java.net.JarURLConnection;
27 import java.net.URL;
28 import java.net.URLDecoder;
29 import java.util;
30 import java.util.jar.JarEntry;
31 import java.util.jar.JarInputStream;
32
33 /**
34 * ClassFinder searches the classpath of the specified ClassLoaderInterface for
35 * packages, classes, constructors, methods, or fields with specific annotations.
36 *
37 * For security reasons ASM is used to find the annotations. Classes are not
38 * loaded unless they match the requirements of a called findAnnotated* method.
39 * Once loaded, these classes are cached.
40 *
41 * The getClassesNotLoaded() method can be used immediately after any find*
42 * method to get a list of classes which matched the find requirements (i.e.
43 * contained the annotation), but were unable to be loaded.
44 *
45 * @author David Blevins
46 * @version $Rev$ $Date$
47 */
48 public class ClassFinder {
49 private static final Logger LOG = LoggerFactory.getLogger(ClassFinder.class);
50
51 private final Map<String, List<Info>> annotated = new HashMap<String, List<Info>>();
52 private final Map<String, ClassInfo> classInfos = new LinkedHashMap<String, ClassInfo>();
53
54 private final List<String> classesNotLoaded = new ArrayList<String>();
55
56 private boolean extractBaseInterfaces;
57 private ClassLoaderInterface classLoaderInterface;
58
59 /**
60 * Creates a ClassFinder that will search the urls in the specified ClassLoaderInterface
61 * excluding the urls in the ClassLoaderInterface's parent.
62 *
63 * To include the parent ClassLoaderInterface, use:
64 *
65 * new ClassFinder(ClassLoaderInterface, false);
66 *
67 * To exclude the parent's parent, use:
68 *
69 * new ClassFinder(ClassLoaderInterface, ClassLoaderInterface.getParent().getParent());
70 *
71 * @param classLoader source of classes to scan
72 * @throws Exception if something goes wrong
73 */
74 public ClassFinder(ClassLoaderInterface classLoader) throws Exception {
75 this(classLoader, true);
76 }
77
78 /**
79 * Creates a ClassFinder that will search the urls in the specified ClassLoaderInterface.
80 *
81 * @param classLoader source of classes to scan
82 * @param excludeParent Allegedly excludes classes from parent ClassLoaderInterface, whatever that might mean
83 * @throws Exception if something goes wrong.
84 */
85 public ClassFinder(ClassLoaderInterface classLoader, boolean excludeParent) throws Exception {
86 this(classLoader, getUrls(classLoader, excludeParent));
87 }
88
89 /**
90 * Creates a ClassFinder that will search the urls in the specified classloader excluding
91 * the urls in the 'exclude' ClassLoaderInterface.
92 *
93 * @param classLoader source of classes to scan
94 * @param exclude source of classes to exclude from scanning
95 * @throws Exception if something goes wrong
96 */
97 public ClassFinder(ClassLoaderInterface classLoader, ClassLoaderInterface exclude) throws Exception {
98 this(classLoader, getUrls(classLoader, exclude));
99 }
100
101 public ClassFinder(ClassLoaderInterface classLoader, URL url) {
102 this(classLoader, Arrays.asList(url));
103 }
104
105 public ClassFinder(ClassLoaderInterface classLoader, String... dirNames) {
106 this(classLoader, getURLs(classLoader, dirNames));
107 }
108
109 public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection<URL> urls) {
110 this(classLoaderInterface, urls, false);
111 }
112
113 public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection<URL> urls, boolean extractBaseInterfaces) {
114 this(classLoaderInterface, urls, extractBaseInterfaces, new HashSet(){
115 {
116 add("jar");
117 }
118 });
119 }
120
121 public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection<URL> urls, boolean extractBaseInterfaces, Set<String> protocols) {
122 this.classLoaderInterface = classLoaderInterface;
123 this.extractBaseInterfaces = extractBaseInterfaces;
124
125 List<String> classNames = new ArrayList<String>();
126 for (URL location : urls) {
127 try {
128 if (protocols.contains(location.getProtocol())) {
129 classNames.addAll(jar(location));
130 } else if ("file".equals(location.getProtocol())) {
131 try {
132 // See if it's actually a jar
133 URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/");
134 JarURLConnection juc = (JarURLConnection) jarUrl.openConnection();
135 juc.getJarFile();
136 classNames.addAll(jar(jarUrl));
137 } catch (IOException e) {
138 classNames.addAll(file(location));
139 }
140 }
141 } catch (Exception e) {
142 if (LOG.isErrorEnabled())
143 LOG.error("Unable to read URL [#0]", e, location.toExternalForm());
144 }
145 }
146
147 for (String className : classNames) {
148 try {
149 readClassDef(className);
150 } catch (Throwable e) {
151 if (LOG.isErrorEnabled())
152 LOG.error("Unable to read class [#0]", e, className);
153 }
154 }
155 }
156
157 public ClassFinder(Class... classes){
158 this(Arrays.asList(classes));
159 }
160
161 public ClassFinder(List<Class> classes){
162 this.classLoaderInterface = null;
163 List<Info> infos = new ArrayList<Info>();
164 List<Package> packages = new ArrayList<Package>();
165 for (Class clazz : classes) {
166
167 Package aPackage = clazz.getPackage();
168 if (aPackage != null && !packages.contains(aPackage)){
169 infos.add(new PackageInfo(aPackage));
170 packages.add(aPackage);
171 }
172
173 ClassInfo classInfo = new ClassInfo(clazz);
174 infos.add(classInfo);
175 classInfos.put(classInfo.getName(), classInfo);
176 for (Method method : clazz.getDeclaredMethods()) {
177 infos.add(new MethodInfo(classInfo, method));
178 }
179
180 for (Constructor constructor : clazz.getConstructors()) {
181 infos.add(new MethodInfo(classInfo, constructor));
182 }
183
184 for (Field field : clazz.getDeclaredFields()) {
185 infos.add(new FieldInfo(classInfo, field));
186 }
187 }
188
189 for (Info info : infos) {
190 for (AnnotationInfo annotation : info.getAnnotations()) {
191 List<Info> annotationInfos = getAnnotationInfos(annotation.getName());
192 annotationInfos.add(info);
193 }
194 }
195 }
196
197 public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
198 List<Info> infos = annotated.get(annotation.getName());
199 return infos != null && !infos.isEmpty();
200 }
201
202 /**
203 * Returns a list of classes that could not be loaded in last invoked findAnnotated* method.
204 * <p/>
205 * The list will only contain entries of classes whose byte code matched the requirements
206 * of last invoked find* method, but were unable to be loaded and included in the results.
207 * <p/>
208 * The list returned is unmodifiable. Once obtained, the returned list will be a live view of the
209 * results from the last findAnnotated* method call.
210 * <p/>
211 * This method is not thread safe.
212 * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call.
213 */
214 public List<String> getClassesNotLoaded() {
215 return Collections.unmodifiableList(classesNotLoaded);
216 }
217
218 public List<Package> findAnnotatedPackages(Class<? extends Annotation> annotation) {
219 classesNotLoaded.clear();
220 List<Package> packages = new ArrayList<Package>();
221 List<Info> infos = getAnnotationInfos(annotation.getName());
222 for (Info info : infos) {
223 if (info instanceof PackageInfo) {
224 PackageInfo packageInfo = (PackageInfo) info;
225 try {
226 Package pkg = packageInfo.get();
227 // double check via proper reflection
228 if (pkg.isAnnotationPresent(annotation)) {
229 packages.add(pkg);
230 }
231 } catch (ClassNotFoundException e) {
232 classesNotLoaded.add(packageInfo.getName());
233 }
234 }
235 }
236 return packages;
237 }
238
239 public List<Class> findAnnotatedClasses(Class<? extends Annotation> annotation) {
240 classesNotLoaded.clear();
241 List<Class> classes = new ArrayList<Class>();
242 List<Info> infos = getAnnotationInfos(annotation.getName());
243 for (Info info : infos) {
244 if (info instanceof ClassInfo) {
245 ClassInfo classInfo = (ClassInfo) info;
246 try {
247 Class clazz = classInfo.get();
248 // double check via proper reflection
249 if (clazz.isAnnotationPresent(annotation)) {
250 classes.add(clazz);
251 }
252 } catch (Throwable e) {
253 if (LOG.isErrorEnabled())
254 LOG.error("Error loading class [#0]", e, classInfo.getName());
255 classesNotLoaded.add(classInfo.getName());
256 }
257 }
258 }
259 return classes;
260 }
261
262 public List<Method> findAnnotatedMethods(Class<? extends Annotation> annotation) {
263 classesNotLoaded.clear();
264 List<ClassInfo> seen = new ArrayList<ClassInfo>();
265 List<Method> methods = new ArrayList<Method>();
266 List<Info> infos = getAnnotationInfos(annotation.getName());
267 for (Info info : infos) {
268 if (info instanceof MethodInfo && !"<init>".equals(info.getName())) {
269 MethodInfo methodInfo = (MethodInfo) info;
270 ClassInfo classInfo = methodInfo.getDeclaringClass();
271
272 if (seen.contains(classInfo)) continue;
273
274 seen.add(classInfo);
275
276 try {
277 Class clazz = classInfo.get();
278 for (Method method : clazz.getDeclaredMethods()) {
279 if (method.isAnnotationPresent(annotation)) {
280 methods.add(method);
281 }
282 }
283 } catch (Throwable e) {
284 if (LOG.isErrorEnabled())
285 LOG.error("Error loading class [#0]", e, classInfo.getName());
286 classesNotLoaded.add(classInfo.getName());
287 }
288 }
289 }
290 return methods;
291 }
292
293 public List<Constructor> findAnnotatedConstructors(Class<? extends Annotation> annotation) {
294 classesNotLoaded.clear();
295 List<ClassInfo> seen = new ArrayList<ClassInfo>();
296 List<Constructor> constructors = new ArrayList<Constructor>();
297 List<Info> infos = getAnnotationInfos(annotation.getName());
298 for (Info info : infos) {
299 if (info instanceof MethodInfo && "<init>".equals(info.getName())) {
300 MethodInfo methodInfo = (MethodInfo) info;
301 ClassInfo classInfo = methodInfo.getDeclaringClass();
302
303 if (seen.contains(classInfo)) continue;
304
305 seen.add(classInfo);
306
307 try {
308 Class clazz = classInfo.get();
309 for (Constructor constructor : clazz.getConstructors()) {
310 if (constructor.isAnnotationPresent(annotation)) {
311 constructors.add(constructor);
312 }
313 }
314 } catch (Throwable e) {
315 if (LOG.isErrorEnabled())
316 LOG.error("Error loading class [#0]", e, classInfo.getName());
317 classesNotLoaded.add(classInfo.getName());
318 }
319 }
320 }
321 return constructors;
322 }
323
324 public List<Field> findAnnotatedFields(Class<? extends Annotation> annotation) {
325 classesNotLoaded.clear();
326 List<ClassInfo> seen = new ArrayList<ClassInfo>();
327 List<Field> fields = new ArrayList<Field>();
328 List<Info> infos = getAnnotationInfos(annotation.getName());
329 for (Info info : infos) {
330 if (info instanceof FieldInfo) {
331 FieldInfo fieldInfo = (FieldInfo) info;
332 ClassInfo classInfo = fieldInfo.getDeclaringClass();
333
334 if (seen.contains(classInfo)) continue;
335
336 seen.add(classInfo);
337
338 try {
339 Class clazz = classInfo.get();
340 for (Field field : clazz.getDeclaredFields()) {
341 if (field.isAnnotationPresent(annotation)) {
342 fields.add(field);
343 }
344 }
345 } catch (Throwable e) {
346 if (LOG.isErrorEnabled())
347 LOG.error("Error loading class [#0]", e, classInfo.getName());
348 classesNotLoaded.add(classInfo.getName());
349 }
350 }
351 }
352 return fields;
353 }
354
355 public List<Class> findClassesInPackage(String packageName, boolean recursive) {
356 classesNotLoaded.clear();
357 List<Class> classes = new ArrayList<Class>();
358 for (ClassInfo classInfo : classInfos.values()) {
359 try {
360 if (recursive && classInfo.getPackageName().startsWith(packageName)){
361 classes.add(classInfo.get());
362 } else if (classInfo.getPackageName().equals(packageName)){
363 classes.add(classInfo.get());
364 }
365 } catch (Throwable e) {
366 if (LOG.isErrorEnabled())
367 LOG.error("Error loading class [#0]", e, classInfo.getName());
368 classesNotLoaded.add(classInfo.getName());
369 }
370 }
371 return classes;
372 }
373
374 public List<Class> findClasses(Test<ClassInfo> test) {
375 classesNotLoaded.clear();
376 List<Class> classes = new ArrayList<Class>();
377 for (ClassInfo classInfo : classInfos.values()) {
378 try {
379 if (test.test(classInfo)) {
380 classes.add(classInfo.get());
381 }
382 } catch (Throwable e) {
383 if (LOG.isErrorEnabled())
384 LOG.error("Error loading class [#0]", e, classInfo.getName());
385 classesNotLoaded.add(classInfo.getName());
386 }
387 }
388 return classes;
389 }
390
391 public List<Class> findClasses() {
392 classesNotLoaded.clear();
393 List<Class> classes = new ArrayList<Class>();
394 for (ClassInfo classInfo : classInfos.values()) {
395 try {
396 classes.add(classInfo.get());
397 } catch (Throwable e) {
398 if (LOG.isErrorEnabled())
399 LOG.error("Error loading class [#0]", e, classInfo.getName());
400 classesNotLoaded.add(classInfo.getName());
401 }
402 }
403 return classes;
404 }
405
406 private static List<URL> getURLs(ClassLoaderInterface classLoader, String[] dirNames) {
407 List<URL> urls = new ArrayList<URL>();
408 for (String dirName : dirNames) {
409 try {
410 Enumeration<URL> classLoaderURLs = classLoader.getResources(dirName);
411 while (classLoaderURLs.hasMoreElements()) {
412 URL url = classLoaderURLs.nextElement();
413 urls.add(url);
414 }
415 } catch (IOException ioe) {
416 if (LOG.isErrorEnabled())
417 LOG.error("Could not read driectory [#0]", ioe, dirName);
418 }
419 }
420
421 return urls;
422 }
423
424 private static Collection<URL> getUrls(ClassLoaderInterface classLoaderInterface, boolean excludeParent) throws IOException {
425 return getUrls(classLoaderInterface, excludeParent? classLoaderInterface.getParent() : null);
426 }
427
428 private static Collection<URL> getUrls(ClassLoaderInterface classLoader, ClassLoaderInterface excludeParent) throws IOException {
429 UrlSet urlSet = new UrlSet(classLoader);
430 if (excludeParent != null){
431 urlSet = urlSet.exclude(excludeParent);
432 }
433 return urlSet.getUrls();
434 }
435
436 private List<String> file(URL location) {
437 List<String> classNames = new ArrayList<String>();
438 File dir = new File(URLDecoder.decode(location.getPath()));
439 if ("META-INF".equals(dir.getName())) {
440 dir = dir.getParentFile(); // Scrape "META-INF" off
441 }
442 if (dir.isDirectory()) {
443 scanDir(dir, classNames, "");
444 }
445 return classNames;
446 }
447
448 private void scanDir(File dir, List<String> classNames, String packageName) {
449 File[] files = dir.listFiles();
450 for (File file : files) {
451 if (file.isDirectory()) {
452 scanDir(file, classNames, packageName + file.getName() + ".");
453 } else if (file.getName().endsWith(".class")) {
454 String name = file.getName();
455 name = name.replaceFirst(".class$", "");
456 classNames.add(packageName + name);
457 }
458 }
459 }
460
461 private List<String> jar(URL location) throws IOException {
462 URL url = URLUtil.normalizeToFileProtocol(location);
463 if (url != null) {
464 InputStream in = url.openStream();
465 try {
466 JarInputStream jarStream = new JarInputStream(in);
467 return jar(jarStream);
468 } finally {
469 in.close();
470 }
471 } else if (LOG.isDebugEnabled())
472 LOG.debug("Unable to read [#0]", location.toExternalForm());
473
474 return Collections.emptyList();
475 }
476
477 private List<String> jar(JarInputStream jarStream) throws IOException {
478 List<String> classNames = new ArrayList<String>();
479
480 JarEntry entry;
481 while ((entry = jarStream.getNextJarEntry()) != null) {
482 if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
483 continue;
484 }
485 String className = entry.getName();
486 className = className.replaceFirst(".class$", "");
487
488 //war files are treated as .jar files, so takeout WEB-INF/classes
489 className = StringUtils.removeStart(className, "WEB-INF/classes/");
490
491 className = className.replace('/', '.');
492 classNames.add(className);
493 }
494
495 return classNames;
496 }
497
498 public class Annotatable {
499 private final List<AnnotationInfo> annotations = new ArrayList<AnnotationInfo>();
500
501 public Annotatable(AnnotatedElement element) {
502 for (Annotation annotation : element.getAnnotations()) {
503 annotations.add(new AnnotationInfo(annotation.annotationType().getName()));
504 }
505 }
506
507 public Annotatable() {
508 }
509
510 public List<AnnotationInfo> getAnnotations() {
511 return annotations;
512 }
513
514 }
515
516 public static interface Info {
517 String getName();
518
519 List<AnnotationInfo> getAnnotations();
520 }
521
522 public class PackageInfo extends Annotatable implements Info {
523 private final String name;
524 private final ClassInfo info;
525 private final Package pkg;
526
527 public PackageInfo(Package pkg){
528 super(pkg);
529 this.pkg = pkg;
530 this.name = pkg.getName();
531 this.info = null;
532 }
533
534 public PackageInfo(String name) {
535 info = new ClassInfo(name, null);
536 this.name = name;
537 this.pkg = null;
538 }
539
540 public String getName() {
541 return name;
542 }
543
544 public Package get() throws ClassNotFoundException {
545 return (pkg != null)?pkg:info.get().getPackage();
546 }
547 }
548
549 public class ClassInfo extends Annotatable implements Info {
550 private final String name;
551 private final List<MethodInfo> methods = new ArrayList<MethodInfo>();
552 private final List<MethodInfo> constructors = new ArrayList<MethodInfo>();
553 private final String superType;
554 private final List<String> interfaces = new ArrayList<String>();
555 private final List<String> superInterfaces = new ArrayList<String>();
556 private final List<FieldInfo> fields = new ArrayList<FieldInfo>();
557 private Class<?> clazz;
558 private ClassNotFoundException notFound;
559
560 public ClassInfo(Class clazz) {
561 super(clazz);
562 this.clazz = clazz;
563 this.name = clazz.getName();
564 Class superclass = clazz.getSuperclass();
565 this.superType = superclass != null ? superclass.getName(): null;
566 }
567
568 public ClassInfo(String name, String superType) {
569 this.name = name;
570 this.superType = superType;
571 }
572
573 public String getPackageName(){
574 return name.indexOf(".") > 0 ? name.substring(0, name.lastIndexOf(".")) : "" ;
575 }
576
577 public List<MethodInfo> getConstructors() {
578 return constructors;
579 }
580
581 public List<String> getInterfaces() {
582 return interfaces;
583 }
584
585 public List<String> getSuperInterfaces() {
586 return superInterfaces;
587 }
588
589 public List<FieldInfo> getFields() {
590 return fields;
591 }
592
593 public List<MethodInfo> getMethods() {
594 return methods;
595 }
596
597 public String getName() {
598 return name;
599 }
600
601 public String getSuperType() {
602 return superType;
603 }
604
605 public Class get() throws ClassNotFoundException {
606 if (clazz != null) return clazz;
607 if (notFound != null) throw notFound;
608 try {
609 this.clazz = classLoaderInterface.loadClass(name);
610 return clazz;
611 } catch (ClassNotFoundException notFound) {
612 classesNotLoaded.add(name);
613 this.notFound = notFound;
614 throw notFound;
615 }
616 }
617
618 @Override
619 public String toString() {
620 return name;
621 }
622 }
623
624 public class MethodInfo extends Annotatable implements Info {
625 private final ClassInfo declaringClass;
626 private final String returnType;
627 private final String name;
628 private final List<List<AnnotationInfo>> parameterAnnotations = new ArrayList<List<AnnotationInfo>>();
629
630 public MethodInfo(ClassInfo info, Constructor constructor){
631 super(constructor);
632 this.declaringClass = info;
633 this.name = "<init>";
634 this.returnType = Void.TYPE.getName();
635 }
636
637 public MethodInfo(ClassInfo info, Method method){
638 super(method);
639 this.declaringClass = info;
640 this.name = method.getName();
641 this.returnType = method.getReturnType().getName();
642 }
643
644 public MethodInfo(ClassInfo declarignClass, String name, String returnType) {
645 this.declaringClass = declarignClass;
646 this.name = name;
647 this.returnType = returnType;
648 }
649
650 public List<List<AnnotationInfo>> getParameterAnnotations() {
651 return parameterAnnotations;
652 }
653
654 public List<AnnotationInfo> getParameterAnnotations(int index) {
655 if (index >= parameterAnnotations.size()) {
656 for (int i = parameterAnnotations.size(); i <= index; i++) {
657 List<AnnotationInfo> annotationInfos = new ArrayList<AnnotationInfo>();
658 parameterAnnotations.add(i, annotationInfos);
659 }
660 }
661 return parameterAnnotations.get(index);
662 }
663
664 public String getName() {
665 return name;
666 }
667
668 public ClassInfo getDeclaringClass() {
669 return declaringClass;
670 }
671
672 public String getReturnType() {
673 return returnType;
674 }
675
676 @Override
677 public String toString() {
678 return declaringClass + "@" + name;
679 }
680 }
681
682 public class FieldInfo extends Annotatable implements Info {
683 private final String name;
684 private final String type;
685 private final ClassInfo declaringClass;
686
687 public FieldInfo(ClassInfo info, Field field){
688 super(field);
689 this.declaringClass = info;
690 this.name = field.getName();
691 this.type = field.getType().getName();
692 }
693
694 public FieldInfo(ClassInfo declaringClass, String name, String type) {
695 this.declaringClass = declaringClass;
696 this.name = name;
697 this.type = type;
698 }
699
700 public String getName() {
701 return name;
702 }
703
704 public ClassInfo getDeclaringClass() {
705 return declaringClass;
706 }
707
708 public String getType() {
709 return type;
710 }
711
712 @Override
713 public String toString() {
714 return declaringClass + "#" + name;
715 }
716 }
717
718 public class AnnotationInfo extends Annotatable implements Info {
719 private final String name;
720
721 public AnnotationInfo(Annotation annotation){
722 this(annotation.getClass().getName());
723 }
724
725 public AnnotationInfo(Class<? extends Annotation> annotation) {
726 this.name = annotation.getName().intern();
727 }
728
729 public AnnotationInfo(String name) {
730 name = name.replaceAll("^L|;$", "");
731 name = name.replace('/', '.');
732 this.name = name.intern();
733 }
734
735 public String getName() {
736 return name;
737 }
738
739 @Override
740 public String toString() {
741 return name;
742 }
743 }
744
745 private List<Info> getAnnotationInfos(String name) {
746 List<Info> infos = annotated.get(name);
747 if (infos == null) {
748 infos = new ArrayList<Info>();
749 annotated.put(name, infos);
750 }
751 return infos;
752 }
753
754 private void readClassDef(String className) {
755 if (!className.endsWith(".class")) {
756 className = className.replace('.', '/') + ".class";
757 }
758 try {
759 URL resource = classLoaderInterface.getResource(className);
760 if (resource != null) {
761 InputStream in = resource.openStream();
762 try {
763 ClassReader classReader = new ClassReader(in);
764 classReader.accept(new InfoBuildingVisitor(), ClassReader.SKIP_DEBUG);
765 } finally {
766 in.close();
767 }
768 } else {
769 throw new XWorkException("Could not load " + className);
770 }
771 } catch (IOException e) {
772 throw new XWorkException("Could not load " + className, e);
773 }
774
775 }
776
777 public class InfoBuildingVisitor extends EmptyVisitor {
778 private Info info;
779
780 public InfoBuildingVisitor() {
781 }
782
783 public InfoBuildingVisitor(Info info) {
784 this.info = info;
785 }
786
787 @Override
788 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
789 if (name.endsWith("package-info")) {
790 info = new PackageInfo(javaName(name));
791 } else {
792 ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName));
793
794 for (String interfce : interfaces) {
795 classInfo.getInterfaces().add(javaName(interfce));
796 }
797 info = classInfo;
798 classInfos.put(classInfo.getName(), classInfo);
799
800 if (extractBaseInterfaces)
801 extractSuperInterfaces(classInfo);
802 }
803 }
804
805 private void extractSuperInterfaces(ClassInfo classInfo) {
806 String superType = classInfo.getSuperType();
807
808 if (superType != null) {
809 ClassInfo base = classInfos.get(superType);
810
811 if (base == null) {
812 //try to load base
813 String resource = superType.replace('.', '/') + ".class";
814 readClassDef(resource);
815 base = classInfos.get(superType);
816 }
817
818 if (base != null) {
819 List<String> interfaces = classInfo.getSuperInterfaces();
820 interfaces.addAll(base.getSuperInterfaces());
821 interfaces.addAll(base.getInterfaces());
822 }
823 }
824 }
825
826 private String javaName(String name) {
827 return (name == null)? null:name.replace('/', '.');
828 }
829
830 @Override
831 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
832 AnnotationInfo annotationInfo = new AnnotationInfo(desc);
833 info.getAnnotations().add(annotationInfo);
834 getAnnotationInfos(annotationInfo.getName()).add(info);
835 return new InfoBuildingVisitor(annotationInfo);
836 }
837
838 @Override
839 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
840 ClassInfo classInfo = ((ClassInfo) info);
841 FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc);
842 classInfo.getFields().add(fieldInfo);
843 return new InfoBuildingVisitor(fieldInfo);
844 }
845
846 @Override
847 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
848 ClassInfo classInfo = ((ClassInfo) info);
849 MethodInfo methodInfo = new MethodInfo(classInfo, name, desc);
850 classInfo.getMethods().add(methodInfo);
851 return new InfoBuildingVisitor(methodInfo);
852 }
853
854 @Override
855 public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) {
856 MethodInfo methodInfo = ((MethodInfo) info);
857 List<AnnotationInfo> annotationInfos = methodInfo.getParameterAnnotations(param);
858 AnnotationInfo annotationInfo = new AnnotationInfo(desc);
859 annotationInfos.add(annotationInfo);
860 return new InfoBuildingVisitor(annotationInfo);
861 }
862 }
863 }
864