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