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: 811210 $ $Date: 2009-09-03 20:07:39 -0700 (Thu, 03 Sep 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                try {
156                    Package aPackage = clazz.getPackage();
157                    if (aPackage != null && !packages.contains(aPackage)){
158                        infos.add(new PackageInfo(aPackage));
159                        packages.add(aPackage);
160                    }
161    
162                    ClassInfo classInfo = new ClassInfo(clazz);
163                    infos.add(classInfo);
164                    classInfos.add(classInfo);
165                    for (Method method : clazz.getDeclaredMethods()) {
166                        infos.add(new MethodInfo(classInfo, method));
167                    }
168    
169                    for (Constructor constructor : clazz.getConstructors()) {
170                        infos.add(new MethodInfo(classInfo, constructor));
171                    }
172    
173                    for (Field field : clazz.getDeclaredFields()) {
174                        infos.add(new FieldInfo(classInfo, field));
175                    }
176                } catch (NoClassDefFoundError e) {
177                    throw new NoClassDefFoundError("Could not fully load class: " + clazz.getName() + "\n due to:" + e.getMessage() + "\n in classLoader: \n" + clazz.getClassLoader());
178                }
179            }
180    
181            for (Info info : infos) {
182                for (AnnotationInfo annotation : info.getAnnotations()) {
183                    List<Info> annotationInfos = getAnnotationInfos(annotation.getName());
184                    annotationInfos.add(info);
185                }
186            }
187        }
188    
189        public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
190            List<Info> infos = annotated.get(annotation.getName());
191            return infos != null && !infos.isEmpty();
192        }
193    
194        /**
195         * Returns a list of classes that could not be loaded in last invoked findAnnotated* method.
196         * <p/>
197         * The list will only contain entries of classes whose byte code matched the requirements
198         * of last invoked find* method, but were unable to be loaded and included in the results.
199         * <p/>
200         * The list returned is unmodifiable.  Once obtained, the returned list will be a live view of the
201         * results from the last findAnnotated* method call.
202         * <p/>
203         * This method is not thread safe.
204         * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call.
205         */
206        public List<String> getClassesNotLoaded() {
207            return Collections.unmodifiableList(classesNotLoaded);
208        }
209    
210        public List<Package> findAnnotatedPackages(Class<? extends Annotation> annotation) {
211            classesNotLoaded.clear();
212            List<Package> packages = new ArrayList<Package>();
213            List<Info> infos = getAnnotationInfos(annotation.getName());
214            for (Info info : infos) {
215                if (info instanceof PackageInfo) {
216                    PackageInfo packageInfo = (PackageInfo) info;
217                    try {
218                        Package pkg = packageInfo.get();
219                        // double check via proper reflection
220                        if (pkg.isAnnotationPresent(annotation)) {
221                            packages.add(pkg);
222                        }
223                    } catch (ClassNotFoundException e) {
224                        classesNotLoaded.add(packageInfo.getName());
225                    }
226                }
227            }
228            return packages;
229        }
230    
231        public List<Class> findAnnotatedClasses(Class<? extends Annotation> annotation) {
232            classesNotLoaded.clear();
233            List<Class> classes = new ArrayList<Class>();
234            List<Info> infos = getAnnotationInfos(annotation.getName());
235            for (Info info : infos) {
236                if (info instanceof ClassInfo) {
237                    ClassInfo classInfo = (ClassInfo) info;
238                    try {
239                        Class clazz = classInfo.get();
240                        // double check via proper reflection
241                        if (clazz.isAnnotationPresent(annotation)) {
242                            classes.add(clazz);
243                        }
244                    } catch (ClassNotFoundException e) {
245                        classesNotLoaded.add(classInfo.getName());
246                    }
247                }
248            }
249            return classes;
250        }
251    
252        /**
253         * Naive implementation - works extremelly slow O(n^3)
254         *
255         * @param annotation
256         * @return list of directly or indirectly (inherited) annotated classes
257         */
258        public List<Class> findInheritedAnnotatedClasses(Class<? extends Annotation> annotation) {
259            classesNotLoaded.clear();
260            List<Class> classes = new ArrayList<Class>();
261            List<Info> infos = getAnnotationInfos(annotation.getName());
262            for (Info info : infos) {
263                try {
264                    if(info instanceof ClassInfo){
265                       classes.add(((ClassInfo) info).get());
266                    }
267                } catch (ClassNotFoundException cnfe) {
268                    // TODO: ignored, but a log message would be appropriate
269                }
270            }
271            boolean annClassFound;
272            List<ClassInfo> tempClassInfos = new ArrayList<ClassInfo>(classInfos);
273            do {
274                annClassFound = false;
275                for (int pos = 0; pos < tempClassInfos.size(); pos++) {
276                    ClassInfo classInfo = tempClassInfos.get(pos);
277                    try {
278                        String superType = classInfo.getSuperType();
279                        for (Class clazz : classes) {
280                            if (superType.equals(clazz.getName())) {
281                                classes.add(classInfo.get());
282                                tempClassInfos.remove(pos);
283                                annClassFound = true;
284                                break;
285                            }
286                        }
287                    } catch (ClassNotFoundException e) {
288                        classesNotLoaded.add(classInfo.getName());
289                    } catch (NoClassDefFoundError e) {
290                        classesNotLoaded.add(classInfo.getName());
291                    }
292                }
293            } while (annClassFound);
294            return classes;
295        }
296    
297        public List<Method> findAnnotatedMethods(Class<? extends Annotation> annotation) {
298            classesNotLoaded.clear();
299            List<ClassInfo> seen = new ArrayList<ClassInfo>();
300            List<Method> methods = new ArrayList<Method>();
301            List<Info> infos = getAnnotationInfos(annotation.getName());
302            for (Info info : infos) {
303                if (info instanceof MethodInfo && !info.getName().equals("<init>")) {
304                    MethodInfo methodInfo = (MethodInfo) info;
305                    ClassInfo classInfo = methodInfo.getDeclaringClass();
306    
307                    if (seen.contains(classInfo)) continue;
308    
309                    seen.add(classInfo);
310    
311                    try {
312                        Class clazz = classInfo.get();
313                        for (Method method : clazz.getDeclaredMethods()) {
314                            if (method.isAnnotationPresent(annotation)) {
315                                methods.add(method);
316                            }
317                        }
318                    } catch (ClassNotFoundException e) {
319                        classesNotLoaded.add(classInfo.getName());
320                    }
321                }
322            }
323            return methods;
324        }
325    
326        public List<Constructor> findAnnotatedConstructors(Class<? extends Annotation> annotation) {
327            classesNotLoaded.clear();
328            List<ClassInfo> seen = new ArrayList<ClassInfo>();
329            List<Constructor> constructors = new ArrayList<Constructor>();
330            List<Info> infos = getAnnotationInfos(annotation.getName());
331            for (Info info : infos) {
332                if (info instanceof MethodInfo && info.getName().equals("<init>")) {
333                    MethodInfo methodInfo = (MethodInfo) info;
334                    ClassInfo classInfo = methodInfo.getDeclaringClass();
335    
336                    if (seen.contains(classInfo)) continue;
337    
338                    seen.add(classInfo);
339    
340                    try {
341                        Class clazz = classInfo.get();
342                        for (Constructor constructor : clazz.getConstructors()) {
343                            if (constructor.isAnnotationPresent(annotation)) {
344                                constructors.add(constructor);
345                            }
346                        }
347                    } catch (ClassNotFoundException e) {
348                        classesNotLoaded.add(classInfo.getName());
349                    }
350                }
351            }
352            return constructors;
353        }
354    
355        public List<Field> findAnnotatedFields(Class<? extends Annotation> annotation) {
356            classesNotLoaded.clear();
357            List<ClassInfo> seen = new ArrayList<ClassInfo>();
358            List<Field> fields = new ArrayList<Field>();
359            List<Info> infos = getAnnotationInfos(annotation.getName());
360            for (Info info : infos) {
361                if (info instanceof FieldInfo) {
362                    FieldInfo fieldInfo = (FieldInfo) info;
363                    ClassInfo classInfo = fieldInfo.getDeclaringClass();
364    
365                    if (seen.contains(classInfo)) continue;
366    
367                    seen.add(classInfo);
368    
369                    try {
370                        Class clazz = classInfo.get();
371                        for (Field field : clazz.getDeclaredFields()) {
372                            if (field.isAnnotationPresent(annotation)) {
373                                fields.add(field);
374                            }
375                        }
376                    } catch (ClassNotFoundException e) {
377                        classesNotLoaded.add(classInfo.getName());
378                    }
379                }
380            }
381            return fields;
382        }
383    
384        public List<Class> findClassesInPackage(String packageName, boolean recursive) {
385            classesNotLoaded.clear();
386            List<Class> classes = new ArrayList<Class>();
387            for (ClassInfo classInfo : classInfos) {
388                try {
389                    if (recursive && classInfo.getPackageName().startsWith(packageName)){
390                        classes.add(classInfo.get());
391                    } else if (classInfo.getPackageName().equals(packageName)){
392                        classes.add(classInfo.get());
393                    }
394                } catch (ClassNotFoundException e) {
395                    classesNotLoaded.add(classInfo.getName());
396                }
397            }
398            return classes;
399        }
400    
401        private static Collection<URL> getUrls(ClassLoader classLoader, boolean excludeParent) throws IOException {
402            return getUrls(classLoader, excludeParent? classLoader.getParent() : null);
403        }
404    
405        private static Collection<URL> getUrls(ClassLoader classLoader, ClassLoader excludeParent) throws IOException {
406            UrlSet urlSet = new UrlSet(classLoader);
407            if (excludeParent != null){
408                urlSet = urlSet.exclude(excludeParent);
409            }
410            return urlSet.getUrls();
411        }
412    
413        private List<String> file(URL location) {
414            List<String> classNames = new ArrayList<String>();
415            File dir = new File(URLDecoder.decode(location.getPath()));
416            if (dir.getName().equals("META-INF")) {
417                dir = dir.getParentFile(); // Scrape "META-INF" off
418            }
419            if (dir.isDirectory()) {
420                scanDir(dir, classNames, "");
421            }
422            return classNames;
423        }
424    
425        private void scanDir(File dir, List<String> classNames, String packageName) {
426            File[] files = dir.listFiles();
427            for (File file : files) {
428                if (file.isDirectory()) {
429                    scanDir(file, classNames, packageName + file.getName() + ".");
430                } else if (file.getName().endsWith(".class")) {
431                    String name = file.getName();
432                    name = name.replaceFirst(".class$", "");
433                    if (name.contains(".")) continue;
434                    classNames.add(packageName + name);
435                }
436            }
437        }
438    
439        private List<String> jar(URL location) throws IOException {
440            String jarPath = location.getFile();
441            if (jarPath.indexOf("!") > -1){
442                jarPath = jarPath.substring(0, jarPath.indexOf("!"));
443            }
444            URL url = new URL(jarPath);
445            InputStream in = url.openStream();
446            try {
447                JarInputStream jarStream = new JarInputStream(in);
448                return jar(jarStream);
449            } finally {
450                in.close();
451            }
452        }
453    
454        private List<String> jar(JarInputStream jarStream) throws IOException {
455            List<String> classNames = new ArrayList<String>();
456    
457            JarEntry entry;
458            while ((entry = jarStream.getNextJarEntry()) != null) {
459                if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
460                    continue;
461                }
462                String className = entry.getName();
463                className = className.replaceFirst(".class$", "");
464                if (className.contains(".")) continue;
465                className = className.replace('/', '.');
466                classNames.add(className);
467            }
468    
469            return classNames;
470        }
471    
472        public class Annotatable {
473            private final List<AnnotationInfo> annotations = new ArrayList<AnnotationInfo>();
474    
475            public Annotatable(AnnotatedElement element) {
476                for (Annotation annotation : element.getAnnotations()) {
477                    annotations.add(new AnnotationInfo(annotation.annotationType().getName()));
478                }
479            }
480    
481            public Annotatable() {
482            }
483    
484            public List<AnnotationInfo> getAnnotations() {
485                return annotations;
486            }
487    
488        }
489    
490        public static interface Info {
491            String getName();
492    
493            List<AnnotationInfo> getAnnotations();
494        }
495    
496        public class PackageInfo extends Annotatable implements Info {
497            private final String name;
498            private final ClassInfo info;
499            private final Package pkg;
500    
501            public PackageInfo(Package pkg){
502                super(pkg);
503                this.pkg = pkg;
504                this.name = pkg.getName();
505                this.info = null;
506            }
507    
508            public PackageInfo(String name) {
509                info = new ClassInfo(name, null);
510                this.name = name;
511                this.pkg = null;
512            }
513    
514            public String getName() {
515                return name;
516            }
517    
518            public Package get() throws ClassNotFoundException {
519                return (pkg != null)?pkg:info.get().getPackage();
520            }
521        }
522    
523        public class ClassInfo extends Annotatable implements Info {
524            private final String name;
525            private final List<MethodInfo> methods = new ArrayList<MethodInfo>();
526            private final List<MethodInfo> constructors = new ArrayList<MethodInfo>();
527            private final String superType;
528            private final List<String> interfaces = new ArrayList<String>();
529            private final List<FieldInfo> fields = new ArrayList<FieldInfo>();
530            private Class<?> clazz;
531            private ClassNotFoundException notFound;
532    
533            public ClassInfo(Class clazz) {
534                super(clazz);
535                this.clazz = clazz;
536                this.name = clazz.getName();
537                Class superclass = clazz.getSuperclass();
538                this.superType = superclass != null ? superclass.getName(): null;
539            }
540    
541            public ClassInfo(String name, String superType) {
542                this.name = name;
543                this.superType = superType;
544            }
545    
546            public String getPackageName(){
547                      return name.substring(0,name.lastIndexOf("."));
548            }
549    
550            public List<MethodInfo> getConstructors() {
551                return constructors;
552            }
553    
554            public List<String> getInterfaces() {
555                return interfaces;
556            }
557    
558            public List<FieldInfo> getFields() {
559                return fields;
560            }
561    
562            public List<MethodInfo> getMethods() {
563                return methods;
564            }
565    
566            public String getName() {
567                return name;
568            }
569    
570            public String getSuperType() {
571                return superType;
572            }
573    
574            public Class get() throws ClassNotFoundException {
575                if (clazz != null) return clazz;
576                if (notFound != null) throw notFound;
577                try {
578                    this.clazz = classLoader.loadClass(name);
579                    return clazz;
580                } catch (ClassNotFoundException notFound) {
581                    classesNotLoaded.add(name);
582                    this.notFound = notFound;
583                    throw notFound;
584                }
585            }
586    
587            public String toString() {
588                return name;
589            }
590        }
591    
592        public class MethodInfo extends Annotatable implements Info {
593            private final ClassInfo declaringClass;
594            private final String returnType;
595            private final String name;
596            private final List<List<AnnotationInfo>> parameterAnnotations = new ArrayList<List<AnnotationInfo>>();
597    
598            public MethodInfo(ClassInfo info, Constructor constructor){
599                super(constructor);
600                this.declaringClass = info;
601                this.name = "<init>";
602                this.returnType = Void.TYPE.getName();
603            }
604    
605            public MethodInfo(ClassInfo info, Method method){
606                super(method);
607                this.declaringClass = info;
608                this.name = method.getName();
609                this.returnType = method.getReturnType().getName();
610            }
611    
612            public MethodInfo(ClassInfo declarignClass, String name, String returnType) {
613                this.declaringClass = declarignClass;
614                this.name = name;
615                this.returnType = returnType;
616            }
617    
618            public List<List<AnnotationInfo>> getParameterAnnotations() {
619                return parameterAnnotations;
620            }
621    
622            public List<AnnotationInfo> getParameterAnnotations(int index) {
623                if (index >= parameterAnnotations.size()) {
624                    for (int i = parameterAnnotations.size(); i <= index; i++) {
625                        List<AnnotationInfo> annotationInfos = new ArrayList<AnnotationInfo>();
626                        parameterAnnotations.add(i, annotationInfos);
627                    }
628                }
629                return parameterAnnotations.get(index);
630            }
631    
632            public String getName() {
633                return name;
634            }
635    
636            public ClassInfo getDeclaringClass() {
637                return declaringClass;
638            }
639    
640            public String getReturnType() {
641                return returnType;
642            }
643    
644            public String toString() {
645                return declaringClass + "@" + name;
646            }
647        }
648    
649        public class FieldInfo extends Annotatable implements Info {
650            private final String name;
651            private final String type;
652            private final ClassInfo declaringClass;
653    
654            public FieldInfo(ClassInfo info, Field field){
655                super(field);
656                this.declaringClass = info;
657                this.name = field.getName();
658                this.type = field.getType().getName();
659            }
660    
661            public FieldInfo(ClassInfo declaringClass, String name, String type) {
662                this.declaringClass = declaringClass;
663                this.name = name;
664                this.type = type;
665            }
666    
667            public String getName() {
668                return name;
669            }
670    
671            public ClassInfo getDeclaringClass() {
672                return declaringClass;
673            }
674    
675            public String getType() {
676                return type;
677            }
678    
679            public String toString() {
680                return declaringClass + "#" + name;
681            }
682        }
683    
684        public class AnnotationInfo extends Annotatable implements Info {
685            private final String name;
686    
687            public AnnotationInfo(Annotation annotation){
688                this(annotation.getClass().getName());
689            }
690    
691            public AnnotationInfo(Class<? extends Annotation> annotation) {
692                this.name = annotation.getName().intern();
693            }
694    
695            public AnnotationInfo(String name) {
696                name = name.replaceAll("^L|;$", "");
697                name = name.replace('/', '.');
698                this.name = name.intern();
699            }
700    
701            public String getName() {
702                return name;
703            }
704    
705            public String toString() {
706                return name;
707            }
708        }
709    
710        private List<Info> getAnnotationInfos(String name) {
711            List<Info> infos = annotated.get(name);
712            if (infos == null) {
713                infos = new ArrayList<Info>();
714                annotated.put(name, infos);
715            }
716            return infos;
717        }
718    
719        private void readClassDef(String className) {
720            if (!className.endsWith(".class")) {
721                className = className.replace('.', '/') + ".class";
722            }
723            try {
724                URL resource = classLoader.getResource(className);
725                if (resource != null) {
726                    InputStream in = resource.openStream();
727                    try {
728                        ClassReader classReader = new ClassReader(in);
729                        classReader.accept(new InfoBuildingVisitor(), ASM_FLAGS);
730                    } finally {
731                        in.close();
732                    }
733                } else {
734                    new Exception("Could not load " + className).printStackTrace();
735                }
736            } catch (IOException e) {
737                e.printStackTrace();
738            }
739    
740        }
741    
742        public class InfoBuildingVisitor extends EmptyVisitor {
743            private Info info;
744    
745            public InfoBuildingVisitor() {
746            }
747    
748            public InfoBuildingVisitor(Info info) {
749                this.info = info;
750            }
751    
752            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
753                if (name.endsWith("package-info")) {
754                    info = new PackageInfo(javaName(name));
755                } else {
756                    ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName));
757    
758                    for (String interfce : interfaces) {
759                        classInfo.getInterfaces().add(javaName(interfce));
760                    }
761                    info = classInfo;
762                    classInfos.add(classInfo);
763                }
764            }
765    
766            private String javaName(String name) {
767                return (name == null)? null:name.replace('/', '.');
768            }
769    
770            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
771                AnnotationInfo annotationInfo = new AnnotationInfo(desc);
772                info.getAnnotations().add(annotationInfo);
773                getAnnotationInfos(annotationInfo.getName()).add(info);
774                return new InfoBuildingVisitor(annotationInfo);
775            }
776    
777            public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
778                ClassInfo classInfo = ((ClassInfo) info);
779                FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc);
780                classInfo.getFields().add(fieldInfo);
781                return new InfoBuildingVisitor(fieldInfo);
782            }
783    
784            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
785                ClassInfo classInfo = ((ClassInfo) info);
786                MethodInfo methodInfo = new MethodInfo(classInfo, name, desc);
787                classInfo.getMethods().add(methodInfo);
788                return new InfoBuildingVisitor(methodInfo);
789            }
790    
791            public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) {
792                MethodInfo methodInfo = ((MethodInfo) info);
793                List<AnnotationInfo> annotationInfos = methodInfo.getParameterAnnotations(param);
794                AnnotationInfo annotationInfo = new AnnotationInfo(desc);
795                annotationInfos.add(annotationInfo);
796                return new InfoBuildingVisitor(annotationInfo);
797            }
798        }
799    }