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