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 1384337 2012-09-13 13:53:19Z olamy $
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         StringBuilder sb = new StringBuilder();
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         StringBuilder sb = new StringBuilder();
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 )
1422             {
1423                 if ( Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) )
1424                 {
1425                     // can not resolve system scope artifact file
1426                     continue;
1427                 }
1428 
1429                 try
1430                 {
1431                     repoUtils.resolve( artifact );
1432                 }
1433                 catch ( ArtifactResolutionException e )
1434                 {
1435                     log.error( "Artifact: " + artifact.getId() + " has no file.", e );
1436                     continue;
1437                 }
1438                 catch ( ArtifactNotFoundException e )
1439                 {
1440                     if ( ( dependencies.getProject().getGroupId().equals( artifact.getGroupId() ) )
1441                         && ( dependencies.getProject().getArtifactId().equals( artifact.getArtifactId() ) )
1442                         && ( dependencies.getProject().getVersion().equals( artifact.getVersion() ) ) )
1443                     {
1444                         log.warn( "The artifact of this project has never been deployed." );
1445                     }
1446                     else
1447                     {
1448                         log.error( "Artifact: " + artifact.getId() + " has no file.", e );
1449                     }
1450 
1451                     continue;
1452                 }
1453             }
1454 
1455             if ( JAR_SUBTYPE.contains( artifact.getType().toLowerCase() ) )
1456             {
1457                 try
1458                 {
1459                     JarData jarDetails = dependencies.getJarDependencyDetails( artifact );
1460                     if ( jarDetails.isSealed() )
1461                     {
1462                         return true;
1463                     }
1464                 }
1465                 catch ( IOException e )
1466                 {
1467                     log.error( "Artifact: " + artifact.getId() + " caused IOException: " + e.getMessage(), e );
1468                 }
1469             }
1470         }
1471         return false;
1472     }
1473 
1474     /**
1475      * @return a valid HTML ID respecting <a href="http://www.w3.org/TR/xhtml1/#C_8">XHTML 1.0 section C.8. Fragment
1476      *         Identifiers</a>
1477      */
1478     private static String getUUID()
1479     {
1480         return "_" + Math.abs( RANDOM.nextInt() );
1481     }
1482 
1483     /**
1484      * Formats file length with the associated <a href="http://en.wikipedia.org/wiki/SI_prefix#Computing">SI</a> unit
1485      * (GB, MB, kB) and using the pattern <code>########.00</code> by default.
1486      * 
1487      * @see <a href="http://en.wikipedia.org/wiki/SI_prefix#Computing>
1488      *      http://en.wikipedia.org/wiki/SI_prefix#Computing</a>
1489      * @see <a href="http://en.wikipedia.org/wiki/Binary_prefix"> http://en.wikipedia.org/wiki/Binary_prefix</a>
1490      * @see <a href="http://en.wikipedia.org/wiki/Octet_(computing)"> http://en.wikipedia.org/wiki/Octet_(computing)</a>
1491      */
1492     static class FileDecimalFormat
1493         extends DecimalFormat
1494     {
1495         private static final long serialVersionUID = 4062503546523610081L;
1496 
1497         private final I18N i18n;
1498 
1499         private final Locale locale;
1500 
1501         /**
1502          * Default constructor
1503          * 
1504          * @param i18n
1505          * @param locale
1506          */
1507         public FileDecimalFormat( I18N i18n, Locale locale )
1508         {
1509             super( "#,###.00" );
1510 
1511             this.i18n = i18n;
1512             this.locale = locale;
1513         }
1514 
1515         /** {@inheritDoc} */
1516         public StringBuffer format( long fs, StringBuffer result, FieldPosition fieldPosition )
1517         {
1518             if ( fs > 1024 * 1024 * 1024 )
1519             {
1520                 result = super.format( (float) fs / ( 1024 * 1024 * 1024 ), result, fieldPosition );
1521                 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.gb" ) );
1522                 return result;
1523             }
1524 
1525             if ( fs > 1024 * 1024 )
1526             {
1527                 result = super.format( (float) fs / ( 1024 * 1024 ), result, fieldPosition );
1528                 result.append( " " ).append( getString( "report.dependencies.file.details.column.size.mb" ) );
1529                 return result;
1530             }
1531 
1532             result = super.format( (float) fs / ( 1024 ), result, fieldPosition );
1533             result.append( " " ).append( getString( "report.dependencies.file.details.column.size.kb" ) );
1534             return result;
1535         }
1536 
1537         private String getString( String key )
1538         {
1539             return i18n.getString( "project-info-report", locale, key );
1540         }
1541     }
1542 
1543     /**
1544      * Combine total and total by scope in a cell.
1545      */
1546     static class TotalCell
1547     {
1548         static final int SCOPES_COUNT = 5;
1549 
1550         final DecimalFormat decimalFormat;
1551 
1552         long total = 0;
1553 
1554         long totalCompileScope = 0;
1555 
1556         long totalTestScope = 0;
1557 
1558         long totalRuntimeScope = 0;
1559 
1560         long totalProvidedScope = 0;
1561 
1562         long totalSystemScope = 0;
1563 
1564         TotalCell( DecimalFormat decimalFormat )
1565         {
1566             this.decimalFormat = decimalFormat;
1567         }
1568 
1569         void incrementTotal( String scope )
1570         {
1571             addTotal( 1, scope );
1572         }
1573 
1574         static String getScope( int index )
1575         {
1576             switch ( index )
1577             {
1578                 case 0:
1579                     return Artifact.SCOPE_COMPILE;
1580                 case 1:
1581                     return Artifact.SCOPE_TEST;
1582                 case 2:
1583                     return Artifact.SCOPE_RUNTIME;
1584                 case 3:
1585                     return Artifact.SCOPE_PROVIDED;
1586                 case 4:
1587                     return Artifact.SCOPE_SYSTEM;
1588                 default:
1589                     return null;
1590             }
1591         }
1592 
1593         long getTotal( int index )
1594         {
1595             switch ( index )
1596             {
1597                 case 0:
1598                     return totalCompileScope;
1599                 case 1:
1600                     return totalTestScope;
1601                 case 2:
1602                     return totalRuntimeScope;
1603                 case 3:
1604                     return totalProvidedScope;
1605                 case 4:
1606                     return totalSystemScope;
1607                 default:
1608                     return total;
1609             }
1610         }
1611 
1612         String getTotalString( int index )
1613         {
1614             long totalString = getTotal( index );
1615 
1616             if ( totalString <= 0 )
1617             {
1618                 return "";
1619             }
1620 
1621             StringBuilder sb = new StringBuilder();
1622             if ( index >= 0 )
1623             {
1624                 sb.append( getScope( index ) ).append( ": " );
1625             }
1626             sb.append( decimalFormat.format( getTotal( index ) ) );
1627             return sb.toString();
1628         }
1629 
1630         void addTotal( long add, String scope )
1631         {
1632             total += add;
1633 
1634             if ( Artifact.SCOPE_COMPILE.equals( scope ) )
1635             {
1636                 totalCompileScope += add;
1637             }
1638             else if ( Artifact.SCOPE_TEST.equals( scope ) )
1639             {
1640                 totalTestScope += add;
1641             }
1642             else if ( Artifact.SCOPE_RUNTIME.equals( scope ) )
1643             {
1644                 totalRuntimeScope += add;
1645             }
1646             else if ( Artifact.SCOPE_PROVIDED.equals( scope ) )
1647             {
1648                 totalProvidedScope += add;
1649             }
1650             else if ( Artifact.SCOPE_SYSTEM.equals( scope ) )
1651             {
1652                 totalSystemScope += add;
1653             }
1654         }
1655 
1656         /** {@inheritDoc} */
1657         public String toString()
1658         {
1659             StringBuilder sb = new StringBuilder();
1660             sb.append( decimalFormat.format( total ) );
1661             sb.append( " (" );
1662 
1663             boolean needSeparator = false;
1664             for ( int i = 0; i < SCOPES_COUNT; i++ )
1665             {
1666                 if ( getTotal( i ) > 0 )
1667                 {
1668                     if ( needSeparator )
1669                     {
1670                         sb.append( ", " );
1671                     }
1672                     sb.append( getTotalString( i ) );
1673                     needSeparator = true;
1674                 }
1675             }
1676 
1677             sb.append( ")" );
1678 
1679             return sb.toString();
1680         }
1681     }
1682 }