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