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