1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.report.projectinfo.dependencies.renderer;
20
21 import javax.swing.text.html.HTML.Attribute;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.PrintWriter;
26 import java.io.StringWriter;
27 import java.text.DecimalFormat;
28 import java.text.DecimalFormatSymbols;
29 import java.text.FieldPosition;
30 import java.text.MessageFormat;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.Comparator;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Locale;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.SortedSet;
42 import java.util.TreeSet;
43
44 import org.apache.maven.artifact.Artifact;
45 import org.apache.maven.doxia.sink.Sink;
46 import org.apache.maven.doxia.sink.SinkEventAttributes;
47 import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
48 import org.apache.maven.doxia.util.HtmlTools;
49 import org.apache.maven.model.License;
50 import org.apache.maven.plugin.logging.Log;
51 import org.apache.maven.project.MavenProject;
52 import org.apache.maven.project.ProjectBuilder;
53 import org.apache.maven.project.ProjectBuildingException;
54 import org.apache.maven.project.ProjectBuildingRequest;
55 import org.apache.maven.report.projectinfo.AbstractProjectInfoRenderer;
56 import org.apache.maven.report.projectinfo.LicenseMapping;
57 import org.apache.maven.report.projectinfo.ProjectInfoReportUtils;
58 import org.apache.maven.report.projectinfo.dependencies.Dependencies;
59 import org.apache.maven.report.projectinfo.dependencies.DependenciesReportConfiguration;
60 import org.apache.maven.report.projectinfo.dependencies.RepositoryUtils;
61 import org.apache.maven.report.projectinfo.dependencies.renderer.DependenciesRenderer.TotalCell.SummaryTableRowOrder;
62 import org.apache.maven.repository.RepositorySystem;
63 import org.apache.maven.shared.dependency.graph.DependencyNode;
64 import org.apache.maven.shared.jar.JarData;
65 import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
66 import org.codehaus.plexus.i18n.I18N;
67 import org.codehaus.plexus.util.StringUtils;
68
69
70
71
72
73
74
75 public class DependenciesRenderer extends AbstractProjectInfoRenderer {
76
77
78 private static final String IMG_INFO_URL = "./images/icon_info_sml.gif";
79
80
81 private static final String IMG_CLOSE_URL = "./images/close.gif";
82
83 private static final Set<String> JAR_SUBTYPE;
84
85 private final DependencyNode dependencyNode;
86
87 private final Dependencies dependencies;
88
89 private final DependenciesReportConfiguration configuration;
90
91 private final Log log;
92
93 private final RepositoryUtils repoUtils;
94
95
96 private final DecimalFormat fileLengthDecimalFormat;
97
98 private final MessageFormat javaVersionFormat =
99 new MessageFormat("{0,choice,0#|1.1#{0,number,0.0}|9#{0,number,0}}", Locale.ROOT);
100
101
102
103 private int section;
104
105
106 private int idCounter = 0;
107
108
109
110
111 private Map<String, Object> licenseMap = new HashMap<String, Object>() {
112 private static final long serialVersionUID = 1L;
113
114
115 @Override
116 public Object put(String key, Object value) {
117
118 @SuppressWarnings("unchecked")
119 SortedSet<Object> valueList = (SortedSet<Object>) get(key);
120 if (valueList == null) {
121 valueList = new TreeSet<>();
122 }
123 valueList.add(value);
124 return super.put(key, valueList);
125 }
126 };
127
128 private final RepositorySystem repositorySystem;
129
130 private final ProjectBuilder projectBuilder;
131
132 private final ProjectBuildingRequest buildingRequest;
133
134 private final Map<String, String> licenseMappings;
135
136 static {
137 Set<String> jarSubtype = new HashSet<>();
138 jarSubtype.add("jar");
139 jarSubtype.add("war");
140 jarSubtype.add("ear");
141 jarSubtype.add("sar");
142 jarSubtype.add("rar");
143 jarSubtype.add("par");
144 jarSubtype.add("ejb");
145 JAR_SUBTYPE = Collections.unmodifiableSet(jarSubtype);
146 }
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164 public DependenciesRenderer(
165 Sink sink,
166 Locale locale,
167 I18N i18n,
168 Log log,
169 Dependencies dependencies,
170 DependencyNode dependencyTreeNode,
171 DependenciesReportConfiguration config,
172 RepositoryUtils repoUtils,
173 RepositorySystem repositorySystem,
174 ProjectBuilder projectBuilder,
175 ProjectBuildingRequest buildingRequest,
176 Map<String, String> licenseMappings) {
177 super(sink, i18n, locale);
178
179 this.log = log;
180 this.dependencies = dependencies;
181 this.dependencyNode = dependencyTreeNode;
182 this.repoUtils = repoUtils;
183 this.configuration = config;
184 this.repositorySystem = repositorySystem;
185 this.projectBuilder = projectBuilder;
186 this.buildingRequest = buildingRequest;
187 this.licenseMappings = licenseMappings;
188 this.fileLengthDecimalFormat = new FileDecimalFormat(i18n, locale);
189 this.fileLengthDecimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(locale));
190 }
191
192 @Override
193 protected String getI18Nsection() {
194 return "dependencies";
195 }
196
197
198
199
200
201 @Override
202 protected void renderBody() {
203
204
205 if (!dependencies.hasDependencies()) {
206 startSection(getTitle());
207
208 paragraph(getI18nString("nolist"));
209
210 endSection();
211
212 return;
213 }
214
215
216 renderSectionProjectDependencies();
217
218
219 renderSectionProjectTransitiveDependencies();
220
221
222 renderSectionProjectDependencyGraph();
223
224
225 renderSectionDependencyLicenseListing();
226
227 if (configuration.getDependencyDetailsEnabled()) {
228
229 renderSectionDependencyFileDetails();
230 }
231 }
232
233
234
235
236
237
238
239
240 @Override
241 protected void startSection(String name) {
242 startSection(name, name);
243 }
244
245
246
247
248
249
250
251
252 protected void startSection(String name, String anchor) {
253 section = section + 1;
254
255 super.sink.anchor(HtmlTools.encodeId(anchor));
256 super.sink.anchor_();
257
258 switch (section) {
259 case 1:
260 sink.section1();
261 sink.sectionTitle1();
262 break;
263 case 2:
264 sink.section2();
265 sink.sectionTitle2();
266 break;
267 case 3:
268 sink.section3();
269 sink.sectionTitle3();
270 break;
271 case 4:
272 sink.section4();
273 sink.sectionTitle4();
274 break;
275 case 5:
276 sink.section5();
277 sink.sectionTitle5();
278 break;
279
280 default:
281
282 break;
283 }
284
285 text(name);
286
287 switch (section) {
288 case 1:
289 sink.sectionTitle1_();
290 break;
291 case 2:
292 sink.sectionTitle2_();
293 break;
294 case 3:
295 sink.sectionTitle3_();
296 break;
297 case 4:
298 sink.sectionTitle4_();
299 break;
300 case 5:
301 sink.sectionTitle5_();
302 break;
303
304 default:
305
306 break;
307 }
308 }
309
310
311
312
313 @Override
314 protected void endSection() {
315 switch (section) {
316 case 1:
317 sink.section1_();
318 break;
319 case 2:
320 sink.section2_();
321 break;
322 case 3:
323 sink.section3_();
324 break;
325 case 4:
326 sink.section4_();
327 break;
328 case 5:
329 sink.section5_();
330 break;
331
332 default:
333
334 break;
335 }
336
337 section = section - 1;
338
339 if (section < 0) {
340 throw new IllegalStateException("Too many closing sections");
341 }
342 }
343
344
345
346
347
348
349
350
351
352
353
354 private String[] getDependencyTableHeader(boolean withClassifier, boolean withOptional) {
355 String groupId = getI18nString("column.groupId");
356 String artifactId = getI18nString("column.artifactId");
357 String version = getI18nString("column.version");
358 String classifier = getI18nString("column.classifier");
359 String type = getI18nString("column.type");
360 String license = getI18nString("column.licenses");
361 String optional = getI18nString("column.optional");
362
363 if (withClassifier) {
364 if (withOptional) {
365 return new String[] {groupId, artifactId, version, classifier, type, license, optional};
366 }
367
368 return new String[] {groupId, artifactId, version, classifier, type, license};
369 }
370
371 if (withOptional) {
372 return new String[] {groupId, artifactId, version, type, license, optional};
373 }
374
375 return new String[] {groupId, artifactId, version, type, license};
376 }
377
378 private void renderSectionProjectDependencies() {
379 startSection(getTitle());
380
381
382 Map<String, List<Artifact>> dependenciesByScope = dependencies.getDependenciesByScope(false);
383
384 renderDependenciesForAllScopes(dependenciesByScope, false);
385
386 endSection();
387 }
388
389
390
391
392
393
394
395
396
397
398 private void renderDependenciesForAllScopes(Map<String, List<Artifact>> dependenciesByScope, boolean isTransitive) {
399 renderDependenciesForScope(
400 Artifact.SCOPE_COMPILE, dependenciesByScope.get(Artifact.SCOPE_COMPILE), isTransitive);
401 renderDependenciesForScope(
402 Artifact.SCOPE_RUNTIME, dependenciesByScope.get(Artifact.SCOPE_RUNTIME), isTransitive);
403 renderDependenciesForScope(Artifact.SCOPE_TEST, dependenciesByScope.get(Artifact.SCOPE_TEST), isTransitive);
404 renderDependenciesForScope(
405 Artifact.SCOPE_PROVIDED, dependenciesByScope.get(Artifact.SCOPE_PROVIDED), isTransitive);
406 renderDependenciesForScope(Artifact.SCOPE_SYSTEM, dependenciesByScope.get(Artifact.SCOPE_SYSTEM), isTransitive);
407 }
408
409 private void renderSectionProjectTransitiveDependencies() {
410 Map<String, List<Artifact>> dependenciesByScope = dependencies.getDependenciesByScope(true);
411
412 startSection(getI18nString("transitive.title"));
413
414 if (dependenciesByScope.values().isEmpty()) {
415 paragraph(getI18nString("transitive.nolist"));
416 } else {
417 paragraph(getI18nString("transitive.intro"));
418
419 renderDependenciesForAllScopes(dependenciesByScope, true);
420 }
421
422 endSection();
423 }
424
425 private void renderSectionProjectDependencyGraph() {
426 startSection(getI18nString("graph.title"));
427
428
429 renderSectionDependencyTree();
430
431 endSection();
432 }
433
434 private void renderSectionDependencyTree() {
435 StringWriter sw = new StringWriter();
436 PrintWriter pw = new PrintWriter(sw);
437
438 pw.println(" function toggleDependencyDetails( divId, imgId )");
439 pw.println(" {");
440 pw.println(" var div = document.getElementById( divId );");
441 pw.println(" var img = document.getElementById( imgId );");
442 pw.println(" if( div.style.display == '' )");
443 pw.println(" {");
444 pw.println(" div.style.display = 'none';");
445 pw.printf(" img.src='%s';%n", IMG_INFO_URL);
446 pw.printf(" img.alt='%s';%n", getI18nString("graph.icon.information"));
447 pw.println(" }");
448 pw.println(" else");
449 pw.println(" {");
450 pw.println(" div.style.display = '';");
451 pw.printf(" img.src='%s';%n", IMG_CLOSE_URL);
452 pw.printf(" img.alt='%s';%n", getI18nString("graph.icon.close"));
453 pw.println(" }");
454 pw.println(" }");
455
456 javaScript(sw.toString());
457
458
459 startSection(getI18nString("graph.tree.title"));
460
461 sink.list();
462 printDependencyListing(dependencyNode);
463 sink.list_();
464
465 endSection();
466 }
467
468 private void renderSectionDependencyFileDetails() {
469 startSection(getI18nString("file.details.title"));
470
471 List<Artifact> alldeps = dependencies.getAllDependencies();
472 Collections.sort(alldeps, getArtifactComparator());
473
474 resolveAtrifacts(alldeps);
475
476
477 String filename = getI18nString("file.details.column.file");
478 String size = getI18nString("file.details.column.size");
479 String entries = getI18nString("file.details.column.entries");
480 String classes = getI18nString("file.details.column.classes");
481 String packages = getI18nString("file.details.column.packages");
482 String javaVersion = getI18nString("file.details.column.javaVersion");
483 String debugInformation = getI18nString("file.details.column.debuginformation");
484 String debugInformationTitle = getI18nString("file.details.columntitle.debuginformation");
485 String debugInformationCellYes = getI18nString("file.details.cell.debuginformation.yes");
486 String debugInformationCellNo = getI18nString("file.details.cell.debuginformation.no");
487 String aSealed = getI18nString("file.details.column.sealed");
488 String sealedCellYes = getI18nString("file.details.cell.sealed.yes");
489 String sealedCellNo = getI18nString("file.details.cell.sealed.no");
490
491 int[] justification = new int[] {
492 Sink.JUSTIFY_LEFT,
493 Sink.JUSTIFY_RIGHT,
494 Sink.JUSTIFY_RIGHT,
495 Sink.JUSTIFY_RIGHT,
496 Sink.JUSTIFY_RIGHT,
497 Sink.JUSTIFY_CENTER,
498 Sink.JUSTIFY_CENTER,
499 Sink.JUSTIFY_CENTER
500 };
501
502 startTable(justification, false);
503
504 TotalCell totaldeps = new TotalCell();
505 TotalCell totaldepsize = new TotalCell(fileLengthDecimalFormat);
506 TotalCell totalentries = new TotalCell();
507 TotalCell totalclasses = new TotalCell();
508 TotalCell totalpackages = new TotalCell();
509 double highestTestJavaVersion = 0.0;
510 double highestNonTestJavaVersion = 0.0;
511 TotalCell totalDebugInformation = new TotalCell();
512 TotalCell totalsealed = new TotalCell();
513
514 boolean hasSealed = hasSealed(alldeps);
515
516
517 String[] tableHeader;
518 String[] tableHeaderTitles;
519 if (hasSealed) {
520 tableHeader =
521 new String[] {filename, size, entries, classes, packages, javaVersion, debugInformation, aSealed};
522 tableHeaderTitles = new String[] {null, null, null, null, null, null, debugInformationTitle, null};
523 } else {
524 tableHeader = new String[] {filename, size, entries, classes, packages, javaVersion, debugInformation};
525 tableHeaderTitles = new String[] {null, null, null, null, null, null, debugInformationTitle};
526 }
527 tableHeader(tableHeader, tableHeaderTitles);
528
529
530 for (Artifact artifact : alldeps) {
531 if (artifact.getFile() == null) {
532 log.warn("Artifact " + artifact.getId() + " has no file"
533 + " and won't be listed in dependency files details.");
534 continue;
535 }
536
537 File artifactFile = dependencies.getFile(artifact);
538
539 totaldeps.incrementTotal(artifact.getScope());
540 totaldepsize.addTotal(artifactFile.length(), artifact.getScope());
541
542 if (JAR_SUBTYPE.contains(artifact.getType().toLowerCase())) {
543 try {
544 JarData jarDetails = dependencies.getJarDependencyDetails(artifact);
545
546 String debugInformationCellValue = debugInformationCellNo;
547 if (jarDetails.isDebugPresent()) {
548 debugInformationCellValue = debugInformationCellYes;
549 totalDebugInformation.incrementTotal(artifact.getScope());
550 }
551
552 totalentries.addTotal(jarDetails.getNumEntries(), artifact.getScope());
553 totalclasses.addTotal(jarDetails.getNumClasses(), artifact.getScope());
554 totalpackages.addTotal(jarDetails.getNumPackages(), artifact.getScope());
555
556 try {
557 if (jarDetails.getJdkRevision() != null) {
558 double jdkRevision = Double.parseDouble(jarDetails.getJdkRevision());
559 boolean isTestScope = Artifact.SCOPE_TEST.equalsIgnoreCase(artifact.getScope());
560 if (isTestScope) {
561 highestTestJavaVersion = Math.max(highestTestJavaVersion, jdkRevision);
562 } else {
563 highestNonTestJavaVersion = Math.max(highestNonTestJavaVersion, jdkRevision);
564 }
565 }
566 } catch (NumberFormatException e) {
567
568 }
569
570 String sealedCellValue = sealedCellNo;
571 if (jarDetails.isSealed()) {
572 sealedCellValue = sealedCellYes;
573 totalsealed.incrementTotal(artifact.getScope());
574 }
575
576 String name = artifactFile.getName();
577 String fileLength = fileLengthDecimalFormat.format(artifactFile.length());
578
579 if (artifactFile.isDirectory()) {
580 File parent = artifactFile.getParentFile();
581 name = parent.getParentFile().getName() + '/' + parent.getName() + '/' + artifactFile.getName();
582 fileLength = "-";
583 }
584
585 tableRow(hasSealed, new String[] {
586 name,
587 fileLength,
588 String.valueOf(jarDetails.getNumEntries()),
589 String.valueOf(jarDetails.getNumClasses()),
590 String.valueOf(jarDetails.getNumPackages()),
591 jarDetails.getJdkRevision(),
592 debugInformationCellValue,
593 sealedCellValue
594 });
595 } catch (IOException e) {
596 createExceptionInfoTableRow(artifact, artifactFile, e, hasSealed);
597 }
598 } else {
599 tableRow(hasSealed, new String[] {
600 artifactFile.getName(),
601 fileLengthDecimalFormat.format(artifactFile.length()),
602 "",
603 "",
604 "",
605 "",
606 "",
607 ""
608 });
609 }
610 }
611
612
613 tableHeader[0] = getI18nString("file.details.total");
614 tableHeader(tableHeader);
615
616 justification[0] = Sink.JUSTIFY_RIGHT;
617 justification[6] = Sink.JUSTIFY_RIGHT;
618
619
620 int rowspan = computeRowspan(totaldeps);
621
622 if (rowspan > 1) {
623 boolean insertRowspanAttr = false;
624 int column = 5;
625 for (SummaryTableRowOrder currentRow : SummaryTableRowOrder.values()) {
626 if (currentRow.getTotal(totaldeps) > 0) {
627 int i = currentRow.ordinal();
628 boolean alreadyInsertedRowspanAttr = insertRowspanAttr
629 && (SummaryTableRowOrder.COMPILE_SCOPE.ordinal() < i
630 && i <= SummaryTableRowOrder.SYSTEM_SCOPE.ordinal());
631 insertRowspanAttr = (SummaryTableRowOrder.COMPILE_SCOPE.ordinal() <= i
632 && i <= SummaryTableRowOrder.SYSTEM_SCOPE.ordinal());
633 justification[column] = (insertRowspanAttr && alreadyInsertedRowspanAttr)
634 ? justification[column + 1]
635 : Sink.JUSTIFY_CENTER;
636 tableRowWithRowspan(
637 hasSealed, insertRowspanAttr, alreadyInsertedRowspanAttr, column, rowspan, new String[] {
638 totaldeps.getTotalString(currentRow),
639 totaldepsize.getTotalString(currentRow),
640 totalentries.getTotalString(currentRow),
641 totalclasses.getTotalString(currentRow),
642 totalpackages.getTotalString(currentRow),
643 currentRow.formatMaxJavaVersionForScope(
644 javaVersionFormat, highestTestJavaVersion, highestNonTestJavaVersion),
645 totalDebugInformation.getTotalString(currentRow),
646 totalsealed.getTotalString(currentRow)
647 });
648 }
649 }
650 } else {
651 for (SummaryTableRowOrder currentRow : SummaryTableRowOrder.values()) {
652 if (currentRow.getTotal(totaldeps) > 0) {
653 tableRow(hasSealed, new String[] {
654 totaldeps.getTotalString(currentRow),
655 totaldepsize.getTotalString(currentRow),
656 totalentries.getTotalString(currentRow),
657 totalclasses.getTotalString(currentRow),
658 totalpackages.getTotalString(currentRow),
659 currentRow.formatMaxJavaVersionForScope(
660 javaVersionFormat, highestTestJavaVersion, highestNonTestJavaVersion),
661 totalDebugInformation.getTotalString(currentRow),
662 totalsealed.getTotalString(currentRow)
663 });
664 }
665 }
666 }
667
668 endTable();
669 endSection();
670 }
671
672 private int computeRowspan(TotalCell totaldeps) {
673 int rowspan = 0;
674 for (int i = SummaryTableRowOrder.COMPILE_SCOPE.ordinal();
675 i <= SummaryTableRowOrder.SYSTEM_SCOPE.ordinal();
676 i++) {
677 SummaryTableRowOrder currentRow = SummaryTableRowOrder.values()[i];
678 if (currentRow.getTotal(totaldeps) > 0) {
679 rowspan++;
680 }
681 }
682 return rowspan;
683 }
684
685
686 private void tableHeader(String[] content, String[] titles) {
687 sink.tableRow();
688
689 if (content != null) {
690 if (titles != null && content.length != titles.length) {
691 throw new IllegalArgumentException("Length of title array must equal the length of the content array");
692 }
693
694 for (int i = 0; i < content.length; i++) {
695 if (titles != null) {
696 tableHeaderCell(content[i], titles[i]);
697 } else {
698 tableHeaderCell(content[i]);
699 }
700 }
701 }
702
703 sink.tableRow_();
704 }
705
706 private void tableHeaderCell(String text, String title) {
707 if (title != null) {
708 sink.tableHeaderCell(new SinkEventAttributeSet(SinkEventAttributes.TITLE, title));
709 } else {
710 sink.tableHeaderCell();
711 }
712
713 text(text);
714
715 sink.tableHeaderCell_();
716 }
717
718 private void tableRowWithRowspan(
719 boolean fullRow, boolean insert, boolean alreadyInserted, int contentIndex, int rowspan, String[] content) {
720 sink.tableRow();
721
722 int count = fullRow ? content.length : (content.length - 1);
723
724 for (int i = 0; i < count; i++) {
725 if (i == contentIndex && insert) {
726 if (!alreadyInserted) {
727 SinkEventAttributes att = new SinkEventAttributeSet();
728 att.addAttribute(Attribute.ROWSPAN, rowspan);
729 att.addAttribute(Attribute.STYLE, "vertical-align: middle");
730 sink.tableCell(att);
731 text(content[i]);
732 sink.tableCell_();
733 }
734 } else {
735 tableCell(content[i]);
736 }
737 }
738
739 sink.tableRow_();
740 }
741
742 private void tableRow(boolean fullRow, String[] content) {
743 sink.tableRow();
744
745 int count = fullRow ? content.length : (content.length - 1);
746
747 for (int i = 0; i < count; i++) {
748 tableCell(content[i]);
749 }
750
751 sink.tableRow_();
752 }
753
754 private void createExceptionInfoTableRow(Artifact artifact, File artifactFile, Exception e, boolean hasSealed) {
755 tableRow(
756 hasSealed,
757 new String[] {artifact.getId(), artifactFile.getAbsolutePath(), e.getMessage(), "", "", "", "", ""});
758 }
759
760 private void renderSectionDependencyLicenseListing() {
761 startSection(getI18nString("graph.tables.licenses"));
762 printGroupedLicenses();
763 endSection();
764 }
765
766 private void renderDependenciesForScope(String scope, List<Artifact> artifacts, boolean isTransitive) {
767 if (artifacts != null) {
768 boolean withClassifier = hasClassifier(artifacts);
769 boolean withOptional = hasOptional(artifacts);
770 String[] tableHeader = getDependencyTableHeader(withClassifier, withOptional);
771
772
773 Collections.sort(artifacts, getArtifactComparator());
774
775 String anchorByScope = isTransitive
776 ? getI18nString("transitive.title") + "_" + scope
777 : getI18nString("title") + "_" + scope;
778 startSection(scope, anchorByScope);
779
780 paragraph(getI18nString("intro." + scope));
781
782 startTable();
783 tableHeader(tableHeader);
784 for (Artifact artifact : artifacts) {
785 renderArtifactRow(artifact, withClassifier, withOptional);
786 }
787 endTable();
788
789 endSection();
790 }
791 }
792
793 private Comparator<Artifact> getArtifactComparator() {
794 return new Comparator<Artifact>() {
795 public int compare(Artifact a1, Artifact a2) {
796
797 if (a1.isOptional() && !a2.isOptional()) {
798 return +1;
799 } else if (!a1.isOptional() && a2.isOptional()) {
800 return -1;
801 } else {
802 return a1.compareTo(a2);
803 }
804 }
805 };
806 }
807
808
809
810
811
812
813
814 private void renderArtifactRow(Artifact artifact, boolean withClassifier, boolean withOptional) {
815 String isOptional =
816 artifact.isOptional() ? getI18nString("column.isOptional") : getI18nString("column.isNotOptional");
817
818 String url = ProjectInfoReportUtils.getArtifactUrl(repositorySystem, artifact, projectBuilder, buildingRequest);
819 String artifactIdCell = ProjectInfoReportUtils.getArtifactIdCell(artifact.getArtifactId(), url);
820
821 MavenProject artifactProject;
822 StringBuilder sb = new StringBuilder();
823 try {
824 artifactProject = repoUtils.getMavenProjectFromRepository(artifact);
825
826 List<License> licenses = artifactProject.getLicenses();
827 for (License license : licenses) {
828 String name = license.getName();
829 if (licenseMappings != null && licenseMappings.containsKey(name)) {
830 name = licenseMappings.get(name);
831 }
832 sb.append(ProjectInfoReportUtils.getArtifactIdCell(name, license.getUrl()));
833 }
834 } catch (ProjectBuildingException e) {
835 if (log.isDebugEnabled()) {
836 log.debug("Unable to create Maven project from repository for artifact '" + artifact.getId() + "'", e);
837 } else {
838 log.info("Unable to create Maven project from repository for artifact '" + artifact.getId()
839 + "', for more information run with -X");
840 }
841 }
842
843 String[] content;
844 if (withClassifier) {
845 content = new String[] {
846 artifact.getGroupId(),
847 artifactIdCell,
848 artifact.getVersion(),
849 artifact.getClassifier(),
850 artifact.getType(),
851 sb.toString(),
852 isOptional
853 };
854 } else {
855 content = new String[] {
856 artifact.getGroupId(),
857 artifactIdCell,
858 artifact.getVersion(),
859 artifact.getType(),
860 sb.toString(),
861 isOptional
862 };
863 }
864
865 tableRow(withOptional, content);
866 }
867
868 private void printDependencyListing(DependencyNode node) {
869 Artifact artifact = node.getArtifact();
870 String id = artifact.getId();
871 String dependencyDetailId = "_dep" + idCounter++;
872 String imgId = "_img" + idCounter++;
873
874 sink.listItem();
875
876 sink.text(id + (StringUtils.isNotEmpty(artifact.getScope()) ? " (" + artifact.getScope() + ") " : " "));
877
878 String javascript = String.format(
879 "<img id=\"%s\" src=\"%s\" alt=\"%s\""
880 + " onclick=\"toggleDependencyDetails( '%s', '%s' );\""
881 + " style=\"cursor: pointer; vertical-align: text-bottom;\" />",
882 imgId, IMG_INFO_URL, getI18nString("graph.icon.information"), dependencyDetailId, imgId);
883
884 sink.rawText(javascript);
885
886 printDescriptionsAndURLs(node, dependencyDetailId);
887
888 if (!node.getChildren().isEmpty()) {
889 boolean toBeIncluded = false;
890 List<DependencyNode> subList = new ArrayList<DependencyNode>();
891 for (DependencyNode dep : node.getChildren()) {
892 if (dependencies.getAllDependencies().contains(dep.getArtifact())) {
893 subList.add(dep);
894 toBeIncluded = true;
895 }
896 }
897
898 if (toBeIncluded) {
899 sink.list();
900 for (DependencyNode dep : subList) {
901 printDependencyListing(dep);
902 }
903 sink.list_();
904 }
905 }
906
907 sink.listItem_();
908 }
909
910 private void printDescriptionsAndURLs(DependencyNode node, String uid) {
911 Artifact artifact = node.getArtifact();
912 String id = artifact.getId();
913 String unknownLicenseMessage = getI18nString("graph.tables.unknown");
914
915 sink.rawText("<div id=\"" + uid + "\" style=\"display:none\">");
916
917 if (!Artifact.SCOPE_SYSTEM.equals(artifact.getScope())) {
918 try {
919 MavenProject artifactProject = repoUtils.getMavenProjectFromRepository(artifact);
920 String artifactDescription = artifactProject.getDescription();
921 String artifactUrl = artifactProject.getUrl();
922 String artifactName = artifactProject.getName();
923
924 List<License> licenses = artifactProject.getLicenses();
925
926 startTable();
927
928 sink.tableRow();
929 sink.tableHeaderCell();
930 sink.text(artifactName);
931 sink.tableHeaderCell_();
932 sink.tableRow_();
933
934 sink.tableRow();
935 sink.tableCell();
936
937 sink.paragraph();
938 sink.bold();
939 sink.text(getI18nString("column.description") + ": ");
940 sink.bold_();
941 if (artifactDescription != null && !artifactDescription.isEmpty()) {
942 sink.text(artifactDescription);
943 } else {
944 sink.text(getI18nString("index", "nodescription"));
945 }
946 sink.paragraph_();
947
948 if (artifactUrl != null && !artifactUrl.isEmpty()) {
949 sink.paragraph();
950 sink.bold();
951 sink.text(getI18nString("column.url") + ": ");
952 sink.bold_();
953 if (ProjectInfoReportUtils.isArtifactUrlValid(artifactUrl)) {
954 sink.link(artifactUrl);
955 sink.text(artifactUrl);
956 sink.link_();
957 } else {
958 sink.text(artifactUrl);
959 }
960 sink.paragraph_();
961 }
962
963 sink.paragraph();
964 sink.bold();
965 sink.text(getI18nString("licenses", "title") + ": ");
966 sink.bold_();
967 if (!licenses.isEmpty()) {
968
969 for (Iterator<License> it = licenses.iterator(); it.hasNext(); ) {
970 License license = it.next();
971
972 String licenseName = license.getName();
973 if (licenseMappings != null && licenseMappings.containsKey(licenseName)) {
974 licenseName = licenseMappings.get(licenseName);
975 }
976 if (licenseName == null || licenseName.isEmpty()) {
977 licenseName = getI18nString("unnamed");
978 }
979
980 String licenseUrl = license.getUrl();
981
982 if (licenseUrl != null) {
983 sink.link(licenseUrl);
984 }
985 sink.text(licenseName);
986
987 if (licenseUrl != null) {
988 sink.link_();
989 }
990
991 if (it.hasNext()) {
992 sink.text(", ");
993 }
994
995 licenseMap.put(licenseName, artifactName);
996 }
997 } else {
998 sink.text(getI18nString("licenses", "nolicense"));
999
1000 licenseMap.put(unknownLicenseMessage, artifactName);
1001 }
1002 sink.paragraph_();
1003
1004 sink.tableCell_();
1005 sink.tableRow_();
1006
1007 endTable();
1008 } catch (ProjectBuildingException e) {
1009 sink.text(getI18nString("index", "nodescription"));
1010 if (log.isDebugEnabled()) {
1011 log.debug(
1012 "Unable to create Maven project from repository for artifact '" + artifact.getId() + "'",
1013 e);
1014 } else {
1015 log.info("Unable to create Maven project from repository for artifact '" + artifact.getId()
1016 + "', for more information run with -X");
1017 }
1018 }
1019 } else {
1020 startTable();
1021
1022 sink.tableRow();
1023 sink.tableHeaderCell();
1024 sink.text(id);
1025 sink.tableHeaderCell_();
1026 sink.tableRow_();
1027
1028 sink.tableRow();
1029 sink.tableCell();
1030
1031 sink.paragraph();
1032 sink.bold();
1033 sink.text(getI18nString("column.description") + ": ");
1034 sink.bold_();
1035 sink.text(getI18nString("index", "nodescription"));
1036 sink.paragraph_();
1037
1038 if (artifact.getFile() != null) {
1039 sink.paragraph();
1040 sink.bold();
1041 sink.text(getI18nString("column.url") + ": ");
1042 sink.bold_();
1043 sink.text(artifact.getFile().getAbsolutePath());
1044 sink.paragraph_();
1045 }
1046
1047 sink.tableCell_();
1048 sink.tableRow_();
1049
1050 endTable();
1051 }
1052
1053 sink.rawText("</div>");
1054 }
1055
1056 private void printGroupedLicenses() {
1057 for (Map.Entry<String, Object> entry : licenseMap.entrySet()) {
1058 String licenseName = entry.getKey();
1059 if (licenseName == null || licenseName.isEmpty()) {
1060 licenseName = getI18nString("unnamed");
1061 }
1062
1063 sink.paragraph();
1064 sink.bold();
1065 sink.text(licenseName);
1066 sink.text(": ");
1067 sink.bold_();
1068
1069 @SuppressWarnings("unchecked")
1070 SortedSet<String> projects = (SortedSet<String>) entry.getValue();
1071
1072 for (Iterator<String> iterator = projects.iterator(); iterator.hasNext(); ) {
1073 String projectName = iterator.next();
1074 sink.text(projectName);
1075 if (iterator.hasNext()) {
1076 sink.text(", ");
1077 }
1078 }
1079
1080 sink.paragraph_();
1081 }
1082 }
1083
1084
1085
1086
1087
1088
1089 private void resolveAtrifacts(List<Artifact> artifacts) {
1090 for (Artifact artifact : artifacts) {
1091
1092 if (artifact.getFile() == null) {
1093 if (Artifact.SCOPE_SYSTEM.equals(artifact.getScope())) {
1094
1095 continue;
1096 }
1097
1098 try {
1099 repoUtils.resolve(artifact);
1100 } catch (ArtifactResolverException e) {
1101 log.error("Artifact " + artifact.getId() + " can't be resolved.", e);
1102 continue;
1103 }
1104
1105 if (artifact.getFile() == null) {
1106 log.error("Artifact " + artifact.getId() + " has no file, even after resolution.");
1107 }
1108 }
1109 }
1110 }
1111
1112
1113
1114
1115
1116 private boolean hasClassifier(List<Artifact> artifacts) {
1117 for (Artifact artifact : artifacts) {
1118 if (StringUtils.isNotEmpty(artifact.getClassifier())) {
1119 return true;
1120 }
1121 }
1122
1123 return false;
1124 }
1125
1126
1127
1128
1129
1130 private boolean hasOptional(List<Artifact> artifacts) {
1131 for (Artifact artifact : artifacts) {
1132 if (artifact.isOptional()) {
1133 return true;
1134 }
1135 }
1136
1137 return false;
1138 }
1139
1140
1141
1142
1143
1144 private boolean hasSealed(List<Artifact> artifacts) {
1145 for (Artifact artifact : artifacts) {
1146 if (artifact.getFile() != null
1147 && JAR_SUBTYPE.contains(artifact.getType().toLowerCase())) {
1148 try {
1149 JarData jarDetails = dependencies.getJarDependencyDetails(artifact);
1150 if (jarDetails.isSealed()) {
1151 return true;
1152 }
1153 } catch (IOException e) {
1154 log.error("Artifact " + artifact.getId() + " caused IOException: " + e.getMessage(), e);
1155 }
1156 }
1157 }
1158 return false;
1159 }
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172 static class FileDecimalFormat extends DecimalFormat {
1173 private static final long serialVersionUID = 4062503546523610081L;
1174
1175 private final I18N i18n;
1176
1177 private final Locale locale;
1178
1179
1180
1181
1182
1183
1184
1185 FileDecimalFormat(I18N i18n, Locale locale) {
1186 super("###0.#");
1187
1188 this.i18n = i18n;
1189 this.locale = locale;
1190 }
1191
1192
1193 @Override
1194 public StringBuffer format(long fs, StringBuffer result, FieldPosition fieldPosition) {
1195 if (fs > 1000 * 1000 * 1000) {
1196 result = super.format((float) fs / (1000 * 1000 * 1000), result, fieldPosition);
1197 result.append(" ").append(getString("report.dependencies.file.details.column.size.gb"));
1198 return result;
1199 }
1200
1201 if (fs > 1000 * 1000) {
1202 result = super.format((float) fs / (1000 * 1000), result, fieldPosition);
1203 result.append(" ").append(getString("report.dependencies.file.details.column.size.mb"));
1204 return result;
1205 }
1206
1207 result = super.format((float) fs / 1000, result, fieldPosition);
1208 result.append(" ").append(getString("report.dependencies.file.details.column.size.kb"));
1209 return result;
1210 }
1211
1212 private String getString(String key) {
1213 return i18n.getString("project-info-reports", locale, key);
1214 }
1215 }
1216
1217
1218
1219
1220 static class TotalCell {
1221 public enum SummaryTableRowOrder {
1222
1223 TOTALS {
1224 @Override
1225 public void addTotal(TotalCell cell, long value) {
1226 cell.total += value;
1227 }
1228
1229 @Override
1230 public long getTotal(TotalCell cell) {
1231 return cell.total;
1232 }
1233
1234 @Override
1235 protected String formatMaxJavaVersionForScope(
1236 MessageFormat javaVersionFormat,
1237 double highestTestJavaVersion,
1238 double highestNonTestJavaVersion) {
1239 double highestJavaVersion = Math.max(highestTestJavaVersion, highestNonTestJavaVersion);
1240 return javaVersionFormat.format(new Object[] {highestJavaVersion});
1241 }
1242 },
1243 COMPILE_SCOPE(Artifact.SCOPE_COMPILE) {
1244 @Override
1245 public void addTotal(TotalCell cell, long value) {
1246 cell.totalCompileScope += value;
1247 }
1248
1249 @Override
1250 public long getTotal(TotalCell cell) {
1251 return cell.totalCompileScope;
1252 }
1253 },
1254 RUNTIME_SCOPE(Artifact.SCOPE_RUNTIME) {
1255 @Override
1256 public void addTotal(TotalCell cell, long value) {
1257 cell.totalRuntimeScope += value;
1258 }
1259
1260 @Override
1261 public long getTotal(TotalCell cell) {
1262 return cell.totalRuntimeScope;
1263 }
1264 },
1265 PROVIDED_SCOPE(Artifact.SCOPE_PROVIDED) {
1266 @Override
1267 public void addTotal(TotalCell cell, long value) {
1268 cell.totalProvidedScope += value;
1269 }
1270
1271 @Override
1272 public long getTotal(TotalCell cell) {
1273 return cell.totalProvidedScope;
1274 }
1275 },
1276 SYSTEM_SCOPE(Artifact.SCOPE_SYSTEM) {
1277 @Override
1278 public void addTotal(TotalCell cell, long value) {
1279 cell.totalSystemScope += value;
1280 }
1281
1282 @Override
1283 public long getTotal(TotalCell cell) {
1284 return cell.totalSystemScope;
1285 }
1286 },
1287 TEST_SCOPE(Artifact.SCOPE_TEST) {
1288 @Override
1289 public void addTotal(TotalCell cell, long value) {
1290 cell.totalTestScope += value;
1291 }
1292
1293 @Override
1294 public long getTotal(TotalCell cell) {
1295 return cell.totalTestScope;
1296 }
1297
1298 @Override
1299 protected String formatMaxJavaVersionForScope(
1300 MessageFormat javaVersionFormat,
1301 double highestTestJavaVersion,
1302 double highestNonTestJavaVersion) {
1303 return javaVersionFormat.format(new Object[] {highestTestJavaVersion});
1304 }
1305 };
1306
1307 private static final Map<String, SummaryTableRowOrder> MAP_BY_SCOPE = new HashMap<>();
1308
1309 static {
1310
1311 for (SummaryTableRowOrder e : SummaryTableRowOrder.values()) {
1312 MAP_BY_SCOPE.put(e.getScope(), e);
1313 }
1314 }
1315
1316 public static SummaryTableRowOrder fromScope(String scope) {
1317 return MAP_BY_SCOPE.get(scope);
1318 }
1319
1320 private String scope;
1321
1322 SummaryTableRowOrder() {
1323 this(null);
1324 }
1325
1326 SummaryTableRowOrder(String scope) {
1327 this.scope = scope;
1328 }
1329
1330 public String getScope() {
1331 return this.scope;
1332 }
1333
1334 protected String formatMaxJavaVersionForScope(
1335 MessageFormat javaVersionFormat, double highestTestJavaVersion, double highestNonTestJavaVersion) {
1336 return javaVersionFormat.format(new Object[] {highestNonTestJavaVersion});
1337 }
1338
1339 public abstract void addTotal(TotalCell cell, long value);
1340
1341 public abstract long getTotal(TotalCell cell);
1342 }
1343
1344 DecimalFormat decimalFormat;
1345
1346 long total = 0;
1347
1348 long totalCompileScope = 0;
1349
1350 long totalTestScope = 0;
1351
1352 long totalRuntimeScope = 0;
1353
1354 long totalProvidedScope = 0;
1355
1356 long totalSystemScope = 0;
1357
1358 TotalCell() {}
1359
1360 TotalCell(DecimalFormat decimalFormat) {
1361 this.decimalFormat = decimalFormat;
1362 }
1363
1364 void incrementTotal(String scope) {
1365 addTotal(1, scope);
1366 }
1367
1368 String getTotalString(SummaryTableRowOrder currentRow) {
1369 long totalString = currentRow.getTotal(this);
1370
1371 if (totalString <= 0) {
1372 return "";
1373 }
1374
1375 StringBuilder sb = new StringBuilder();
1376 if (currentRow.compareTo(SummaryTableRowOrder.COMPILE_SCOPE) >= 0) {
1377 sb.append(currentRow.getScope()).append(": ");
1378 }
1379 if (decimalFormat != null) {
1380 sb.append(decimalFormat.format(currentRow.getTotal(this)));
1381 } else {
1382 sb.append(currentRow.getTotal(this));
1383 }
1384
1385 return sb.toString();
1386 }
1387
1388 void addTotal(long add, String scope) {
1389 SummaryTableRowOrder.TOTALS.addTotal(this, add);
1390 SummaryTableRowOrder currentRow = SummaryTableRowOrder.fromScope(scope);
1391 currentRow.addTotal(this, add);
1392 }
1393
1394
1395 public String toString() {
1396 StringBuilder sb = new StringBuilder();
1397 if (decimalFormat != null) {
1398 sb.append(decimalFormat.format(total));
1399 } else {
1400 sb.append(total);
1401 }
1402
1403 sb.append(" (");
1404
1405 boolean needSeparator = false;
1406 for (int i = SummaryTableRowOrder.COMPILE_SCOPE.ordinal();
1407 i < SummaryTableRowOrder.TEST_SCOPE.ordinal();
1408 i++) {
1409 SummaryTableRowOrder currentRow = SummaryTableRowOrder.values()[i];
1410 if (currentRow.getTotal(this) > 0) {
1411 if (needSeparator) {
1412 sb.append(", ");
1413 }
1414 sb.append(getTotalString(currentRow));
1415 needSeparator = true;
1416 }
1417 }
1418
1419 sb.append(")");
1420
1421 return sb.toString();
1422 }
1423 }
1424 }