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