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;\" />",
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 sink.tableRows( null, false );
915
916 sink.tableRow();
917 sink.tableHeaderCell();
918 sink.text( artifactName );
919 sink.tableHeaderCell_();
920 sink.tableRow_();
921
922 sink.tableRow();
923 sink.tableCell();
924
925 sink.paragraph();
926 sink.bold();
927 sink.text( getI18nString( "column.description" ) + ": " );
928 sink.bold_();
929 if ( StringUtils.isNotEmpty( artifactDescription ) )
930 {
931 sink.text( artifactDescription );
932 }
933 else
934 {
935 sink.text( getI18nString( "index", "nodescription" ) );
936 }
937 sink.paragraph_();
938
939 if ( StringUtils.isNotEmpty( artifactUrl ) )
940 {
941 sink.paragraph();
942 sink.bold();
943 sink.text( getI18nString( "column.url" ) + ": " );
944 sink.bold_();
945 if ( ProjectInfoReportUtils.isArtifactUrlValid( artifactUrl ) )
946 {
947 sink.link( artifactUrl );
948 sink.text( artifactUrl );
949 sink.link_();
950 }
951 else
952 {
953 sink.text( artifactUrl );
954 }
955 sink.paragraph_();
956 }
957
958 sink.paragraph();
959 sink.bold();
960 sink.text( getI18nString( "licenses", "title" ) + ": " );
961 sink.bold_();
962 if ( !licenses.isEmpty() )
963 {
964
965 for ( Iterator<License> it = licenses.iterator(); it.hasNext(); )
966 {
967 License license = it.next();
968
969 String licenseName = license.getName();
970 if ( licenseMappings != null && licenseMappings.containsKey( licenseName ) )
971 {
972 licenseName = licenseMappings.get( licenseName );
973 }
974 if ( StringUtils.isEmpty( licenseName ) )
975 {
976 licenseName = getI18nString( "unnamed" );
977 }
978
979 String licenseUrl = license.getUrl();
980
981 if ( licenseUrl != null )
982 {
983 sink.link( licenseUrl );
984 }
985 sink.text( licenseName );
986
987 if ( licenseUrl != null )
988 {
989 sink.link_();
990 }
991
992 if ( it.hasNext() )
993 {
994 sink.text( ", " );
995 }
996
997 licenseMap.put( licenseName, artifactName );
998 }
999 }
1000 else
1001 {
1002 sink.text( getI18nString( "licenses", "nolicense" ) );
1003
1004 licenseMap.put( unknownLicenseMessage, artifactName );
1005 }
1006 sink.paragraph_();
1007
1008 sink.tableCell_();
1009 sink.tableRow_();
1010
1011 sink.tableRows_();
1012 sink.table_();
1013 }
1014 catch ( ProjectBuildingException e )
1015 {
1016 sink.text( getI18nString( "index", "nodescription" ) );
1017 if ( log.isDebugEnabled() )
1018 {
1019 log.debug( "Unable to create Maven project from repository for artifact '"
1020 + artifact.getId() + "'", e );
1021 }
1022 else
1023 {
1024 log.info( "Unable to create Maven project from repository for artifact '"
1025 + artifact.getId() + "', for more information run with -X" );
1026 }
1027 }
1028 }
1029 else
1030 {
1031 sink.table();
1032 sink.tableRows( null, false );
1033
1034 sink.tableRow();
1035 sink.tableHeaderCell();
1036 sink.text( id );
1037 sink.tableHeaderCell_();
1038 sink.tableRow_();
1039
1040 sink.tableRow();
1041 sink.tableCell();
1042
1043 sink.paragraph();
1044 sink.bold();
1045 sink.text( getI18nString( "column.description" ) + ": " );
1046 sink.bold_();
1047 sink.text( getI18nString( "index", "nodescription" ) );
1048 sink.paragraph_();
1049
1050 if ( artifact.getFile() != null )
1051 {
1052 sink.paragraph();
1053 sink.bold();
1054 sink.text( getI18nString( "column.url" ) + ": " );
1055 sink.bold_();
1056 sink.text( artifact.getFile().getAbsolutePath() );
1057 sink.paragraph_();
1058 }
1059
1060 sink.tableCell_();
1061 sink.tableRow_();
1062
1063 sink.tableRows_();
1064 sink.table_();
1065 }
1066
1067 sink.rawText( "</div>" );
1068 }
1069
1070 private void printGroupedLicenses()
1071 {
1072 for ( Map.Entry<String, Object> entry : licenseMap.entrySet() )
1073 {
1074 String licenseName = entry.getKey();
1075 if ( StringUtils.isEmpty( licenseName ) )
1076 {
1077 licenseName = getI18nString( "unnamed" );
1078 }
1079
1080 sink.paragraph();
1081 sink.bold();
1082 sink.text( licenseName );
1083 sink.text( ": " );
1084 sink.bold_();
1085
1086 @SuppressWarnings( "unchecked" )
1087 SortedSet<String> projects = (SortedSet<String>) entry.getValue();
1088
1089 for ( Iterator<String> iterator = projects.iterator(); iterator.hasNext(); )
1090 {
1091 String projectName = iterator.next();
1092 sink.text( projectName );
1093 if ( iterator.hasNext() )
1094 {
1095 sink.text( ", " );
1096 }
1097 }
1098
1099 sink.paragraph_();
1100 }
1101 }
1102
1103
1104
1105
1106
1107
1108 private void resolveAtrifacts( List<Artifact> artifacts )
1109 {
1110 for ( Artifact artifact : artifacts )
1111 {
1112
1113 if ( artifact.getFile() == null )
1114 {
1115 if ( Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) )
1116 {
1117
1118 continue;
1119 }
1120
1121 try
1122 {
1123 repoUtils.resolve( artifact );
1124 }
1125 catch ( ArtifactResolverException e )
1126 {
1127 log.error( "Artifact " + artifact.getId() + " can't be resolved.", e );
1128 continue;
1129 }
1130
1131 if ( artifact.getFile() == null )
1132 {
1133 log.error( "Artifact " + artifact.getId() + " has no file, even after resolution." );
1134 }
1135 }
1136 }
1137 }
1138
1139
1140
1141
1142
1143 private boolean hasClassifier( List<Artifact> artifacts )
1144 {
1145 for ( Artifact artifact : artifacts )
1146 {
1147 if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
1148 {
1149 return true;
1150 }
1151 }
1152
1153 return false;
1154 }
1155
1156
1157
1158
1159
1160 private boolean hasOptional( List<Artifact> artifacts )
1161 {
1162 for ( Artifact artifact : artifacts )
1163 {
1164 if ( artifact.isOptional() )
1165 {
1166 return true;
1167 }
1168 }
1169
1170 return false;
1171 }
1172
1173
1174
1175
1176
1177 private boolean hasSealed( List<Artifact> artifacts )
1178 {
1179 for ( Artifact artifact : artifacts )
1180 {
1181 if ( artifact.getFile() != null && JAR_SUBTYPE.contains( artifact.getType().toLowerCase() ) )
1182 {
1183 try
1184 {
1185 JarData jarDetails = dependencies.getJarDependencyDetails( artifact );
1186 if ( jarDetails.isSealed() )
1187 {
1188 return true;
1189 }
1190 }
1191 catch ( IOException e )
1192 {
1193 log.error( "Artifact " + artifact.getId() + " caused IOException: " + e.getMessage(), e );
1194 }
1195 }
1196 }
1197 return false;
1198 }
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211 static class FileDecimalFormat
1212 extends DecimalFormat
1213 {
1214 private static final long serialVersionUID = 4062503546523610081L;
1215
1216 private final I18N i18n;
1217
1218 private final Locale locale;
1219
1220
1221
1222
1223
1224
1225
1226 FileDecimalFormat( I18N i18n, Locale locale )
1227 {
1228 super( "###0.#" );
1229
1230 this.i18n = i18n;
1231 this.locale = locale;
1232 }
1233
1234
1235 @Override
1236 public StringBuffer format( long fs, StringBuffer result, FieldPosition fieldPosition )
1237 {
1238 if ( fs > 1000 * 1000 * 1000 )
1239 {
1240 result = super.format( (float) fs / ( 1000 * 1000 * 1000 ), result, fieldPosition );
1241 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.gb" ) );
1242 return result;
1243 }
1244
1245 if ( fs > 1000 * 1000 )
1246 {
1247 result = super.format( (float) fs / ( 1000 * 1000 ), result, fieldPosition );
1248 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.mb" ) );
1249 return result;
1250 }
1251
1252 result = super.format( (float) fs / ( 1000 ), result, fieldPosition );
1253 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.kb" ) );
1254 return result;
1255 }
1256
1257 private String getString( String key )
1258 {
1259 return i18n.getString( "project-info-reports", locale, key );
1260 }
1261 }
1262
1263
1264
1265
1266 static class TotalCell
1267 {
1268 static final int SCOPES_COUNT = 5;
1269
1270 final DecimalFormat decimalFormat;
1271
1272 long total = 0;
1273
1274 long totalCompileScope = 0;
1275
1276 long totalTestScope = 0;
1277
1278 long totalRuntimeScope = 0;
1279
1280 long totalProvidedScope = 0;
1281
1282 long totalSystemScope = 0;
1283
1284 TotalCell( DecimalFormat decimalFormat )
1285 {
1286 this.decimalFormat = decimalFormat;
1287 }
1288
1289 void incrementTotal( String scope )
1290 {
1291 addTotal( 1, scope );
1292 }
1293
1294 static String getScope( int index )
1295 {
1296 switch ( index )
1297 {
1298 case 0:
1299 return Artifact.SCOPE_COMPILE;
1300 case 1:
1301 return Artifact.SCOPE_TEST;
1302 case 2:
1303 return Artifact.SCOPE_RUNTIME;
1304 case 3:
1305 return Artifact.SCOPE_PROVIDED;
1306 case 4:
1307 return Artifact.SCOPE_SYSTEM;
1308 default:
1309 return null;
1310 }
1311 }
1312
1313 long getTotal( int index )
1314 {
1315 switch ( index )
1316 {
1317 case 0:
1318 return totalCompileScope;
1319 case 1:
1320 return totalTestScope;
1321 case 2:
1322 return totalRuntimeScope;
1323 case 3:
1324 return totalProvidedScope;
1325 case 4:
1326 return totalSystemScope;
1327 default:
1328 return total;
1329 }
1330 }
1331
1332 String getTotalString( int index )
1333 {
1334 long totalString = getTotal( index );
1335
1336 if ( totalString <= 0 )
1337 {
1338 return "";
1339 }
1340
1341 StringBuilder sb = new StringBuilder();
1342 if ( index >= 0 )
1343 {
1344 sb.append( getScope( index ) ).append( ": " );
1345 }
1346 sb.append( decimalFormat.format( getTotal( index ) ) );
1347 return sb.toString();
1348 }
1349
1350 void addTotal( long add, String scope )
1351 {
1352 total += add;
1353
1354 if ( Artifact.SCOPE_COMPILE.equals( scope ) )
1355 {
1356 totalCompileScope += add;
1357 }
1358 else if ( Artifact.SCOPE_TEST.equals( scope ) )
1359 {
1360 totalTestScope += add;
1361 }
1362 else if ( Artifact.SCOPE_RUNTIME.equals( scope ) )
1363 {
1364 totalRuntimeScope += add;
1365 }
1366 else if ( Artifact.SCOPE_PROVIDED.equals( scope ) )
1367 {
1368 totalProvidedScope += add;
1369 }
1370 else if ( Artifact.SCOPE_SYSTEM.equals( scope ) )
1371 {
1372 totalSystemScope += add;
1373 }
1374 }
1375
1376
1377 public String toString()
1378 {
1379 StringBuilder sb = new StringBuilder();
1380 sb.append( decimalFormat.format( total ) );
1381 sb.append( " (" );
1382
1383 boolean needSeparator = false;
1384 for ( int i = 0; i < SCOPES_COUNT; i++ )
1385 {
1386 if ( getTotal( i ) > 0 )
1387 {
1388 if ( needSeparator )
1389 {
1390 sb.append( ", " );
1391 }
1392 sb.append( getTotalString( i ) );
1393 needSeparator = true;
1394 }
1395 }
1396
1397 sb.append( ")" );
1398
1399 return sb.toString();
1400 }
1401 }
1402 }