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}