001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *  http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020
021package org.apache.xbean.finder;
022
023import java.io.IOException;
024import java.io.InputStream;
025import java.lang.annotation.Annotation;
026import java.lang.reflect.AnnotatedElement;
027import java.lang.reflect.Constructor;
028import java.lang.reflect.Field;
029import java.lang.reflect.Method;
030import java.net.URL;
031import java.util.ArrayList;
032import java.util.Collections;
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036
037import org.apache.xbean.asm9.original.commons.EmptyVisitor;
038import org.apache.xbean.finder.util.SingleLinkedList;
039import org.objectweb.asm.AnnotationVisitor;
040import org.objectweb.asm.ClassReader;
041import org.objectweb.asm.FieldVisitor;
042import org.objectweb.asm.MethodVisitor;
043
044/**
045 * @version $Rev: 1881759 $ $Date: 2020-09-16 10:29:43 +0200 (Wed, 16 Sep 2020) $
046 */
047public abstract class AbstractFinder implements IAnnotationFinder {
048    private final Map<String, List<Info>> annotated = new HashMap<String, List<Info>>();
049    protected final Map<String, ClassInfo> classInfos = new HashMap<String, ClassInfo>();
050    protected final Map<String, ClassInfo> originalInfos = new HashMap<String, ClassInfo>();
051    private final List<String> classesNotLoaded = new ArrayList<String>();
052    private final int ASM_FLAGS = ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES;
053
054    protected abstract URL getResource(String className);
055
056    protected abstract Class<?> loadClass(String fixedName) throws ClassNotFoundException;
057
058    public List<String> getAnnotatedClassNames() {
059        return new ArrayList<String>(originalInfos.keySet());
060    }
061
062    /**
063     * The link() method must be called to successfully use the findSubclasses and findImplementations methods
064     * @return
065     * @throws IOException
066     */
067    public AbstractFinder link() throws IOException {
068        // already linked?
069        if (originalInfos.size() > 0) return this;
070
071        // keep track of what was originally from the archives
072        originalInfos.putAll(classInfos);
073
074        for (ClassInfo classInfo : classInfos.values().toArray(new ClassInfo[classInfos.size()])) {
075
076            linkParent(classInfo);
077        }
078
079        for (ClassInfo classInfo : classInfos.values().toArray(new ClassInfo[classInfos.size()])) {
080
081            linkInterfaces(classInfo);
082        }
083
084        return this;
085    }
086
087    private void linkParent(ClassInfo classInfo) throws IOException {
088        if (classInfo.superType == null) return;
089        if (classInfo.superType.equals("java.lang.Object")) return;
090        
091        ClassInfo parentInfo = classInfo.superclassInfo;
092
093        if (parentInfo == null) {
094
095            parentInfo = classInfos.get(classInfo.superType);
096
097            if (parentInfo == null) {
098
099                if (classInfo.clazz != null) {
100                    readClassDef(((Class<?>) classInfo.clazz).getSuperclass());
101                } else {
102                    readClassDef(classInfo.superType);
103                }
104
105                parentInfo = classInfos.get(classInfo.superType);
106
107                if (parentInfo == null) return;
108                
109                linkParent(parentInfo);
110            }
111
112            classInfo.superclassInfo = parentInfo;
113        }
114
115        if (!parentInfo.subclassInfos.contains(classInfo)) {
116            parentInfo.subclassInfos.add(classInfo);
117        }
118    }
119
120    private void linkInterfaces(ClassInfo classInfo) throws IOException {
121        final List<ClassInfo> infos = new ArrayList<ClassInfo>();
122
123        if (classInfo.clazz != null){
124            final Class<?>[] interfaces = classInfo.clazz.getInterfaces();
125
126            for (Class<?> clazz : interfaces) {
127                ClassInfo interfaceInfo = classInfos.get(clazz.getName());
128
129                if (interfaceInfo == null){
130                    readClassDef(clazz);
131                }
132
133                interfaceInfo = classInfos.get(clazz.getName());
134
135                if (interfaceInfo != null) {
136                    infos.add(interfaceInfo);
137                }
138            }
139        } else {
140            for (String className : classInfo.interfaces) {
141                ClassInfo interfaceInfo = classInfos.get(className);
142
143                if (interfaceInfo == null){
144                    readClassDef(className);
145                }
146
147                interfaceInfo = classInfos.get(className);
148
149                if (interfaceInfo != null) {
150                    infos.add(interfaceInfo);
151                }
152            }
153        }
154
155        for (ClassInfo info : infos) {
156            linkInterfaces(info);
157        }
158    }
159
160    public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
161        List<Info> infos = annotated.get(annotation.getName());
162        return infos != null && !infos.isEmpty();
163    }
164
165    /**
166     * Returns a list of classes that could not be loaded in last invoked findAnnotated* method.
167     * <p/>
168     * The list will only contain entries of classes whose byte code matched the requirements
169     * of last invoked find* method, but were unable to be loaded and included in the results.
170     * <p/>
171     * The list returned is unmodifiable.  Once obtained, the returned list will be a live view of the
172     * results from the last findAnnotated* method call.
173     * <p/>
174     * This method is not thread safe.
175     * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call.
176     */
177    public List<String> getClassesNotLoaded() {
178        return Collections.unmodifiableList(classesNotLoaded);
179    }
180
181    public List<Package> findAnnotatedPackages(Class<? extends Annotation> annotation) {
182        classesNotLoaded.clear();
183        List<Package> packages = new ArrayList<Package>();
184        List<Info> infos = getAnnotationInfos(annotation.getName());
185        for (Info info : infos) {
186            if (info instanceof PackageInfo) {
187                PackageInfo packageInfo = (PackageInfo) info;
188                try {
189                    Package pkg = packageInfo.get();
190                    // double check via proper reflection
191                    if (pkg.isAnnotationPresent(annotation)) {
192                        packages.add(pkg);
193                    }
194                } catch (ClassNotFoundException e) {
195                    classesNotLoaded.add(packageInfo.getName());
196                }
197            }
198        }
199        return packages;
200    }
201
202    public List<Class<?>> findAnnotatedClasses(Class<? extends Annotation> annotation) {
203        classesNotLoaded.clear();
204        List<Class<?>> classes = new ArrayList<Class<?>>();
205        List<Info> infos = getAnnotationInfos(annotation.getName());
206        for (Info info : infos) {
207            if (info instanceof ClassInfo) {
208                ClassInfo classInfo = (ClassInfo) info;
209                try {
210                    Class clazz = classInfo.get();
211                    // double check via proper reflection
212                    if (clazz.isAnnotationPresent(annotation)) {
213                        classes.add(clazz);
214                    }
215                } catch (ClassNotFoundException e) {
216                    classesNotLoaded.add(classInfo.getName());
217                }
218            }
219        }
220        return classes;
221    }
222
223    public List<Annotated<Class<?>>> findMetaAnnotatedClasses(Class<? extends Annotation> annotation) {
224        List<Class<?>> classes = findAnnotatedClasses(annotation);
225        List<Annotated<Class<?>>> list = new ArrayList<Annotated<Class<?>>>();
226        for (final Class<?> clazz : classes) {
227            list.add(new MetaAnnotatedClass(clazz));
228        }
229        return list;
230    }
231
232    /**
233     * Naive implementation - works extremelly slow O(n^3)
234     *
235     * @param annotation
236     * @return list of directly or indirectly (inherited) annotated classes
237     */
238    public List<Class<?>> findInheritedAnnotatedClasses(Class<? extends Annotation> annotation) {
239        classesNotLoaded.clear();
240        List<Class<?>> classes = new ArrayList<Class<?>>();
241        List<Info> infos = getAnnotationInfos(annotation.getName());
242        for (Info info : infos) {
243            try {
244                if(info instanceof ClassInfo){
245                   classes.add(((ClassInfo) info).get());
246                }
247            } catch (ClassNotFoundException cnfe) {
248                // TODO: ignored, but a log message would be appropriate
249            }
250        }
251        boolean annClassFound;
252        List<ClassInfo> tempClassInfos = new ArrayList<ClassInfo>(classInfos.values());
253        do {
254            annClassFound = false;
255            for (int pos = 0; pos < tempClassInfos.size(); pos++) {
256                ClassInfo classInfo = tempClassInfos.get(pos);
257                try {
258                    // check whether any superclass is annotated
259                    String superType = classInfo.getSuperType();
260                    for (Class clazz : classes) {
261                        if (superType.equals(clazz.getName())) {
262                            classes.add(classInfo.get());
263                            tempClassInfos.remove(pos);
264                            annClassFound = true;
265                            break;
266                        }
267                    }
268                    // check whether any interface is annotated
269                    List<String> interfces = classInfo.getInterfaces();
270                    for (String interfce: interfces) {
271                        for (Class clazz : classes) {
272                            if (interfce.replaceFirst("<.*>","").equals(clazz.getName())) {
273                                classes.add(classInfo.get());
274                                tempClassInfos.remove(pos);
275                                annClassFound = true;
276                                break;
277                            }
278                        }
279                    }
280                } catch (ClassNotFoundException e) {
281                    classesNotLoaded.add(classInfo.getName());
282                } catch (NoClassDefFoundError e) {
283                    classesNotLoaded.add(classInfo.getName());
284                }
285            }
286        } while (annClassFound);
287        return classes;
288    }
289
290    public List<Method> findAnnotatedMethods(Class<? extends Annotation> annotation) {
291        classesNotLoaded.clear();
292        List<ClassInfo> seen = new ArrayList<ClassInfo>();
293        List<Method> methods = new ArrayList<Method>();
294        List<Info> infos = getAnnotationInfos(annotation.getName());
295        for (Info info : infos) {
296            if (info instanceof MethodInfo && !info.getName().equals("<init>")) {
297                MethodInfo methodInfo = (MethodInfo) info;
298                ClassInfo classInfo = methodInfo.getDeclaringClass();
299
300                if (seen.contains(classInfo)) continue;
301
302                seen.add(classInfo);
303
304                try {
305                    Class clazz = classInfo.get();
306                    for (Method method : clazz.getDeclaredMethods()) {
307                        if (method.isAnnotationPresent(annotation)) {
308                            methods.add(method);
309                        }
310                    }
311                } catch (ClassNotFoundException e) {
312                    classesNotLoaded.add(classInfo.getName());
313                }
314            }
315        }
316        return methods;
317    }
318
319    public List<Annotated<Method>> findMetaAnnotatedMethods(Class<? extends Annotation> annotation) {
320        List<Method> methods = findAnnotatedMethods(annotation);
321        List<Annotated<Method>> list = new ArrayList<Annotated<Method>>();
322        for (final Method method : methods) {
323            list.add(new MetaAnnotatedMethod(method));
324        }
325        return list;
326    }
327
328    public List<Constructor> findAnnotatedConstructors(Class<? extends Annotation> annotation) {
329        classesNotLoaded.clear();
330        List<ClassInfo> seen = new ArrayList<ClassInfo>();
331        List<Constructor> constructors = new ArrayList<Constructor>();
332        List<Info> infos = getAnnotationInfos(annotation.getName());
333        for (Info info : infos) {
334            if (info instanceof MethodInfo && info.getName().equals("<init>")) {
335                MethodInfo methodInfo = (MethodInfo) info;
336                ClassInfo classInfo = methodInfo.getDeclaringClass();
337
338                if (seen.contains(classInfo)) continue;
339
340                seen.add(classInfo);
341
342                try {
343                    Class clazz = classInfo.get();
344                    for (Constructor constructor : clazz.getConstructors()) {
345                        if (constructor.isAnnotationPresent(annotation)) {
346                            constructors.add(constructor);
347                        }
348                    }
349                } catch (ClassNotFoundException e) {
350                    classesNotLoaded.add(classInfo.getName());
351                }
352            }
353        }
354        return constructors;
355    }
356
357    public List<Field> findAnnotatedFields(Class<? extends Annotation> annotation) {
358        classesNotLoaded.clear();
359        List<ClassInfo> seen = new ArrayList<ClassInfo>();
360        List<Field> fields = new ArrayList<Field>();
361        List<Info> infos = getAnnotationInfos(annotation.getName());
362        for (Info info : infos) {
363            if (info instanceof FieldInfo) {
364                FieldInfo fieldInfo = (FieldInfo) info;
365                ClassInfo classInfo = fieldInfo.getDeclaringClass();
366
367                if (seen.contains(classInfo)) continue;
368
369                seen.add(classInfo);
370
371                try {
372                    Class clazz = classInfo.get();
373                    for (Field field : clazz.getDeclaredFields()) {
374                        if (field.isAnnotationPresent(annotation)) {
375                            fields.add(field);
376                        }
377                    }
378                } catch (ClassNotFoundException e) {
379                    classesNotLoaded.add(classInfo.getName());
380                }
381            }
382        }
383        return fields;
384    }
385
386    public List<Annotated<Field>> findMetaAnnotatedFields(Class<? extends Annotation> annotation) {
387        List<Field> fields = findAnnotatedFields(annotation);
388        List<Annotated<Field>> list = new ArrayList<Annotated<Field>>();
389        for (final Field field : fields) {
390            list.add(new MetaAnnotatedField(field));
391        }
392
393        return list;
394    }
395
396    public List<Class<?>> findClassesInPackage(String packageName, boolean recursive) {
397        classesNotLoaded.clear();
398        List<Class<?>> classes = new ArrayList<Class<?>>();
399        for (ClassInfo classInfo : classInfos.values()) {
400            try {
401                if (recursive && classInfo.getPackageName().startsWith(packageName)){
402                    classes.add(classInfo.get());
403                } else if (classInfo.getPackageName().equals(packageName)){
404                    classes.add(classInfo.get());
405                }
406            } catch (ClassNotFoundException e) {
407                classesNotLoaded.add(classInfo.getName());
408            }
409        }
410        return classes;
411    }
412
413    public <T> List<Class<? extends T>> findSubclasses(Class<T> clazz) {
414        if (clazz == null) throw new NullPointerException("class cannot be null");
415
416        classesNotLoaded.clear();
417
418        final ClassInfo classInfo = classInfos.get(clazz.getName());
419
420        List<Class<? extends T>> found = new ArrayList<Class<? extends T>>();
421
422        if (classInfo == null) return found;
423
424        findSubclasses(classInfo, found, clazz);
425
426        return found;
427    }
428
429    private <T> void findSubclasses(ClassInfo classInfo, List<Class<? extends T>> found, Class<T> clazz) {
430
431        for (ClassInfo subclassInfo : classInfo.subclassInfos) {
432
433            try {
434                found.add(subclassInfo.get().asSubclass(clazz));
435            } catch (ClassNotFoundException e) {
436                classesNotLoaded.add(subclassInfo.getName());
437            }
438
439            findSubclasses(subclassInfo, found, clazz);
440        }
441    }
442
443    private <T> List<Class<? extends T>> _findSubclasses(Class<T> clazz) {
444        if (clazz == null) throw new NullPointerException("class cannot be null");
445
446        List<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
447
448
449        for (ClassInfo classInfo : classInfos.values()) {
450
451            try {
452
453                if (clazz.getName().equals(classInfo.superType)) {
454
455                    if (clazz.isAssignableFrom(classInfo.get())) {
456
457                        classes.add(classInfo.get().asSubclass(clazz));
458
459                        classes.addAll(_findSubclasses(classInfo.get().asSubclass(clazz)));
460                    }
461                }
462
463            } catch (ClassNotFoundException e) {
464                classesNotLoaded.add(classInfo.getName());
465            }
466
467        }
468
469        return classes;
470    }
471
472    public <T> List<Class<? extends T>> findImplementations(Class<T> clazz) {
473        if (clazz == null) throw new NullPointerException("class cannot be null");
474        if (!clazz.isInterface()) new IllegalArgumentException("class must be an interface");
475        classesNotLoaded.clear();
476
477        final String interfaceName = clazz.getName();
478
479        // Collect all interfaces extending the main interface (recursively)
480        // Collect all implementations of interfaces
481        // i.e. all *directly* implementing classes
482        List<ClassInfo> infos = collectImplementations(interfaceName);
483
484        // Collect all subclasses of implementations
485        List<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
486        for (ClassInfo info : infos) {
487            try {
488                final Class<? extends T> impl = (Class<? extends T>) info.get();
489
490                if (clazz.isAssignableFrom(impl)) {
491                    classes.add(impl);
492
493                    // Optimization: Don't need to call this method if parent class was already searched
494
495
496
497                    classes.addAll(_findSubclasses(impl));
498                }
499
500            } catch (ClassNotFoundException e) {
501                classesNotLoaded.add(info.getName());
502            }
503        }
504        return classes;
505    }
506
507    private List<ClassInfo> collectImplementations(String interfaceName) {
508        final List<ClassInfo> infos = new ArrayList<ClassInfo>();
509
510        for (ClassInfo classInfo : classInfos.values()) {
511
512            if (classInfo.interfaces.contains(interfaceName)) {
513
514                infos.add(classInfo);
515
516                try {
517
518                    final Class clazz = classInfo.get();
519
520                    if (clazz.isInterface() && !clazz.isAnnotation()) {
521
522                        infos.addAll(collectImplementations(classInfo.name));
523
524                    }
525                    
526                } catch (ClassNotFoundException ignore) {
527                    // we'll deal with this later
528                }
529            }
530        }
531        return infos;
532    }
533
534    protected List<Info> getAnnotationInfos(String name) {
535        List<Info> infos = annotated.get(name);
536        if (infos == null) {
537            infos = new SingleLinkedList<Info>();
538            annotated.put(name, infos);
539        }
540        return infos;
541    }
542
543    protected void readClassDef(String className) {
544        int pos = className.indexOf("<");
545        if (pos > -1) {
546            className = className.substring(0, pos);
547        }
548        pos = className.indexOf(">");
549        if (pos > -1) {
550            className = className.substring(0, pos);
551        }
552        if (!className.endsWith(".class")) {
553            className = className.replace('.', '/') + ".class";
554        }
555        try {
556            // TODO: check out META-INF/versions/<version>/className
557            URL resource = getResource(className);
558            if (resource != null) {
559                InputStream in = resource.openStream();
560                try {
561                    readClassDef(in);
562                } finally {
563                    in.close();
564                }
565            } else {
566                classesNotLoaded.add(className + " (no resource found for class)");
567            }
568        } catch (IOException e) {
569            classesNotLoaded.add(className + e.getMessage());
570        }
571
572    }
573
574    protected void readClassDef(InputStream in) throws IOException {
575        readClassDef(in, null);
576    }
577
578    protected void readClassDef(InputStream in, String path) throws IOException {
579        ClassReader classReader = new ClassReader(in);
580        classReader.accept(new InfoBuildingVisitor(path), ASM_FLAGS);
581    }
582
583    protected void readClassDef(Class clazz) {
584        List<Info> infos = new ArrayList<Info>();
585
586        Package aPackage = clazz.getPackage();
587        if (aPackage != null){
588            final PackageInfo info = new PackageInfo(aPackage);
589            for (AnnotationInfo annotation : info.getAnnotations()) {
590                List<Info> annotationInfos = getAnnotationInfos(annotation.getName());
591                if (!annotationInfos.contains(info)) {
592                    annotationInfos.add(info);
593                }
594            }
595        }
596
597        ClassInfo classInfo = new ClassInfo(clazz);
598        infos.add(classInfo);
599        classInfos.put(clazz.getName(), classInfo);
600        for (Method method : clazz.getDeclaredMethods()) {
601            infos.add(new MethodInfo(classInfo, method));
602        }
603
604        for (Constructor constructor : clazz.getConstructors()) {
605            infos.add(new MethodInfo(classInfo, constructor));
606        }
607
608        for (Field field : clazz.getDeclaredFields()) {
609            infos.add(new FieldInfo(classInfo, field));
610        }
611
612        for (Info info : infos) {
613            for (AnnotationInfo annotation : info.getAnnotations()) {
614                List<Info> annotationInfos = getAnnotationInfos(annotation.getName());
615                annotationInfos.add(info);
616            }
617        }
618    }
619
620    public class Annotatable {
621        private final List<AnnotationInfo> annotations = new ArrayList<AnnotationInfo>();
622
623        public Annotatable(AnnotatedElement element) {
624            for (Annotation annotation : getAnnotations(element)) {
625                annotations.add(new AnnotationInfo(annotation.annotationType().getName()));
626            }
627        }
628
629        public Annotatable() {
630        }
631
632        public List<AnnotationInfo> getAnnotations() {
633            return annotations;
634        }
635        
636        /**
637         * Utility method to get around some errors caused by 
638         * interactions between the Equinox class loaders and 
639         * the OpenJPA transformation process.  There is a window 
640         * where the OpenJPA transformation process can cause 
641         * an annotation being processed to get defined in a 
642         * classloader during the actual defineClass call for 
643         * that very class (e.g., recursively).  This results in 
644         * a LinkageError exception.  If we see one of these, 
645         * retry the request.  Since the annotation will be 
646         * defined on the second pass, this should succeed.  If 
647         * we get a second exception, then it's likely some 
648         * other problem. 
649         * 
650         * @param element The AnnotatedElement we need information for.
651         * 
652         * @return An array of the Annotations defined on the element. 
653         */
654        private Annotation[] getAnnotations(AnnotatedElement element) {
655            try {
656                return element.getAnnotations();
657            } catch (LinkageError e) {
658                return element.getAnnotations();
659            }
660        }
661
662    }
663
664    public static interface Info {
665        String getName();
666
667        List<AnnotationInfo> getAnnotations();
668    }
669
670    public class PackageInfo extends Annotatable implements Info {
671        private final String name;
672        private final ClassInfo info;
673        private final Package pkg;
674
675        public PackageInfo(Package pkg){
676            super(pkg);
677            this.pkg = pkg;
678            this.name = pkg.getName();
679            this.info = null;
680        }
681
682        public PackageInfo(String name) {
683            info = new ClassInfo(name, null);
684            this.name = name;
685            this.pkg = null;
686        }
687
688        public String getName() {
689            return name;
690        }
691
692        public Package get() throws ClassNotFoundException {
693            return (pkg != null)?pkg:info.get().getPackage();
694        }
695
696        @Override
697        public boolean equals(Object o) {
698            if (this == o) return true;
699            if (o == null || getClass() != o.getClass()) return false;
700
701            PackageInfo that = (PackageInfo) o;
702
703            if (name != null ? !name.equals(that.name) : that.name != null) return false;
704
705            return true;
706        }
707
708        @Override
709        public int hashCode() {
710            return name != null ? name.hashCode() : 0;
711        }
712    }
713
714    public class ClassInfo extends Annotatable implements Info {
715        private String name;
716        private final List<MethodInfo> methods = new SingleLinkedList<MethodInfo>();
717        private final List<MethodInfo> constructors = new SingleLinkedList<MethodInfo>();
718        private String superType;
719        private ClassInfo superclassInfo;
720        private final List<ClassInfo> subclassInfos = new SingleLinkedList<ClassInfo>();
721        private final List<String> interfaces = new SingleLinkedList<String>();
722        private final List<FieldInfo> fields = new SingleLinkedList<FieldInfo>();
723        //e.g. bundle class path prefix.
724        private String path;
725        private Class<?> clazz;
726
727        public ClassInfo(Class clazz) {
728            super(clazz);
729            this.clazz = clazz;
730            this.name = clazz.getName();
731            Class superclass = clazz.getSuperclass();
732            this.superType = superclass != null ? superclass.getName(): null;
733            for (Class intrface : clazz.getInterfaces()) {
734                this.interfaces.add(intrface.getName());
735            }
736        }
737
738        public ClassInfo(String name, String superType) {
739            this.name = name;
740            this.superType = superType;
741        }
742
743        public String getPackageName(){
744            return name.indexOf(".") > 0 ? name.substring(0, name.lastIndexOf(".")) : "" ;
745        }
746
747        public List<MethodInfo> getConstructors() {
748            return constructors;
749        }
750
751        public List<String> getInterfaces() {
752            return interfaces;
753        }
754
755        public List<FieldInfo> getFields() {
756            return fields;
757        }
758
759        public List<MethodInfo> getMethods() {
760            return methods;
761        }
762
763        public String getName() {
764            return name;
765        }
766
767        public String getSuperType() {
768            return superType;
769        }
770
771        public Class<?> get() throws ClassNotFoundException {
772            if (clazz != null) return clazz;
773            try {
774                String fixedName = name.replaceFirst("<.*>", "");
775                this.clazz = loadClass(fixedName);
776                return clazz;
777            } catch (ClassNotFoundException notFound) {
778                classesNotLoaded.add(name);
779                throw notFound;
780            }
781        }
782
783        public String toString() {
784            return name;
785        }
786
787        public String getPath() {
788            return path;
789        }
790    }
791
792    public class MethodInfo extends Annotatable implements Info {
793        private final ClassInfo declaringClass;
794        private final String returnType;
795        private final String name;
796        private final List<List<AnnotationInfo>> parameterAnnotations = new ArrayList<List<AnnotationInfo>>();
797
798        public MethodInfo(ClassInfo info, Constructor constructor){
799            super(constructor);
800            this.declaringClass = info;
801            this.name = "<init>";
802            this.returnType = Void.TYPE.getName();
803        }
804
805        public MethodInfo(ClassInfo info, Method method){
806            super(method);
807            this.declaringClass = info;
808            this.name = method.getName();
809            this.returnType = method.getReturnType().getName();
810        }
811
812        public MethodInfo(ClassInfo declarignClass, String name, String returnType) {
813            this.declaringClass = declarignClass;
814            this.name = name;
815            this.returnType = returnType;
816        }
817
818        public List<List<AnnotationInfo>> getParameterAnnotations() {
819            return parameterAnnotations;
820        }
821
822        public List<AnnotationInfo> getParameterAnnotations(int index) {
823            if (index >= parameterAnnotations.size()) {
824                for (int i = parameterAnnotations.size(); i <= index; i++) {
825                    List<AnnotationInfo> annotationInfos = new ArrayList<AnnotationInfo>();
826                    parameterAnnotations.add(i, annotationInfos);
827                }
828            }
829            return parameterAnnotations.get(index);
830        }
831
832        public String getName() {
833            return name;
834        }
835
836        public ClassInfo getDeclaringClass() {
837            return declaringClass;
838        }
839
840        public String getReturnType() {
841            return returnType;
842        }
843
844        public String toString() {
845            return declaringClass + "@" + name;
846        }
847    }
848
849    public class FieldInfo extends Annotatable implements Info {
850        private final String name;
851        private final String type;
852        private final ClassInfo declaringClass;
853
854        public FieldInfo(ClassInfo info, Field field){
855            super(field);
856            this.declaringClass = info;
857            this.name = field.getName();
858            this.type = field.getType().getName();
859        }
860
861        public FieldInfo(ClassInfo declaringClass, String name, String type) {
862            this.declaringClass = declaringClass;
863            this.name = name;
864            this.type = type;
865        }
866
867        public String getName() {
868            return name;
869        }
870
871        public ClassInfo getDeclaringClass() {
872            return declaringClass;
873        }
874
875        public String getType() {
876            return type;
877        }
878
879        public String toString() {
880            return declaringClass + "#" + name;
881        }
882    }
883
884    public class AnnotationInfo extends Annotatable implements Info {
885        private final String name;
886
887        public AnnotationInfo(Annotation annotation){
888            this(annotation.getClass().getName());
889        }
890
891        public AnnotationInfo(Class<? extends Annotation> annotation) {
892            this.name = annotation.getName().intern();
893        }
894
895        public AnnotationInfo(String name) {
896            name = name.replaceAll("^L|;$", "");
897            name = name.replace('/', '.');
898            this.name = name.intern();
899        }
900
901        public String getName() {
902            return name;
903        }
904
905        public String toString() {
906            return name;
907        }
908    }
909
910    public class InfoBuildingVisitor extends EmptyVisitor {
911        private Info info;
912        private String path;
913
914        public InfoBuildingVisitor(String path) {
915            this.path = path;
916        }
917
918        public InfoBuildingVisitor(Info info) {
919            this.info = info;
920        }
921
922        @Override
923        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
924            if (name.endsWith("package-info")) {
925                info = new PackageInfo(javaName(name));
926            } else {
927                ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName));
928                classInfo.path = path;
929//                if (signature == null) {
930                    for (String interfce : interfaces) {
931                        classInfo.getInterfaces().add(javaName(interfce));
932                    }
933//                } else {
934//                    // the class uses generics
935//                    new SignatureReader(signature).accept(new GenericAwareInfoBuildingVisitor(GenericAwareInfoBuildingVisitor.TYPE.CLASS, classInfo));
936//                }
937                info = classInfo;
938                classInfos.put(classInfo.getName(), classInfo);
939            }
940        }
941
942        private String javaName(String name) {
943            return (name == null)? null:name.replace('/', '.');
944        }
945
946        @Override
947        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
948            AnnotationInfo annotationInfo = new AnnotationInfo(desc);
949            info.getAnnotations().add(annotationInfo);
950            getAnnotationInfos(annotationInfo.getName()).add(info);
951            return new InfoBuildingVisitor(annotationInfo).annotationVisitor();
952        }
953
954        @Override
955        public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
956            ClassInfo classInfo = ((ClassInfo) info);
957            FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc);
958            classInfo.getFields().add(fieldInfo);
959            return new InfoBuildingVisitor(fieldInfo).fieldVisitor();
960        }
961
962        @Override
963        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
964            ClassInfo classInfo = ((ClassInfo) info);
965            MethodInfo methodInfo = new MethodInfo(classInfo, name, desc);
966            classInfo.getMethods().add(methodInfo);
967            return new InfoBuildingVisitor(methodInfo).methodVisitor();
968        }
969
970        @Override
971        public AnnotationVisitor visitMethodParameterAnnotation(int param, String desc, boolean visible) {
972            MethodInfo methodInfo = ((MethodInfo) info);
973            List<AnnotationInfo> annotationInfos = methodInfo.getParameterAnnotations(param);
974            AnnotationInfo annotationInfo = new AnnotationInfo(desc);
975            annotationInfos.add(annotationInfo);
976            return new InfoBuildingVisitor(annotationInfo).annotationVisitor();
977        }
978    }
979}