View Javadoc

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