View Javadoc
1   package org.apache.maven.tools.plugin.extractor.annotations.scanner;
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 org.apache.maven.artifact.Artifact;
23  import org.apache.maven.plugins.annotations.Component;
24  import org.apache.maven.plugins.annotations.Execute;
25  import org.apache.maven.plugins.annotations.Mojo;
26  import org.apache.maven.plugins.annotations.Parameter;
27  import org.apache.maven.tools.plugin.extractor.ExtractionException;
28  import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ComponentAnnotationContent;
29  import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ExecuteAnnotationContent;
30  import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent;
31  import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ParameterAnnotationContent;
32  import org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors.MojoAnnotationVisitor;
33  import org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors.MojoClassVisitor;
34  import org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors.MojoFieldVisitor;
35  import org.codehaus.plexus.logging.AbstractLogEnabled;
36  import org.codehaus.plexus.util.DirectoryScanner;
37  import org.codehaus.plexus.util.IOUtil;
38  import org.codehaus.plexus.util.StringUtils;
39  import org.codehaus.plexus.util.reflection.Reflector;
40  import org.codehaus.plexus.util.reflection.ReflectorException;
41  import org.objectweb.asm.ClassReader;
42  import org.objectweb.asm.Type;
43  
44  import java.io.BufferedInputStream;
45  import java.io.File;
46  import java.io.FileInputStream;
47  import java.io.IOException;
48  import java.io.InputStream;
49  import java.util.HashMap;
50  import java.util.List;
51  import java.util.Map;
52  import java.util.zip.ZipEntry;
53  import java.util.zip.ZipInputStream;
54  
55  /**
56   * @author Olivier Lamy
57   * @since 3.0
58   */
59  @org.codehaus.plexus.component.annotations.Component( role = MojoAnnotationsScanner.class )
60  public class DefaultMojoAnnotationsScanner
61      extends AbstractLogEnabled
62      implements MojoAnnotationsScanner
63  {
64      private Reflector reflector = new Reflector();
65  
66      public Map<String, MojoAnnotatedClass> scan( MojoAnnotationsScannerRequest request )
67          throws ExtractionException
68      {
69          Map<String, MojoAnnotatedClass> mojoAnnotatedClasses = new HashMap<String, MojoAnnotatedClass>();
70  
71          try
72          {
73              for ( Artifact dependency : request.getDependencies() )
74              {
75                  scan( mojoAnnotatedClasses, dependency.getFile(), request.getIncludePatterns(), dependency, true );
76              }
77  
78              for ( File classDirectory : request.getClassesDirectories() )
79              {
80                  scan( mojoAnnotatedClasses, classDirectory, request.getIncludePatterns(),
81                        request.getProject().getArtifact(), false );
82              }
83          }
84          catch ( IOException e )
85          {
86              throw new ExtractionException( e.getMessage(), e );
87          }
88  
89          return mojoAnnotatedClasses;
90      }
91  
92      protected void scan( Map<String, MojoAnnotatedClass> mojoAnnotatedClasses, File source,
93                           List<String> includePatterns, Artifact artifact, boolean excludeMojo )
94          throws IOException, ExtractionException
95      {
96          if ( source == null || ! source.exists() )
97          {
98              return;
99          }
100 
101         Map<String, MojoAnnotatedClass> scanResult;
102         if ( source.isDirectory() )
103         {
104             scanResult = scanDirectory( source, includePatterns, artifact, excludeMojo );
105         }
106         else
107         {
108             scanResult = scanArchive( source, artifact, excludeMojo );
109         }
110 
111         mojoAnnotatedClasses.putAll( scanResult );
112     }
113 
114     /**
115      * @param archiveFile
116      * @param artifact
117      * @param excludeMojo     for dependencies, we exclude Mojo annotations found
118      * @return annotated classes found
119      * @throws IOException
120      * @throws ExtractionException
121      */
122     protected Map<String, MojoAnnotatedClass> scanArchive( File archiveFile, Artifact artifact, boolean excludeMojo )
123         throws IOException, ExtractionException
124     {
125         Map<String, MojoAnnotatedClass> mojoAnnotatedClasses = new HashMap<String, MojoAnnotatedClass>();
126 
127         ZipInputStream archiveStream = new ZipInputStream( new FileInputStream( archiveFile ) );
128 
129         try
130         {
131             for ( ZipEntry zipEntry = archiveStream.getNextEntry(); zipEntry != null;
132                   zipEntry = archiveStream.getNextEntry() )
133             {
134                 if ( !zipEntry.getName().endsWith( ".class" ) )
135                 {
136                     continue;
137                 }
138 
139                 analyzeClassStream( mojoAnnotatedClasses, archiveStream, artifact, excludeMojo );
140             }
141         }
142         finally
143         {
144             IOUtil.close( archiveStream );
145         }
146 
147         return mojoAnnotatedClasses;
148     }
149 
150     /**
151      * @param classDirectory
152      * @param includePatterns
153      * @param artifact
154      * @param excludeMojo     for dependencies, we exclude Mojo annotations found
155      * @return annotated classes found
156      * @throws IOException
157      * @throws ExtractionException
158      */
159     protected Map<String, MojoAnnotatedClass> scanDirectory( File classDirectory, List<String> includePatterns,
160                                                              Artifact artifact, boolean excludeMojo )
161         throws IOException, ExtractionException
162     {
163         Map<String, MojoAnnotatedClass> mojoAnnotatedClasses = new HashMap<String, MojoAnnotatedClass>();
164 
165         DirectoryScanner scanner = new DirectoryScanner();
166         scanner.setBasedir( classDirectory );
167         scanner.addDefaultExcludes();
168         if ( includePatterns != null )
169         {
170             scanner.setIncludes( includePatterns.toArray( new String[includePatterns.size()] ) );
171         }
172         scanner.scan();
173         String[] classFiles = scanner.getIncludedFiles();
174 
175         for ( String classFile : classFiles )
176         {
177             if ( !classFile.endsWith( ".class" ) )
178             {
179                 continue;
180             }
181 
182             InputStream is = new BufferedInputStream( new FileInputStream( new File( classDirectory, classFile ) ) );
183             try
184             {
185                 analyzeClassStream( mojoAnnotatedClasses, is, artifact, excludeMojo );
186             }
187             finally
188             {
189                 IOUtil.close( is );
190             }
191         }
192         return mojoAnnotatedClasses;
193     }
194 
195     private void analyzeClassStream( Map<String, MojoAnnotatedClass> mojoAnnotatedClasses, InputStream is,
196                                      Artifact artifact, boolean excludeMojo )
197         throws IOException, ExtractionException
198     {
199         MojoClassVisitor mojoClassVisitor = new MojoClassVisitor( getLogger() );
200 
201         ClassReader rdr = new ClassReader( is );
202         rdr.accept( mojoClassVisitor, ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG );
203 
204         analyzeVisitors( mojoClassVisitor );
205 
206         MojoAnnotatedClass mojoAnnotatedClass = mojoClassVisitor.getMojoAnnotatedClass();
207 
208         if ( excludeMojo )
209         {
210             mojoAnnotatedClass.setMojo( null );
211         }
212 
213         if ( mojoAnnotatedClass != null ) // see MPLUGIN-206 we can have intermediate classes without annotations
214         {
215             if ( getLogger().isDebugEnabled() && mojoAnnotatedClass.hasAnnotations() )
216             {
217                 getLogger().debug( "found MojoAnnotatedClass:" + mojoAnnotatedClass.getClassName() + ":"
218                                        + mojoAnnotatedClass );
219             }
220             mojoAnnotatedClass.setArtifact( artifact );
221             mojoAnnotatedClasses.put( mojoAnnotatedClass.getClassName(), mojoAnnotatedClass );
222         }
223     }
224 
225     protected void populateAnnotationContent( Object content, MojoAnnotationVisitor mojoAnnotationVisitor )
226         throws ReflectorException
227     {
228         for ( Map.Entry<String, Object> entry : mojoAnnotationVisitor.getAnnotationValues().entrySet() )
229         {
230             reflector.invoke( content, entry.getKey(), new Object[] { entry.getValue() } );
231         }
232     }
233 
234     protected void analyzeVisitors( MojoClassVisitor mojoClassVisitor )
235         throws ExtractionException
236     {
237         final MojoAnnotatedClass mojoAnnotatedClass = mojoClassVisitor.getMojoAnnotatedClass();
238 
239         try
240         {
241             // @Mojo annotation
242             MojoAnnotationVisitor mojoAnnotationVisitor = mojoClassVisitor.getAnnotationVisitor( Mojo.class );
243             if ( mojoAnnotationVisitor != null )
244             {
245                 MojoAnnotationContent mojoAnnotationContent = new MojoAnnotationContent();
246                 populateAnnotationContent( mojoAnnotationContent, mojoAnnotationVisitor );
247                 mojoAnnotatedClass.setMojo( mojoAnnotationContent );
248             }
249 
250             // @Execute annotation
251             mojoAnnotationVisitor = mojoClassVisitor.getAnnotationVisitor( Execute.class );
252             if ( mojoAnnotationVisitor != null )
253             {
254                 ExecuteAnnotationContent executeAnnotationContent = new ExecuteAnnotationContent();
255                 populateAnnotationContent( executeAnnotationContent, mojoAnnotationVisitor );
256                 mojoAnnotatedClass.setExecute( executeAnnotationContent );
257             }
258 
259             // @Parameter annotations
260             List<MojoFieldVisitor> mojoFieldVisitors = mojoClassVisitor.findFieldWithAnnotation( Parameter.class );
261             for ( MojoFieldVisitor mojoFieldVisitor : mojoFieldVisitors )
262             {
263                 ParameterAnnotationContent parameterAnnotationContent =
264                     new ParameterAnnotationContent( mojoFieldVisitor.getFieldName(), mojoFieldVisitor.getClassName() );
265                 if ( mojoFieldVisitor.getMojoAnnotationVisitor() != null )
266                 {
267                     populateAnnotationContent( parameterAnnotationContent,
268                                                mojoFieldVisitor.getMojoAnnotationVisitor() );
269                 }
270 
271                 mojoAnnotatedClass.getParameters().put( parameterAnnotationContent.getFieldName(),
272                                                         parameterAnnotationContent );
273             }
274 
275             // @Component annotations
276             mojoFieldVisitors = mojoClassVisitor.findFieldWithAnnotation( Component.class );
277             for ( MojoFieldVisitor mojoFieldVisitor : mojoFieldVisitors )
278             {
279                 ComponentAnnotationContent componentAnnotationContent =
280                     new ComponentAnnotationContent( mojoFieldVisitor.getFieldName() );
281 
282                 MojoAnnotationVisitor annotationVisitor = mojoFieldVisitor.getMojoAnnotationVisitor();
283                 if ( annotationVisitor != null )
284                 {
285                     for ( Map.Entry<String, Object> entry : annotationVisitor.getAnnotationValues().entrySet() )
286                     {
287                         String methodName = entry.getKey();
288                         if ( StringUtils.equals( "role", methodName ) )
289                         {
290                             Type type = (Type) entry.getValue();
291                             componentAnnotationContent.setRoleClassName( type.getClassName() );
292                         }
293                         else
294                         {
295                             reflector.invoke( componentAnnotationContent, entry.getKey(),
296                                               new Object[]{ entry.getValue() } );
297                         }
298                     }
299 
300                     if ( StringUtils.isEmpty( componentAnnotationContent.getRoleClassName() ) )
301                     {
302                         componentAnnotationContent.setRoleClassName( mojoFieldVisitor.getClassName() );
303                     }
304                 }
305                 mojoAnnotatedClass.getComponents().put( componentAnnotationContent.getFieldName(),
306                                                         componentAnnotationContent );
307             }
308         }
309         catch ( ReflectorException e )
310         {
311             throw new ExtractionException( e.getMessage(), e );
312         }
313     }
314 }