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