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