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