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