View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.stream.Collectors;
30  import java.util.stream.Stream;
31  
32  import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotatedClass;
33  import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotationsScanner;
34  import org.codehaus.plexus.util.StringUtils;
35  import org.objectweb.asm.AnnotationVisitor;
36  import org.objectweb.asm.ClassVisitor;
37  import org.objectweb.asm.FieldVisitor;
38  import org.objectweb.asm.MethodVisitor;
39  import org.objectweb.asm.Opcodes;
40  import org.objectweb.asm.Type;
41  import org.objectweb.asm.signature.SignatureReader;
42  import org.objectweb.asm.util.TraceSignatureVisitor;
43  
44  import static org.apache.maven.tools.plugin.extractor.annotations.scanner.DefaultMojoAnnotationsScanner.PARAMETER_V3;
45  import static org.apache.maven.tools.plugin.extractor.annotations.scanner.DefaultMojoAnnotationsScanner.PARAMETER_V4;
46  
47  /**
48   * Visitor for Mojo classes.
49   *
50   * @author Olivier Lamy
51   * @since 3.0
52   */
53  public class MojoClassVisitor extends ClassVisitor {
54      private MojoAnnotatedClass mojoAnnotatedClass;
55  
56      private Map<String, MojoAnnotationVisitor> annotationVisitorMap = new HashMap<>();
57  
58      private List<MojoFieldVisitor> fieldVisitors = new ArrayList<>();
59  
60      private List<MojoMethodVisitor> methodVisitors = new ArrayList<>();
61  
62      private int version;
63  
64      public MojoClassVisitor() {
65          super(Opcodes.ASM9);
66      }
67  
68      public MojoAnnotatedClass getMojoAnnotatedClass() {
69          return mojoAnnotatedClass;
70      }
71  
72      public int getVersion() {
73          return version;
74      }
75  
76      public MojoAnnotationVisitor getAnnotationVisitor(Class<?> annotation) {
77          return getAnnotationVisitor(annotation.getName());
78      }
79  
80      public MojoAnnotationVisitor getAnnotationVisitor(String name) {
81          return annotationVisitorMap.get(name);
82      }
83  
84      public List<MojoFieldVisitor> findFieldWithAnnotation(Class<?> annotation) {
85          return findFieldWithAnnotation(Collections.singleton(annotation.getName()));
86      }
87  
88      public List<MojoFieldVisitor> findFieldWithAnnotation(Set<String> annotationClassNames) {
89          List<MojoFieldVisitor> mojoFieldVisitors = new ArrayList<>();
90  
91          for (MojoFieldVisitor mojoFieldVisitor : this.fieldVisitors) {
92              Map<String, MojoAnnotationVisitor> filedVisitorMap = mojoFieldVisitor.getAnnotationVisitorMap();
93              if (filedVisitorMap.keySet().stream().anyMatch(annotationClassNames::contains)) {
94                  mojoFieldVisitors.add(mojoFieldVisitor);
95              }
96          }
97  
98          return mojoFieldVisitors;
99      }
100 
101     public List<MojoParameterVisitor> findParameterVisitors() {
102         return findParameterVisitors(new HashSet<>(Arrays.asList(PARAMETER_V3, PARAMETER_V4)));
103     }
104 
105     public List<MojoParameterVisitor> findParameterVisitors(Set<String> annotationClassNames) {
106         return Stream.concat(
107                         findFieldWithAnnotation(annotationClassNames).stream(),
108                         methodVisitors.stream().filter(method -> method.getAnnotationVisitorMap().keySet().stream()
109                                 .anyMatch(annotationClassNames::contains)))
110                 .collect(Collectors.toList());
111     }
112 
113     @Override
114     public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
115         this.version = version;
116         mojoAnnotatedClass = new MojoAnnotatedClass();
117         mojoAnnotatedClass.setClassName(Type.getObjectType(name).getClassName());
118         if (superName != null) {
119             mojoAnnotatedClass.setParentClassName(Type.getObjectType(superName).getClassName());
120         }
121     }
122 
123     @Override
124     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
125         String annotationClassName = Type.getType(desc).getClassName();
126         if (!MojoAnnotationsScanner.CLASS_LEVEL_ANNOTATIONS.contains(annotationClassName)) {
127             return null;
128         }
129         if (annotationClassName.startsWith(MojoAnnotationsScanner.V4_API_ANNOTATIONS_PACKAGE)) {
130             mojoAnnotatedClass.setV4Api(true);
131         }
132         MojoAnnotationVisitor mojoAnnotationVisitor = new MojoAnnotationVisitor(annotationClassName);
133         annotationVisitorMap.put(annotationClassName, mojoAnnotationVisitor);
134         return mojoAnnotationVisitor;
135     }
136 
137     @Override
138     public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
139         List<String> typeParameters = extractTypeParameters(access, signature, true);
140         MojoFieldVisitor mojoFieldVisitor =
141                 new MojoFieldVisitor(name, Type.getType(desc).getClassName(), typeParameters);
142         fieldVisitors.add(mojoFieldVisitor);
143         return mojoFieldVisitor;
144     }
145 
146     /**
147      * Parses the signature according to
148      * <a href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4">JVMS 4.3.4</a>
149      * and returns the type parameters.
150      * @param access
151      * @param signature
152      * @param isField
153      * @return the list of type parameters (may be empty)
154      */
155     private List<String> extractTypeParameters(int access, String signature, boolean isField) {
156         if (signature == null || signature.isEmpty()) {
157             return Collections.emptyList();
158         }
159         TraceSignatureVisitor traceSignatureVisitor = new TraceSignatureVisitor(access);
160         SignatureReader signatureReader = new SignatureReader(signature);
161         if (isField) {
162             signatureReader.acceptType(traceSignatureVisitor);
163         } else {
164             signatureReader.accept(traceSignatureVisitor);
165         }
166         String declaration = traceSignatureVisitor.getDeclaration();
167         int startTypeParameters = declaration.indexOf('<');
168         if (startTypeParameters == -1) {
169             return Collections.emptyList();
170         }
171         String typeParameters = declaration.substring(startTypeParameters + 1, declaration.lastIndexOf('>'));
172         return Arrays.asList(typeParameters.split(", "));
173     }
174 
175     @Override
176     public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
177         if ((access & Opcodes.ACC_PUBLIC) != Opcodes.ACC_PUBLIC
178                 || (access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC) {
179             return null;
180         }
181 
182         if (name.length() < 4 || !(name.startsWith("add") || name.startsWith("set"))) {
183             return null;
184         }
185 
186         Type type = Type.getType(desc);
187 
188         if ("void".equals(type.getReturnType().getClassName()) && type.getArgumentTypes().length == 1) {
189             String fieldName = StringUtils.lowercaseFirstLetter(name.substring(3));
190             String className = type.getArgumentTypes()[0].getClassName();
191             List<String> typeParameters = extractTypeParameters(access, signature, false);
192 
193             MojoMethodVisitor mojoMethodVisitor = new MojoMethodVisitor(fieldName, className, typeParameters);
194             methodVisitors.add(mojoMethodVisitor);
195             return mojoMethodVisitor;
196         }
197 
198         return null;
199     }
200 }