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