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