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.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
664 throw new IllegalArgumentException( "Length of title array must equal the length of the content array" );
665
666 }
667
668 for ( int i = 0; i < content.length; i++ )
669 {
670 if ( titles != null )
671 {
672 tableHeaderCell( content[i], titles[i] );
673 }
674 else
675 {
676 tableHeaderCell( content[i] );
677 }
678 }
679 }
680
681 sink.tableRow_();
682 }
683
684 private void tableHeaderCell( String text, String title )
685 {
686 if ( title != null )
687 {
688 sink.tableHeaderCell( new SinkEventAttributeSet( SinkEventAttributes.TITLE, title ) );
689 }
690 else
691 {
692 sink.tableHeaderCell();
693 }
694
695 text( text );
696
697 sink.tableHeaderCell_();
698 }
699
700 private void tableRow( boolean fullRow, String[] content )
701 {
702 sink.tableRow();
703
704 int count = fullRow ? content.length : ( content.length - 1 );
705
706 for ( int i = 0; i < count; i++ )
707 {
708 tableCell( content[i] );
709 }
710
711 sink.tableRow_();
712 }
713
714 private void createExceptionInfoTableRow( Artifact artifact, File artifactFile, Exception e, boolean hasSealed )
715 {
716 tableRow( hasSealed, new String[] { artifact.getId(), artifactFile.getAbsolutePath(), e.getMessage(), "", "",
717 "", "", "" } );
718 }
719
720 private void renderSectionDependencyLicenseListing()
721 {
722 startSection( getI18nString( "graph.tables.licenses" ) );
723 printGroupedLicenses();
724 endSection();
725 }
726
727 private void renderDependenciesForScope( String scope, List<Artifact> artifacts, boolean isTransitive )
728 {
729 if ( artifacts != null )
730 {
731 boolean withClassifier = hasClassifier( artifacts );
732 boolean withOptional = hasOptional( artifacts );
733 String[] tableHeader = getDependencyTableHeader( withClassifier, withOptional );
734
735
736 Collections.sort( artifacts, getArtifactComparator() );
737
738 String anchorByScope =
739 ( isTransitive ? getI18nString( "transitive.title" ) + "_" + scope : getI18nString( "title" ) + "_"
740 + scope );
741 startSection( anchorByScope, scope );
742
743 paragraph( getI18nString( "intro." + scope ) );
744
745 startTable();
746 tableHeader( tableHeader );
747 for ( Artifact artifact : artifacts )
748 {
749 renderArtifactRow( artifact, withClassifier, withOptional );
750 }
751 endTable();
752
753 endSection();
754 }
755 }
756
757 private Comparator<Artifact> getArtifactComparator()
758 {
759 return new Comparator<Artifact>()
760 {
761 public int compare( Artifact a1, Artifact a2 )
762 {
763
764 if ( a1.isOptional() && !a2.isOptional() )
765 {
766 return +1;
767 }
768 else if ( !a1.isOptional() && a2.isOptional() )
769 {
770 return -1;
771 }
772 else
773 {
774 return a1.compareTo( a2 );
775 }
776 }
777 };
778 }
779
780
781
782
783
784
785
786 private void renderArtifactRow( Artifact artifact, boolean withClassifier, boolean withOptional )
787 {
788 String isOptional =
789 artifact.isOptional() ? getI18nString( "column.isOptional" ) : getI18nString( "column.isNotOptional" );
790
791 String url =
792 ProjectInfoReportUtils.getArtifactUrl( repositorySystem, artifact, projectBuilder, buildingRequest );
793 String artifactIdCell = ProjectInfoReportUtils.getArtifactIdCell( artifact.getArtifactId(), url );
794
795 MavenProject artifactProject;
796 StringBuilder sb = new StringBuilder();
797 try
798 {
799 artifactProject = repoUtils.getMavenProjectFromRepository( artifact );
800
801 List<License> licenses = artifactProject.getLicenses();
802 for ( License license : licenses )
803 {
804 sb.append( ProjectInfoReportUtils.getArtifactIdCell( license.getName(), license.getUrl() ) );
805 }
806 }
807 catch ( ProjectBuildingException e )
808 {
809 log.warn( "Unable to create Maven project from repository.", e );
810 }
811
812 String[] content;
813 if ( withClassifier )
814 {
815 content =
816 new String[] { artifact.getGroupId(), artifactIdCell, artifact.getVersion(), artifact.getClassifier(),
817 artifact.getType(), sb.toString(), isOptional };
818 }
819 else
820 {
821 content =
822 new String[] { artifact.getGroupId(), artifactIdCell, artifact.getVersion(), artifact.getType(),
823 sb.toString(), isOptional };
824 }
825
826 tableRow( withOptional, content );
827 }
828
829 private void printDependencyListing( DependencyNode node )
830 {
831 Artifact artifact = node.getArtifact();
832 String id = artifact.getId();
833 String dependencyDetailId = "_dep" + idCounter++;
834 String imgId = "_img" + idCounter++;
835
836 sink.listItem();
837
838 sink.text( id + ( StringUtils.isNotEmpty( artifact.getScope() ) ? " (" + artifact.getScope() + ") " : " " ) );
839
840 String javascript = String.format( "<img id=\"%s\" src=\"%s\" alt=\"%s\""
841 + " onclick=\"toggleDependencyDetails( '%s', '%s' );\""
842 + " style=\"cursor: pointer; vertical-align: text-bottom;\"></img>",
843 imgId, IMG_INFO_URL, getI18nString( "graph.icon.information" ), dependencyDetailId, imgId );
844
845 sink.rawText( javascript );
846
847 printDescriptionsAndURLs( node, dependencyDetailId );
848
849 if ( !node.getChildren().isEmpty() )
850 {
851 boolean toBeIncluded = false;
852 List<DependencyNode> subList = new ArrayList<DependencyNode>();
853 for ( DependencyNode dep : node.getChildren() )
854 {
855 if ( dependencies.getAllDependencies().contains( dep.getArtifact() ) )
856 {
857 subList.add( dep );
858 toBeIncluded = true;
859 }
860 }
861
862 if ( toBeIncluded )
863 {
864 sink.list();
865 for ( DependencyNode dep : subList )
866 {
867 printDependencyListing( dep );
868 }
869 sink.list_();
870 }
871 }
872
873 sink.listItem_();
874 }
875
876 private void printDescriptionsAndURLs( DependencyNode node, String uid )
877 {
878 Artifact artifact = node.getArtifact();
879 String id = artifact.getId();
880 String unknownLicenseMessage = getI18nString( "graph.tables.unknown" );
881
882 sink.rawText( "<div id=\"" + uid + "\" style=\"display:none\">" );
883
884 sink.table();
885
886 if ( !Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) )
887 {
888 try
889 {
890 MavenProject artifactProject = repoUtils.getMavenProjectFromRepository( artifact );
891 String artifactDescription = artifactProject.getDescription();
892 String artifactUrl = artifactProject.getUrl();
893 String artifactName = artifactProject.getName();
894
895 List<License> licenses = artifactProject.getLicenses();
896
897 sink.tableRow();
898 sink.tableHeaderCell();
899 sink.text( artifactName );
900 sink.tableHeaderCell_();
901 sink.tableRow_();
902
903 sink.tableRow();
904 sink.tableCell();
905
906 sink.paragraph();
907 sink.bold();
908 sink.text( getI18nString( "column.description" ) + ": " );
909 sink.bold_();
910 if ( StringUtils.isNotEmpty( artifactDescription ) )
911 {
912 sink.text( artifactDescription );
913 }
914 else
915 {
916 sink.text( getI18nString( "index", "nodescription" ) );
917 }
918 sink.paragraph_();
919
920 if ( StringUtils.isNotEmpty( artifactUrl ) )
921 {
922 sink.paragraph();
923 sink.bold();
924 sink.text( getI18nString( "column.url" ) + ": " );
925 sink.bold_();
926 if ( ProjectInfoReportUtils.isArtifactUrlValid( artifactUrl ) )
927 {
928 sink.link( artifactUrl );
929 sink.text( artifactUrl );
930 sink.link_();
931 }
932 else
933 {
934 sink.text( artifactUrl );
935 }
936 sink.paragraph_();
937 }
938
939 sink.paragraph();
940 sink.bold();
941 sink.text( getI18nString( "licenses", "title" ) + ": " );
942 sink.bold_();
943 if ( !licenses.isEmpty() )
944 {
945
946 for ( Iterator<License> it = licenses.iterator(); it.hasNext(); )
947 {
948 License license = it.next();
949
950 String licenseName = license.getName();
951 if ( StringUtils.isEmpty( licenseName ) )
952 {
953 licenseName = getI18nString( "unnamed" );
954 }
955
956 String licenseUrl = license.getUrl();
957
958 if ( licenseUrl != null )
959 {
960 sink.link( licenseUrl );
961 }
962 sink.text( licenseName );
963
964 if ( licenseUrl != null )
965 {
966 sink.link_();
967 }
968
969 if ( it.hasNext() )
970 {
971 sink.text( ", " );
972 }
973
974 licenseMap.put( licenseName, artifactName );
975 }
976 }
977 else
978 {
979 sink.text( getI18nString( "licenses", "nolicense" ) );
980
981 licenseMap.put( unknownLicenseMessage, artifactName );
982 }
983 sink.paragraph_();
984 }
985 catch ( ProjectBuildingException e )
986 {
987 log.warn( "Unable to create Maven project from repository for artifact " + artifact.getId(), e );
988 }
989 }
990 else
991 {
992 sink.tableRow();
993 sink.tableHeaderCell();
994 sink.text( id );
995 sink.tableHeaderCell_();
996 sink.tableRow_();
997
998 sink.tableRow();
999 sink.tableCell();
1000
1001 sink.paragraph();
1002 sink.bold();
1003 sink.text( getI18nString( "column.description" ) + ": " );
1004 sink.bold_();
1005 sink.text( getI18nString( "index", "nodescription" ) );
1006 sink.paragraph_();
1007
1008 if ( artifact.getFile() != null )
1009 {
1010 sink.paragraph();
1011 sink.bold();
1012 sink.text( getI18nString( "column.url" ) + ": " );
1013 sink.bold_();
1014 sink.text( artifact.getFile().getAbsolutePath() );
1015 sink.paragraph_();
1016 }
1017 }
1018
1019 sink.tableCell_();
1020 sink.tableRow_();
1021
1022 sink.table_();
1023
1024 sink.rawText( "</div>" );
1025 }
1026
1027 private void printGroupedLicenses()
1028 {
1029 for ( Map.Entry<String, Object> entry : licenseMap.entrySet() )
1030 {
1031 String licenseName = entry.getKey();
1032 if ( StringUtils.isEmpty( licenseName ) )
1033 {
1034 licenseName = getI18nString( "unnamed" );
1035 }
1036
1037 sink.paragraph();
1038 sink.bold();
1039 sink.text( licenseName );
1040 sink.text( ": " );
1041 sink.bold_();
1042
1043 @SuppressWarnings( "unchecked" )
1044 SortedSet<String> projects = (SortedSet<String>) entry.getValue();
1045
1046 for ( Iterator<String> iterator = projects.iterator(); iterator.hasNext(); )
1047 {
1048 String projectName = iterator.next();
1049 sink.text( projectName );
1050 if ( iterator.hasNext() )
1051 {
1052 sink.text( ", " );
1053 }
1054 }
1055
1056 sink.paragraph_();
1057 }
1058 }
1059
1060
1061
1062
1063
1064
1065 private void resolveAtrifacts( List<Artifact> artifacts )
1066 {
1067 for ( Artifact artifact : artifacts )
1068 {
1069
1070 if ( artifact.getFile() == null )
1071 {
1072 if ( Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) )
1073 {
1074
1075 continue;
1076 }
1077
1078 try
1079 {
1080 repoUtils.resolve( artifact );
1081 }
1082 catch ( ArtifactResolverException e )
1083 {
1084 log.error( "Artifact " + artifact.getId() + " can't be resolved.", e );
1085 continue;
1086 }
1087
1088 if ( artifact.getFile() == null )
1089 {
1090 log.error( "Artifact " + artifact.getId() + " has no file, even after resolution." );
1091 }
1092 }
1093 }
1094 }
1095
1096
1097
1098
1099
1100 private boolean hasClassifier( List<Artifact> artifacts )
1101 {
1102 for ( Artifact artifact : artifacts )
1103 {
1104 if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
1105 {
1106 return true;
1107 }
1108 }
1109
1110 return false;
1111 }
1112
1113
1114
1115
1116
1117 private boolean hasOptional( List<Artifact> artifacts )
1118 {
1119 for ( Artifact artifact : artifacts )
1120 {
1121 if ( artifact.isOptional() )
1122 {
1123 return true;
1124 }
1125 }
1126
1127 return false;
1128 }
1129
1130
1131
1132
1133
1134 private boolean hasSealed( List<Artifact> artifacts )
1135 {
1136 for ( Artifact artifact : artifacts )
1137 {
1138 if ( artifact.getFile() != null && JAR_SUBTYPE.contains( artifact.getType().toLowerCase() ) )
1139 {
1140 try
1141 {
1142 JarData jarDetails = dependencies.getJarDependencyDetails( artifact );
1143 if ( jarDetails.isSealed() )
1144 {
1145 return true;
1146 }
1147 }
1148 catch ( IOException e )
1149 {
1150 log.error( "Artifact " + artifact.getId() + " caused IOException: " + e.getMessage(), e );
1151 }
1152 }
1153 }
1154 return false;
1155 }
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168 static class FileDecimalFormat
1169 extends DecimalFormat
1170 {
1171 private static final long serialVersionUID = 4062503546523610081L;
1172
1173 private final I18N i18n;
1174
1175 private final Locale locale;
1176
1177
1178
1179
1180
1181
1182
1183 FileDecimalFormat( I18N i18n, Locale locale )
1184 {
1185 super( "###0.#" );
1186
1187 this.i18n = i18n;
1188 this.locale = locale;
1189 }
1190
1191
1192 @Override
1193 public StringBuffer format( long fs, StringBuffer result, FieldPosition fieldPosition )
1194 {
1195 if ( fs > 1000 * 1000 * 1000 )
1196 {
1197 result = super.format( (float) fs / ( 1000 * 1000 * 1000 ), result, fieldPosition );
1198 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.gb" ) );
1199 return result;
1200 }
1201
1202 if ( fs > 1000 * 1000 )
1203 {
1204 result = super.format( (float) fs / ( 1000 * 1000 ), result, fieldPosition );
1205 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.mb" ) );
1206 return result;
1207 }
1208
1209 result = super.format( (float) fs / ( 1000 ), result, fieldPosition );
1210 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.kb" ) );
1211 return result;
1212 }
1213
1214 private String getString( String key )
1215 {
1216 return i18n.getString( "project-info-reports", locale, key );
1217 }
1218 }
1219
1220
1221
1222
1223 static class TotalCell
1224 {
1225 static final int SCOPES_COUNT = 5;
1226
1227 final DecimalFormat decimalFormat;
1228
1229 long total = 0;
1230
1231 long totalCompileScope = 0;
1232
1233 long totalTestScope = 0;
1234
1235 long totalRuntimeScope = 0;
1236
1237 long totalProvidedScope = 0;
1238
1239 long totalSystemScope = 0;
1240
1241 TotalCell( DecimalFormat decimalFormat )
1242 {
1243 this.decimalFormat = decimalFormat;
1244 }
1245
1246 void incrementTotal( String scope )
1247 {
1248 addTotal( 1, scope );
1249 }
1250
1251 static String getScope( int index )
1252 {
1253 switch ( index )
1254 {
1255 case 0:
1256 return Artifact.SCOPE_COMPILE;
1257 case 1:
1258 return Artifact.SCOPE_TEST;
1259 case 2:
1260 return Artifact.SCOPE_RUNTIME;
1261 case 3:
1262 return Artifact.SCOPE_PROVIDED;
1263 case 4:
1264 return Artifact.SCOPE_SYSTEM;
1265 default:
1266 return null;
1267 }
1268 }
1269
1270 long getTotal( int index )
1271 {
1272 switch ( index )
1273 {
1274 case 0:
1275 return totalCompileScope;
1276 case 1:
1277 return totalTestScope;
1278 case 2:
1279 return totalRuntimeScope;
1280 case 3:
1281 return totalProvidedScope;
1282 case 4:
1283 return totalSystemScope;
1284 default:
1285 return total;
1286 }
1287 }
1288
1289 String getTotalString( int index )
1290 {
1291 long totalString = getTotal( index );
1292
1293 if ( totalString <= 0 )
1294 {
1295 return "";
1296 }
1297
1298 StringBuilder sb = new StringBuilder();
1299 if ( index >= 0 )
1300 {
1301 sb.append( getScope( index ) ).append( ": " );
1302 }
1303 sb.append( decimalFormat.format( getTotal( index ) ) );
1304 return sb.toString();
1305 }
1306
1307 void addTotal( long add, String scope )
1308 {
1309 total += add;
1310
1311 if ( Artifact.SCOPE_COMPILE.equals( scope ) )
1312 {
1313 totalCompileScope += add;
1314 }
1315 else if ( Artifact.SCOPE_TEST.equals( scope ) )
1316 {
1317 totalTestScope += add;
1318 }
1319 else if ( Artifact.SCOPE_RUNTIME.equals( scope ) )
1320 {
1321 totalRuntimeScope += add;
1322 }
1323 else if ( Artifact.SCOPE_PROVIDED.equals( scope ) )
1324 {
1325 totalProvidedScope += add;
1326 }
1327 else if ( Artifact.SCOPE_SYSTEM.equals( scope ) )
1328 {
1329 totalSystemScope += add;
1330 }
1331 }
1332
1333
1334 public String toString()
1335 {
1336 StringBuilder sb = new StringBuilder();
1337 sb.append( decimalFormat.format( total ) );
1338 sb.append( " (" );
1339
1340 boolean needSeparator = false;
1341 for ( int i = 0; i < SCOPES_COUNT; i++ )
1342 {
1343 if ( getTotal( i ) > 0 )
1344 {
1345 if ( needSeparator )
1346 {
1347 sb.append( ", " );
1348 }
1349 sb.append( getTotalString( i ) );
1350 needSeparator = true;
1351 }
1352 }
1353
1354 sb.append( ")" );
1355
1356 return sb.toString();
1357 }
1358 }
1359 }