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