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