1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package com.jcabi.maven.plugin;
31
32 import com.jcabi.log.Logger;
33 import java.io.File;
34 import java.io.IOException;
35 import java.nio.file.Files;
36 import java.util.Arrays;
37 import java.util.Collection;
38 import java.util.LinkedList;
39 import java.util.List;
40 import java.util.concurrent.CopyOnWriteArrayList;
41 import org.apache.commons.io.FileUtils;
42 import org.apache.commons.io.filefilter.FileFilterUtils;
43 import org.apache.commons.io.filefilter.IOFileFilter;
44 import org.apache.commons.io.filefilter.TrueFileFilter;
45 import org.apache.commons.lang3.StringUtils;
46 import org.apache.maven.artifact.Artifact;
47 import org.apache.maven.artifact.handler.ArtifactHandler;
48 import org.apache.maven.execution.MavenSession;
49 import org.apache.maven.plugin.AbstractMojo;
50 import org.apache.maven.plugin.MojoExecution;
51 import org.apache.maven.plugin.MojoFailureException;
52 import org.apache.maven.plugins.annotations.LifecyclePhase;
53 import org.apache.maven.plugins.annotations.Mojo;
54 import org.apache.maven.plugins.annotations.Parameter;
55 import org.apache.maven.plugins.annotations.ResolutionScope;
56 import org.apache.maven.project.DefaultProjectBuildingRequest;
57 import org.apache.maven.project.MavenProject;
58 import org.apache.maven.project.ProjectBuildingRequest;
59 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
60 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
61 import org.apache.maven.shared.dependency.graph.DependencyNode;
62 import org.aspectj.bridge.IMessage;
63 import org.aspectj.bridge.IMessageHolder;
64 import org.aspectj.tools.ajc.Main;
65 import org.codehaus.plexus.PlexusConstants;
66 import org.codehaus.plexus.PlexusContainer;
67 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
68 import org.codehaus.plexus.context.Context;
69 import org.codehaus.plexus.context.ContextException;
70 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
71 import org.eclipse.aether.RepositorySystemSession;
72 import org.eclipse.aether.util.artifact.JavaScopes;
73 import org.slf4j.impl.StaticLoggerBinder;
74
75
76
77
78
79
80
81 @Mojo(
82 name = "ajc",
83 defaultPhase = LifecyclePhase.PROCESS_CLASSES,
84 threadSafe = true,
85 requiresDependencyResolution = ResolutionScope.COMPILE
86 )
87 @SuppressWarnings({ "PMD.TooManyMethods", "PMD.ExcessiveImports", "PMD.GodClass" })
88 public final class AjcMojo extends AbstractMojo implements Contextualizable {
89
90
91
92
93 private static final String SEP = System.getProperty("path.separator");
94
95
96
97
98 @Parameter(defaultValue = "${project}", readonly = true)
99 private transient MavenProject project;
100
101
102
103
104 @Parameter(defaultValue = "${mojoExecution}", readonly = true)
105 private transient MojoExecution execution;
106
107
108
109
110 @Parameter(defaultValue = "${session}", readonly = true)
111 private transient MavenSession session;
112
113
114
115
116 @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
117 private transient RepositorySystemSession rsession;
118
119
120
121
122
123 @Parameter(defaultValue = "${project.build.outputDirectory}")
124 private transient File classesDirectory;
125
126
127
128
129
130 @Parameter(defaultValue = "${project.build.directory}/unwoven")
131 private transient File unwovenClassesDir;
132
133
134
135
136
137 @Parameter(defaultValue = "false")
138 private transient boolean disableCopy;
139
140
141
142
143
144 @Parameter
145 private transient File[] aspectDirectories;
146
147
148
149
150
151 @Parameter(defaultValue = "${project.build.directory}/jcabi-ajc")
152 private transient File tempDirectory;
153
154
155
156
157 @Parameter
158 private transient String[] scopes;
159
160
161
162
163 private transient PlexusContainer container;
164
165
166
167
168 @Parameter(property = "source", defaultValue = "1.8")
169 private transient String source;
170
171
172
173
174 @Parameter(property = "target", defaultValue = "1.8")
175 private transient String target;
176
177
178
179
180
181 @Parameter(
182 defaultValue = "${project.compileClasspathElements}",
183 required = true,
184 readonly = true
185 )
186 private transient List<String> classpathElements;
187
188
189
190
191 @Parameter(
192 property = "log",
193 defaultValue = "${project.build.directory}/jcabi-ajc.log"
194 )
195 private transient String log;
196
197 @Override
198 public void contextualize(final Context context) throws ContextException {
199 this.container = (PlexusContainer) context
200 .get(PlexusConstants.PLEXUS_KEY);
201 }
202
203 @Override
204 public void execute() throws MojoFailureException {
205 StaticLoggerBinder.getSingleton().setMavenLog(this.getLog());
206 final ArtifactHandler handler = this.project.getArtifact()
207 .getArtifactHandler();
208 if (!"java".equalsIgnoreCase(handler.getLanguage())) {
209 Logger.warn(
210 this,
211
212 "Not executing AJC as the project is not a Java classpath-capable package"
213 );
214 return;
215 }
216 if (this.classesDirectory.mkdirs()) {
217 Logger.info(this, "Created classes dir %s", this.classesDirectory);
218 }
219 if (!this.disableCopy
220 && !this.unwovenClassesDir.equals(this.classesDirectory)) {
221 this.copyUnwovenClasses();
222 }
223 if (this.hasClasses() || this.hasSourceroots()) {
224 try {
225 this.executeAjc();
226 } catch (final IOException ex) {
227 throw new IllegalStateException(ex);
228 }
229 } else {
230 Logger.warn(
231 this,
232
233 "Not executing AJC as there is no .class file or source roots file."
234 );
235 }
236 }
237
238
239
240
241
242
243
244 private void executeAjc() throws MojoFailureException, IOException {
245 if (this.tempDirectory.mkdirs()) {
246 Logger.info(this, "Created temp dir %s", this.tempDirectory);
247 }
248 final Main main = new Main();
249 final IMessageHolder mholder = new AjcMojo.MsgHolder();
250 main.run(
251 new String[] {
252 "-Xset:avoidFinal=true",
253 "-Xlint:warning",
254 "-inpath",
255 this.classesDirectory.getAbsolutePath(),
256 "-sourceroots",
257 this.sourceroots(),
258 "-d",
259 this.tempDirectory.getAbsolutePath(),
260 "-classpath",
261 StringUtils.join(this.classpath(), AjcMojo.SEP),
262 "-aspectpath",
263 this.aspectpath(),
264 "-source",
265 this.source,
266 "-target",
267 this.target,
268 "-g:none",
269 "-encoding",
270 "UTF-8",
271 "-time",
272 "-log",
273 this.log,
274 "-showWeaveInfo",
275 "-warn:constructorName",
276 "-warn:packageDefaultMethod",
277 "-warn:deprecation",
278 "-warn:maskedCatchBlocks",
279 "-warn:unusedLocals",
280 "-warn:unusedArguments",
281 "-warn:unusedImports",
282 "-warn:syntheticAccess",
283 "-warn:assertIdentifier",
284 },
285 mholder
286 );
287 try {
288 FileUtils.copyDirectory(this.tempDirectory, this.classesDirectory);
289 FileUtils.cleanDirectory(this.tempDirectory);
290 } catch (final IOException ex) {
291 throw new MojoFailureException(
292 "failed to copy files and clean temp",
293 ex
294 );
295 }
296 Logger.info(
297 this,
298
299 "ajc result: %d file(s) processed, %d pointcut(s) woven, %d error(s), %d warning(s)",
300 AjcMojo.files(this.classesDirectory).size(),
301 mholder.numMessages(IMessage.WEAVEINFO, false),
302 mholder.numMessages(IMessage.ERROR, true),
303 mholder.numMessages(IMessage.WARNING, false)
304 );
305 if (mholder.hasAnyMessage(IMessage.ERROR, true)) {
306 throw new MojoFailureException("AJC failed, see log above");
307 }
308 }
309
310
311
312
313
314 private Collection<String> classpath() {
315 final Collection<String> scps;
316 if (this.scopes == null) {
317 scps = AjcMojo.scope();
318 } else {
319 scps = Arrays.asList(this.scopes);
320 }
321 final Collection<String> elements = new LinkedList<>();
322 try {
323 final DependencyGraphBuilder builder =
324 DependencyGraphBuilder.class.cast(
325 this.container.lookup(
326 DependencyGraphBuilder.class.getCanonicalName(),
327 "default"
328 )
329 );
330 final ProjectBuildingRequest request =
331 new DefaultProjectBuildingRequest();
332 request.setProject(this.project);
333 request.setRepositorySession(this.rsession);
334 final DependencyNode node = builder.buildDependencyGraph(
335 request,
336 artifact -> scps.contains(artifact.getScope())
337 );
338 elements.addAll(this.dependencies(node, scps));
339 } catch (final DependencyGraphBuilderException
340 | ComponentLookupException ex) {
341 throw new IllegalStateException(ex);
342 }
343 elements.addAll(this.classpathElements);
344 return elements;
345 }
346
347
348
349
350
351
352
353 private Collection<String> dependencies(final DependencyNode node,
354 final Collection<String> scps) {
355 final Artifact artifact = node.getArtifact();
356 final Collection<String> files = new LinkedList<>();
357 if (artifact.getScope() == null
358 || scps.contains(artifact.getScope())) {
359 if (artifact.getScope() == null) {
360 files.add(artifact.getFile().toString());
361 } else {
362 files.add(
363 this.session.getLocalRepository().find(artifact).getFile()
364 .toString()
365 );
366 }
367 for (final DependencyNode child : node.getChildren()) {
368 if (child.getArtifact().compareTo(node.getArtifact()) != 0) {
369 files.addAll(this.dependencies(child, scps));
370 }
371 }
372 }
373 return files;
374 }
375
376
377
378
379
380 private static Collection<String> scope() {
381 final List<String> scps;
382 if (AjcMojo.eclipseAether()) {
383 scps = Arrays.asList(
384 JavaScopes.COMPILE,
385 JavaScopes.PROVIDED,
386 JavaScopes.RUNTIME,
387 JavaScopes.SYSTEM
388 );
389 } else {
390 scps = Arrays.asList(
391 JavaScopes.COMPILE,
392 JavaScopes.RUNTIME,
393 JavaScopes.PROVIDED,
394 JavaScopes.SYSTEM
395 );
396 }
397 return scps;
398 }
399
400
401
402
403
404 private static boolean eclipseAether() {
405 boolean found = false;
406 try {
407 Thread.currentThread().getContextClassLoader()
408 .loadClass("org.sonatype.aether.graph.DependencyFilter");
409 } catch (final ClassNotFoundException ex) {
410 found = true;
411 }
412 return found;
413 }
414
415
416
417
418
419 private String aspectpath() {
420 return new StringBuilder(0)
421 .append(StringUtils.join(this.classpath(), AjcMojo.SEP))
422 .append(AjcMojo.SEP)
423 .append(System.getProperty("java.class.path"))
424 .toString();
425 }
426
427
428
429
430
431
432 private String sourceroots() throws IOException {
433 final String path;
434 if (this.aspectDirectories == null
435 || this.aspectDirectories.length == 0) {
436 path = Files.createTempDirectory("temp")
437 .toAbsolutePath().toString();
438 } else {
439 for (final File dir : this.aspectDirectories) {
440 if (!dir.exists()) {
441 throw new IllegalStateException(
442 String.format("source directory %s is absent", dir)
443 );
444 }
445 }
446 path = StringUtils.join(this.aspectDirectories, AjcMojo.SEP);
447 }
448 return path;
449 }
450
451
452
453
454
455 private boolean hasClasses() {
456 return this.listClasses().size() > 0;
457 }
458
459
460
461
462
463 private Collection<File> listClasses() {
464 final IOFileFilter filter = FileFilterUtils
465 .suffixFileFilter(".class");
466 return FileUtils.listFiles(
467 this.classesDirectory, filter, FileFilterUtils
468 .directoryFileFilter()
469 );
470 }
471
472
473
474
475
476 private boolean hasSourceroots() {
477 return this.aspectDirectories != null
478 && this.aspectDirectories.length > 0;
479 }
480
481
482
483
484
485
486 private static Collection<File> files(final File dir) {
487 final Collection<File> files = new LinkedList<>();
488 final Collection<File> all = FileUtils.listFiles(
489 dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE
490 );
491 for (final File file : all) {
492 if (file.isFile()) {
493 files.add(file);
494 }
495 }
496 return files;
497 }
498
499
500
501
502
503
504 private void copyUnwovenClasses()
505 throws MojoFailureException {
506 if (this.hasClasses()) {
507 new UnwovenClasses(
508 this.unwovenClassesDir,
509 this.classesDirectory,
510 this.execution.getLifecyclePhase()
511 ).copy();
512 } else {
513 Logger.warn(
514 this,
515 "No classes found at %s. Nothing will be copied to %s",
516 this.classesDirectory,
517 this.unwovenClassesDir
518 );
519 }
520 }
521
522
523
524
525
526
527 private static final class MsgHolder implements IMessageHolder {
528
529
530
531 private final transient Collection<IMessage> messages =
532 new CopyOnWriteArrayList<>();
533
534 @Override
535 public boolean hasAnyMessage(final IMessage.Kind kind,
536 final boolean greater) {
537 boolean has = false;
538 for (final IMessage msg : this.messages) {
539 has = msg.getKind().equals(kind) || greater
540 && IMessage.Kind.COMPARATOR
541 .compare(msg.getKind(), kind) > 0;
542 if (has) {
543 break;
544 }
545 }
546 return has;
547 }
548
549 @Override
550 public int numMessages(final IMessage.Kind kind,
551 final boolean greater) {
552 int num = 0;
553 for (final IMessage msg : this.messages) {
554 final boolean has = msg.getKind().equals(kind) || greater
555 && IMessage.Kind.COMPARATOR
556 .compare(msg.getKind(), kind) > 0;
557 if (has) {
558 ++num;
559 }
560 }
561 return num;
562 }
563
564 @Override
565 public IMessage[] getMessages(final IMessage.Kind kind,
566 final boolean greater) {
567 throw new UnsupportedOperationException();
568 }
569
570 @Override
571 public List<IMessage> getUnmodifiableListView() {
572 throw new UnsupportedOperationException();
573 }
574
575 @Override
576 public void clearMessages() {
577 throw new UnsupportedOperationException();
578 }
579
580 @Override
581 public boolean handleMessage(final IMessage msg) {
582 if (msg.getKind().equals(IMessage.ERROR)
583 || msg.getKind().equals(IMessage.FAIL)
584 || msg.getKind().equals(IMessage.ABORT)) {
585 Logger.error(AjcMojo.class, msg.getMessage());
586 } else if (msg.getKind().equals(IMessage.WARNING)) {
587 Logger.warn(AjcMojo.class, msg.getMessage());
588 } else {
589 Logger.debug(AjcMojo.class, msg.getMessage());
590 }
591 this.messages.add(msg);
592 return true;
593 }
594
595 @Override
596 public boolean isIgnoring(final IMessage.Kind kind) {
597 return false;
598 }
599
600 @Override
601 public void dontIgnore(final IMessage.Kind kind) {
602 assert kind != null;
603 }
604
605 @Override
606 public void ignore(final IMessage.Kind kind) {
607 assert kind != null;
608 }
609 }
610
611 }