1 package org.codehaus.mojo.taglist;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStreamWriter;
26 import java.nio.charset.Charset;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.ResourceBundle;
33 import java.util.concurrent.atomic.AtomicReference;
34
35 import org.apache.maven.model.ReportPlugin;
36 import org.apache.maven.model.Reporting;
37 import org.apache.maven.plugins.annotations.Mojo;
38 import org.apache.maven.plugins.annotations.Parameter;
39 import org.apache.maven.plugins.annotations.ResolutionScope;
40 import org.apache.maven.project.MavenProject;
41 import org.apache.maven.reporting.AbstractMavenReport;
42 import org.apache.maven.reporting.MavenReportException;
43 import org.codehaus.mojo.taglist.beans.FileReport;
44 import org.codehaus.mojo.taglist.beans.TagReport;
45 import org.codehaus.mojo.taglist.options.Tag;
46 import org.codehaus.mojo.taglist.output.TagListXMLComment;
47 import org.codehaus.mojo.taglist.output.TagListXMLFile;
48 import org.codehaus.mojo.taglist.output.TagListXMLReport;
49 import org.codehaus.mojo.taglist.output.TagListXMLTag;
50 import org.codehaus.mojo.taglist.output.io.xpp3.TaglistOutputXpp3Writer;
51 import org.codehaus.mojo.taglist.tags.AbsTag;
52 import org.codehaus.mojo.taglist.tags.InvalidTagException;
53 import org.codehaus.mojo.taglist.tags.TagClass;
54 import org.codehaus.mojo.taglist.tags.TagFactory;
55 import org.codehaus.plexus.util.FileUtils;
56 import org.codehaus.plexus.util.PathTool;
57 import org.codehaus.plexus.util.StringUtils;
58
59
60
61
62
63
64 @Mojo(name = "taglist", requiresDependencyResolution = ResolutionScope.COMPILE)
65 public class TagListReport extends AbstractMavenReport {
66
67
68
69
70
71
72 @Parameter(property = "sourceFileLocale", defaultValue = "en")
73 private String sourceFileLocale;
74
75
76
77
78
79
80 @Parameter(defaultValue = "**/*.java")
81 private String[] includes;
82
83
84
85
86
87
88 @Parameter()
89 private String[] excludes;
90
91
92
93
94
95
96
97
98 @Parameter(defaultValue = "${project.build.directory}/taglist", required = true)
99 private File xmlOutputDirectory;
100
101
102
103
104
105 @Parameter(defaultValue = "true")
106 private boolean multipleLineComments;
107
108
109
110
111 @Parameter(defaultValue = "true")
112 private boolean emptyComments;
113
114
115
116
117
118 @Parameter(defaultValue = "true", property = "taglists.linkXRef")
119 private boolean linkXRef;
120
121
122
123
124
125
126 @Parameter
127 private File xrefLocation;
128
129
130
131
132
133
134 @Parameter
135 private File testXrefLocation;
136
137
138
139
140 @Parameter(defaultValue = "false", property = "taglists.aggregate")
141 private boolean aggregate;
142
143
144
145
146
147
148 @Parameter(defaultValue = "false")
149 private boolean showEmptyDetails;
150
151
152
153
154
155
156 @Parameter(defaultValue = "false")
157 private boolean skipTestSources;
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 @Parameter
184 private org.codehaus.mojo.taglist.options.TagListOptions tagListOptions;
185
186
187
188
189
190
191 @Parameter(property = "taglist.skipEmptyReport", defaultValue = "false")
192 private boolean skipEmptyReport;
193
194 private final AtomicReference<List<String>> sourceDirs = new AtomicReference<>();
195
196 private Collection<TagReport> tagReportsResult;
197
198
199
200
201
202
203 @Override
204 protected void executeReport(Locale locale) throws MavenReportException {
205
206 if (StringUtils.isEmpty(getInputEncoding())) {
207 getLog().warn("File encoding has not been set, using platform encoding "
208 + Charset.defaultCharset().displayName() + ", i.e. build is platform dependent!");
209 }
210
211 executeAnalysis();
212
213
214 TaglistReportRenderer renderer = new TaglistReportRenderer(this, tagReportsResult);
215 renderer.setXrefLocation(constructXrefLocation(false));
216 renderer.setTestXrefLocation(constructXrefLocation(true));
217 renderer.setBundle(getBundle(locale));
218 renderer.render();
219
220
221 generateXmlReport(tagReportsResult);
222 }
223
224 protected String constructXrefLocation(boolean test) {
225 String location = null;
226 if (linkXRef) {
227 File xrefLocation = getXrefLocation(test);
228
229 String relativePath = PathTool.getRelativePath(
230 getReportOutputDirectory().getAbsolutePath(), xrefLocation.getAbsolutePath());
231 if (relativePath == null || relativePath.isEmpty()) {
232 relativePath = ".";
233 }
234 relativePath = relativePath + "/" + xrefLocation.getName();
235 if (xrefLocation.exists()) {
236
237 location = relativePath;
238 } else {
239
240 Reporting reporting = project.getModel().getReporting();
241 List<ReportPlugin> reportPlugins = reporting != null ? reporting.getPlugins() : Collections.emptyList();
242 for (ReportPlugin plugin : reportPlugins) {
243 String artifactId = plugin.getArtifactId();
244 if ("maven-jxr-plugin".equals(artifactId)) {
245 location = relativePath;
246 }
247 }
248 }
249
250 if (location == null) {
251 getLog().warn("Unable to locate" + (test ? " Test" : "") + " Source XRef to link to - DISABLED");
252 }
253 }
254 return location;
255 }
256
257 protected File getXrefLocation(boolean test) {
258 File location = test ? testXrefLocation : xrefLocation;
259 return location != null ? location : new File(getReportOutputDirectory(), test ? "xref-test" : "xref");
260 }
261
262 private void executeAnalysis() throws MavenReportException {
263 if (tagReportsResult != null) {
264
265 return;
266 }
267
268
269 List<TagClass> tagClasses = new ArrayList<>();
270
271
272 if (tagListOptions != null && !tagListOptions.getTagClasses().isEmpty()) {
273
274 for (org.codehaus.mojo.taglist.options.TagClass tcOption : tagListOptions.getTagClasses()) {
275
276 TagClass tc = new TagClass(tcOption.getDisplayName());
277
278
279 for (Tag tagOption : tcOption.getTags()) {
280
281 String matchType = tagOption.getMatchType();
282 if (matchType == null || matchType.isEmpty()) {
283 matchType = TagFactory.getDefaultTagType();
284 }
285
286 try {
287
288 AbsTag newTag = TagFactory.createTag(matchType, tagOption.getMatchString());
289 tc.addTag(newTag);
290 } catch (InvalidTagException e) {
291
292 getLog().error("Invalid tag type used. tag type: " + matchType);
293 }
294 }
295
296
297 tagClasses.add(tc);
298 }
299 }
300
301
302 if (tagClasses.isEmpty()) {
303 tagClasses.add(createTagClass("@todo"));
304 tagClasses.add(createTagClass("TODO"));
305 tagClasses.add(createTagClass("FIXME"));
306 }
307
308
309 FileAnalyser fileAnalyser = new FileAnalyser(this, tagClasses);
310 try {
311 tagReportsResult = fileAnalyser.execute();
312 } catch (IOException e) {
313 throw new MavenReportException(e.getMessage(), e);
314 }
315 }
316
317 private TagClass createTagClass(String tag) {
318 TagClass tc = new TagClass(tag);
319 try {
320 AbsTag newTag = TagFactory.createTag("exact", tag);
321 tc.addTag(newTag);
322 } catch (InvalidTagException e) {
323
324 }
325 return tc;
326 }
327
328
329
330
331
332
333 private void generateXmlReport(Collection<TagReport> tagReports) {
334 TagListXMLReport report = new TagListXMLReport();
335 report.setModelEncoding(getInputEncoding());
336
337
338 for (TagReport tagReport : tagReports) {
339 TagListXMLTag tag = new TagListXMLTag();
340 tag.setName(tagReport.getTagName());
341 tag.setCount(Integer.toString(tagReport.getTagCount()));
342
343
344
345 for (FileReport fileReport : tagReport.getFileReports()) {
346 TagListXMLFile file = new TagListXMLFile();
347 file.setName(fileReport.getClassName());
348 file.setCount(Integer.toString(fileReport.getLineIndexes().size()));
349
350
351
352 for (Integer lineNumber : fileReport.getLineIndexes()) {
353 TagListXMLComment comment = new TagListXMLComment();
354 comment.setLineNumber(Integer.toString(lineNumber));
355 comment.setComment(fileReport.getComment(lineNumber));
356
357 file.addComment(comment);
358 }
359 tag.addFile(file);
360 }
361 report.addTag(tag);
362 }
363
364
365 xmlOutputDirectory.mkdirs();
366 File xmlFile = new File(xmlOutputDirectory, "taglist.xml");
367
368 try (FileOutputStream fos = new FileOutputStream(xmlFile);
369 OutputStreamWriter output = new OutputStreamWriter(fos, getInputEncoding())) {
370
371
372 TaglistOutputXpp3Writer xmlWriter = new TaglistOutputXpp3Writer();
373 xmlWriter.write(output, report);
374 } catch (Exception e) {
375 getLog().warn("Could not save taglist xml file: " + e.getMessage());
376 }
377 }
378
379
380
381
382
383
384
385 private String getRelativePath(File location) {
386 String relativePath =
387 PathTool.getRelativePath(getReportOutputDirectory().getAbsolutePath(), location.getAbsolutePath());
388 if (StringUtils.isEmpty(relativePath)) {
389 relativePath = ".";
390 }
391 relativePath = relativePath + "/" + location.getName();
392 return relativePath;
393 }
394
395
396
397
398
399
400 @Override
401 public boolean canGenerateReport() throws MavenReportException {
402 boolean canGenerate = !getSourceDirs().isEmpty();
403 if (aggregate && !getProject().isExecutionRoot()) {
404 canGenerate = false;
405 }
406
407 if (canGenerate) {
408 executeAnalysis();
409 return !skipEmptyReport || tagReportsResult.stream().anyMatch(tagReport -> tagReport.getTagCount() > 0);
410 }
411
412 return false;
413 }
414
415
416
417
418
419
420
421 private List<String> pruneSourceDirs(List<String> sourceDirectories) throws IOException {
422 List<String> pruned = new ArrayList<>(sourceDirectories.size());
423 for (String dir : sourceDirectories) {
424 if (!pruned.contains(dir) && hasSources(new File(dir))) {
425 pruned.add(dir);
426 }
427 }
428 return pruned;
429 }
430
431
432
433
434
435
436
437
438 private boolean hasSources(File dir) throws IOException {
439 if (dir.exists() && dir.isDirectory()) {
440 if (!FileUtils.getFiles(dir, getIncludesCommaSeparated(), getExcludesCommaSeparated())
441 .isEmpty()) {
442 return true;
443 }
444
445 File[] files = dir.listFiles();
446 if (files != null) {
447 for (File currentFile : files) {
448 if (currentFile.isDirectory()) {
449 boolean hasSources = hasSources(currentFile);
450 if (hasSources) {
451 return true;
452 }
453 }
454 }
455 }
456 }
457 return false;
458 }
459
460
461
462
463
464
465 private List<String> constructSourceDirs() {
466 List<String> dirs = new ArrayList<>(getProject().getCompileSourceRoots());
467 if (!skipTestSources) {
468 dirs.addAll(getProject().getTestCompileSourceRoots());
469 }
470
471 if (aggregate) {
472 for (MavenProject reactorProject : reactorProjects) {
473 if ("java"
474 .equals(reactorProject
475 .getArtifact()
476 .getArtifactHandler()
477 .getLanguage())) {
478 dirs.addAll(reactorProject.getCompileSourceRoots());
479 if (!skipTestSources) {
480 dirs.addAll(reactorProject.getTestCompileSourceRoots());
481 }
482 }
483 }
484 }
485
486
487
488
489
490
491
492
493 try {
494 dirs = pruneSourceDirs(dirs);
495 } catch (IOException javaIoIOException) {
496 getLog().warn("Unable to prune source dirs.", javaIoIOException);
497 }
498
499 return dirs;
500 }
501
502 protected List<String> getSourceDirs() {
503 if (sourceDirs.get() == null) {
504 sourceDirs.compareAndSet(null, constructSourceDirs());
505 }
506
507 return sourceDirs.get();
508 }
509
510
511
512
513 String getIncludesCommaSeparated() {
514 if (includes != null) {
515 return String.join(",", includes);
516 } else {
517 return "";
518 }
519 }
520
521
522
523
524 String getExcludesCommaSeparated() {
525 if (excludes != null) {
526 return String.join(",", excludes);
527 } else {
528 return "";
529 }
530 }
531
532 void setSourceFileLocale(String sourceFileLocale) {
533 this.sourceFileLocale = sourceFileLocale;
534 }
535
536
537
538
539
540
541 public Locale getSourceFileLocale() {
542 String[] items = sourceFileLocale.split("_");
543 if (sourceFileLocale.isEmpty() || items.length > 3) {
544 getLog().warn("Invalid java.util.Locale format '" + sourceFileLocale
545 + "' for 'sourceFileLocale' using 'ENGLISH' locale");
546 return Locale.ENGLISH;
547 }
548
549 String language = "";
550 String country = "";
551 String variant = "";
552
553 if (items.length > 0) {
554 language = items[0];
555 }
556
557 if (items.length > 1) {
558 country = items[1];
559 }
560
561 if (items.length > 2) {
562 variant = items[2];
563 }
564 return new Locale(language, country, variant);
565 }
566
567
568
569
570
571
572 public boolean isMultipleLineComments() {
573 return multipleLineComments;
574 }
575
576
577
578
579
580
581 public boolean isEmptyComments() {
582 return emptyComments;
583 }
584
585
586
587
588
589
590 public boolean isShowEmptyDetails() {
591 return showEmptyDetails;
592 }
593
594
595
596
597
598
599 protected String getXMLOutputDirectory() {
600 return xmlOutputDirectory.getAbsolutePath();
601 }
602
603
604
605
606
607
608 @Override
609 public String getDescription(Locale locale) {
610 return getBundle(locale).getString("report.taglist.description");
611 }
612
613
614
615
616
617
618 @Override
619 public String getName(Locale locale) {
620 return getBundle(locale).getString("report.taglist.name");
621 }
622
623
624
625
626
627
628 @Override
629 public String getOutputName() {
630 return "taglist";
631 }
632
633
634
635
636
637
638
639 private ResourceBundle getBundle(Locale locale) {
640 return ResourceBundle.getBundle(
641 "taglist-report", locale, this.getClass().getClassLoader());
642 }
643
644 @Override
645 protected String getInputEncoding() {
646 return super.getInputEncoding();
647 }
648 }