001package org.apache.maven.tools.plugin.extractor.annotations.scanner;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.maven.artifact.Artifact;
023import org.apache.maven.plugins.annotations.Component;
024import org.apache.maven.plugins.annotations.Execute;
025import org.apache.maven.plugins.annotations.Mojo;
026import org.apache.maven.plugins.annotations.Parameter;
027import org.apache.maven.tools.plugin.extractor.ExtractionException;
028import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ComponentAnnotationContent;
029import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ExecuteAnnotationContent;
030import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent;
031import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ParameterAnnotationContent;
032import org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors.MojoAnnotationVisitor;
033import org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors.MojoClassVisitor;
034import org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors.MojoFieldVisitor;
035import org.codehaus.plexus.logging.AbstractLogEnabled;
036import org.codehaus.plexus.util.DirectoryScanner;
037import org.codehaus.plexus.util.IOUtil;
038import org.codehaus.plexus.util.StringUtils;
039import org.codehaus.plexus.util.reflection.Reflector;
040import org.codehaus.plexus.util.reflection.ReflectorException;
041import org.objectweb.asm.ClassReader;
042import org.objectweb.asm.Type;
043
044import java.io.BufferedInputStream;
045import java.io.File;
046import java.io.FileInputStream;
047import java.io.IOException;
048import java.io.InputStream;
049import java.util.HashMap;
050import java.util.List;
051import java.util.Map;
052import java.util.zip.ZipEntry;
053import java.util.zip.ZipInputStream;
054
055/**
056 * @author Olivier Lamy
057 * @since 3.0
058 */
059@org.codehaus.plexus.component.annotations.Component( role = MojoAnnotationsScanner.class )
060public class DefaultMojoAnnotationsScanner
061    extends AbstractLogEnabled
062    implements MojoAnnotationsScanner
063{
064    private Reflector reflector = new Reflector();
065
066    public Map<String, MojoAnnotatedClass> scan( MojoAnnotationsScannerRequest request )
067        throws ExtractionException
068    {
069        Map<String, MojoAnnotatedClass> mojoAnnotatedClasses = new HashMap<String, MojoAnnotatedClass>();
070
071        try
072        {
073            for ( Artifact dependency : request.getDependencies() )
074            {
075                scan( mojoAnnotatedClasses, dependency.getFile(), request.getIncludePatterns(), dependency, true );
076            }
077
078            for ( File classDirectory : request.getClassesDirectories() )
079            {
080                scan( mojoAnnotatedClasses, classDirectory, request.getIncludePatterns(),
081                      request.getProject().getArtifact(), false );
082            }
083        }
084        catch ( IOException e )
085        {
086            throw new ExtractionException( e.getMessage(), e );
087        }
088
089        return mojoAnnotatedClasses;
090    }
091
092    protected void scan( Map<String, MojoAnnotatedClass> mojoAnnotatedClasses, File source,
093                         List<String> includePatterns, Artifact artifact, boolean excludeMojo )
094        throws IOException, ExtractionException
095    {
096        if ( source == null || ! source.exists() )
097        {
098            return;
099        }
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}