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.lang.reflect.InvocationTargetException;
25 import java.net.URL;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.SecureRandom;
28 import java.text.DecimalFormat;
29 import java.text.DecimalFormatSymbols;
30 import java.text.FieldPosition;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.Comparator;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Locale;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.SortedSet;
42 import java.util.TreeSet;
43
44 import org.apache.commons.lang.SystemUtils;
45 import org.apache.maven.artifact.Artifact;
46 import org.apache.maven.artifact.factory.ArtifactFactory;
47 import org.apache.maven.artifact.repository.ArtifactRepository;
48 import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
49 import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
50 import org.apache.maven.artifact.resolver.ArtifactResolutionException;
51 import org.apache.maven.doxia.sink.Sink;
52 import org.apache.maven.doxia.util.HtmlTools;
53 import org.apache.maven.model.License;
54 import org.apache.maven.plugin.logging.Log;
55 import org.apache.maven.project.MavenProject;
56 import org.apache.maven.project.MavenProjectBuilder;
57 import org.apache.maven.project.ProjectBuildingException;
58 import org.apache.maven.report.projectinfo.AbstractProjectInfoRenderer;
59 import org.apache.maven.report.projectinfo.ProjectInfoReportUtils;
60 import org.apache.maven.report.projectinfo.dependencies.Dependencies;
61 import org.apache.maven.report.projectinfo.dependencies.DependenciesReportConfiguration;
62 import org.apache.maven.report.projectinfo.dependencies.RepositoryUtils;
63 import org.apache.maven.settings.Settings;
64 import org.apache.maven.shared.dependency.graph.DependencyNode;
65 import org.apache.maven.shared.jar.JarData;
66 import org.codehaus.plexus.i18n.I18N;
67 import org.codehaus.plexus.util.StringUtils;
68
69
70
71
72
73
74
75 public class DependenciesRenderer
76 extends AbstractProjectInfoRenderer
77 {
78
79 private static final String IMG_INFO_URL = "./images/icon_info_sml.gif";
80
81
82 private static final String IMG_CLOSE_URL = "./images/close.gif";
83
84
85 private static final SecureRandom RANDOM;
86
87
88 protected static final DecimalFormat DEFAULT_DECIMAL_FORMAT = new DecimalFormat( "#,##0" );
89
90 private static final Set<String> JAR_SUBTYPE;
91
92
93
94
95 private static final String JAVASCRIPT;
96
97 private final DependencyNode dependencyNode;
98
99 private final Dependencies dependencies;
100
101 private final DependenciesReportConfiguration configuration;
102
103 private final Log log;
104
105 private final Settings settings;
106
107 private final RepositoryUtils repoUtils;
108
109
110 private final DecimalFormat fileLengthDecimalFormat;
111
112
113
114
115 private int section;
116
117
118
119
120 private Map<String, Object> licenseMap = new HashMap<String, Object>()
121 {
122 private static final long serialVersionUID = 1L;
123
124
125 public Object put( String key, Object value )
126 {
127
128 @SuppressWarnings( "unchecked" )
129 SortedSet<Object> valueList = (SortedSet<Object>) get( key );
130 if ( valueList == null )
131 {
132 valueList = new TreeSet<Object>();
133 }
134 valueList.add( value );
135 return super.put( key, valueList );
136 }
137 };
138
139 private final ArtifactFactory artifactFactory;
140
141 private final MavenProjectBuilder mavenProjectBuilder;
142
143 private final List<ArtifactRepository> remoteRepositories;
144
145 private final ArtifactRepository localRepository;
146
147 static
148 {
149 Set<String> jarSubtype = new HashSet<String>();
150 jarSubtype.add( "jar" );
151 jarSubtype.add( "war" );
152 jarSubtype.add( "ear" );
153 jarSubtype.add( "sar" );
154 jarSubtype.add( "rar" );
155 jarSubtype.add( "par" );
156 jarSubtype.add( "ejb" );
157 JAR_SUBTYPE = Collections.unmodifiableSet( jarSubtype );
158
159 try
160 {
161 RANDOM = SecureRandom.getInstance( "SHA1PRNG" );
162 }
163 catch ( NoSuchAlgorithmException e )
164 {
165 throw new RuntimeException( e );
166 }
167
168 StringBuilder sb = new StringBuilder();
169 sb.append( "<script language=\"javascript\" type=\"text/javascript\">" ).append( SystemUtils.LINE_SEPARATOR );
170 sb.append( " function toggleDependencyDetail( divId, imgId )" ).append( SystemUtils.LINE_SEPARATOR );
171 sb.append( " {" ).append( SystemUtils.LINE_SEPARATOR );
172 sb.append( " var div = document.getElementById( divId );" ).append( SystemUtils.LINE_SEPARATOR );
173 sb.append( " var img = document.getElementById( imgId );" ).append( SystemUtils.LINE_SEPARATOR );
174 sb.append( " if( div.style.display == '' )" ).append( SystemUtils.LINE_SEPARATOR );
175 sb.append( " {" ).append( SystemUtils.LINE_SEPARATOR );
176 sb.append( " div.style.display = 'none';" ).append( SystemUtils.LINE_SEPARATOR );
177 sb.append( " img.src='" + IMG_INFO_URL + "';" ).append( SystemUtils.LINE_SEPARATOR );
178 sb.append( " }" ).append( SystemUtils.LINE_SEPARATOR );
179 sb.append( " else" ).append( SystemUtils.LINE_SEPARATOR );
180 sb.append( " {" ).append( SystemUtils.LINE_SEPARATOR );
181 sb.append( " div.style.display = '';" ).append( SystemUtils.LINE_SEPARATOR );
182 sb.append( " img.src='" + IMG_CLOSE_URL + "';" ).append( SystemUtils.LINE_SEPARATOR );
183 sb.append( " }" ).append( SystemUtils.LINE_SEPARATOR );
184 sb.append( " }" ).append( SystemUtils.LINE_SEPARATOR );
185 sb.append( "</script>" ).append( SystemUtils.LINE_SEPARATOR );
186 JAVASCRIPT = sb.toString();
187 }
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206 public DependenciesRenderer( Sink sink, Locale locale, I18N i18n, Log log, Settings settings,
207 Dependencies dependencies, DependencyNode dependencyTreeNode,
208 DependenciesReportConfiguration config, RepositoryUtils repoUtils,
209 ArtifactFactory artifactFactory, MavenProjectBuilder mavenProjectBuilder,
210 List<ArtifactRepository> remoteRepositories, ArtifactRepository localRepository )
211 {
212 super( sink, i18n, locale );
213
214 this.log = log;
215 this.settings = settings;
216 this.dependencies = dependencies;
217 this.dependencyNode = dependencyTreeNode;
218 this.repoUtils = repoUtils;
219 this.configuration = config;
220 this.artifactFactory = artifactFactory;
221 this.mavenProjectBuilder = mavenProjectBuilder;
222 this.remoteRepositories = remoteRepositories;
223 this.localRepository = localRepository;
224
225
226 DEFAULT_DECIMAL_FORMAT.setDecimalFormatSymbols( new DecimalFormatSymbols( locale ) );
227
228 this.fileLengthDecimalFormat = new FileDecimalFormat( i18n, locale );
229 this.fileLengthDecimalFormat.setDecimalFormatSymbols( new DecimalFormatSymbols( locale ) );
230 }
231
232 @Override
233 protected String getI18Nsection()
234 {
235 return "dependencies";
236 }
237
238
239
240
241
242 @Override
243 public void renderBody()
244 {
245
246
247 if ( !dependencies.hasDependencies() )
248 {
249 startSection( getTitle() );
250
251
252 paragraph( getI18nString( "nolist" ) );
253
254 endSection();
255
256 return;
257 }
258
259
260 renderSectionProjectDependencies();
261
262
263 renderSectionProjectTransitiveDependencies();
264
265
266 renderSectionProjectDependencyGraph();
267
268
269 renderSectionDependencyLicenseListing();
270
271 if ( configuration.getDependencyDetailsEnabled() )
272 {
273
274 renderSectionDependencyFileDetails();
275 }
276
277 if ( configuration.getDependencyLocationsEnabled() )
278 {
279
280 renderSectionDependencyRepositoryLocations();
281 }
282 }
283
284
285
286
287
288
289
290
291 protected void startSection( String name )
292 {
293 startSection( name, name );
294 }
295
296
297
298
299
300
301
302 protected void startSection( String anchor, String name )
303 {
304 section = section + 1;
305
306 super.sink.anchor( HtmlTools.encodeId( anchor ) );
307 super.sink.anchor_();
308
309 switch ( section )
310 {
311 case 1:
312 sink.section1();
313 sink.sectionTitle1();
314 break;
315 case 2:
316 sink.section2();
317 sink.sectionTitle2();
318 break;
319 case 3:
320 sink.section3();
321 sink.sectionTitle3();
322 break;
323 case 4:
324 sink.section4();
325 sink.sectionTitle4();
326 break;
327 case 5:
328 sink.section5();
329 sink.sectionTitle5();
330 break;
331
332 default:
333
334 break;
335 }
336
337 text( name );
338
339 switch ( section )
340 {
341 case 1:
342 sink.sectionTitle1_();
343 break;
344 case 2:
345 sink.sectionTitle2_();
346 break;
347 case 3:
348 sink.sectionTitle3_();
349 break;
350 case 4:
351 sink.sectionTitle4_();
352 break;
353 case 5:
354 sink.sectionTitle5_();
355 break;
356
357 default:
358
359 break;
360 }
361 }
362
363
364
365
366 protected void endSection()
367 {
368 switch ( section )
369 {
370 case 1:
371 sink.section1_();
372 break;
373 case 2:
374 sink.section2_();
375 break;
376 case 3:
377 sink.section3_();
378 break;
379 case 4:
380 sink.section4_();
381 break;
382 case 5:
383 sink.section5_();
384 break;
385
386 default:
387
388 break;
389 }
390
391 section = section - 1;
392
393 if ( section < 0 )
394 {
395 throw new IllegalStateException( "Too many closing sections" );
396 }
397 }
398
399
400
401
402
403
404
405
406
407
408
409 private String[] getDependencyTableHeader( boolean withClassifier, boolean withOptional )
410 {
411 String groupId = getI18nString( "column.groupId" );
412 String artifactId = getI18nString( "column.artifactId" );
413 String version = getI18nString( "column.version" );
414 String classifier = getI18nString( "column.classifier" );
415 String type = getI18nString( "column.type" );
416 String license = getI18nString( "column.license" );
417 String optional = getI18nString( "column.optional" );
418
419 if ( withClassifier )
420 {
421 if ( withOptional )
422 {
423 return new String[] { groupId, artifactId, version, classifier, type, license, optional };
424 }
425
426 return new String[] { groupId, artifactId, version, classifier, type, license };
427 }
428
429 if ( withOptional )
430 {
431 return new String[] { groupId, artifactId, version, type, license, optional };
432 }
433
434 return new String[] { groupId, artifactId, version, type, license };
435 }
436
437 private void renderSectionProjectDependencies()
438 {
439 startSection( getTitle() );
440
441
442 Map<String, List<Artifact>> dependenciesByScope = dependencies.getDependenciesByScope( false );
443
444 renderDependenciesForAllScopes( dependenciesByScope, false );
445
446 endSection();
447 }
448
449
450
451
452
453
454
455
456
457
458 private void renderDependenciesForAllScopes( Map<String, List<Artifact>> dependenciesByScope, boolean isTransitive )
459 {
460 renderDependenciesForScope( Artifact.SCOPE_COMPILE, dependenciesByScope.get( Artifact.SCOPE_COMPILE ),
461 isTransitive );
462 renderDependenciesForScope( Artifact.SCOPE_RUNTIME, dependenciesByScope.get( Artifact.SCOPE_RUNTIME ),
463 isTransitive );
464 renderDependenciesForScope( Artifact.SCOPE_TEST, dependenciesByScope.get( Artifact.SCOPE_TEST ), isTransitive );
465 renderDependenciesForScope( Artifact.SCOPE_PROVIDED, dependenciesByScope.get( Artifact.SCOPE_PROVIDED ),
466 isTransitive );
467 renderDependenciesForScope( Artifact.SCOPE_SYSTEM, dependenciesByScope.get( Artifact.SCOPE_SYSTEM ),
468 isTransitive );
469 }
470
471 private void renderSectionProjectTransitiveDependencies()
472 {
473 Map<String, List<Artifact>> dependenciesByScope = dependencies.getDependenciesByScope( true );
474
475 startSection( getI18nString( "transitive.title" ) );
476
477 if ( dependenciesByScope.values().isEmpty() )
478 {
479 paragraph( getI18nString( "transitive.nolist" ) );
480 }
481 else
482 {
483 paragraph( getI18nString( "transitive.intro" ) );
484
485 renderDependenciesForAllScopes( dependenciesByScope, true );
486 }
487
488 endSection();
489 }
490
491 private void renderSectionProjectDependencyGraph()
492 {
493 startSection( getI18nString( "graph.title" ) );
494
495
496 renderSectionDependencyTree();
497
498 endSection();
499 }
500
501 private void renderSectionDependencyTree()
502 {
503 sink.rawText( JAVASCRIPT );
504
505
506 startSection( getI18nString( "graph.tree.title" ) );
507
508 sink.list();
509 printDependencyListing( dependencyNode );
510 sink.list_();
511
512 endSection();
513 }
514
515 private void renderSectionDependencyFileDetails()
516 {
517 startSection( getI18nString( "file.details.title" ) );
518
519 List<Artifact> alldeps = dependencies.getAllDependencies();
520 Collections.sort( alldeps, getArtifactComparator() );
521
522
523 String filename = getI18nString( "file.details.column.file" );
524 String size = getI18nString( "file.details.column.size" );
525 String entries = getI18nString( "file.details.column.entries" );
526 String classes = getI18nString( "file.details.column.classes" );
527 String packages = getI18nString( "file.details.column.packages" );
528 String jdkrev = getI18nString( "file.details.column.jdkrev" );
529 String debug = getI18nString( "file.details.column.debug" );
530 String sealed = getI18nString( "file.details.column.sealed" );
531
532 int[] justification =
533 new int[] { Sink.JUSTIFY_LEFT, Sink.JUSTIFY_RIGHT, Sink.JUSTIFY_RIGHT, Sink.JUSTIFY_RIGHT,
534 Sink.JUSTIFY_RIGHT, Sink.JUSTIFY_CENTER, Sink.JUSTIFY_CENTER, Sink.JUSTIFY_CENTER };
535
536 startTable( justification, false );
537
538 TotalCell totaldeps = new TotalCell( DEFAULT_DECIMAL_FORMAT );
539 TotalCell totaldepsize = new TotalCell( fileLengthDecimalFormat );
540 TotalCell totalentries = new TotalCell( DEFAULT_DECIMAL_FORMAT );
541 TotalCell totalclasses = new TotalCell( DEFAULT_DECIMAL_FORMAT );
542 TotalCell totalpackages = new TotalCell( DEFAULT_DECIMAL_FORMAT );
543 double highestjdk = 0.0;
544 TotalCell totaldebug = new TotalCell( DEFAULT_DECIMAL_FORMAT );
545 TotalCell totalsealed = new TotalCell( DEFAULT_DECIMAL_FORMAT );
546
547 boolean hasSealed = hasSealed( alldeps );
548
549
550 String[] tableHeader;
551 if ( hasSealed )
552 {
553 tableHeader = new String[] { filename, size, entries, classes, packages, jdkrev, debug, sealed };
554 }
555 else
556 {
557 tableHeader = new String[] { filename, size, entries, classes, packages, jdkrev, debug };
558 }
559 tableHeader( tableHeader );
560
561
562 for ( Artifact artifact : alldeps )
563 {
564 if ( artifact.getFile() == null )
565 {
566 log.error( "Artifact: " + artifact.getId() + " has no file." );
567 continue;
568 }
569
570 File artifactFile = artifact.getFile();
571
572 totaldeps.incrementTotal( artifact.getScope() );
573 totaldepsize.addTotal( artifactFile.length(), artifact.getScope() );
574
575 if ( JAR_SUBTYPE.contains( artifact.getType().toLowerCase() ) )
576 {
577 try
578 {
579 JarData jarDetails = dependencies.getJarDependencyDetails( artifact );
580
581 String debugstr = "release";
582 if ( jarDetails.isDebugPresent() )
583 {
584 debugstr = "debug";
585 totaldebug.incrementTotal( artifact.getScope() );
586 }
587
588 totalentries.addTotal( jarDetails.getNumEntries(), artifact.getScope() );
589 totalclasses.addTotal( jarDetails.getNumClasses(), artifact.getScope() );
590 totalpackages.addTotal( jarDetails.getNumPackages(), artifact.getScope() );
591
592 try
593 {
594 if ( jarDetails.getJdkRevision() != null )
595 {
596 highestjdk = Math.max( highestjdk, Double.parseDouble( jarDetails.getJdkRevision() ) );
597 }
598 }
599 catch ( NumberFormatException e )
600 {
601
602 }
603
604 String sealedstr = "";
605 if ( jarDetails.isSealed() )
606 {
607 sealedstr = "sealed";
608 totalsealed.incrementTotal( artifact.getScope() );
609 }
610
611 String name = artifactFile.getName();
612 String fileLength = fileLengthDecimalFormat.format( artifactFile.length() );
613
614 if ( artifactFile.isDirectory() )
615 {
616 File parent = artifactFile.getParentFile();
617 name = parent.getParentFile().getName() + '/' + parent.getName() + '/' + artifactFile.getName();
618 fileLength = "-";
619 }
620
621 tableRow( hasSealed,
622 new String[] { name, fileLength,
623 DEFAULT_DECIMAL_FORMAT.format( jarDetails.getNumEntries() ),
624 DEFAULT_DECIMAL_FORMAT.format( jarDetails.getNumClasses() ),
625 DEFAULT_DECIMAL_FORMAT.format( jarDetails.getNumPackages() ),
626 jarDetails.getJdkRevision(), debugstr, sealedstr } );
627 }
628 catch ( IOException e )
629 {
630 createExceptionInfoTableRow( artifact, artifactFile, e, hasSealed );
631 }
632 }
633 else
634 {
635 tableRow( hasSealed,
636 new String[] { artifactFile.getName(),
637 fileLengthDecimalFormat.format( artifactFile.length() ), "", "", "", "", "", "" } );
638 }
639 }
640
641
642 tableHeader[0] = getI18nString( "file.details.total" );
643 tableHeader( tableHeader );
644
645 justification[0] = Sink.JUSTIFY_RIGHT;
646 justification[6] = Sink.JUSTIFY_RIGHT;
647
648 for ( int i = -1; i < TotalCell.SCOPES_COUNT; i++ )
649 {
650 if ( totaldeps.getTotal( i ) > 0 )
651 {
652 tableRow( hasSealed,
653 new String[] { totaldeps.getTotalString( i ), totaldepsize.getTotalString( i ),
654 totalentries.getTotalString( i ), totalclasses.getTotalString( i ),
655 totalpackages.getTotalString( i ), ( i < 0 ) ? String.valueOf( highestjdk ) : "",
656 totaldebug.getTotalString( i ), totalsealed.getTotalString( i ) } );
657 }
658 }
659
660 endTable();
661 endSection();
662 }
663
664 private void tableRow( boolean fullRow, String[] content )
665 {
666 sink.tableRow();
667
668 int count = fullRow ? content.length : ( content.length - 1 );
669
670 for ( int i = 0; i < count; i++ )
671 {
672 tableCell( content[i] );
673 }
674
675 sink.tableRow_();
676 }
677
678 private void createExceptionInfoTableRow( Artifact artifact, File artifactFile, Exception e, boolean hasSealed )
679 {
680 tableRow( hasSealed, new String[] { artifact.getId(), artifactFile.getAbsolutePath(), e.getMessage(), "", "",
681 "", "", "" } );
682 }
683
684 private void populateRepositoryMap( Map<String, ArtifactRepository> repos, List<ArtifactRepository> rowRepos )
685 {
686 for ( ArtifactRepository repo : rowRepos )
687 {
688 repos.put( repo.getId(), repo );
689 }
690 }
691
692 private void blacklistRepositoryMap( Map<String, ArtifactRepository> repos, List<String> repoUrlBlackListed )
693 {
694 for ( ArtifactRepository repo : repos.values() )
695 {
696
697 if ( repo.isBlacklisted() )
698 {
699 repoUrlBlackListed.add( repo.getUrl() );
700 }
701 else
702 {
703 if ( repoUrlBlackListed.contains( repo.getUrl() ) )
704 {
705 repo.setBlacklisted( true );
706 }
707 else
708 {
709 try
710 {
711 URL repoUrl = new URL( repo.getUrl() );
712 if ( ProjectInfoReportUtils.getContent( repoUrl, settings ) == null )
713 {
714 log.warn( "The repository url '" + repoUrl + "' has no stream - Repository '"
715 + repo.getId() + "' will be blacklisted." );
716 repo.setBlacklisted( true );
717 repoUrlBlackListed.add( repo.getUrl() );
718 }
719 }
720 catch ( IOException e )
721 {
722 log.warn( "The repository url '" + repo.getUrl() + "' is invalid - Repository '" + repo.getId()
723 + "' will be blacklisted." );
724 repo.setBlacklisted( true );
725 repoUrlBlackListed.add( repo.getUrl() );
726 }
727 }
728 }
729 }
730 }
731
732 @SuppressWarnings( "unchecked" )
733 private void renderSectionDependencyRepositoryLocations()
734 {
735 startSection( getI18nString( "repo.locations.title" ) );
736
737
738 List<Artifact> alldeps = dependencies.getAllDependencies();
739 Collections.sort( alldeps, getArtifactComparator() );
740
741
742 Map<String, ArtifactRepository> repoMap = new HashMap<String, ArtifactRepository>();
743
744 populateRepositoryMap( repoMap, repoUtils.getRemoteArtifactRepositories() );
745 for ( Artifact artifact : alldeps )
746 {
747 try
748 {
749 MavenProject artifactProject = repoUtils.getMavenProjectFromRepository( artifact );
750 populateRepositoryMap( repoMap, artifactProject.getRemoteArtifactRepositories() );
751 }
752 catch ( ProjectBuildingException e )
753 {
754 log.warn( "Unable to create Maven project from repository for artifact " + artifact.getId(), e );
755 }
756 }
757
758 List<String> repoUrlBlackListed = new ArrayList<String>();
759 blacklistRepositoryMap( repoMap, repoUrlBlackListed );
760
761
762
763 printRepositories( repoMap, repoUrlBlackListed );
764
765
766
767 printArtifactsLocations( repoMap, repoUrlBlackListed, alldeps );
768
769 endSection();
770 }
771
772 private void renderSectionDependencyLicenseListing()
773 {
774 startSection( getI18nString( "graph.tables.licenses" ) );
775 printGroupedLicenses();
776 endSection();
777 }
778
779 private void renderDependenciesForScope( String scope, List<Artifact> artifacts, boolean isTransitive )
780 {
781 if ( artifacts != null )
782 {
783 boolean withClassifier = hasClassifier( artifacts );
784 boolean withOptional = hasOptional( artifacts );
785 String[] tableHeader = getDependencyTableHeader( withClassifier, withOptional );
786
787
788 Collections.sort( artifacts, getArtifactComparator() );
789
790 String anchorByScope =
791 ( isTransitive ? getI18nString( "transitive.title" ) + "_" + scope : getI18nString( "title" ) + "_"
792 + scope );
793 startSection( anchorByScope, scope );
794
795 paragraph( getI18nString( "intro." + scope ) );
796
797 startTable();
798 tableHeader( tableHeader );
799 for ( Artifact artifact : artifacts )
800 {
801 renderArtifactRow( artifact, withClassifier, withOptional );
802 }
803 endTable();
804
805 endSection();
806 }
807 }
808
809 private Comparator<Artifact> getArtifactComparator()
810 {
811 return new Comparator<Artifact>()
812 {
813 public int compare( Artifact a1, Artifact a2 )
814 {
815
816 if ( a1.isOptional() && !a2.isOptional() )
817 {
818 return +1;
819 }
820 else if ( !a1.isOptional() && a2.isOptional() )
821 {
822 return -1;
823 }
824 else
825 {
826 return a1.compareTo( a2 );
827 }
828 }
829 };
830 }
831
832
833
834
835
836
837
838 private void renderArtifactRow( Artifact artifact, boolean withClassifier, boolean withOptional )
839 {
840 String isOptional =
841 artifact.isOptional() ? getI18nString( "column.isOptional" ) : getI18nString( "column.isNotOptional" );
842
843 String url =
844 ProjectInfoReportUtils.getArtifactUrl( artifactFactory, artifact, mavenProjectBuilder, remoteRepositories,
845 localRepository );
846 String artifactIdCell = ProjectInfoReportUtils.getArtifactIdCell( artifact.getArtifactId(), url );
847
848 MavenProject artifactProject;
849 StringBuilder sb = new StringBuilder();
850 try
851 {
852 artifactProject = repoUtils.getMavenProjectFromRepository( artifact );
853 @SuppressWarnings( "unchecked" )
854 List<License> licenses = artifactProject.getLicenses();
855 for ( License license : licenses )
856 {
857 sb.append( ProjectInfoReportUtils.getArtifactIdCell( license.getName(), license.getUrl() ) );
858 }
859 }
860 catch ( ProjectBuildingException e )
861 {
862 log.warn( "Unable to create Maven project from repository.", e );
863 }
864
865 String content[];
866 if ( withClassifier )
867 {
868 content =
869 new String[] { artifact.getGroupId(), artifactIdCell, artifact.getVersion(), artifact.getClassifier(),
870 artifact.getType(), sb.toString(), isOptional };
871 }
872 else
873 {
874 content =
875 new String[] { artifact.getGroupId(), artifactIdCell, artifact.getVersion(), artifact.getType(),
876 sb.toString(), isOptional };
877 }
878
879 tableRow( withOptional, content );
880 }
881
882 private void printDependencyListing( DependencyNode node )
883 {
884 Artifact artifact = node.getArtifact();
885 String id = artifact.getId();
886 String dependencyDetailId = getUUID();
887 String imgId = getUUID();
888
889 sink.listItem();
890
891 sink.text( id + ( StringUtils.isNotEmpty( artifact.getScope() ) ? " (" + artifact.getScope() + ") " : " " ) );
892 sink.rawText( "<img id=\"" + imgId + "\" src=\"" + IMG_INFO_URL
893 + "\" alt=\"Information\" onclick=\"toggleDependencyDetail( '" + dependencyDetailId + "', '" + imgId
894 + "' );\" style=\"cursor: pointer;vertical-align:text-bottom;\"></img>" );
895
896 printDescriptionsAndURLs( node, dependencyDetailId );
897
898 if ( !node.getChildren().isEmpty() )
899 {
900 boolean toBeIncluded = false;
901 List<DependencyNode> subList = new ArrayList<DependencyNode>();
902 for ( DependencyNode dep : node.getChildren() )
903 {
904 if ( dependencies.getAllDependencies().contains( dep.getArtifact() ) )
905 {
906 subList.add( dep );
907 toBeIncluded = true;
908 }
909 }
910
911 if ( toBeIncluded )
912 {
913 sink.list();
914 for ( DependencyNode dep : subList )
915 {
916 printDependencyListing( dep );
917 }
918 sink.list_();
919 }
920 }
921
922 sink.listItem_();
923 }
924
925 private void printDescriptionsAndURLs( DependencyNode node, String uid )
926 {
927 Artifact artifact = node.getArtifact();
928 String id = artifact.getId();
929 String unknownLicenseMessage = getI18nString( "graph.tables.unknown" );
930
931 sink.rawText( "<div id=\"" + uid + "\" style=\"display:none\">" );
932
933 sink.table();
934
935 if ( !Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) )
936 {
937 try
938 {
939 MavenProject artifactProject = repoUtils.getMavenProjectFromRepository( artifact );
940 String artifactDescription = artifactProject.getDescription();
941 String artifactUrl = artifactProject.getUrl();
942 String artifactName = artifactProject.getName();
943 @SuppressWarnings( "unchecked" )
944 List<License> licenses = artifactProject.getLicenses();
945
946 sink.tableRow();
947 sink.tableHeaderCell();
948 sink.text( artifactName );
949 sink.tableHeaderCell_();
950 sink.tableRow_();
951
952 sink.tableRow();
953 sink.tableCell();
954
955 sink.paragraph();
956 sink.bold();
957 sink.text( getI18nString( "column.description" ) + ": " );
958 sink.bold_();
959 if ( StringUtils.isNotEmpty( artifactDescription ) )
960 {
961 sink.text( artifactDescription );
962 }
963 else
964 {
965 sink.text( getI18nString( "index", "nodescription" ) );
966 }
967 sink.paragraph_();
968
969 if ( StringUtils.isNotEmpty( artifactUrl ) )
970 {
971 sink.paragraph();
972 sink.bold();
973 sink.text( getI18nString( "column.url" ) + ": " );
974 sink.bold_();
975 if ( ProjectInfoReportUtils.isArtifactUrlValid( artifactUrl ) )
976 {
977 sink.link( artifactUrl );
978 sink.text( artifactUrl );
979 sink.link_();
980 }
981 else
982 {
983 sink.text( artifactUrl );
984 }
985 sink.paragraph_();
986 }
987
988 sink.paragraph();
989 sink.bold();
990 sink.text( getI18nString( "license", "title" ) + ": " );
991 sink.bold_();
992 if ( !licenses.isEmpty() )
993 {
994 for ( License element : licenses )
995 {
996 String licenseName = element.getName();
997 String licenseUrl = element.getUrl();
998
999 if ( licenseUrl != null )
1000 {
1001 sink.link( licenseUrl );
1002 }
1003 sink.text( licenseName );
1004
1005 if ( licenseUrl != null )
1006 {
1007 sink.link_();
1008 }
1009
1010 licenseMap.put( licenseName, artifactName );
1011 }
1012 }
1013 else
1014 {
1015 sink.text( getI18nString( "license", "nolicense" ) );
1016
1017 licenseMap.put( unknownLicenseMessage, artifactName );
1018 }
1019 sink.paragraph_();
1020 }
1021 catch ( ProjectBuildingException e )
1022 {
1023 log.warn( "Unable to create Maven project from repository for artifact " + artifact.getId(), e );
1024 }
1025 }
1026 else
1027 {
1028 sink.tableRow();
1029 sink.tableHeaderCell();
1030 sink.text( id );
1031 sink.tableHeaderCell_();
1032 sink.tableRow_();
1033
1034 sink.tableRow();
1035 sink.tableCell();
1036
1037 sink.paragraph();
1038 sink.bold();
1039 sink.text( getI18nString( "column.description" ) + ": " );
1040 sink.bold_();
1041 sink.text( getI18nString( "index", "nodescription" ) );
1042 sink.paragraph_();
1043
1044 if ( artifact.getFile() != null )
1045 {
1046 sink.paragraph();
1047 sink.bold();
1048 sink.text( getI18nString( "column.url" ) + ": " );
1049 sink.bold_();
1050 sink.text( artifact.getFile().getAbsolutePath() );
1051 sink.paragraph_();
1052 }
1053 }
1054
1055 sink.tableCell_();
1056 sink.tableRow_();
1057
1058 sink.table_();
1059
1060 sink.rawText( "</div>" );
1061 }
1062
1063 private void printGroupedLicenses()
1064 {
1065 for ( Map.Entry<String, Object> entry : licenseMap.entrySet() )
1066 {
1067 String licenseName = entry.getKey();
1068 sink.paragraph();
1069 sink.bold();
1070 if ( StringUtils.isEmpty( licenseName ) )
1071 {
1072 sink.text( getI18nString( "unamed" ) );
1073 }
1074 else
1075 {
1076 sink.text( licenseName );
1077 }
1078 sink.text( ": " );
1079 sink.bold_();
1080
1081 @SuppressWarnings( "unchecked" )
1082 SortedSet<String> projects = (SortedSet<String>) entry.getValue();
1083
1084 for ( Iterator<String> iterator = projects.iterator(); iterator.hasNext(); )
1085 {
1086 String projectName = iterator.next();
1087 sink.text( projectName );
1088 if ( iterator.hasNext() )
1089 {
1090 sink.text( ", " );
1091 }
1092 }
1093
1094 sink.paragraph_();
1095 }
1096 }
1097
1098 private void printRepositories( Map<String, ArtifactRepository> repoMap, List<String> repoUrlBlackListed )
1099 {
1100
1101 String repoid = getI18nString( "repo.locations.column.repoid" );
1102 String url = getI18nString( "repo.locations.column.url" );
1103 String release = getI18nString( "repo.locations.column.release" );
1104 String snapshot = getI18nString( "repo.locations.column.snapshot" );
1105 String blacklisted = getI18nString( "repo.locations.column.blacklisted" );
1106 String releaseEnabled = getI18nString( "repo.locations.cell.release.enabled" );
1107 String releaseDisabled = getI18nString( "repo.locations.cell.release.disabled" );
1108 String snapshotEnabled = getI18nString( "repo.locations.cell.snapshot.enabled" );
1109 String snapshotDisabled = getI18nString( "repo.locations.cell.snapshot.disabled" );
1110 String blacklistedEnabled = getI18nString( "repo.locations.cell.blacklisted.enabled" );
1111 String blacklistedDisabled = getI18nString( "repo.locations.cell.blacklisted.disabled" );
1112
1113
1114
1115 String[] tableHeader;
1116 int[] justificationRepo;
1117 if ( repoUrlBlackListed.isEmpty() )
1118 {
1119 tableHeader = new String[] { repoid, url, release, snapshot };
1120 justificationRepo =
1121 new int[] { Sink.JUSTIFY_LEFT, Sink.JUSTIFY_LEFT, Sink.JUSTIFY_CENTER, Sink.JUSTIFY_CENTER };
1122 }
1123 else
1124 {
1125 tableHeader = new String[] { repoid, url, release, snapshot, blacklisted };
1126 justificationRepo =
1127 new int[] { Sink.JUSTIFY_LEFT, Sink.JUSTIFY_LEFT, Sink.JUSTIFY_CENTER, Sink.JUSTIFY_CENTER,
1128 Sink.JUSTIFY_CENTER };
1129 }
1130
1131 startTable( justificationRepo, false );
1132
1133 tableHeader( tableHeader );
1134
1135
1136
1137 for ( ArtifactRepository repo : repoMap.values() )
1138 {
1139 List<ArtifactRepository> mirroredRepos = getMirroredRepositories( repo );
1140
1141 sink.tableRow();
1142 sink.tableCell();
1143 boolean addLineBreak = false;
1144 for ( ArtifactRepository r : mirroredRepos )
1145 {
1146 if ( addLineBreak )
1147 {
1148 sink.lineBreak();
1149 }
1150 addLineBreak = true;
1151 sink.text( r.getId() );
1152 }
1153 sink.tableCell_();
1154
1155 sink.tableCell();
1156 addLineBreak = false;
1157 for ( ArtifactRepository r : mirroredRepos )
1158 {
1159 if ( addLineBreak )
1160 {
1161 sink.lineBreak();
1162 }
1163 addLineBreak = true;
1164 if ( repo.isBlacklisted() )
1165 {
1166 sink.text( r.getUrl() );
1167 }
1168 else
1169 {
1170 sink.link( r.getUrl() );
1171 sink.text( r.getUrl() );
1172 sink.link_();
1173 }
1174 }
1175 sink.tableCell_();
1176
1177 ArtifactRepositoryPolicy releasePolicy = repo.getReleases();
1178 tableCell( releasePolicy.isEnabled() ? releaseEnabled : releaseDisabled );
1179
1180 ArtifactRepositoryPolicy snapshotPolicy = repo.getSnapshots();
1181 tableCell( snapshotPolicy.isEnabled() ? snapshotEnabled : snapshotDisabled );
1182
1183 if ( !repoUrlBlackListed.isEmpty() )
1184 {
1185 tableCell( repoUrlBlackListed.contains( repo.getUrl() ) ? blacklistedEnabled : blacklistedDisabled );
1186 }
1187
1188 sink.tableRow_();
1189 }
1190
1191 endTable();
1192 }
1193
1194 private Object invoke( Object object, String method )
1195 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
1196 {
1197 return object.getClass().getMethod( method ).invoke( object );
1198 }
1199
1200
1201
1202
1203
1204
1205
1206 private List<ArtifactRepository> getMirroredRepositories( ArtifactRepository repo )
1207 {
1208 try
1209 {
1210 @SuppressWarnings( "unchecked" )
1211 List<ArtifactRepository> mirroredRepos =
1212 (List<ArtifactRepository>) invoke( repo, "getMirroredRepositories" );
1213
1214 if ( ( mirroredRepos != null ) && ( !mirroredRepos.isEmpty() ) )
1215 {
1216 return mirroredRepos;
1217 }
1218 }
1219 catch ( IllegalArgumentException e )
1220 {
1221
1222 }
1223 catch ( SecurityException e )
1224 {
1225
1226 }
1227 catch ( IllegalAccessException e )
1228 {
1229
1230 }
1231 catch ( InvocationTargetException e )
1232 {
1233
1234 }
1235 catch ( NoSuchMethodException e )
1236 {
1237
1238 }
1239
1240 return Collections.singletonList( repo );
1241 }
1242
1243 private void printArtifactsLocations( Map<String, ArtifactRepository> repoMap, List<String> repoUrlBlackListed,
1244 List<Artifact> alldeps )
1245 {
1246
1247 String artifact = getI18nString( "repo.locations.column.artifact" );
1248
1249 sink.paragraph();
1250 sink.text( getI18nString( "repo.locations.artifact.breakdown" ) );
1251 sink.paragraph_();
1252
1253 List<String> repoIdList = new ArrayList<String>();
1254
1255 for ( Map.Entry<String, ArtifactRepository> entry : repoMap.entrySet() )
1256 {
1257 String repokey = entry.getKey();
1258 ArtifactRepository repo = entry.getValue();
1259 if ( !( repo.isBlacklisted() || repoUrlBlackListed.contains( repo.getUrl() ) ) )
1260 {
1261 repoIdList.add( repokey );
1262 }
1263 }
1264
1265 String[] tableHeader = new String[repoIdList.size() + 1];
1266 int[] justificationRepo = new int[repoIdList.size() + 1];
1267
1268 tableHeader[0] = artifact;
1269 justificationRepo[0] = Sink.JUSTIFY_LEFT;
1270
1271 int idnum = 1;
1272 for ( String id : repoIdList )
1273 {
1274 tableHeader[idnum] = id;
1275 justificationRepo[idnum] = Sink.JUSTIFY_CENTER;
1276 idnum++;
1277 }
1278
1279 Map<String, Integer> totalByRepo = new HashMap<String, Integer>();
1280 TotalCell totaldeps = new TotalCell( DEFAULT_DECIMAL_FORMAT );
1281
1282 startTable( justificationRepo, false );
1283
1284 tableHeader( tableHeader );
1285
1286 for ( Artifact dependency : alldeps )
1287 {
1288 totaldeps.incrementTotal( dependency.getScope() );
1289
1290 sink.tableRow();
1291
1292 tableCell( dependency.getId() );
1293
1294 if ( Artifact.SCOPE_SYSTEM.equals( dependency.getScope() ) )
1295 {
1296 for ( @SuppressWarnings( "unused" )
1297 String repoId : repoIdList )
1298 {
1299 tableCell( "-" );
1300 }
1301 }
1302 else
1303 {
1304 for ( String repokey : repoIdList )
1305 {
1306 ArtifactRepository repo = repoMap.get( repokey );
1307
1308 String depUrl = repoUtils.getDependencyUrlFromRepository( dependency, repo );
1309
1310 Integer old = totalByRepo.get( repokey );
1311 if ( old == null )
1312 {
1313 old = new Integer( 0 );
1314 totalByRepo.put( repokey, old );
1315 }
1316
1317 boolean dependencyExists = false;
1318
1319 if ( ( dependency.isSnapshot() && repo.getSnapshots().isEnabled() )
1320 || ( !dependency.isSnapshot() && repo.getReleases().isEnabled() ) )
1321 {
1322 dependencyExists = repoUtils.dependencyExistsInRepo( repo, dependency );
1323 }
1324
1325 if ( dependencyExists )
1326 {
1327 sink.tableCell();
1328 if ( StringUtils.isNotEmpty( depUrl ) )
1329 {
1330 sink.link( depUrl );
1331 }
1332 else
1333 {
1334 sink.text( depUrl );
1335 }
1336
1337 sink.figure();
1338 sink.figureCaption();
1339 sink.text( "Found at " + repo.getUrl() );
1340 sink.figureCaption_();
1341 sink.figureGraphics( "images/icon_success_sml.gif" );
1342 sink.figure_();
1343
1344 sink.link_();
1345 sink.tableCell_();
1346
1347 totalByRepo.put( repokey, new Integer( old.intValue() + 1 ) );
1348 }
1349 else
1350 {
1351 tableCell( "-" );
1352 }
1353 }
1354 }
1355
1356 sink.tableRow_();
1357 }
1358
1359
1360
1361
1362 tableHeader[0] = getI18nString( "file.details.total" );
1363 tableHeader( tableHeader );
1364 String[] totalRow = new String[repoIdList.size() + 1];
1365 totalRow[0] = totaldeps.toString();
1366 idnum = 1;
1367 for ( String repokey : repoIdList )
1368 {
1369 Integer deps = totalByRepo.get( repokey );
1370 totalRow[idnum++] = deps != null ? deps.toString() : "0";
1371 }
1372
1373 tableRow( totalRow );
1374
1375 endTable();
1376 }
1377
1378
1379
1380
1381
1382 private boolean hasClassifier( List<Artifact> artifacts )
1383 {
1384 for ( Artifact artifact : artifacts )
1385 {
1386 if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
1387 {
1388 return true;
1389 }
1390 }
1391
1392 return false;
1393 }
1394
1395
1396
1397
1398
1399 private boolean hasOptional( List<Artifact> artifacts )
1400 {
1401 for ( Artifact artifact : artifacts )
1402 {
1403 if ( artifact.isOptional() )
1404 {
1405 return true;
1406 }
1407 }
1408
1409 return false;
1410 }
1411
1412
1413
1414
1415
1416 private boolean hasSealed( List<Artifact> artifacts )
1417 {
1418 for ( Artifact artifact : artifacts )
1419 {
1420
1421 if ( artifact.getFile() == null )
1422 {
1423 if ( Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) )
1424 {
1425
1426 continue;
1427 }
1428
1429 try
1430 {
1431 repoUtils.resolve( artifact );
1432 }
1433 catch ( ArtifactResolutionException e )
1434 {
1435 log.error( "Artifact: " + artifact.getId() + " has no file.", e );
1436 continue;
1437 }
1438 catch ( ArtifactNotFoundException e )
1439 {
1440 if ( ( dependencies.getProject().getGroupId().equals( artifact.getGroupId() ) )
1441 && ( dependencies.getProject().getArtifactId().equals( artifact.getArtifactId() ) )
1442 && ( dependencies.getProject().getVersion().equals( artifact.getVersion() ) ) )
1443 {
1444 log.warn( "The artifact of this project has never been deployed." );
1445 }
1446 else
1447 {
1448 log.error( "Artifact: " + artifact.getId() + " has no file.", e );
1449 }
1450
1451 continue;
1452 }
1453 }
1454
1455 if ( JAR_SUBTYPE.contains( artifact.getType().toLowerCase() ) )
1456 {
1457 try
1458 {
1459 JarData jarDetails = dependencies.getJarDependencyDetails( artifact );
1460 if ( jarDetails.isSealed() )
1461 {
1462 return true;
1463 }
1464 }
1465 catch ( IOException e )
1466 {
1467 log.error( "Artifact: " + artifact.getId() + " caused IOException: " + e.getMessage(), e );
1468 }
1469 }
1470 }
1471 return false;
1472 }
1473
1474
1475
1476
1477
1478 private static String getUUID()
1479 {
1480 return "_" + Math.abs( RANDOM.nextInt() );
1481 }
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492 static class FileDecimalFormat
1493 extends DecimalFormat
1494 {
1495 private static final long serialVersionUID = 4062503546523610081L;
1496
1497 private final I18N i18n;
1498
1499 private final Locale locale;
1500
1501
1502
1503
1504
1505
1506
1507 public FileDecimalFormat( I18N i18n, Locale locale )
1508 {
1509 super( "#,###.00" );
1510
1511 this.i18n = i18n;
1512 this.locale = locale;
1513 }
1514
1515
1516 public StringBuffer format( long fs, StringBuffer result, FieldPosition fieldPosition )
1517 {
1518 if ( fs > 1024 * 1024 * 1024 )
1519 {
1520 result = super.format( (float) fs / ( 1024 * 1024 * 1024 ), result, fieldPosition );
1521 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.gb" ) );
1522 return result;
1523 }
1524
1525 if ( fs > 1024 * 1024 )
1526 {
1527 result = super.format( (float) fs / ( 1024 * 1024 ), result, fieldPosition );
1528 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.mb" ) );
1529 return result;
1530 }
1531
1532 result = super.format( (float) fs / ( 1024 ), result, fieldPosition );
1533 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.kb" ) );
1534 return result;
1535 }
1536
1537 private String getString( String key )
1538 {
1539 return i18n.getString( "project-info-report", locale, key );
1540 }
1541 }
1542
1543
1544
1545
1546 static class TotalCell
1547 {
1548 static final int SCOPES_COUNT = 5;
1549
1550 final DecimalFormat decimalFormat;
1551
1552 long total = 0;
1553
1554 long totalCompileScope = 0;
1555
1556 long totalTestScope = 0;
1557
1558 long totalRuntimeScope = 0;
1559
1560 long totalProvidedScope = 0;
1561
1562 long totalSystemScope = 0;
1563
1564 TotalCell( DecimalFormat decimalFormat )
1565 {
1566 this.decimalFormat = decimalFormat;
1567 }
1568
1569 void incrementTotal( String scope )
1570 {
1571 addTotal( 1, scope );
1572 }
1573
1574 static String getScope( int index )
1575 {
1576 switch ( index )
1577 {
1578 case 0:
1579 return Artifact.SCOPE_COMPILE;
1580 case 1:
1581 return Artifact.SCOPE_TEST;
1582 case 2:
1583 return Artifact.SCOPE_RUNTIME;
1584 case 3:
1585 return Artifact.SCOPE_PROVIDED;
1586 case 4:
1587 return Artifact.SCOPE_SYSTEM;
1588 default:
1589 return null;
1590 }
1591 }
1592
1593 long getTotal( int index )
1594 {
1595 switch ( index )
1596 {
1597 case 0:
1598 return totalCompileScope;
1599 case 1:
1600 return totalTestScope;
1601 case 2:
1602 return totalRuntimeScope;
1603 case 3:
1604 return totalProvidedScope;
1605 case 4:
1606 return totalSystemScope;
1607 default:
1608 return total;
1609 }
1610 }
1611
1612 String getTotalString( int index )
1613 {
1614 long totalString = getTotal( index );
1615
1616 if ( totalString <= 0 )
1617 {
1618 return "";
1619 }
1620
1621 StringBuilder sb = new StringBuilder();
1622 if ( index >= 0 )
1623 {
1624 sb.append( getScope( index ) ).append( ": " );
1625 }
1626 sb.append( decimalFormat.format( getTotal( index ) ) );
1627 return sb.toString();
1628 }
1629
1630 void addTotal( long add, String scope )
1631 {
1632 total += add;
1633
1634 if ( Artifact.SCOPE_COMPILE.equals( scope ) )
1635 {
1636 totalCompileScope += add;
1637 }
1638 else if ( Artifact.SCOPE_TEST.equals( scope ) )
1639 {
1640 totalTestScope += add;
1641 }
1642 else if ( Artifact.SCOPE_RUNTIME.equals( scope ) )
1643 {
1644 totalRuntimeScope += add;
1645 }
1646 else if ( Artifact.SCOPE_PROVIDED.equals( scope ) )
1647 {
1648 totalProvidedScope += add;
1649 }
1650 else if ( Artifact.SCOPE_SYSTEM.equals( scope ) )
1651 {
1652 totalSystemScope += add;
1653 }
1654 }
1655
1656
1657 public String toString()
1658 {
1659 StringBuilder sb = new StringBuilder();
1660 sb.append( decimalFormat.format( total ) );
1661 sb.append( " (" );
1662
1663 boolean needSeparator = false;
1664 for ( int i = 0; i < SCOPES_COUNT; i++ )
1665 {
1666 if ( getTotal( i ) > 0 )
1667 {
1668 if ( needSeparator )
1669 {
1670 sb.append( ", " );
1671 }
1672 sb.append( getTotalString( i ) );
1673 needSeparator = true;
1674 }
1675 }
1676
1677 sb.append( ")" );
1678
1679 return sb.toString();
1680 }
1681 }
1682 }