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