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