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