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