AsmAnalyzer.java revision 1cf5df38f4bdafa1beb2674ca548ad6d9650766b
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.tools.layoutlib.create;
18
19import org.objectweb.asm.AnnotationVisitor;
20import org.objectweb.asm.Attribute;
21import org.objectweb.asm.ClassReader;
22import org.objectweb.asm.ClassVisitor;
23import org.objectweb.asm.FieldVisitor;
24import org.objectweb.asm.Label;
25import org.objectweb.asm.MethodVisitor;
26import org.objectweb.asm.Opcodes;
27import org.objectweb.asm.Type;
28import org.objectweb.asm.signature.SignatureReader;
29import org.objectweb.asm.signature.SignatureVisitor;
30
31import java.io.IOException;
32import java.util.ArrayList;
33import java.util.Enumeration;
34import java.util.List;
35import java.util.Map;
36import java.util.Map.Entry;
37import java.util.Set;
38import java.util.TreeMap;
39import java.util.regex.Pattern;
40import java.util.zip.ZipEntry;
41import java.util.zip.ZipFile;
42
43/**
44 * Analyzes the input JAR using the ASM java bytecode manipulation library
45 * to list the desired classes and their dependencies.
46 */
47public class AsmAnalyzer {
48
49    // Note: a bunch of stuff has package-level access for unit tests. Consider it private.
50
51    /** Output logger. */
52    private final Log mLog;
53    /** The input source JAR to parse. */
54    private final List<String> mOsSourceJar;
55    /** The generator to fill with the class list and dependency list. */
56    private final AsmGenerator mGen;
57    /** Keep all classes that derive from these one (these included). */
58    private final String[] mDeriveFrom;
59    /** Glob patterns of classes to keep, e.g. "com.foo.*" */
60    private final String[] mIncludeGlobs;
61    /** The set of classes to exclude.*/
62    private final Set<String> mExcludedClasses;
63
64    /**
65     * Creates a new analyzer.
66     *
67     * @param log The log output.
68     * @param osJarPath The input source JARs to parse.
69     * @param gen The generator to fill with the class list and dependency list.
70     * @param deriveFrom Keep all classes that derive from these one (these included).
71     * @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*"
72     *        ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
73     */
74    public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
75            String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses) {
76        mLog = log;
77        mGen = gen;
78        mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
79        mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
80        mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
81        mExcludedClasses = excludeClasses;
82    }
83
84    /**
85     * Starts the analysis using parameters from the constructor.
86     * Fills the generator with classes & dependencies found.
87     */
88    public void analyze() throws IOException, LogAbortException {
89        Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar);
90        mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
91                mOsSourceJar.size() > 1 ? "s" : "");
92
93        Map<String, ClassReader> found = findIncludes(zipClasses);
94        Map<String, ClassReader> deps = findDeps(zipClasses, found);
95
96        if (mGen != null) {
97            mGen.setKeep(found);
98            mGen.setDeps(deps);
99        }
100    }
101
102    /**
103     * Parses a JAR file and returns a list of all classes founds using a map
104     * class name => ASM ClassReader. Class names are in the form "android.view.View".
105     */
106    Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
107        TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
108
109        for (String jarPath : jarPathList) {
110            ZipFile zip = new ZipFile(jarPath);
111            Enumeration<? extends ZipEntry> entries = zip.entries();
112            ZipEntry entry;
113            while (entries.hasMoreElements()) {
114                entry = entries.nextElement();
115                if (entry.getName().endsWith(".class")) {
116                    ClassReader cr = new ClassReader(zip.getInputStream(entry));
117                    String className = classReaderToClassName(cr);
118                    classes.put(className, cr);
119                }
120            }
121        }
122
123        return classes;
124    }
125
126    /**
127     * Utility that returns the fully qualified binary class name for a ClassReader.
128     * E.g. it returns something like android.view.View.
129     */
130    static String classReaderToClassName(ClassReader classReader) {
131        if (classReader == null) {
132            return null;
133        } else {
134            return classReader.getClassName().replace('/', '.');
135        }
136    }
137
138    /**
139     * Utility that returns the fully qualified binary class name from a path-like FQCN.
140     * E.g. it returns android.view.View from android/view/View.
141     */
142    static String internalToBinaryClassName(String className) {
143        if (className == null) {
144            return null;
145        } else {
146            return className.replace('/', '.');
147        }
148    }
149
150    /**
151     * Process the "includes" arrays.
152     * <p/>
153     * This updates the in_out_found map.
154     */
155    Map<String, ClassReader> findIncludes(Map<String, ClassReader> zipClasses)
156            throws LogAbortException {
157        TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
158
159        mLog.debug("Find classes to include.");
160
161        for (String s : mIncludeGlobs) {
162            findGlobs(s, zipClasses, found);
163        }
164        for (String s : mDeriveFrom) {
165            findClassesDerivingFrom(s, zipClasses, found);
166        }
167
168        return found;
169    }
170
171
172    /**
173     * Uses ASM to find the class reader for the given FQCN class name.
174     * If found, insert it in the in_out_found map.
175     * Returns the class reader object.
176     */
177    ClassReader findClass(String className, Map<String, ClassReader> zipClasses,
178            Map<String, ClassReader> inOutFound) throws LogAbortException {
179        ClassReader classReader = zipClasses.get(className);
180        if (classReader == null) {
181            throw new LogAbortException("Class %s not found by ASM in %s",
182                    className, mOsSourceJar);
183        }
184
185        inOutFound.put(className, classReader);
186        return classReader;
187    }
188
189    /**
190     * Insert in the inOutFound map all classes found in zipClasses that match the
191     * given glob pattern.
192     * <p/>
193     * The glob pattern is not a regexp. It only accepts the "*" keyword to mean
194     * "anything but a period". The "." and "$" characters match themselves.
195     * The "**" keyword means everything including ".".
196     * <p/>
197     * Examples:
198     * <ul>
199     * <li>com.foo.* matches all classes in the package com.foo but NOT sub-packages.
200     * <li>com.foo*.*$Event matches all internal Event classes in a com.foo*.* class.
201     * </ul>
202     */
203    void findGlobs(String globPattern, Map<String, ClassReader> zipClasses,
204            Map<String, ClassReader> inOutFound) throws LogAbortException {
205        // transforms the glob pattern in a regexp:
206        // - escape "." with "\."
207        // - replace "*" by "[^.]*"
208        // - escape "$" with "\$"
209        // - add end-of-line match $
210        globPattern = globPattern.replaceAll("\\$", "\\\\\\$");
211        globPattern = globPattern.replaceAll("\\.", "\\\\.");
212        // prevent ** from being altered by the next rule, then process the * rule and finally
213        // the real ** rule (which is now @)
214        globPattern = globPattern.replaceAll("\\*\\*", "@");
215        globPattern = globPattern.replaceAll("\\*", "[^.]*");
216        globPattern = globPattern.replaceAll("@", ".*");
217        globPattern += "$";
218
219        Pattern regexp = Pattern.compile(globPattern);
220
221        for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
222            String class_name = entry.getKey();
223            if (regexp.matcher(class_name).matches()) {
224                findClass(class_name, zipClasses, inOutFound);
225            }
226        }
227    }
228
229    /**
230     * Checks all the classes defined in the JarClassName instance and uses BCEL to
231     * determine if they are derived from the given FQCN super class name.
232     * Inserts the super class and all the class objects found in the map.
233     */
234    void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
235            Map<String, ClassReader> inOutFound) throws LogAbortException {
236        findClass(super_name, zipClasses, inOutFound);
237
238        for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
239            String className = entry.getKey();
240            if (super_name.equals(className)) {
241                continue;
242            }
243            ClassReader classReader = entry.getValue();
244            ClassReader parent_cr = classReader;
245            while (parent_cr != null) {
246                String parent_name = internalToBinaryClassName(parent_cr.getSuperName());
247                if (parent_name == null) {
248                    // not found
249                    break;
250                } else if (super_name.equals(parent_name)) {
251                    inOutFound.put(className, classReader);
252                    break;
253                }
254                parent_cr = zipClasses.get(parent_name);
255            }
256        }
257    }
258
259    /**
260     * Instantiates a new DependencyVisitor. Useful for unit tests.
261     */
262    DependencyVisitor getVisitor(Map<String, ClassReader> zipClasses,
263            Map<String, ClassReader> inKeep,
264            Map<String, ClassReader> outKeep,
265            Map<String, ClassReader> inDeps,
266            Map<String, ClassReader> outDeps) {
267        return new DependencyVisitor(zipClasses, inKeep, outKeep, inDeps, outDeps);
268    }
269
270    /**
271     * Finds all dependencies for all classes in keepClasses which are also
272     * listed in zipClasses. Returns a map of all the dependencies found.
273     */
274    Map<String, ClassReader> findDeps(Map<String, ClassReader> zipClasses,
275            Map<String, ClassReader> inOutKeepClasses) {
276
277        TreeMap<String, ClassReader> deps = new TreeMap<String, ClassReader>();
278        TreeMap<String, ClassReader> new_deps = new TreeMap<String, ClassReader>();
279        TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
280        TreeMap<String, ClassReader> temp = new TreeMap<String, ClassReader>();
281
282        DependencyVisitor visitor = getVisitor(zipClasses,
283                inOutKeepClasses, new_keep,
284                deps, new_deps);
285
286        for (ClassReader cr : inOutKeepClasses.values()) {
287            cr.accept(visitor, 0 /* flags */);
288        }
289
290        while (new_deps.size() > 0 || new_keep.size() > 0) {
291            deps.putAll(new_deps);
292            inOutKeepClasses.putAll(new_keep);
293
294            temp.clear();
295            temp.putAll(new_deps);
296            temp.putAll(new_keep);
297            new_deps.clear();
298            new_keep.clear();
299            mLog.debug("Found %1$d to keep, %2$d dependencies.",
300                    inOutKeepClasses.size(), deps.size());
301
302            for (ClassReader cr : temp.values()) {
303                cr.accept(visitor, 0 /* flags */);
304            }
305        }
306
307        mLog.info("Found %1$d classes to keep, %2$d class dependencies.",
308                inOutKeepClasses.size(), deps.size());
309
310        return deps;
311    }
312
313
314
315    // ----------------------------------
316
317    /**
318     * Visitor to collect all the type dependencies from a class.
319     */
320    public class DependencyVisitor extends ClassVisitor {
321
322        /** All classes found in the source JAR. */
323        private final Map<String, ClassReader> mZipClasses;
324        /** Classes from which dependencies are to be found. */
325        private final Map<String, ClassReader> mInKeep;
326        /** Dependencies already known. */
327        private final Map<String, ClassReader> mInDeps;
328        /** New dependencies found by this visitor. */
329        private final Map<String, ClassReader> mOutDeps;
330        /** New classes to keep as-is found by this visitor. */
331        private final Map<String, ClassReader> mOutKeep;
332
333        /**
334         * Creates a new visitor that will find all the dependencies for the visited class.
335         * Types which are already in the zipClasses, keepClasses or inDeps are not marked.
336         * New dependencies are marked in outDeps.
337         *
338         * @param zipClasses All classes found in the source JAR.
339         * @param inKeep Classes from which dependencies are to be found.
340         * @param inDeps Dependencies already known.
341         * @param outDeps New dependencies found by this visitor.
342         */
343        public DependencyVisitor(Map<String, ClassReader> zipClasses,
344                Map<String, ClassReader> inKeep,
345                Map<String, ClassReader> outKeep,
346                Map<String,ClassReader> inDeps,
347                Map<String,ClassReader> outDeps) {
348            super(Opcodes.ASM4);
349            mZipClasses = zipClasses;
350            mInKeep = inKeep;
351            mOutKeep = outKeep;
352            mInDeps = inDeps;
353            mOutDeps = outDeps;
354        }
355
356        /**
357         * Considers the given class name as a dependency.
358         * If it does, add to the mOutDeps map.
359         */
360        public void considerName(String className) {
361            if (className == null) {
362                return;
363            }
364
365            className = internalToBinaryClassName(className);
366
367            // exclude classes that have already been found or are marked to be excluded
368            if (mInKeep.containsKey(className) ||
369                    mOutKeep.containsKey(className) ||
370                    mInDeps.containsKey(className) ||
371                    mOutDeps.containsKey(className) ||
372                    mExcludedClasses.contains(getBaseName(className))) {
373                return;
374            }
375
376            // exclude classes that are not part of the JAR file being examined
377            ClassReader cr = mZipClasses.get(className);
378            if (cr == null) {
379                return;
380            }
381
382            try {
383                // exclude classes that are part of the default JRE (the one executing this program)
384                if (getClass().getClassLoader().loadClass(className) != null) {
385                    return;
386                }
387            } catch (ClassNotFoundException e) {
388                // ignore
389            }
390
391            // accept this class:
392            // - android classes are added to dependencies
393            // - non-android classes are added to the list of classes to keep as-is (they don't need
394            //   to be stubbed).
395            if (className.indexOf("android") >= 0) {  // TODO make configurable
396                mOutDeps.put(className, cr);
397            } else {
398                mOutKeep.put(className, cr);
399            }
400        }
401
402        /**
403         * Considers this array of names using considerName().
404         */
405        public void considerNames(String[] classNames) {
406            if (classNames != null) {
407                for (String className : classNames) {
408                    considerName(className);
409                }
410            }
411        }
412
413        /**
414         * Considers this signature or type signature by invoking the {@link SignatureVisitor}
415         * on it.
416         */
417        public void considerSignature(String signature) {
418            if (signature != null) {
419                SignatureReader sr = new SignatureReader(signature);
420                // SignatureReader.accept will call accessType so we don't really have
421                // to differentiate where the signature comes from.
422                sr.accept(new MySignatureVisitor());
423            }
424        }
425
426        /**
427         * Considers this {@link Type}. For arrays, the element type is considered.
428         * If the type is an object, it's internal name is considered.
429         */
430        public void considerType(Type t) {
431            if (t != null) {
432                if (t.getSort() == Type.ARRAY) {
433                    t = t.getElementType();
434                }
435                if (t.getSort() == Type.OBJECT) {
436                    considerName(t.getInternalName());
437                }
438            }
439        }
440
441        /**
442         * Considers a descriptor string. The descriptor is converted to a {@link Type}
443         * and then considerType() is invoked.
444         */
445        public void considerDesc(String desc) {
446            if (desc != null) {
447                try {
448                    Type t = Type.getType(desc);
449                    considerType(t);
450                } catch (ArrayIndexOutOfBoundsException e) {
451                    // ignore, not a valid type.
452                }
453            }
454        }
455
456        private String getBaseName(String className) {
457            int pos = className.indexOf('$');
458            if (pos > 0) {
459                return className.substring(0, pos);
460            }
461            return className;
462        }
463
464        // ---------------------------------------------------
465        // --- ClassVisitor, FieldVisitor
466        // ---------------------------------------------------
467
468        // Visits a class header
469        @Override
470        public void visit(int version, int access, String name,
471                String signature, String superName, String[] interfaces) {
472            // signature is the signature of this class. May be null if the class is not a generic
473            // one, and does not extend or implement generic classes or interfaces.
474
475            if (signature != null) {
476                considerSignature(signature);
477            }
478
479            // superName is the internal of name of the super class (see getInternalName).
480            // For interfaces, the super class is Object. May be null but only for the Object class.
481            considerName(superName);
482
483            // interfaces is the internal names of the class's interfaces (see getInternalName).
484            // May be null.
485            considerNames(interfaces);
486        }
487
488
489        @Override
490        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
491            // desc is the class descriptor of the annotation class.
492            considerDesc(desc);
493            return new MyAnnotationVisitor();
494        }
495
496        @Override
497        public void visitAttribute(Attribute attr) {
498            // pass
499        }
500
501        // Visits the end of a class
502        @Override
503        public void visitEnd() {
504            // pass
505        }
506
507        private class MyFieldVisitor extends FieldVisitor {
508
509            public MyFieldVisitor() {
510                super(Opcodes.ASM4);
511            }
512
513            @Override
514            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
515                // desc is the class descriptor of the annotation class.
516                considerDesc(desc);
517                return new MyAnnotationVisitor();
518            }
519
520            @Override
521            public void visitAttribute(Attribute attr) {
522                // pass
523            }
524
525            // Visits the end of a class
526            @Override
527            public void visitEnd() {
528                // pass
529            }
530        }
531
532        @Override
533        public FieldVisitor visitField(int access, String name, String desc,
534                String signature, Object value) {
535            // desc is the field's descriptor (see Type).
536            considerDesc(desc);
537
538            // signature is the field's signature. May be null if the field's type does not use
539            // generic types.
540            considerSignature(signature);
541
542            return new MyFieldVisitor();
543        }
544
545        @Override
546        public void visitInnerClass(String name, String outerName, String innerName, int access) {
547            // name is the internal name of an inner class (see getInternalName).
548            considerName(name);
549        }
550
551        @Override
552        public MethodVisitor visitMethod(int access, String name, String desc,
553                String signature, String[] exceptions) {
554            // desc is the method's descriptor (see Type).
555            considerDesc(desc);
556            // signature is the method's signature. May be null if the method parameters, return
557            // type and exceptions do not use generic types.
558            considerSignature(signature);
559
560            return new MyMethodVisitor();
561        }
562
563        @Override
564        public void visitOuterClass(String owner, String name, String desc) {
565            // pass
566        }
567
568        @Override
569        public void visitSource(String source, String debug) {
570            // pass
571        }
572
573
574        // ---------------------------------------------------
575        // --- MethodVisitor
576        // ---------------------------------------------------
577
578        private class MyMethodVisitor extends MethodVisitor {
579
580            public MyMethodVisitor() {
581                super(Opcodes.ASM4);
582            }
583
584
585            @Override
586            public AnnotationVisitor visitAnnotationDefault() {
587                return new MyAnnotationVisitor();
588            }
589
590            @Override
591            public void visitCode() {
592                // pass
593            }
594
595            // field instruction
596            @Override
597            public void visitFieldInsn(int opcode, String owner, String name, String desc) {
598                // name is the field's name.
599                considerName(name);
600                // desc is the field's descriptor (see Type).
601                considerDesc(desc);
602            }
603
604            @Override
605            public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) {
606                // pass
607            }
608
609            @Override
610            public void visitIincInsn(int var, int increment) {
611                // pass -- an IINC instruction
612            }
613
614            @Override
615            public void visitInsn(int opcode) {
616                // pass -- a zero operand instruction
617            }
618
619            @Override
620            public void visitIntInsn(int opcode, int operand) {
621                // pass -- a single int operand instruction
622            }
623
624            @Override
625            public void visitJumpInsn(int opcode, Label label) {
626                // pass -- a jump instruction
627            }
628
629            @Override
630            public void visitLabel(Label label) {
631                // pass -- a label target
632            }
633
634            // instruction to load a constant from the stack
635            @Override
636            public void visitLdcInsn(Object cst) {
637                if (cst instanceof Type) {
638                    considerType((Type) cst);
639                }
640            }
641
642            @Override
643            public void visitLineNumber(int line, Label start) {
644                // pass
645            }
646
647            @Override
648            public void visitLocalVariable(String name, String desc,
649                    String signature, Label start, Label end, int index) {
650                // desc is the type descriptor of this local variable.
651                considerDesc(desc);
652                // signature is the type signature of this local variable. May be null if the local
653                // variable type does not use generic types.
654                considerSignature(signature);
655            }
656
657            @Override
658            public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
659                // pass -- a lookup switch instruction
660            }
661
662            @Override
663            public void visitMaxs(int maxStack, int maxLocals) {
664                // pass
665            }
666
667            // instruction that invokes a method
668            @Override
669            public void visitMethodInsn(int opcode, String owner, String name, String desc) {
670
671                // owner is the internal name of the method's owner class
672                considerName(owner);
673                // desc is the method's descriptor (see Type).
674                considerDesc(desc);
675            }
676
677            // instruction multianewarray, whatever that is
678            @Override
679            public void visitMultiANewArrayInsn(String desc, int dims) {
680
681                // desc an array type descriptor.
682                considerDesc(desc);
683            }
684
685            @Override
686            public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
687                    boolean visible) {
688                // desc is the class descriptor of the annotation class.
689                considerDesc(desc);
690                return new MyAnnotationVisitor();
691            }
692
693            @Override
694            public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
695                // pass -- table switch instruction
696
697            }
698
699            @Override
700            public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
701                // type is the internal name of the type of exceptions handled by the handler,
702                // or null to catch any exceptions (for "finally" blocks).
703                considerName(type);
704            }
705
706            // type instruction
707            @Override
708            public void visitTypeInsn(int opcode, String type) {
709                // type is the operand of the instruction to be visited. This operand must be the
710                // internal name of an object or array class.
711                considerName(type);
712            }
713
714            @Override
715            public void visitVarInsn(int opcode, int var) {
716                // pass -- local variable instruction
717            }
718        }
719
720        private class MySignatureVisitor extends SignatureVisitor {
721
722            public MySignatureVisitor() {
723                super(Opcodes.ASM4);
724            }
725
726            // ---------------------------------------------------
727            // --- SignatureVisitor
728            // ---------------------------------------------------
729
730            private String mCurrentSignatureClass = null;
731
732            // Starts the visit of a signature corresponding to a class or interface type
733            @Override
734            public void visitClassType(String name) {
735                mCurrentSignatureClass = name;
736                considerName(name);
737            }
738
739            // Visits an inner class
740            @Override
741            public void visitInnerClassType(String name) {
742                if (mCurrentSignatureClass != null) {
743                    mCurrentSignatureClass += "$" + name;
744                    considerName(mCurrentSignatureClass);
745                }
746            }
747
748            @Override
749            public SignatureVisitor visitArrayType() {
750                return new MySignatureVisitor();
751            }
752
753            @Override
754            public void visitBaseType(char descriptor) {
755                // pass -- a primitive type, ignored
756            }
757
758            @Override
759            public SignatureVisitor visitClassBound() {
760                return new MySignatureVisitor();
761            }
762
763            @Override
764            public SignatureVisitor visitExceptionType() {
765                return new MySignatureVisitor();
766            }
767
768            @Override
769            public void visitFormalTypeParameter(String name) {
770                // pass
771            }
772
773            @Override
774            public SignatureVisitor visitInterface() {
775                return new MySignatureVisitor();
776            }
777
778            @Override
779            public SignatureVisitor visitInterfaceBound() {
780                return new MySignatureVisitor();
781            }
782
783            @Override
784            public SignatureVisitor visitParameterType() {
785                return new MySignatureVisitor();
786            }
787
788            @Override
789            public SignatureVisitor visitReturnType() {
790                return new MySignatureVisitor();
791            }
792
793            @Override
794            public SignatureVisitor visitSuperclass() {
795                return new MySignatureVisitor();
796            }
797
798            @Override
799            public SignatureVisitor visitTypeArgument(char wildcard) {
800                return new MySignatureVisitor();
801            }
802
803            @Override
804            public void visitTypeVariable(String name) {
805                // pass
806            }
807
808            @Override
809            public void visitTypeArgument() {
810                // pass
811            }
812        }
813
814
815        // ---------------------------------------------------
816        // --- AnnotationVisitor
817        // ---------------------------------------------------
818
819        private class MyAnnotationVisitor extends AnnotationVisitor {
820
821            public MyAnnotationVisitor() {
822                super(Opcodes.ASM4);
823            }
824
825            // Visits a primitive value of an annotation
826            @Override
827            public void visit(String name, Object value) {
828                // value is the actual value, whose type must be Byte, Boolean, Character, Short,
829                // Integer, Long, Float, Double, String or Type
830                if (value instanceof Type) {
831                    considerType((Type) value);
832                }
833            }
834
835            @Override
836            public AnnotationVisitor visitAnnotation(String name, String desc) {
837                // desc is the class descriptor of the nested annotation class.
838                considerDesc(desc);
839                return new MyAnnotationVisitor();
840            }
841
842            @Override
843            public AnnotationVisitor visitArray(String name) {
844                return new MyAnnotationVisitor();
845            }
846
847            @Override
848            public void visitEnum(String name, String desc, String value) {
849                // desc is the class descriptor of the enumeration class.
850                considerDesc(desc);
851            }
852        }
853    }
854}
855