001    package org.apache.maven.tools.plugin.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    
022    import org.apache.maven.artifact.Artifact;
023    import org.apache.maven.plugins.annotations.Component;
024    import org.apache.maven.plugins.annotations.Execute;
025    import org.apache.maven.plugins.annotations.Mojo;
026    import org.apache.maven.plugins.annotations.Parameter;
027    import org.apache.maven.tools.plugin.annotations.datamodel.ComponentAnnotationContent;
028    import org.apache.maven.tools.plugin.annotations.datamodel.ExecuteAnnotationContent;
029    import org.apache.maven.tools.plugin.annotations.datamodel.MojoAnnotationContent;
030    import org.apache.maven.tools.plugin.annotations.datamodel.ParameterAnnotationContent;
031    import org.apache.maven.tools.plugin.annotations.scanner.visitors.MojoAnnotationVisitor;
032    import org.apache.maven.tools.plugin.annotations.scanner.visitors.MojoClassVisitor;
033    import org.apache.maven.tools.plugin.annotations.scanner.visitors.MojoFieldVisitor;
034    import org.apache.maven.tools.plugin.extractor.ExtractionException;
035    import org.codehaus.plexus.logging.AbstractLogEnabled;
036    import org.codehaus.plexus.util.DirectoryScanner;
037    import org.codehaus.plexus.util.IOUtil;
038    import org.codehaus.plexus.util.StringUtils;
039    import org.codehaus.plexus.util.reflection.Reflector;
040    import org.objectweb.asm.ClassReader;
041    import org.objectweb.asm.Type;
042    
043    import java.io.BufferedInputStream;
044    import java.io.File;
045    import java.io.FileInputStream;
046    import java.io.IOException;
047    import java.io.InputStream;
048    import java.util.Collections;
049    import java.util.HashMap;
050    import java.util.List;
051    import java.util.Map;
052    import java.util.zip.ZipEntry;
053    import 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 )
060    public 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            try
071            {
072    
073                for ( Artifact dependency : request.getDependencies() )
074                {
075                    File dependencyFile = dependency.getFile();
076                    if ( dependencyFile != null && dependencyFile.exists() )
077                    {
078                        if ( dependencyFile.isDirectory() )
079                        {
080                            mojoAnnotatedClasses.putAll(
081                                scanDirectory( dependencyFile, request.getIncludePatterns(), dependency, true ) );
082                        }
083                        else
084                        {
085                            mojoAnnotatedClasses.putAll(
086                                scanFile( dependencyFile, request.getIncludePatterns(), dependency, true ) );
087                        }
088                    }
089                }
090    
091                for ( File classDirectory : request.getClassesDirectories() )
092                {
093                    if ( classDirectory.exists() && classDirectory.isDirectory() )
094                    {
095                        mojoAnnotatedClasses.putAll(
096                            scanDirectory( classDirectory, request.getIncludePatterns(), request.getProject().getArtifact(),
097                                           false ) );
098                    }
099                }
100    
101                return mojoAnnotatedClasses;
102            }
103            catch ( IOException e )
104            {
105                throw new ExtractionException( e.getMessage(), e );
106            }
107        }
108    
109        /**
110         * @param archiveFile
111         * @param includePatterns
112         * @param artifact
113         * @param excludeMojo     for dependencies we exclude Mojo annotations found
114         * @return
115         * @throws IOException
116         * @throws ExtractionException
117         */
118        protected Map<String, MojoAnnotatedClass> scanFile( File archiveFile, List<String> includePatterns,
119                                                            Artifact artifact, boolean excludeMojo )
120            throws IOException, ExtractionException
121        {
122            if ( !archiveFile.exists() )
123            {
124                return Collections.emptyMap();
125            }
126            Map<String, MojoAnnotatedClass> mojoAnnotatedClasses = new HashMap<String, MojoAnnotatedClass>();
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                        MojoClassVisitor mojoClassVisitor = new MojoClassVisitor( getLogger() );
137    
138                        ClassReader rdr = new ClassReader( archiveStream );
139                        rdr.accept( mojoClassVisitor,
140                                    ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG );
141                        analyzeVisitors( mojoClassVisitor );
142                        if ( excludeMojo )
143                        {
144                            mojoClassVisitor.getMojoAnnotatedClass().setMojo( null );
145                        }
146                        if ( isStoreClass( mojoClassVisitor.getMojoAnnotatedClass() ) != null )
147                        {
148                            getLogger().debug(
149                                "found MojoAnnotatedClass:" + mojoClassVisitor.getMojoAnnotatedClass().getClassName() + ":"
150                                    + mojoClassVisitor.getMojoAnnotatedClass() );
151                            mojoClassVisitor.getMojoAnnotatedClass().setArtifact( artifact );
152                            mojoAnnotatedClasses.put( mojoClassVisitor.getMojoAnnotatedClass().getClassName(),
153                                                      mojoClassVisitor.getMojoAnnotatedClass() );
154                        }
155                    }
156                }
157            }
158            finally
159            {
160                IOUtil.close( archiveStream );
161            }
162            return mojoAnnotatedClasses;
163        }
164    
165        /**
166         * @param classDirectory
167         * @param includePatterns
168         * @param artifact
169         * @param excludeMojo     for dependencies we exclude Mojo annotations found
170         * @return
171         * @throws IOException
172         * @throws ExtractionException
173         */
174        protected Map<String, MojoAnnotatedClass> scanDirectory( File classDirectory, List<String> includePatterns,
175                                                                 Artifact artifact, boolean excludeMojo )
176            throws IOException, ExtractionException
177        {
178            if ( !classDirectory.exists() )
179            {
180                return Collections.emptyMap();
181            }
182            Map<String, MojoAnnotatedClass> mojoAnnotatedClasses = new HashMap<String, MojoAnnotatedClass>();
183            DirectoryScanner scanner = new DirectoryScanner();
184            scanner.setBasedir( classDirectory );
185            scanner.addDefaultExcludes();
186            if ( includePatterns != null )
187            {
188                scanner.setIncludes( includePatterns.toArray( new String[includePatterns.size()] ) );
189            }
190            scanner.scan();
191            String[] classFiles = scanner.getIncludedFiles();
192    
193            for ( String classFile : classFiles )
194            {
195                InputStream is = new BufferedInputStream( new FileInputStream( new File( classDirectory, classFile ) ) );
196                try
197                {
198    
199                    if ( classFile.endsWith( ".class" ) )
200                    {
201                        MojoClassVisitor mojoClassVisitor = new MojoClassVisitor( getLogger() );
202                        ClassReader rdr = new ClassReader( is );
203                        rdr.accept( mojoClassVisitor,
204                                    ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG );
205                        analyzeVisitors( mojoClassVisitor );
206                        if ( excludeMojo )
207                        {
208                            mojoClassVisitor.getMojoAnnotatedClass().setMojo( null );
209                        }
210                        if ( isStoreClass( mojoClassVisitor.getMojoAnnotatedClass() ) != null )
211                        {
212                            getLogger().debug(
213                                "found MojoAnnotatedClass:" + mojoClassVisitor.getMojoAnnotatedClass().getClassName() + ":"
214                                    + mojoClassVisitor.getMojoAnnotatedClass() );
215                            mojoClassVisitor.getMojoAnnotatedClass().setArtifact( artifact );
216                            mojoAnnotatedClasses.put( mojoClassVisitor.getMojoAnnotatedClass().getClassName(),
217                                                      mojoClassVisitor.getMojoAnnotatedClass() );
218                        }
219    
220                    }
221                }
222                finally
223                {
224                    IOUtil.close( is );
225                }
226    
227            }
228            return mojoAnnotatedClasses;
229        }
230    
231        private MojoAnnotatedClass isStoreClass( MojoAnnotatedClass mojoAnnotatedClass )
232        {
233            // see MPLUGIN-206 we can have intermediate classes without annotations
234            if ( mojoAnnotatedClass == null )
235            {
236                return null;
237            }
238            return mojoAnnotatedClass;
239            /**
240             if ( !mojoAnnotatedClass.getComponents().isEmpty() || !mojoAnnotatedClass.getParameters().isEmpty()
241             || mojoAnnotatedClass.getExecute() != null || mojoAnnotatedClass.getMojo() != null )
242             {
243             return mojoAnnotatedClass;
244             }
245             return null;
246             **/
247        }
248    
249    
250        protected void analyzeVisitors( MojoClassVisitor mojoClassVisitor )
251            throws ExtractionException
252        {
253    
254            try
255            {
256                MojoAnnotationVisitor mojoAnnotationVisitor =
257                    mojoClassVisitor.getAnnotationVisitorMap().get( Mojo.class.getName() );
258                if ( mojoAnnotationVisitor != null )
259                {
260                    MojoAnnotationContent mojoAnnotationContent = new MojoAnnotationContent();
261                    for ( Map.Entry<String, Object> entry : mojoAnnotationVisitor.getAnnotationValues().entrySet() )
262                    {
263                        reflector.invoke( mojoAnnotationContent, entry.getKey(), new Object[]{ entry.getValue() } );
264                    }
265                    mojoClassVisitor.getMojoAnnotatedClass().setMojo( mojoAnnotationContent );
266                }
267    
268                mojoAnnotationVisitor = mojoClassVisitor.getAnnotationVisitorMap().get( Execute.class.getName() );
269                if ( mojoAnnotationVisitor != null )
270                {
271                    ExecuteAnnotationContent executeAnnotationContent = new ExecuteAnnotationContent();
272    
273                    for ( Map.Entry<String, Object> entry : mojoAnnotationVisitor.getAnnotationValues().entrySet() )
274                    {
275                        reflector.invoke( executeAnnotationContent, entry.getKey(), new Object[]{ entry.getValue() } );
276                    }
277                    mojoClassVisitor.getMojoAnnotatedClass().setExecute( executeAnnotationContent );
278                }
279    
280                List<MojoFieldVisitor> mojoFieldVisitors =
281                    mojoClassVisitor.findFieldWithAnnotationClass( Parameter.class.getName() );
282    
283                for ( MojoFieldVisitor mojoFieldVisitor : mojoFieldVisitors )
284                {
285                    ParameterAnnotationContent parameterAnnotationContent =
286                        new ParameterAnnotationContent( mojoFieldVisitor.getFieldName(), mojoFieldVisitor.getClassName() );
287                    if ( mojoFieldVisitor.getMojoAnnotationVisitor() != null )
288                    {
289                        for ( Map.Entry<String, Object> entry : mojoFieldVisitor.getMojoAnnotationVisitor().getAnnotationValues().entrySet() )
290                        {
291                            reflector.invoke( parameterAnnotationContent, entry.getKey(),
292                                              new Object[]{ entry.getValue() } );
293                        }
294    
295                    }
296                    mojoClassVisitor.getMojoAnnotatedClass().getParameters().put( parameterAnnotationContent.getFieldName(),
297                                                                                  parameterAnnotationContent );
298                }
299    
300                mojoFieldVisitors = mojoClassVisitor.findFieldWithAnnotationClass( Component.class.getName() );
301    
302                for ( MojoFieldVisitor mojoFieldVisitor : mojoFieldVisitors )
303                {
304                    ComponentAnnotationContent componentAnnotationContent =
305                        new ComponentAnnotationContent( mojoFieldVisitor.getFieldName() );
306    
307                    if ( mojoFieldVisitor.getMojoAnnotationVisitor() != null )
308                    {
309                        for ( Map.Entry<String, Object> entry : mojoFieldVisitor.getMojoAnnotationVisitor().getAnnotationValues().entrySet() )
310                        {
311                            String methodName = entry.getKey();
312                            if ( StringUtils.equals( "role", methodName ) )
313                            {
314                                Type type = (Type) entry.getValue();
315                                componentAnnotationContent.setRoleClassName( type.getClassName() );
316                            }
317                            else
318                            {
319                                reflector.invoke( componentAnnotationContent, entry.getKey(),
320                                                  new Object[]{ entry.getValue() } );
321                            }
322                        }
323                        if ( StringUtils.isEmpty( componentAnnotationContent.getRoleClassName() ) )
324                        {
325                            componentAnnotationContent.setRoleClassName( mojoFieldVisitor.getClassName() );
326                        }
327                    }
328                    mojoClassVisitor.getMojoAnnotatedClass().getComponents().put( componentAnnotationContent.getFieldName(),
329                                                                                  componentAnnotationContent );
330                }
331    
332            }
333            catch ( Exception e )
334            {
335                throw new ExtractionException( e.getMessage(), e );
336            }
337        }
338    }