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 if (SummaryTableRowOrder.fromScope(artifact.getScope()) == null) {
537 log.warn("Artifact " + artifact.getId() + " has invalid scope"
538 + " and won't be listed in dependency files details.");
539 continue;
540 }
541
542 File artifactFile = dependencies.getFile(artifact);
543
544 totaldeps.incrementTotal(artifact.getScope());
545 totaldepsize.addTotal(artifactFile.length(), artifact.getScope());
546
547 if (JAR_SUBTYPE.contains(artifact.getType().toLowerCase())) {
548 try {
549 JarData jarDetails = dependencies.getJarDependencyDetails(artifact);
550
551 totalentries.addTotal(jarDetails.getNumEntries(), artifact.getScope());
552 totalclasses.addTotal(jarDetails.getNumClasses(), artifact.getScope());
553 totalpackages.addTotal(jarDetails.getNumPackages(), artifact.getScope());
554
555 String jdkRevisionCellValue = jarDetails.getJdkRevision();
556 String debugInformationCellValue = null;
557 String sealedCellValue = null;
558
559 if (jdkRevisionCellValue != null) {
560 try {
561 double jdkRevision = Double.parseDouble(jdkRevisionCellValue);
562 boolean isTestScope = Artifact.SCOPE_TEST.equalsIgnoreCase(artifact.getScope());
563 if (isTestScope) {
564 highestTestJavaVersion = Math.max(highestTestJavaVersion, jdkRevision);
565 } else {
566 highestNonTestJavaVersion = Math.max(highestNonTestJavaVersion, jdkRevision);
567 }
568 } catch (NumberFormatException e) {
569
570 }
571
572 debugInformationCellValue = debugInformationCellNo;
573 if (jarDetails.isDebugPresent()) {
574 debugInformationCellValue = debugInformationCellYes;
575 totalDebugInformation.incrementTotal(artifact.getScope());
576 }
577
578 sealedCellValue = sealedCellNo;
579 if (jarDetails.isSealed()) {
580 sealedCellValue = sealedCellYes;
581 totalsealed.incrementTotal(artifact.getScope());
582 }
583 }
584
585 String name = artifactFile.getName();
586 String fileLength = fileLengthDecimalFormat.format(artifactFile.length());
587
588 if (artifactFile.isDirectory()) {
589 File parent = artifactFile.getParentFile();
590 name = parent.getParentFile().getName() + '/' + parent.getName() + '/' + artifactFile.getName();
591 fileLength = "-";
592 }
593
594 tableRow(hasSealed, new String[] {
595 name,
596 fileLength,
597 String.valueOf(jarDetails.getNumEntries()),
598 String.valueOf(jarDetails.getNumClasses()),
599 String.valueOf(jarDetails.getNumPackages()),
600 jdkRevisionCellValue,
601 debugInformationCellValue,
602 sealedCellValue
603 });
604 } catch (IOException e) {
605 createExceptionInfoTableRow(artifact, artifactFile, e, hasSealed);
606 }
607 } else {
608 tableRow(hasSealed, new String[] {
609 artifactFile.getName(),
610 fileLengthDecimalFormat.format(artifactFile.length()),
611 "",
612 "",
613 "",
614 "",
615 "",
616 ""
617 });
618 }
619 }
620
621
622 tableHeader[0] = getI18nString("file.details.total");
623 tableHeader(tableHeader);
624
625 justification[0] = Sink.JUSTIFY_RIGHT;
626 justification[6] = Sink.JUSTIFY_RIGHT;
627
628
629 int rowspan = computeRowspan(totaldeps);
630
631 if (rowspan > 1) {
632 boolean insertRowspanAttr = false;
633 int column = 5;
634 for (SummaryTableRowOrder currentRow : SummaryTableRowOrder.values()) {
635 if (currentRow.getTotal(totaldeps) > 0) {
636 int i = currentRow.ordinal();
637 boolean alreadyInsertedRowspanAttr = insertRowspanAttr
638 && (SummaryTableRowOrder.COMPILE_SCOPE.ordinal() < i
639 && i <= SummaryTableRowOrder.SYSTEM_SCOPE.ordinal());
640 insertRowspanAttr = (SummaryTableRowOrder.COMPILE_SCOPE.ordinal() <= i
641 && i <= SummaryTableRowOrder.SYSTEM_SCOPE.ordinal());
642 justification[column] = (insertRowspanAttr && alreadyInsertedRowspanAttr)
643 ? justification[column + 1]
644 : Sink.JUSTIFY_CENTER;
645 tableRowWithRowspan(
646 hasSealed, insertRowspanAttr, alreadyInsertedRowspanAttr, column, rowspan, new String[] {
647 totaldeps.getTotalString(currentRow),
648 totaldepsize.getTotalString(currentRow),
649 totalentries.getTotalString(currentRow),
650 totalclasses.getTotalString(currentRow),
651 totalpackages.getTotalString(currentRow),
652 currentRow.formatMaxJavaVersionForScope(
653 javaVersionFormat, highestTestJavaVersion, highestNonTestJavaVersion),
654 totalDebugInformation.getTotalString(currentRow),
655 totalsealed.getTotalString(currentRow)
656 });
657 }
658 }
659 } else {
660 for (SummaryTableRowOrder currentRow : SummaryTableRowOrder.values()) {
661 if (currentRow.getTotal(totaldeps) > 0) {
662 tableRow(hasSealed, new String[] {
663 totaldeps.getTotalString(currentRow),
664 totaldepsize.getTotalString(currentRow),
665 totalentries.getTotalString(currentRow),
666 totalclasses.getTotalString(currentRow),
667 totalpackages.getTotalString(currentRow),
668 currentRow.formatMaxJavaVersionForScope(
669 javaVersionFormat, highestTestJavaVersion, highestNonTestJavaVersion),
670 totalDebugInformation.getTotalString(currentRow),
671 totalsealed.getTotalString(currentRow)
672 });
673 }
674 }
675 }
676
677 endTable();
678 endSection();
679 }
680
681 private int computeRowspan(TotalCell totaldeps) {
682 int rowspan = 0;
683 for (int i = SummaryTableRowOrder.COMPILE_SCOPE.ordinal();
684 i <= SummaryTableRowOrder.SYSTEM_SCOPE.ordinal();
685 i++) {
686 SummaryTableRowOrder currentRow = SummaryTableRowOrder.values()[i];
687 if (currentRow.getTotal(totaldeps) > 0) {
688 rowspan++;
689 }
690 }
691 return rowspan;
692 }
693
694
695 private void tableHeader(String[] content, String[] titles) {
696 sink.tableRow();
697
698 if (content != null) {
699 if (titles != null && content.length != titles.length) {
700 throw new IllegalArgumentException("Length of title array must equal the length of the content array");
701 }
702
703 for (int i = 0; i < content.length; i++) {
704 if (titles != null) {
705 tableHeaderCell(content[i], titles[i]);
706 } else {
707 tableHeaderCell(content[i]);
708 }
709 }
710 }
711
712 sink.tableRow_();
713 }
714
715 private void tableHeaderCell(String text, String title) {
716 if (title != null) {
717 sink.tableHeaderCell(new SinkEventAttributeSet(SinkEventAttributes.TITLE, title));
718 } else {
719 sink.tableHeaderCell();
720 }
721
722 text(text);
723
724 sink.tableHeaderCell_();
725 }
726
727 private void tableRowWithRowspan(
728 boolean fullRow, boolean insert, boolean alreadyInserted, int contentIndex, int rowspan, String[] content) {
729 sink.tableRow();
730
731 int count = fullRow ? content.length : (content.length - 1);
732
733 for (int i = 0; i < count; i++) {
734 if (i == contentIndex && insert) {
735 if (!alreadyInserted) {
736 SinkEventAttributes att = new SinkEventAttributeSet();
737 att.addAttribute(Attribute.ROWSPAN, rowspan);
738 att.addAttribute(Attribute.STYLE, "vertical-align: middle;");
739 sink.tableCell(att);
740 text(content[i]);
741 sink.tableCell_();
742 }
743 } else {
744 tableCell(content[i]);
745 }
746 }
747
748 sink.tableRow_();
749 }
750
751 private void tableRow(boolean fullRow, String[] content) {
752 sink.tableRow();
753
754 int count = fullRow ? content.length : (content.length - 1);
755
756 for (int i = 0; i < count; i++) {
757 tableCell(content[i]);
758 }
759
760 sink.tableRow_();
761 }
762
763 private void createExceptionInfoTableRow(Artifact artifact, File artifactFile, Exception e, boolean hasSealed) {
764 tableRow(
765 hasSealed,
766 new String[] {artifact.getId(), artifactFile.getAbsolutePath(), e.getMessage(), "", "", "", "", ""});
767 }
768
769 private void renderSectionDependencyLicenseListing() {
770 startSection(getI18nString("graph.tables.licenses"));
771 printGroupedLicenses();
772 endSection();
773 }
774
775 private void renderDependenciesForScope(String scope, List<Artifact> artifacts, boolean isTransitive) {
776 if (artifacts != null) {
777 boolean withClassifier = hasClassifier(artifacts);
778 boolean withOptional = hasOptional(artifacts);
779 String[] tableHeader = getDependencyTableHeader(withClassifier, withOptional);
780
781
782 Collections.sort(artifacts, getArtifactComparator());
783
784 String anchorByScope = isTransitive
785 ? getI18nString("transitive.title") + "_" + scope
786 : getI18nString("title") + "_" + scope;
787 startSection(scope, anchorByScope);
788
789 paragraph(getI18nString("intro." + scope));
790
791 startTable();
792 tableHeader(tableHeader);
793 for (Artifact artifact : artifacts) {
794 renderArtifactRow(artifact, withClassifier, withOptional);
795 }
796 endTable();
797
798 endSection();
799 }
800 }
801
802 private Comparator<Artifact> getArtifactComparator() {
803 return new Comparator<Artifact>() {
804 public int compare(Artifact a1, Artifact a2) {
805
806 if (a1.isOptional() && !a2.isOptional()) {
807 return +1;
808 } else if (!a1.isOptional() && a2.isOptional()) {
809 return -1;
810 } else {
811 return a1.compareTo(a2);
812 }
813 }
814 };
815 }
816
817
818
819
820
821
822
823 private void renderArtifactRow(Artifact artifact, boolean withClassifier, boolean withOptional) {
824 String isOptional =
825 artifact.isOptional() ? getI18nString("column.isOptional") : getI18nString("column.isNotOptional");
826
827 String url = ProjectInfoReportUtils.getArtifactUrl(repositorySystem, artifact, projectBuilder, buildingRequest);
828 String artifactIdCell = ProjectInfoReportUtils.getArtifactIdCell(artifact.getArtifactId(), url);
829
830 MavenProject artifactProject;
831 StringBuilder sb = new StringBuilder();
832 try {
833 artifactProject = repoUtils.getMavenProjectFromRepository(artifact);
834
835 List<License> licenses = artifactProject.getLicenses();
836 for (License license : licenses) {
837 String name = license.getName();
838 if (licenseMappings != null && licenseMappings.containsKey(name)) {
839 name = licenseMappings.get(name);
840 }
841 sb.append(ProjectInfoReportUtils.getArtifactIdCell(name, license.getUrl()));
842 }
843 } catch (ProjectBuildingException e) {
844 if (log.isDebugEnabled()) {
845 log.debug("Unable to create Maven project from repository for artifact '" + artifact.getId() + "'", e);
846 } else {
847 log.info("Unable to create Maven project from repository for artifact '" + artifact.getId()
848 + "', for more information run with -X");
849 }
850 }
851
852 String[] content;
853 if (withClassifier) {
854 content = new String[] {
855 artifact.getGroupId(),
856 artifactIdCell,
857 artifact.getVersion(),
858 artifact.getClassifier(),
859 artifact.getType(),
860 sb.toString(),
861 isOptional
862 };
863 } else {
864 content = new String[] {
865 artifact.getGroupId(),
866 artifactIdCell,
867 artifact.getVersion(),
868 artifact.getType(),
869 sb.toString(),
870 isOptional
871 };
872 }
873
874 tableRow(withOptional, content);
875 }
876
877 private void printDependencyListing(DependencyNode node) {
878 Artifact artifact = node.getArtifact();
879 String id = artifact.getId();
880 String dependencyDetailId = "_dep" + idCounter++;
881 String imgId = "_img" + idCounter++;
882
883 sink.listItem();
884
885 sink.text(id + (StringUtils.isNotEmpty(artifact.getScope()) ? " (" + artifact.getScope() + ") " : " "));
886
887 String javascript = String.format(
888 "<img id=\"%s\" src=\"%s\" alt=\"%s\""
889 + " onclick=\"toggleDependencyDetails( '%s', '%s' );\""
890 + " style=\"cursor: pointer; vertical-align: text-bottom;\" />",
891 imgId, IMG_INFO_URL, getI18nString("graph.icon.information"), dependencyDetailId, imgId);
892
893 sink.rawText(javascript);
894
895 printDescriptionsAndURLs(node, dependencyDetailId);
896
897 if (!node.getChildren().isEmpty()) {
898 boolean toBeIncluded = false;
899 List<DependencyNode> subList = new ArrayList<>();
900 for (DependencyNode dep : node.getChildren()) {
901 if (dependencies.getAllDependencies().contains(dep.getArtifact())) {
902 subList.add(dep);
903 toBeIncluded = true;
904 }
905 }
906
907 if (toBeIncluded) {
908 sink.list();
909 for (DependencyNode dep : subList) {
910 printDependencyListing(dep);
911 }
912 sink.list_();
913 }
914 }
915
916 sink.listItem_();
917 }
918
919 private void printDescriptionsAndURLs(DependencyNode node, String uid) {
920 Artifact artifact = node.getArtifact();
921 String id = artifact.getId();
922 String unknownLicenseMessage = getI18nString("graph.tables.unknown");
923
924 sink.rawText("<div id=\"" + uid + "\" style=\"display:none\">");
925
926 if (!Artifact.SCOPE_SYSTEM.equals(artifact.getScope())) {
927 try {
928 MavenProject artifactProject = repoUtils.getMavenProjectFromRepository(artifact);
929 String artifactDescription = artifactProject.getDescription();
930 String artifactUrl = artifactProject.getUrl();
931 String artifactName = artifactProject.getName();
932
933 List<License> licenses = artifactProject.getLicenses();
934
935 startTable();
936
937 sink.tableRow();
938 sink.tableHeaderCell();
939 sink.text(artifactName);
940 sink.tableHeaderCell_();
941 sink.tableRow_();
942
943 sink.tableRow();
944 sink.tableCell();
945
946 sink.paragraph();
947 sink.bold();
948 sink.text(getI18nString("column.description") + ": ");
949 sink.bold_();
950 if (artifactDescription != null && !artifactDescription.isEmpty()) {
951 sink.text(artifactDescription);
952 } else {
953 sink.text(getI18nString("index", "nodescription"));
954 }
955 sink.paragraph_();
956
957 if (artifactUrl != null && !artifactUrl.isEmpty()) {
958 sink.paragraph();
959 sink.bold();
960 sink.text(getI18nString("column.url") + ": ");
961 sink.bold_();
962 if (ProjectInfoReportUtils.isArtifactUrlValid(artifactUrl)) {
963 sink.link(artifactUrl);
964 sink.text(artifactUrl);
965 sink.link_();
966 } else {
967 sink.text(artifactUrl);
968 }
969 sink.paragraph_();
970 }
971
972 sink.paragraph();
973 sink.bold();
974 sink.text(getI18nString("licenses", "title") + ": ");
975 sink.bold_();
976 if (!licenses.isEmpty()) {
977
978 for (Iterator<License> it = licenses.iterator(); it.hasNext(); ) {
979 License license = it.next();
980
981 String licenseName = license.getName();
982 if (licenseMappings != null && licenseMappings.containsKey(licenseName)) {
983 licenseName = licenseMappings.get(licenseName);
984 }
985 if (licenseName == null || licenseName.isEmpty()) {
986 licenseName = getI18nString("unnamed");
987 }
988
989 String licenseUrl = license.getUrl();
990
991 if (licenseUrl != null) {
992 sink.link(licenseUrl);
993 }
994 sink.text(licenseName);
995
996 if (licenseUrl != null) {
997 sink.link_();
998 }
999
1000 if (it.hasNext()) {
1001 sink.text(", ");
1002 }
1003
1004 licenseMap.put(licenseName, artifactName);
1005 }
1006 } else {
1007 sink.text(getI18nString("licenses", "nolicense"));
1008
1009 licenseMap.put(unknownLicenseMessage, artifactName);
1010 }
1011 sink.paragraph_();
1012
1013 sink.tableCell_();
1014 sink.tableRow_();
1015
1016 endTable();
1017 } catch (ProjectBuildingException e) {
1018 sink.text(getI18nString("index", "nodescription"));
1019 if (log.isDebugEnabled()) {
1020 log.debug(
1021 "Unable to create Maven project from repository for artifact '" + artifact.getId() + "'",
1022 e);
1023 } else {
1024 log.info("Unable to create Maven project from repository for artifact '" + artifact.getId()
1025 + "', for more information run with -X");
1026 }
1027 }
1028 } else {
1029 startTable();
1030
1031 sink.tableRow();
1032 sink.tableHeaderCell();
1033 sink.text(id);
1034 sink.tableHeaderCell_();
1035 sink.tableRow_();
1036
1037 sink.tableRow();
1038 sink.tableCell();
1039
1040 sink.paragraph();
1041 sink.bold();
1042 sink.text(getI18nString("column.description") + ": ");
1043 sink.bold_();
1044 sink.text(getI18nString("index", "nodescription"));
1045 sink.paragraph_();
1046
1047 if (artifact.getFile() != null) {
1048 sink.paragraph();
1049 sink.bold();
1050 sink.text(getI18nString("column.url") + ": ");
1051 sink.bold_();
1052 sink.text(artifact.getFile().getAbsolutePath());
1053 sink.paragraph_();
1054 }
1055
1056 sink.tableCell_();
1057 sink.tableRow_();
1058
1059 endTable();
1060 }
1061
1062 sink.rawText("</div>");
1063 }
1064
1065 private void printGroupedLicenses() {
1066 for (Map.Entry<String, Object> entry : licenseMap.entrySet()) {
1067 String licenseName = entry.getKey();
1068 if (licenseName == null || licenseName.isEmpty()) {
1069 licenseName = getI18nString("unnamed");
1070 }
1071
1072 sink.paragraph();
1073 sink.bold();
1074 sink.text(licenseName);
1075 sink.text(": ");
1076 sink.bold_();
1077
1078 @SuppressWarnings("unchecked")
1079 SortedSet<String> projects = (SortedSet<String>) entry.getValue();
1080
1081 for (Iterator<String> iterator = projects.iterator(); iterator.hasNext(); ) {
1082 String projectName = iterator.next();
1083 sink.text(projectName);
1084 if (iterator.hasNext()) {
1085 sink.text(", ");
1086 }
1087 }
1088
1089 sink.paragraph_();
1090 }
1091 }
1092
1093
1094
1095
1096
1097
1098 private void resolveAtrifacts(List<Artifact> artifacts) {
1099 for (Artifact artifact : artifacts) {
1100
1101 if (artifact.getFile() == null) {
1102 if (Artifact.SCOPE_SYSTEM.equals(artifact.getScope())) {
1103
1104 continue;
1105 }
1106
1107 try {
1108 repoUtils.resolve(artifact);
1109 } catch (ArtifactResolverException e) {
1110 log.error("Artifact " + artifact.getId() + " can't be resolved.", e);
1111 continue;
1112 }
1113
1114 if (artifact.getFile() == null) {
1115 log.error("Artifact " + artifact.getId() + " has no file, even after resolution.");
1116 }
1117 }
1118 }
1119 }
1120
1121
1122
1123
1124
1125 private boolean hasClassifier(List<Artifact> artifacts) {
1126 for (Artifact artifact : artifacts) {
1127 if (StringUtils.isNotEmpty(artifact.getClassifier())) {
1128 return true;
1129 }
1130 }
1131
1132 return false;
1133 }
1134
1135
1136
1137
1138
1139 private boolean hasOptional(List<Artifact> artifacts) {
1140 for (Artifact artifact : artifacts) {
1141 if (artifact.isOptional()) {
1142 return true;
1143 }
1144 }
1145
1146 return false;
1147 }
1148
1149
1150
1151
1152
1153 private boolean hasSealed(List<Artifact> artifacts) {
1154 for (Artifact artifact : artifacts) {
1155 if (artifact.getFile() != null
1156 && JAR_SUBTYPE.contains(artifact.getType().toLowerCase())) {
1157 try {
1158 JarData jarDetails = dependencies.getJarDependencyDetails(artifact);
1159 if (jarDetails.isSealed()) {
1160 return true;
1161 }
1162 } catch (IOException e) {
1163 log.error("Artifact " + artifact.getId() + " caused IOException: " + e.getMessage(), e);
1164 }
1165 }
1166 }
1167 return false;
1168 }
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181 static class FileDecimalFormat extends DecimalFormat {
1182 private static final long serialVersionUID = 4062503546523610081L;
1183
1184 private final I18N i18n;
1185
1186 private final Locale locale;
1187
1188
1189
1190
1191
1192
1193
1194 FileDecimalFormat(I18N i18n, Locale locale) {
1195 super("###0.#");
1196
1197 this.i18n = i18n;
1198 this.locale = locale;
1199 }
1200
1201
1202 @Override
1203 public StringBuffer format(long fs, StringBuffer result, FieldPosition fieldPosition) {
1204 if (fs > 1000 * 1000 * 1000) {
1205 result = super.format((float) fs / (1000 * 1000 * 1000), result, fieldPosition);
1206 result.append(" ").append(getString("report.dependencies.file.details.column.size.gb"));
1207 return result;
1208 }
1209
1210 if (fs > 1000 * 1000) {
1211 result = super.format((float) fs / (1000 * 1000), result, fieldPosition);
1212 result.append(" ").append(getString("report.dependencies.file.details.column.size.mb"));
1213 return result;
1214 }
1215
1216 result = super.format((float) fs / 1000, result, fieldPosition);
1217 result.append(" ").append(getString("report.dependencies.file.details.column.size.kb"));
1218 return result;
1219 }
1220
1221 private String getString(String key) {
1222 return i18n.getString("project-info-reports", locale, key);
1223 }
1224 }
1225
1226
1227
1228
1229 static class TotalCell {
1230 public enum SummaryTableRowOrder {
1231
1232 TOTALS {
1233 @Override
1234 public void addTotal(TotalCell cell, long value) {
1235 cell.total += value;
1236 }
1237
1238 @Override
1239 public long getTotal(TotalCell cell) {
1240 return cell.total;
1241 }
1242
1243 @Override
1244 protected String formatMaxJavaVersionForScope(
1245 MessageFormat javaVersionFormat,
1246 double highestTestJavaVersion,
1247 double highestNonTestJavaVersion) {
1248 double highestJavaVersion = Math.max(highestTestJavaVersion, highestNonTestJavaVersion);
1249 return javaVersionFormat.format(new Object[] {highestJavaVersion});
1250 }
1251 },
1252 COMPILE_SCOPE(Artifact.SCOPE_COMPILE) {
1253 @Override
1254 public void addTotal(TotalCell cell, long value) {
1255 cell.totalCompileScope += value;
1256 }
1257
1258 @Override
1259 public long getTotal(TotalCell cell) {
1260 return cell.totalCompileScope;
1261 }
1262 },
1263 RUNTIME_SCOPE(Artifact.SCOPE_RUNTIME) {
1264 @Override
1265 public void addTotal(TotalCell cell, long value) {
1266 cell.totalRuntimeScope += value;
1267 }
1268
1269 @Override
1270 public long getTotal(TotalCell cell) {
1271 return cell.totalRuntimeScope;
1272 }
1273 },
1274 PROVIDED_SCOPE(Artifact.SCOPE_PROVIDED) {
1275 @Override
1276 public void addTotal(TotalCell cell, long value) {
1277 cell.totalProvidedScope += value;
1278 }
1279
1280 @Override
1281 public long getTotal(TotalCell cell) {
1282 return cell.totalProvidedScope;
1283 }
1284 },
1285 SYSTEM_SCOPE(Artifact.SCOPE_SYSTEM) {
1286 @Override
1287 public void addTotal(TotalCell cell, long value) {
1288 cell.totalSystemScope += value;
1289 }
1290
1291 @Override
1292 public long getTotal(TotalCell cell) {
1293 return cell.totalSystemScope;
1294 }
1295 },
1296 TEST_SCOPE(Artifact.SCOPE_TEST) {
1297 @Override
1298 public void addTotal(TotalCell cell, long value) {
1299 cell.totalTestScope += value;
1300 }
1301
1302 @Override
1303 public long getTotal(TotalCell cell) {
1304 return cell.totalTestScope;
1305 }
1306
1307 @Override
1308 protected String formatMaxJavaVersionForScope(
1309 MessageFormat javaVersionFormat,
1310 double highestTestJavaVersion,
1311 double highestNonTestJavaVersion) {
1312 return javaVersionFormat.format(new Object[] {highestTestJavaVersion});
1313 }
1314 };
1315
1316 private static final Map<String, SummaryTableRowOrder> MAP_BY_SCOPE = new HashMap<>();
1317
1318 static {
1319
1320 for (SummaryTableRowOrder e : SummaryTableRowOrder.values()) {
1321 MAP_BY_SCOPE.put(e.getScope(), e);
1322 }
1323 }
1324
1325 public static SummaryTableRowOrder fromScope(String scope) {
1326 return MAP_BY_SCOPE.get(scope);
1327 }
1328
1329 private String scope;
1330
1331 SummaryTableRowOrder() {
1332 this(null);
1333 }
1334
1335 SummaryTableRowOrder(String scope) {
1336 this.scope = scope;
1337 }
1338
1339 public String getScope() {
1340 return this.scope;
1341 }
1342
1343 protected String formatMaxJavaVersionForScope(
1344 MessageFormat javaVersionFormat, double highestTestJavaVersion, double highestNonTestJavaVersion) {
1345 return javaVersionFormat.format(new Object[] {highestNonTestJavaVersion});
1346 }
1347
1348 public abstract void addTotal(TotalCell cell, long value);
1349
1350 public abstract long getTotal(TotalCell cell);
1351 }
1352
1353 DecimalFormat decimalFormat;
1354
1355 long total = 0;
1356
1357 long totalCompileScope = 0;
1358
1359 long totalTestScope = 0;
1360
1361 long totalRuntimeScope = 0;
1362
1363 long totalProvidedScope = 0;
1364
1365 long totalSystemScope = 0;
1366
1367 TotalCell() {}
1368
1369 TotalCell(DecimalFormat decimalFormat) {
1370 this.decimalFormat = decimalFormat;
1371 }
1372
1373 void incrementTotal(String scope) {
1374 addTotal(1, scope);
1375 }
1376
1377 String getTotalString(SummaryTableRowOrder currentRow) {
1378 long totalString = currentRow.getTotal(this);
1379
1380 if (totalString <= 0) {
1381 return "";
1382 }
1383
1384 StringBuilder sb = new StringBuilder();
1385 if (currentRow.compareTo(SummaryTableRowOrder.COMPILE_SCOPE) >= 0) {
1386 sb.append(currentRow.getScope()).append(": ");
1387 }
1388 if (decimalFormat != null) {
1389 sb.append(decimalFormat.format(currentRow.getTotal(this)));
1390 } else {
1391 sb.append(currentRow.getTotal(this));
1392 }
1393
1394 return sb.toString();
1395 }
1396
1397 void addTotal(long add, String scope) {
1398 SummaryTableRowOrder.TOTALS.addTotal(this, add);
1399 SummaryTableRowOrder currentRow = SummaryTableRowOrder.fromScope(scope);
1400 currentRow.addTotal(this, add);
1401 }
1402
1403
1404 public String toString() {
1405 StringBuilder sb = new StringBuilder();
1406 if (decimalFormat != null) {
1407 sb.append(decimalFormat.format(total));
1408 } else {
1409 sb.append(total);
1410 }
1411
1412 sb.append(" (");
1413
1414 boolean needSeparator = false;
1415 for (int i = SummaryTableRowOrder.COMPILE_SCOPE.ordinal();
1416 i < SummaryTableRowOrder.TEST_SCOPE.ordinal();
1417 i++) {
1418 SummaryTableRowOrder currentRow = SummaryTableRowOrder.values()[i];
1419 if (currentRow.getTotal(this) > 0) {
1420 if (needSeparator) {
1421 sb.append(", ");
1422 }
1423 sb.append(getTotalString(currentRow));
1424 needSeparator = true;
1425 }
1426 }
1427
1428 sb.append(")");
1429
1430 return sb.toString();
1431 }
1432 }
1433 }