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 java.io.BufferedInputStream; 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.IOException; 026import java.io.InputStream; 027import java.util.HashMap; 028import java.util.List; 029import java.util.Map; 030import java.util.regex.Pattern; 031import java.util.zip.ZipEntry; 032import java.util.zip.ZipInputStream; 033 034import org.apache.maven.artifact.Artifact; 035import org.apache.maven.plugins.annotations.Component; 036import org.apache.maven.plugins.annotations.Execute; 037import org.apache.maven.plugins.annotations.Mojo; 038import org.apache.maven.plugins.annotations.Parameter; 039import org.apache.maven.tools.plugin.extractor.ExtractionException; 040import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ComponentAnnotationContent; 041import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ExecuteAnnotationContent; 042import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent; 043import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ParameterAnnotationContent; 044import org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors.MojoAnnotationVisitor; 045import org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors.MojoClassVisitor; 046import org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors.MojoFieldVisitor; 047import org.codehaus.plexus.logging.AbstractLogEnabled; 048import org.codehaus.plexus.util.DirectoryScanner; 049import org.codehaus.plexus.util.IOUtil; 050import org.codehaus.plexus.util.StringUtils; 051import org.codehaus.plexus.util.reflection.Reflector; 052import org.codehaus.plexus.util.reflection.ReflectorException; 053import org.objectweb.asm.ClassReader; 054import org.objectweb.asm.Type; 055 056/** 057 * @author Olivier Lamy 058 * @since 3.0 059 */ 060@org.codehaus.plexus.component.annotations.Component( role = MojoAnnotationsScanner.class ) 061public class DefaultMojoAnnotationsScanner 062 extends AbstractLogEnabled 063 implements MojoAnnotationsScanner 064{ 065 // classes with a dash must be ignored 066 private static final Pattern SCANNABLE_CLASS = Pattern.compile( "[^-]+\\.class" ); 067 068 private Reflector reflector = new Reflector(); 069 070 public Map<String, MojoAnnotatedClass> scan( MojoAnnotationsScannerRequest request ) 071 throws ExtractionException 072 { 073 Map<String, MojoAnnotatedClass> mojoAnnotatedClasses = new HashMap<String, MojoAnnotatedClass>(); 074 075 try 076 { 077 for ( Artifact dependency : request.getDependencies() ) 078 { 079 scan( mojoAnnotatedClasses, dependency.getFile(), request.getIncludePatterns(), dependency, true ); 080 } 081 082 for ( File classDirectory : request.getClassesDirectories() ) 083 { 084 scan( mojoAnnotatedClasses, classDirectory, request.getIncludePatterns(), 085 request.getProject().getArtifact(), false ); 086 } 087 } 088 catch ( IOException e ) 089 { 090 throw new ExtractionException( e.getMessage(), e ); 091 } 092 093 return mojoAnnotatedClasses; 094 } 095 096 protected void scan( Map<String, MojoAnnotatedClass> mojoAnnotatedClasses, File source, 097 List<String> includePatterns, Artifact artifact, boolean excludeMojo ) 098 throws IOException, ExtractionException 099 { 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}