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