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