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    
021    package org.apache.xbean.finder;
022    
023    import java.io.IOException;
024    import java.io.InputStream;
025    import java.lang.annotation.Annotation;
026    import java.lang.reflect.AnnotatedElement;
027    import java.lang.reflect.Constructor;
028    import java.lang.reflect.Field;
029    import java.lang.reflect.Method;
030    import java.net.URL;
031    import java.util.ArrayList;
032    import java.util.Collections;
033    import java.util.HashMap;
034    import java.util.List;
035    import java.util.Map;
036    
037    import org.objectweb.asm.AnnotationVisitor;
038    import org.objectweb.asm.ClassReader;
039    import org.objectweb.asm.FieldVisitor;
040    import org.objectweb.asm.MethodVisitor;
041    import org.objectweb.asm.commons.EmptyVisitor;
042    import org.objectweb.asm.signature.SignatureReader;
043    import org.objectweb.asm.signature.SignatureVisitor;
044    
045    /**
046     * @version $Rev: 924423 $ $Date: 2010-03-17 20:06:14 +0100 (Wed, 17 Mar 2010) $
047     */
048    public abstract class AbstractFinder {
049        private final Map<String, List<Info>> annotated = new HashMap<String, List<Info>>();
050        protected final List<ClassInfo> classInfos = new ArrayList<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 boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
059            List<Info> infos = annotated.get(annotation.getName());
060            return infos != null && !infos.isEmpty();
061        }
062    
063        /**
064         * Returns a list of classes that could not be loaded in last invoked findAnnotated* method.
065         * <p/>
066         * The list will only contain entries of classes whose byte code matched the requirements
067         * of last invoked find* method, but were unable to be loaded and included in the results.
068         * <p/>
069         * The list returned is unmodifiable.  Once obtained, the returned list will be a live view of the
070         * results from the last findAnnotated* method call.
071         * <p/>
072         * This method is not thread safe.
073         * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call.
074         */
075        public List<String> getClassesNotLoaded() {
076            return Collections.unmodifiableList(classesNotLoaded);
077        }
078    
079        public List<Package> findAnnotatedPackages(Class<? extends Annotation> annotation) {
080            classesNotLoaded.clear();
081            List<Package> packages = new ArrayList<Package>();
082            List<Info> infos = getAnnotationInfos(annotation.getName());
083            for (Info info : infos) {
084                if (info instanceof PackageInfo) {
085                    PackageInfo packageInfo = (PackageInfo) info;
086                    try {
087                        Package pkg = packageInfo.get();
088                        // double check via proper reflection
089                        if (pkg.isAnnotationPresent(annotation)) {
090                            packages.add(pkg);
091                        }
092                    } catch (ClassNotFoundException e) {
093                        classesNotLoaded.add(packageInfo.getName());
094                    }
095                }
096            }
097            return packages;
098        }
099    
100        public List<Class> findAnnotatedClasses(Class<? extends Annotation> annotation) {
101            classesNotLoaded.clear();
102            List<Class> classes = new ArrayList<Class>();
103            List<Info> infos = getAnnotationInfos(annotation.getName());
104            for (Info info : infos) {
105                if (info instanceof ClassInfo) {
106                    ClassInfo classInfo = (ClassInfo) info;
107                    try {
108                        Class clazz = classInfo.get();
109                        // double check via proper reflection
110                        if (clazz.isAnnotationPresent(annotation)) {
111                            classes.add(clazz);
112                        }
113                    } catch (ClassNotFoundException e) {
114                        classesNotLoaded.add(classInfo.getName());
115                    }
116                }
117            }
118            return classes;
119        }
120    
121        /**
122         * Naive implementation - works extremelly slow O(n^3)
123         *
124         * @param annotation
125         * @return list of directly or indirectly (inherited) annotated classes
126         */
127        public List<Class> findInheritedAnnotatedClasses(Class<? extends Annotation> annotation) {
128            classesNotLoaded.clear();
129            List<Class> classes = new ArrayList<Class>();
130            List<Info> infos = getAnnotationInfos(annotation.getName());
131            for (Info info : infos) {
132                try {
133                    if(info instanceof ClassInfo){
134                       classes.add(((ClassInfo) info).get());
135                    }
136                } catch (ClassNotFoundException cnfe) {
137                    // TODO: ignored, but a log message would be appropriate
138                }
139            }
140            boolean annClassFound;
141            List<ClassInfo> tempClassInfos = new ArrayList<ClassInfo>(classInfos);
142            do {
143                annClassFound = false;
144                for (int pos = 0; pos < tempClassInfos.size(); pos++) {
145                    ClassInfo classInfo = tempClassInfos.get(pos);
146                    try {
147                        // check whether any superclass is annotated
148                        String superType = classInfo.getSuperType();
149                        for (Class clazz : classes) {
150                            if (superType.equals(clazz.getName())) {
151                                classes.add(classInfo.get());
152                                tempClassInfos.remove(pos);
153                                annClassFound = true;
154                                break;
155                            }
156                        }
157                        // check whether any interface is annotated
158                        List<String> interfces = classInfo.getInterfaces();
159                        for (String interfce: interfces) {
160                            for (Class clazz : classes) {
161                                if (interfce.replaceFirst("<.*>","").equals(clazz.getName())) {
162                                    classes.add(classInfo.get());
163                                    tempClassInfos.remove(pos);
164                                    annClassFound = true;
165                                    break;
166                                }
167                            }
168                        }
169                    } catch (ClassNotFoundException e) {
170                        classesNotLoaded.add(classInfo.getName());
171                    } catch (NoClassDefFoundError e) {
172                        classesNotLoaded.add(classInfo.getName());
173                    }
174                }
175            } while (annClassFound);
176            return classes;
177        }
178    
179        public List<Method> findAnnotatedMethods(Class<? extends Annotation> annotation) {
180            classesNotLoaded.clear();
181            List<ClassInfo> seen = new ArrayList<ClassInfo>();
182            List<Method> methods = new ArrayList<Method>();
183            List<Info> infos = getAnnotationInfos(annotation.getName());
184            for (Info info : infos) {
185                if (info instanceof MethodInfo && !info.getName().equals("<init>")) {
186                    MethodInfo methodInfo = (MethodInfo) info;
187                    ClassInfo classInfo = methodInfo.getDeclaringClass();
188    
189                    if (seen.contains(classInfo)) continue;
190    
191                    seen.add(classInfo);
192    
193                    try {
194                        Class clazz = classInfo.get();
195                        for (Method method : clazz.getDeclaredMethods()) {
196                            if (method.isAnnotationPresent(annotation)) {
197                                methods.add(method);
198                            }
199                        }
200                    } catch (ClassNotFoundException e) {
201                        classesNotLoaded.add(classInfo.getName());
202                    }
203                }
204            }
205            return methods;
206        }
207    
208        public List<Constructor> findAnnotatedConstructors(Class<? extends Annotation> annotation) {
209            classesNotLoaded.clear();
210            List<ClassInfo> seen = new ArrayList<ClassInfo>();
211            List<Constructor> constructors = new ArrayList<Constructor>();
212            List<Info> infos = getAnnotationInfos(annotation.getName());
213            for (Info info : infos) {
214                if (info instanceof MethodInfo && info.getName().equals("<init>")) {
215                    MethodInfo methodInfo = (MethodInfo) info;
216                    ClassInfo classInfo = methodInfo.getDeclaringClass();
217    
218                    if (seen.contains(classInfo)) continue;
219    
220                    seen.add(classInfo);
221    
222                    try {
223                        Class clazz = classInfo.get();
224                        for (Constructor constructor : clazz.getConstructors()) {
225                            if (constructor.isAnnotationPresent(annotation)) {
226                                constructors.add(constructor);
227                            }
228                        }
229                    } catch (ClassNotFoundException e) {
230                        classesNotLoaded.add(classInfo.getName());
231                    }
232                }
233            }
234            return constructors;
235        }
236    
237        public List<Field> findAnnotatedFields(Class<? extends Annotation> annotation) {
238            classesNotLoaded.clear();
239            List<ClassInfo> seen = new ArrayList<ClassInfo>();
240            List<Field> fields = new ArrayList<Field>();
241            List<Info> infos = getAnnotationInfos(annotation.getName());
242            for (Info info : infos) {
243                if (info instanceof FieldInfo) {
244                    FieldInfo fieldInfo = (FieldInfo) info;
245                    ClassInfo classInfo = fieldInfo.getDeclaringClass();
246    
247                    if (seen.contains(classInfo)) continue;
248    
249                    seen.add(classInfo);
250    
251                    try {
252                        Class clazz = classInfo.get();
253                        for (Field field : clazz.getDeclaredFields()) {
254                            if (field.isAnnotationPresent(annotation)) {
255                                fields.add(field);
256                            }
257                        }
258                    } catch (ClassNotFoundException e) {
259                        classesNotLoaded.add(classInfo.getName());
260                    }
261                }
262            }
263            return fields;
264        }
265    
266        public List<Class> findClassesInPackage(String packageName, boolean recursive) {
267            classesNotLoaded.clear();
268            List<Class> classes = new ArrayList<Class>();
269            for (ClassInfo classInfo : classInfos) {
270                try {
271                    if (recursive && classInfo.getPackageName().startsWith(packageName)){
272                        classes.add(classInfo.get());
273                    } else if (classInfo.getPackageName().equals(packageName)){
274                        classes.add(classInfo.get());
275                    }
276                } catch (ClassNotFoundException e) {
277                    classesNotLoaded.add(classInfo.getName());
278                }
279            }
280            return classes;
281        }
282    
283        protected List<Info> getAnnotationInfos(String name) {
284            List<Info> infos = annotated.get(name);
285            if (infos == null) {
286                infos = new ArrayList<Info>();
287                annotated.put(name, infos);
288            }
289            return infos;
290        }
291    
292        protected void readClassDef(InputStream in) throws IOException {
293            ClassReader classReader = new ClassReader(in);
294            classReader.accept(new InfoBuildingVisitor(), ASM_FLAGS);
295        }
296    
297        public class Annotatable {
298            private final List<ClassFinder.AnnotationInfo> annotations = new ArrayList<ClassFinder.AnnotationInfo>();
299    
300            public Annotatable(AnnotatedElement element) {
301                for (Annotation annotation : element.getAnnotations()) {
302                    annotations.add(new ClassFinder.AnnotationInfo(annotation.annotationType().getName()));
303                }
304            }
305    
306            public Annotatable() {
307            }
308    
309            public List<ClassFinder.AnnotationInfo> getAnnotations() {
310                return annotations;
311            }
312    
313        }
314    
315        public static interface Info {
316            String getName();
317    
318            List<ClassFinder.AnnotationInfo> getAnnotations();
319        }
320    
321        public class PackageInfo extends Annotatable implements Info {
322            private final String name;
323            private final ClassFinder.ClassInfo info;
324            private final Package pkg;
325    
326            public PackageInfo(Package pkg){
327                super(pkg);
328                this.pkg = pkg;
329                this.name = pkg.getName();
330                this.info = null;
331            }
332    
333            public PackageInfo(String name) {
334                info = new ClassFinder.ClassInfo(name, null);
335                this.name = name;
336                this.pkg = null;
337            }
338    
339            public String getName() {
340                return name;
341            }
342    
343            public Package get() throws ClassNotFoundException {
344                return (pkg != null)?pkg:info.get().getPackage();
345            }
346        }
347    
348        public class ClassInfo extends Annotatable implements Info {
349            private String name;
350            private final List<ClassFinder.MethodInfo> methods = new ArrayList<ClassFinder.MethodInfo>();
351            private final List<ClassFinder.MethodInfo> constructors = new ArrayList<ClassFinder.MethodInfo>();
352            private String superType;
353            private final List<String> interfaces = new ArrayList<String>();
354            private final List<ClassFinder.FieldInfo> fields = new ArrayList<ClassFinder.FieldInfo>();
355            private Class<?> clazz;
356            private ClassNotFoundException notFound;
357    
358            public ClassInfo(Class clazz) {
359                super(clazz);
360                this.clazz = clazz;
361                this.name = clazz.getName();
362                Class superclass = clazz.getSuperclass();
363                this.superType = superclass != null ? superclass.getName(): null;
364            }
365    
366            public ClassInfo(String name, String superType) {
367                this.name = name;
368                this.superType = superType;
369            }
370    
371            public String getPackageName(){
372                      return name.substring(0,name.lastIndexOf("."));
373            }
374    
375            public List<ClassFinder.MethodInfo> getConstructors() {
376                return constructors;
377            }
378    
379            public List<String> getInterfaces() {
380                return interfaces;
381            }
382    
383            public List<ClassFinder.FieldInfo> getFields() {
384                return fields;
385            }
386    
387            public List<ClassFinder.MethodInfo> getMethods() {
388                return methods;
389            }
390    
391            public String getName() {
392                return name;
393            }
394    
395            public String getSuperType() {
396                return superType;
397            }
398    
399            public Class get() throws ClassNotFoundException {
400                if (clazz != null) return clazz;
401                if (notFound != null) throw notFound;
402                try {
403                    String fixedName = name.replaceFirst("<.*>", "");
404                    this.clazz = loadClass(fixedName);
405                    return clazz;
406                } catch (ClassNotFoundException notFound) {
407                    classesNotLoaded.add(name);
408                    this.notFound = notFound;
409                    throw notFound;
410                }
411            }
412    
413    
414            public String toString() {
415                return name;
416            }
417        }
418    
419        public class MethodInfo extends Annotatable implements Info {
420            private final ClassInfo declaringClass;
421            private final String returnType;
422            private final String name;
423            private final List<List<ClassFinder.AnnotationInfo>> parameterAnnotations = new ArrayList<List<ClassFinder.AnnotationInfo>>();
424    
425            public MethodInfo(ClassInfo info, Constructor constructor){
426                super(constructor);
427                this.declaringClass = info;
428                this.name = "<init>";
429                this.returnType = Void.TYPE.getName();
430            }
431    
432            public MethodInfo(ClassInfo info, Method method){
433                super(method);
434                this.declaringClass = info;
435                this.name = method.getName();
436                this.returnType = method.getReturnType().getName();
437            }
438    
439            public MethodInfo(ClassInfo declarignClass, String name, String returnType) {
440                this.declaringClass = declarignClass;
441                this.name = name;
442                this.returnType = returnType;
443            }
444    
445            public List<List<ClassFinder.AnnotationInfo>> getParameterAnnotations() {
446                return parameterAnnotations;
447            }
448    
449            public List<ClassFinder.AnnotationInfo> getParameterAnnotations(int index) {
450                if (index >= parameterAnnotations.size()) {
451                    for (int i = parameterAnnotations.size(); i <= index; i++) {
452                        List<ClassFinder.AnnotationInfo> annotationInfos = new ArrayList<ClassFinder.AnnotationInfo>();
453                        parameterAnnotations.add(i, annotationInfos);
454                    }
455                }
456                return parameterAnnotations.get(index);
457            }
458    
459            public String getName() {
460                return name;
461            }
462    
463            public ClassInfo getDeclaringClass() {
464                return declaringClass;
465            }
466    
467            public String getReturnType() {
468                return returnType;
469            }
470    
471            public String toString() {
472                return declaringClass + "@" + name;
473            }
474        }
475    
476        public class FieldInfo extends Annotatable implements Info {
477            private final String name;
478            private final String type;
479            private final ClassInfo declaringClass;
480    
481            public FieldInfo(ClassInfo info, Field field){
482                super(field);
483                this.declaringClass = info;
484                this.name = field.getName();
485                this.type = field.getType().getName();
486            }
487    
488            public FieldInfo(ClassInfo declaringClass, String name, String type) {
489                this.declaringClass = declaringClass;
490                this.name = name;
491                this.type = type;
492            }
493    
494            public String getName() {
495                return name;
496            }
497    
498            public ClassInfo getDeclaringClass() {
499                return declaringClass;
500            }
501    
502            public String getType() {
503                return type;
504            }
505    
506            public String toString() {
507                return declaringClass + "#" + name;
508            }
509        }
510    
511        public class AnnotationInfo extends Annotatable implements Info {
512            private final String name;
513    
514            public AnnotationInfo(Annotation annotation){
515                this(annotation.getClass().getName());
516            }
517    
518            public AnnotationInfo(Class<? extends Annotation> annotation) {
519                this.name = annotation.getName().intern();
520            }
521    
522            public AnnotationInfo(String name) {
523                name = name.replaceAll("^L|;$", "");
524                name = name.replace('/', '.');
525                this.name = name.intern();
526            }
527    
528            public String getName() {
529                return name;
530            }
531    
532            public String toString() {
533                return name;
534            }
535        }
536    
537        public class InfoBuildingVisitor extends EmptyVisitor {
538            private Info info;
539    
540            public InfoBuildingVisitor() {
541            }
542    
543            public InfoBuildingVisitor(Info info) {
544                this.info = info;
545            }
546    
547            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
548                if (name.endsWith("package-info")) {
549                    info = new PackageInfo(javaName(name));
550                } else {
551                    ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName));
552    
553                    if (signature == null) {
554                        for (String interfce : interfaces) {
555                            classInfo.getInterfaces().add(javaName(interfce));
556                        }
557                    } else {
558                        // the class uses generics
559                        new SignatureReader(signature).accept(new ClassFinder.GenericAwareInfoBuildingVisitor(ClassFinder.GenericAwareInfoBuildingVisitor.TYPE.CLASS, classInfo));
560                    }
561                    info = classInfo;
562                    classInfos.add(classInfo);
563                }
564            }
565    
566            private String javaName(String name) {
567                return (name == null)? null:name.replace('/', '.');
568            }
569    
570            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
571                AnnotationInfo annotationInfo = new AnnotationInfo(desc);
572                info.getAnnotations().add(annotationInfo);
573                getAnnotationInfos(annotationInfo.getName()).add(info);
574                return new ClassFinder.InfoBuildingVisitor(annotationInfo);
575            }
576    
577            public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
578                ClassInfo classInfo = ((ClassInfo) info);
579                FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc);
580                classInfo.getFields().add(fieldInfo);
581                return new ClassFinder.InfoBuildingVisitor(fieldInfo);
582            }
583    
584            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
585                ClassInfo classInfo = ((ClassInfo) info);
586                MethodInfo methodInfo = new MethodInfo(classInfo, name, desc);
587                classInfo.getMethods().add(methodInfo);
588                return new ClassFinder.InfoBuildingVisitor(methodInfo);
589            }
590    
591            public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) {
592                MethodInfo methodInfo = ((MethodInfo) info);
593                List<AnnotationInfo> annotationInfos = methodInfo.getParameterAnnotations(param);
594                AnnotationInfo annotationInfo = new AnnotationInfo(desc);
595                annotationInfos.add(annotationInfo);
596                return new ClassFinder.InfoBuildingVisitor(annotationInfo);
597            }
598        }
599    
600        public static class GenericAwareInfoBuildingVisitor implements SignatureVisitor {
601    
602            public enum TYPE {
603                CLASS
604            }
605    
606            public enum STATE {
607                BEGIN, END, SUPERCLASS, INTERFACE, FORMAL_TYPE_PARAM
608            }
609    
610            private Info info;
611            private ClassFinder.GenericAwareInfoBuildingVisitor.TYPE type;
612            private ClassFinder.GenericAwareInfoBuildingVisitor.STATE state;
613    
614            private static boolean debug = false;
615    
616            public GenericAwareInfoBuildingVisitor() {
617            }
618    
619            public GenericAwareInfoBuildingVisitor(ClassFinder.GenericAwareInfoBuildingVisitor.TYPE type, Info info) {
620                this.type = type;
621                this.info = info;
622                this.state = ClassFinder.GenericAwareInfoBuildingVisitor.STATE.BEGIN;
623            }
624    
625            public void visitFormalTypeParameter(String s) {
626                if (debug) System.out.println(" s=" + s);
627                switch (state) {
628                    case BEGIN:
629                        ((ClassInfo) info).name += "<" + s;
630                }
631                state = ClassFinder.GenericAwareInfoBuildingVisitor.STATE.FORMAL_TYPE_PARAM;
632            }
633    
634            public SignatureVisitor visitClassBound() {
635                if (debug) System.out.println(" visitClassBound()");
636                return this;
637            }
638    
639            public SignatureVisitor visitInterfaceBound() {
640                if (debug) System.out.println(" visitInterfaceBound()");
641                return this;
642            }
643    
644            public SignatureVisitor visitSuperclass() {
645                if (debug) System.out.println(" visitSuperclass()");
646                state = ClassFinder.GenericAwareInfoBuildingVisitor.STATE.SUPERCLASS;
647                return this;
648            }
649    
650            public SignatureVisitor visitInterface() {
651                if (debug) System.out.println(" visitInterface()");
652                ((ClassInfo) info).getInterfaces().add("");
653                state = ClassFinder.GenericAwareInfoBuildingVisitor.STATE.INTERFACE;
654                return this;
655            }
656    
657            public SignatureVisitor visitParameterType() {
658                if (debug) System.out.println(" visitParameterType()");
659                return this;
660            }
661    
662            public SignatureVisitor visitReturnType() {
663                if (debug) System.out.println(" visitReturnType()");
664                return this;
665            }
666    
667            public SignatureVisitor visitExceptionType() {
668                if (debug) System.out.println(" visitExceptionType()");
669                return this;
670            }
671    
672            public void visitBaseType(char c) {
673                if (debug) System.out.println(" visitBaseType(" + c + ")");
674            }
675    
676            public void visitTypeVariable(String s) {
677                if (debug) System.out.println(" visitTypeVariable(" + s + ")");
678            }
679    
680            public SignatureVisitor visitArrayType() {
681                if (debug) System.out.println(" visitArrayType()");
682                return this;
683            }
684    
685            public void visitClassType(String s) {
686                if (debug) System.out.println(" visitClassType(" + s + ")");
687                switch (state) {
688                    case INTERFACE:
689                        List<String> interfces = ((ClassInfo) info).getInterfaces();
690                        int idx = interfces.size() - 1;
691                        String interfce = interfces.get(idx);
692                        if (interfce.length() == 0) {
693                            interfce = javaName(s);
694                        } else {
695                            interfce += javaName(s);
696                        }
697                        interfces.set(idx, interfce);
698                        break;
699                    case SUPERCLASS:
700                        if (!s.equals("java/lang/Object")) {
701                            ((ClassInfo) info).superType = javaName(s);
702                        }
703                }
704            }
705    
706            public void visitInnerClassType(String s) {
707                if (debug) System.out.println(" visitInnerClassType(" + s + ")");
708            }
709    
710            public void visitTypeArgument() {
711                if (debug) System.out.println(" visitTypeArgument()");
712                switch (state) {
713                    case INTERFACE:
714                        List<String> interfces = ((ClassInfo) info).getInterfaces();
715                        int idx = interfces.size() - 1;
716                        String interfce = interfces.get(idx);
717                        interfce += "<";
718                        interfces.set(idx, interfce);
719                }
720            }
721    
722            public SignatureVisitor visitTypeArgument(char c) {
723                if (debug) System.out.println(" visitTypeArgument(" + c + ")");
724                switch (state) {
725                    case INTERFACE:
726                        List<String> interfces = ((ClassInfo) info).getInterfaces();
727                        int idx = interfces.size() - 1;
728                        String interfce = interfces.get(idx);
729                        interfce += "<";
730                        interfces.set(idx, interfce);
731                }
732                return this;
733            }
734    
735            public void visitEnd() {
736                if (debug) System.out.println(" visitEnd()");
737                switch (state) {
738                    case INTERFACE:
739                        List<String> interfces = ((ClassInfo) info).getInterfaces();
740                        int idx = interfces.size() - 1;
741                        String interfce = interfces.get(idx);
742                        interfce += ">";
743                        interfces.set(idx, interfce);
744                        break;
745                    case FORMAL_TYPE_PARAM:
746                        String name = ((ClassInfo) info).name;
747                        if (name.contains("<")) {
748                            ((ClassInfo) info).name += ">";
749                        }
750                }
751                state = ClassFinder.GenericAwareInfoBuildingVisitor.STATE.END;
752            }
753    
754            private String javaName(String name) {
755                return (name == null)? null:name.replace('/', '.');
756            }
757    
758        }
759    }