001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.xbean.finder;
018    
019    import org.objectweb.asm.AnnotationVisitor;
020    import org.objectweb.asm.ClassReader;
021    import org.objectweb.asm.FieldVisitor;
022    import org.objectweb.asm.MethodVisitor;
023    import org.objectweb.asm.commons.EmptyVisitor;
024    
025    import java.io.File;
026    import java.io.IOException;
027    import java.io.InputStream;
028    import java.lang.annotation.Annotation;
029    import java.lang.reflect.Constructor;
030    import java.lang.reflect.Field;
031    import java.lang.reflect.Method;
032    import java.lang.reflect.AnnotatedElement;
033    import java.net.URL;
034    import java.net.JarURLConnection;
035    import java.net.URLDecoder;
036    import java.util.ArrayList;
037    import java.util.Arrays;
038    import java.util.Collection;
039    import java.util.Collections;
040    import java.util.Enumeration;
041    import java.util.HashMap;
042    import java.util.List;
043    import java.util.Map;
044    import java.util.jar.JarEntry;
045    import java.util.jar.JarInputStream;
046    
047    /**
048     * ClassFinder searches the classpath of the specified classloader for
049     * packages, classes, constructors, methods, or fields with specific annotations.
050     *
051     * For security reasons ASM is used to find the annotations.  Classes are not
052     * loaded unless they match the requirements of a called findAnnotated* method.
053     * Once loaded, these classes are cached.
054     *
055     * The getClassesNotLoaded() method can be used immediately after any find*
056     * method to get a list of classes which matched the find requirements (i.e.
057     * contained the annotation), but were unable to be loaded.
058     *
059     * @author David Blevins
060     * @version $Rev: 731945 $ $Date: 2009-01-06 09:19:28 -0500 (Tue, 06 Jan 2009) $
061     */
062    public class ClassFinder {
063        private final Map<String, List<Info>> annotated = new HashMap<String, List<Info>>();
064        private final List<ClassInfo> classInfos = new ArrayList<ClassInfo>();
065    
066        private final ClassLoader classLoader;
067        private final List<String> classesNotLoaded = new ArrayList<String>();
068        private final int ASM_FLAGS = ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES;
069    
070        /**
071         * Creates a ClassFinder that will search the urls in the specified classloader
072         * excluding the urls in the classloader's parent.
073         *
074         * To include the parent classloader, use:
075         *
076         *    new ClassFinder(classLoader, false);
077         *
078         * To exclude the parent's parent, use:
079         *
080         *    new ClassFinder(classLoader, classLoader.getParent().getParent());
081         *
082         * @param classLoader source of classes to scan
083         * @throws Exception if something goes wrong
084         */
085        public ClassFinder(ClassLoader classLoader) throws Exception {
086            this(classLoader, true);
087        }
088    
089        /**
090         * Creates a ClassFinder that will search the urls in the specified classloader.
091         *
092         * @param classLoader source of classes to scan
093         * @param excludeParent Allegedly excludes classes from parent classloader, whatever that might mean
094         * @throws Exception if something goes wrong.
095         */
096        public ClassFinder(ClassLoader classLoader, boolean excludeParent) throws Exception {
097            this(classLoader, getUrls(classLoader, excludeParent));
098        }
099    
100        /**
101         * Creates a ClassFinder that will search the urls in the specified classloader excluding
102         * the urls in the 'exclude' classloader.
103         *
104         * @param classLoader source of classes to scan
105         * @param exclude source of classes to exclude from scanning
106         * @throws Exception if something goes wrong
107         */
108        public ClassFinder(ClassLoader classLoader, ClassLoader exclude) throws Exception {
109            this(classLoader, getUrls(classLoader, exclude));
110        }
111    
112        public ClassFinder(ClassLoader classLoader, URL url) {
113            this(classLoader, Arrays.asList(url));
114        }
115    
116        public ClassFinder(ClassLoader classLoader, Collection<URL> urls) {
117            this.classLoader = classLoader;
118    
119            List<String> classNames = new ArrayList<String>();
120            for (URL location : urls) {
121                try {
122                    if (location.getProtocol().equals("jar")) {
123                        classNames.addAll(jar(location));
124                    } else if (location.getProtocol().equals("file")) {
125                        try {
126                            // See if it's actually a jar
127                            URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/");
128                            JarURLConnection juc = (JarURLConnection) jarUrl.openConnection();
129                            juc.getJarFile();
130                            classNames.addAll(jar(jarUrl));
131                        } catch (IOException e) {
132                            classNames.addAll(file(location));
133                        }
134                    }
135                } catch (Exception e) {
136                    e.printStackTrace();
137                }
138            }
139    
140            for (String className : classNames) {
141                readClassDef(className);
142            }
143        }
144    
145        public ClassFinder(Class... classes){
146            this(Arrays.asList(classes));
147        }
148    
149        public ClassFinder(List<Class> classes){
150            this.classLoader = null;
151            List<Info> infos = new ArrayList<Info>();
152            List<Package> packages = new ArrayList<Package>();
153            for (Class clazz : classes) {
154    
155                Package aPackage = clazz.getPackage();
156                if (aPackage != null && !packages.contains(aPackage)){
157                    infos.add(new PackageInfo(aPackage));
158                    packages.add(aPackage);
159                }
160    
161                ClassInfo classInfo = new ClassInfo(clazz);
162                infos.add(classInfo);
163                classInfos.add(classInfo);
164                for (Method method : clazz.getDeclaredMethods()) {
165                    infos.add(new MethodInfo(classInfo, method));
166                }
167    
168                for (Constructor constructor : clazz.getConstructors()) {
169                    infos.add(new MethodInfo(classInfo, constructor));
170                }
171    
172                for (Field field : clazz.getDeclaredFields()) {
173                    infos.add(new FieldInfo(classInfo, field));
174                }
175            }
176    
177            for (Info info : infos) {
178                for (AnnotationInfo annotation : info.getAnnotations()) {
179                    List<Info> annotationInfos = getAnnotationInfos(annotation.getName());
180                    annotationInfos.add(info);
181                }
182            }
183        }
184    
185        public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
186            List<Info> infos = annotated.get(annotation.getName());
187            return infos != null && !infos.isEmpty();
188        }
189    
190        /**
191         * Returns a list of classes that could not be loaded in last invoked findAnnotated* method.
192         * <p/>
193         * The list will only contain entries of classes whose byte code matched the requirements
194         * of last invoked find* method, but were unable to be loaded and included in the results.
195         * <p/>
196         * The list returned is unmodifiable.  Once obtained, the returned list will be a live view of the
197         * results from the last findAnnotated* method call.
198         * <p/>
199         * This method is not thread safe.
200         * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call.
201         */
202        public List<String> getClassesNotLoaded() {
203            return Collections.unmodifiableList(classesNotLoaded);
204        }
205    
206        public List<Package> findAnnotatedPackages(Class<? extends Annotation> annotation) {
207            classesNotLoaded.clear();
208            List<Package> packages = new ArrayList<Package>();
209            List<Info> infos = getAnnotationInfos(annotation.getName());
210            for (Info info : infos) {
211                if (info instanceof PackageInfo) {
212                    PackageInfo packageInfo = (PackageInfo) info;
213                    try {
214                        Package pkg = packageInfo.get();
215                        // double check via proper reflection
216                        if (pkg.isAnnotationPresent(annotation)) {
217                            packages.add(pkg);
218                        }
219                    } catch (ClassNotFoundException e) {
220                        classesNotLoaded.add(packageInfo.getName());
221                    }
222                }
223            }
224            return packages;
225        }
226    
227        public List<Class> findAnnotatedClasses(Class<? extends Annotation> annotation) {
228            classesNotLoaded.clear();
229            List<Class> classes = new ArrayList<Class>();
230            List<Info> infos = getAnnotationInfos(annotation.getName());
231            for (Info info : infos) {
232                if (info instanceof ClassInfo) {
233                    ClassInfo classInfo = (ClassInfo) info;
234                    try {
235                        Class clazz = classInfo.get();
236                        // double check via proper reflection
237                        if (clazz.isAnnotationPresent(annotation)) {
238                            classes.add(clazz);
239                        }
240                    } catch (ClassNotFoundException e) {
241                        classesNotLoaded.add(classInfo.getName());
242                    }
243                }
244            }
245            return classes;
246        }
247    
248        /**
249         * Naive implementation - works extremelly slow O(n^3)
250         *
251         * @param annotation
252         * @return list of directly or indirectly (inherited) annotated classes
253         */
254        public List<Class> findInheritedAnnotatedClasses(Class<? extends Annotation> annotation) {
255            classesNotLoaded.clear();
256            List<Class> classes = new ArrayList<Class>();
257            List<Info> infos = getAnnotationInfos(annotation.getName());
258            for (Info info : infos) {
259                try {
260                    classes.add(((ClassInfo) info).get());
261                } catch (ClassNotFoundException cnfe) {
262                    // TODO: ignored, but a log message would be appropriate
263                }
264            }
265            boolean annClassFound;
266            List<ClassInfo> tempClassInfos = new ArrayList<ClassInfo>(classInfos);
267            do {
268                annClassFound = false;
269                for (int pos = 0; pos < tempClassInfos.size(); pos++) {
270                    ClassInfo classInfo = tempClassInfos.get(pos);
271                    try {
272                        String superType = classInfo.getSuperType();
273                        for (Class clazz : classes) {
274                            if (superType.equals(clazz.getName())) {
275                                classes.add(classInfo.get());
276                                tempClassInfos.remove(pos);
277                                annClassFound = true;
278                                break;
279                            }
280                        }
281                    } catch (ClassNotFoundException e) {
282                        classesNotLoaded.add(classInfo.getName());
283                    } catch (NoClassDefFoundError e) {
284                        classesNotLoaded.add(classInfo.getName());
285                    }
286                }
287            } while (annClassFound);
288            return classes;
289        }
290    
291        public List<Method> findAnnotatedMethods(Class<? extends Annotation> annotation) {
292            classesNotLoaded.clear();
293            List<ClassInfo> seen = new ArrayList<ClassInfo>();
294            List<Method> methods = new ArrayList<Method>();
295            List<Info> infos = getAnnotationInfos(annotation.getName());
296            for (Info info : infos) {
297                if (info instanceof MethodInfo && !info.getName().equals("<init>")) {
298                    MethodInfo methodInfo = (MethodInfo) info;
299                    ClassInfo classInfo = methodInfo.getDeclaringClass();
300    
301                    if (seen.contains(classInfo)) continue;
302    
303                    seen.add(classInfo);
304    
305                    try {
306                        Class clazz = classInfo.get();
307                        for (Method method : clazz.getDeclaredMethods()) {
308                            if (method.isAnnotationPresent(annotation)) {
309                                methods.add(method);
310                            }
311                        }
312                    } catch (ClassNotFoundException e) {
313                        classesNotLoaded.add(classInfo.getName());
314                    }
315                }
316            }
317            return methods;
318        }
319    
320        public List<Constructor> findAnnotatedConstructors(Class<? extends Annotation> annotation) {
321            classesNotLoaded.clear();
322            List<ClassInfo> seen = new ArrayList<ClassInfo>();
323            List<Constructor> constructors = new ArrayList<Constructor>();
324            List<Info> infos = getAnnotationInfos(annotation.getName());
325            for (Info info : infos) {
326                if (info instanceof MethodInfo && info.getName().equals("<init>")) {
327                    MethodInfo methodInfo = (MethodInfo) info;
328                    ClassInfo classInfo = methodInfo.getDeclaringClass();
329    
330                    if (seen.contains(classInfo)) continue;
331    
332                    seen.add(classInfo);
333    
334                    try {
335                        Class clazz = classInfo.get();
336                        for (Constructor constructor : clazz.getConstructors()) {
337                            if (constructor.isAnnotationPresent(annotation)) {
338                                constructors.add(constructor);
339                            }
340                        }
341                    } catch (ClassNotFoundException e) {
342                        classesNotLoaded.add(classInfo.getName());
343                    }
344                }
345            }
346            return constructors;
347        }
348    
349        public List<Field> findAnnotatedFields(Class<? extends Annotation> annotation) {
350            classesNotLoaded.clear();
351            List<ClassInfo> seen = new ArrayList<ClassInfo>();
352            List<Field> fields = new ArrayList<Field>();
353            List<Info> infos = getAnnotationInfos(annotation.getName());
354            for (Info info : infos) {
355                if (info instanceof FieldInfo) {
356                    FieldInfo fieldInfo = (FieldInfo) info;
357                    ClassInfo classInfo = fieldInfo.getDeclaringClass();
358    
359                    if (seen.contains(classInfo)) continue;
360    
361                    seen.add(classInfo);
362    
363                    try {
364                        Class clazz = classInfo.get();
365                        for (Field field : clazz.getDeclaredFields()) {
366                            if (field.isAnnotationPresent(annotation)) {
367                                fields.add(field);
368                            }
369                        }
370                    } catch (ClassNotFoundException e) {
371                        classesNotLoaded.add(classInfo.getName());
372                    }
373                }
374            }
375            return fields;
376        }
377    
378        public List<Class> findClassesInPackage(String packageName, boolean recursive) {
379            classesNotLoaded.clear();
380            List<Class> classes = new ArrayList<Class>();
381            for (ClassInfo classInfo : classInfos) {
382                try {
383                    if (recursive && classInfo.getPackageName().startsWith(packageName)){
384                        classes.add(classInfo.get());
385                    } else if (classInfo.getPackageName().equals(packageName)){
386                        classes.add(classInfo.get());
387                    }
388                } catch (ClassNotFoundException e) {
389                    classesNotLoaded.add(classInfo.getName());
390                }
391            }
392            return classes;
393        }
394    
395        private static Collection<URL> getUrls(ClassLoader classLoader, boolean excludeParent) throws IOException {
396            return getUrls(classLoader, excludeParent? classLoader.getParent() : null);
397        }
398    
399        private static Collection<URL> getUrls(ClassLoader classLoader, ClassLoader excludeParent) throws IOException {
400            UrlSet urlSet = new UrlSet(classLoader);
401            if (excludeParent != null){
402                urlSet = urlSet.exclude(excludeParent);
403            }
404            return urlSet.getUrls();
405        }
406    
407        private List<String> file(URL location) {
408            List<String> classNames = new ArrayList<String>();
409            File dir = new File(URLDecoder.decode(location.getPath()));
410            if (dir.getName().equals("META-INF")) {
411                dir = dir.getParentFile(); // Scrape "META-INF" off
412            }
413            if (dir.isDirectory()) {
414                scanDir(dir, classNames, "");
415            }
416            return classNames;
417        }
418    
419        private void scanDir(File dir, List<String> classNames, String packageName) {
420            File[] files = dir.listFiles();
421            for (File file : files) {
422                if (file.isDirectory()) {
423                    scanDir(file, classNames, packageName + file.getName() + ".");
424                } else if (file.getName().endsWith(".class")) {
425                    String name = file.getName();
426                    name = name.replaceFirst(".class$", "");
427                    classNames.add(packageName + name);
428                }
429            }
430        }
431    
432        private List<String> jar(URL location) throws IOException {
433            String jarPath = location.getFile();
434            if (jarPath.indexOf("!") > -1){
435                jarPath = jarPath.substring(0, jarPath.indexOf("!"));
436            }
437            URL url = new URL(jarPath);
438            InputStream in = url.openStream();
439            try {
440                JarInputStream jarStream = new JarInputStream(in);
441                return jar(jarStream);
442            } finally {
443                in.close();
444            }
445        }
446    
447        private List<String> jar(JarInputStream jarStream) throws IOException {
448            List<String> classNames = new ArrayList<String>();
449    
450            JarEntry entry;
451            while ((entry = jarStream.getNextJarEntry()) != null) {
452                if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
453                    continue;
454                }
455                String className = entry.getName();
456                className = className.replaceFirst(".class$", "");
457                className = className.replace('/', '.');
458                classNames.add(className);
459            }
460    
461            return classNames;
462        }
463    
464        public class Annotatable {
465            private final List<AnnotationInfo> annotations = new ArrayList<AnnotationInfo>();
466    
467            public Annotatable(AnnotatedElement element) {
468                for (Annotation annotation : element.getAnnotations()) {
469                    annotations.add(new AnnotationInfo(annotation.annotationType().getName()));
470                }
471            }
472    
473            public Annotatable() {
474            }
475    
476            public List<AnnotationInfo> getAnnotations() {
477                return annotations;
478            }
479    
480        }
481    
482        public static interface Info {
483            String getName();
484    
485            List<AnnotationInfo> getAnnotations();
486        }
487    
488        public class PackageInfo extends Annotatable implements Info {
489            private final String name;
490            private final ClassInfo info;
491            private final Package pkg;
492    
493            public PackageInfo(Package pkg){
494                super(pkg);
495                this.pkg = pkg;
496                this.name = pkg.getName();
497                this.info = null;
498            }
499    
500            public PackageInfo(String name) {
501                info = new ClassInfo(name, null);
502                this.name = name;
503                this.pkg = null;
504            }
505    
506            public String getName() {
507                return name;
508            }
509    
510            public Package get() throws ClassNotFoundException {
511                return (pkg != null)?pkg:info.get().getPackage();
512            }
513        }
514    
515        public class ClassInfo extends Annotatable implements Info {
516            private final String name;
517            private final List<MethodInfo> methods = new ArrayList<MethodInfo>();
518            private final List<MethodInfo> constructors = new ArrayList<MethodInfo>();
519            private final String superType;
520            private final List<String> interfaces = new ArrayList<String>();
521            private final List<FieldInfo> fields = new ArrayList<FieldInfo>();
522            private Class<?> clazz;
523            private ClassNotFoundException notFound;
524    
525            public ClassInfo(Class clazz) {
526                super(clazz);
527                this.clazz = clazz;
528                this.name = clazz.getName();
529                Class superclass = clazz.getSuperclass();
530                this.superType = superclass != null ? superclass.getName(): null;
531            }
532    
533            public ClassInfo(String name, String superType) {
534                this.name = name;
535                this.superType = superType;
536            }
537    
538            public String getPackageName(){
539                return name.substring(name.lastIndexOf(".")+1, name.length());
540            }
541    
542            public List<MethodInfo> getConstructors() {
543                return constructors;
544            }
545    
546            public List<String> getInterfaces() {
547                return interfaces;
548            }
549    
550            public List<FieldInfo> getFields() {
551                return fields;
552            }
553    
554            public List<MethodInfo> getMethods() {
555                return methods;
556            }
557    
558            public String getName() {
559                return name;
560            }
561    
562            public String getSuperType() {
563                return superType;
564            }
565    
566            public Class get() throws ClassNotFoundException {
567                if (clazz != null) return clazz;
568                if (notFound != null) throw notFound;
569                try {
570                    this.clazz = classLoader.loadClass(name);
571                    return clazz;
572                } catch (ClassNotFoundException notFound) {
573                    classesNotLoaded.add(name);
574                    this.notFound = notFound;
575                    throw notFound;
576                }
577            }
578    
579            public String toString() {
580                return name;
581            }
582        }
583    
584        public class MethodInfo extends Annotatable implements Info {
585            private final ClassInfo declaringClass;
586            private final String returnType;
587            private final String name;
588            private final List<List<AnnotationInfo>> parameterAnnotations = new ArrayList<List<AnnotationInfo>>();
589    
590            public MethodInfo(ClassInfo info, Constructor constructor){
591                super(constructor);
592                this.declaringClass = info;
593                this.name = "<init>";
594                this.returnType = Void.TYPE.getName();
595            }
596    
597            public MethodInfo(ClassInfo info, Method method){
598                super(method);
599                this.declaringClass = info;
600                this.name = method.getName();
601                this.returnType = method.getReturnType().getName();
602            }
603    
604            public MethodInfo(ClassInfo declarignClass, String name, String returnType) {
605                this.declaringClass = declarignClass;
606                this.name = name;
607                this.returnType = returnType;
608            }
609    
610            public List<List<AnnotationInfo>> getParameterAnnotations() {
611                return parameterAnnotations;
612            }
613    
614            public List<AnnotationInfo> getParameterAnnotations(int index) {
615                if (index >= parameterAnnotations.size()) {
616                    for (int i = parameterAnnotations.size(); i <= index; i++) {
617                        List<AnnotationInfo> annotationInfos = new ArrayList<AnnotationInfo>();
618                        parameterAnnotations.add(i, annotationInfos);
619                    }
620                }
621                return parameterAnnotations.get(index);
622            }
623    
624            public String getName() {
625                return name;
626            }
627    
628            public ClassInfo getDeclaringClass() {
629                return declaringClass;
630            }
631    
632            public String getReturnType() {
633                return returnType;
634            }
635    
636            public String toString() {
637                return declaringClass + "@" + name;
638            }
639        }
640    
641        public class FieldInfo extends Annotatable implements Info {
642            private final String name;
643            private final String type;
644            private final ClassInfo declaringClass;
645    
646            public FieldInfo(ClassInfo info, Field field){
647                super(field);
648                this.declaringClass = info;
649                this.name = field.getName();
650                this.type = field.getType().getName();
651            }
652    
653            public FieldInfo(ClassInfo declaringClass, String name, String type) {
654                this.declaringClass = declaringClass;
655                this.name = name;
656                this.type = type;
657            }
658    
659            public String getName() {
660                return name;
661            }
662    
663            public ClassInfo getDeclaringClass() {
664                return declaringClass;
665            }
666    
667            public String getType() {
668                return type;
669            }
670    
671            public String toString() {
672                return declaringClass + "#" + name;
673            }
674        }
675    
676        public class AnnotationInfo extends Annotatable implements Info {
677            private final String name;
678    
679            public AnnotationInfo(Annotation annotation){
680                this(annotation.getClass().getName());
681            }
682    
683            public AnnotationInfo(Class<? extends Annotation> annotation) {
684                this.name = annotation.getName().intern();
685            }
686    
687            public AnnotationInfo(String name) {
688                name = name.replaceAll("^L|;$", "");
689                name = name.replace('/', '.');
690                this.name = name.intern();
691            }
692    
693            public String getName() {
694                return name;
695            }
696    
697            public String toString() {
698                return name;
699            }
700        }
701    
702        private List<Info> getAnnotationInfos(String name) {
703            List<Info> infos = annotated.get(name);
704            if (infos == null) {
705                infos = new ArrayList<Info>();
706                annotated.put(name, infos);
707            }
708            return infos;
709        }
710    
711        private void readClassDef(String className) {
712            if (!className.endsWith(".class")) {
713                className = className.replace('.', '/') + ".class";
714            }
715            try {
716                URL resource = classLoader.getResource(className);
717                if (resource != null) {
718                    InputStream in = resource.openStream();
719                    try {
720                        ClassReader classReader = new ClassReader(in);
721                        classReader.accept(new InfoBuildingVisitor(), ASM_FLAGS);
722                    } finally {
723                        in.close();
724                    }
725                } else {
726                    new Exception("Could not load " + className).printStackTrace();
727                }
728            } catch (IOException e) {
729                e.printStackTrace();
730            }
731    
732        }
733    
734        public class InfoBuildingVisitor extends EmptyVisitor {
735            private Info info;
736    
737            public InfoBuildingVisitor() {
738            }
739    
740            public InfoBuildingVisitor(Info info) {
741                this.info = info;
742            }
743    
744            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
745                if (name.endsWith("package-info")) {
746                    info = new PackageInfo(javaName(name));
747                } else {
748                    ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName));
749    
750                    for (String interfce : interfaces) {
751                        classInfo.getInterfaces().add(javaName(interfce));
752                    }
753                    info = classInfo;
754                    classInfos.add(classInfo);
755                }
756            }
757    
758            private String javaName(String name) {
759                return (name == null)? null:name.replace('/', '.');
760            }
761    
762            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
763                AnnotationInfo annotationInfo = new AnnotationInfo(desc);
764                info.getAnnotations().add(annotationInfo);
765                getAnnotationInfos(annotationInfo.getName()).add(info);
766                return new InfoBuildingVisitor(annotationInfo);
767            }
768    
769            public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
770                ClassInfo classInfo = ((ClassInfo) info);
771                FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc);
772                classInfo.getFields().add(fieldInfo);
773                return new InfoBuildingVisitor(fieldInfo);
774            }
775    
776            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
777                ClassInfo classInfo = ((ClassInfo) info);
778                MethodInfo methodInfo = new MethodInfo(classInfo, name, desc);
779                classInfo.getMethods().add(methodInfo);
780                return new InfoBuildingVisitor(methodInfo);
781            }
782    
783            public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) {
784                MethodInfo methodInfo = ((MethodInfo) info);
785                List<AnnotationInfo> annotationInfos = methodInfo.getParameterAnnotations(param);
786                AnnotationInfo annotationInfo = new AnnotationInfo(desc);
787                annotationInfos.add(annotationInfo);
788                return new InfoBuildingVisitor(annotationInfo);
789            }
790        }
791    }