| package org.apache.maven.report.projectinfo.dependencies.renderer; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.text.DecimalFormat; |
| import java.text.DecimalFormatSymbols; |
| import java.text.FieldPosition; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.doxia.sink.Sink; |
| import org.apache.maven.doxia.sink.SinkEventAttributes; |
| import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet; |
| import org.apache.maven.doxia.util.HtmlTools; |
| import org.apache.maven.model.License; |
| import org.apache.maven.plugin.logging.Log; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.project.ProjectBuilder; |
| import org.apache.maven.project.ProjectBuildingException; |
| import org.apache.maven.project.ProjectBuildingRequest; |
| import org.apache.maven.report.projectinfo.AbstractProjectInfoRenderer; |
| import org.apache.maven.report.projectinfo.ProjectInfoReportUtils; |
| import org.apache.maven.report.projectinfo.dependencies.Dependencies; |
| import org.apache.maven.report.projectinfo.dependencies.DependenciesReportConfiguration; |
| import org.apache.maven.report.projectinfo.dependencies.RepositoryUtils; |
| import org.apache.maven.repository.RepositorySystem; |
| import org.apache.maven.shared.artifact.resolve.ArtifactResolverException; |
| import org.apache.maven.shared.dependency.graph.DependencyNode; |
| import org.apache.maven.shared.jar.JarData; |
| import org.codehaus.plexus.i18n.I18N; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| /** |
| * Renderer the dependencies report. |
| * |
| * @version $Id$ |
| * @since 2.1 |
| */ |
| public class DependenciesRenderer |
| extends AbstractProjectInfoRenderer |
| { |
| /** URL for the 'icon_info_sml.gif' image */ |
| private static final String IMG_INFO_URL = "./images/icon_info_sml.gif"; |
| |
| /** URL for the 'close.gif' image */ |
| private static final String IMG_CLOSE_URL = "./images/close.gif"; |
| |
| /** Used to format decimal values in the "Dependency File Details" table */ |
| protected static final DecimalFormat DEFAULT_DECIMAL_FORMAT = new DecimalFormat( "###0" ); |
| |
| private static final Set<String> JAR_SUBTYPE; |
| |
| private final DependencyNode dependencyNode; |
| |
| private final Dependencies dependencies; |
| |
| private final DependenciesReportConfiguration configuration; |
| |
| private final Log log; |
| |
| private final RepositoryUtils repoUtils; |
| |
| /** Used to format file length values */ |
| private final DecimalFormat fileLengthDecimalFormat; |
| |
| /** |
| * @since 2.1.1 |
| */ |
| private int section; |
| |
| /** Counter for unique IDs that is consistent across generations. */ |
| private int idCounter = 0; |
| |
| /** |
| * Will be filled with license name / set of projects. |
| */ |
| private Map<String, Object> licenseMap = new HashMap<String, Object>() |
| { |
| private static final long serialVersionUID = 1L; |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Object put( String key, Object value ) |
| { |
| // handle multiple values as a set to avoid duplicates |
| @SuppressWarnings( "unchecked" ) |
| SortedSet<Object> valueList = (SortedSet<Object>) get( key ); |
| if ( valueList == null ) |
| { |
| valueList = new TreeSet<>(); |
| } |
| valueList.add( value ); |
| return super.put( key, valueList ); |
| } |
| }; |
| |
| private final RepositorySystem repositorySystem; |
| |
| private final ProjectBuilder projectBuilder; |
| |
| private final ProjectBuildingRequest buildingRequest; |
| |
| static |
| { |
| Set<String> jarSubtype = new HashSet<>(); |
| jarSubtype.add( "jar" ); |
| jarSubtype.add( "war" ); |
| jarSubtype.add( "ear" ); |
| jarSubtype.add( "sar" ); |
| jarSubtype.add( "rar" ); |
| jarSubtype.add( "par" ); |
| jarSubtype.add( "ejb" ); |
| JAR_SUBTYPE = Collections.unmodifiableSet( jarSubtype ); |
| } |
| |
| /** |
| * |
| /** |
| * Default constructor. |
| * |
| * @param sink {@link Sink} |
| * @param locale {@link Locale} |
| * @param i18n {@link I18N} |
| * @param log {@link Log} |
| * @param dependencies {@link Dependencies} |
| * @param dependencyTreeNode {@link DependencyNode} |
| * @param config {@link DependenciesReportConfiguration} |
| * @param repoUtils {@link RepositoryUtils} |
| * @param repositorySystem {@link RepositorySystem} |
| * @param projectBuilder {@link ProjectBuilder} |
| * @param buildingRequest {@link ProjectBuildingRequest} |
| */ |
| public DependenciesRenderer( Sink sink, Locale locale, I18N i18n, Log log, |
| Dependencies dependencies, DependencyNode dependencyTreeNode, |
| DependenciesReportConfiguration config, RepositoryUtils repoUtils, |
| RepositorySystem repositorySystem, ProjectBuilder projectBuilder, |
| ProjectBuildingRequest buildingRequest ) |
| { |
| super( sink, i18n, locale ); |
| |
| this.log = log; |
| this.dependencies = dependencies; |
| this.dependencyNode = dependencyTreeNode; |
| this.repoUtils = repoUtils; |
| this.configuration = config; |
| this.repositorySystem = repositorySystem; |
| this.projectBuilder = projectBuilder; |
| this.buildingRequest = buildingRequest; |
| |
| // Using the right set of symbols depending of the locale |
| DEFAULT_DECIMAL_FORMAT.setDecimalFormatSymbols( new DecimalFormatSymbols( locale ) ); |
| |
| this.fileLengthDecimalFormat = new FileDecimalFormat( i18n, locale ); |
| this.fileLengthDecimalFormat.setDecimalFormatSymbols( new DecimalFormatSymbols( locale ) ); |
| } |
| |
| @Override |
| protected String getI18Nsection() |
| { |
| return "dependencies"; |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Public methods |
| // ---------------------------------------------------------------------- |
| |
| @Override |
| public void renderBody() |
| { |
| // Dependencies report |
| |
| if ( !dependencies.hasDependencies() ) |
| { |
| startSection( getTitle() ); |
| |
| paragraph( getI18nString( "nolist" ) ); |
| |
| endSection(); |
| |
| return; |
| } |
| |
| // === Section: Project Dependencies. |
| renderSectionProjectDependencies(); |
| |
| // === Section: Project Transitive Dependencies. |
| renderSectionProjectTransitiveDependencies(); |
| |
| // === Section: Project Dependency Graph. |
| renderSectionProjectDependencyGraph(); |
| |
| // === Section: Licenses |
| renderSectionDependencyLicenseListing(); |
| |
| if ( configuration.getDependencyDetailsEnabled() ) |
| { |
| // === Section: Dependency File Details. |
| renderSectionDependencyFileDetails(); |
| } |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Protected methods |
| // ---------------------------------------------------------------------- |
| |
| /** {@inheritDoc} */ |
| // workaround for MPIR-140 |
| // TODO Remove me when MSHARED-390 has been resolved |
| @Override |
| protected void startSection( String name ) |
| { |
| startSection( name, name ); |
| } |
| |
| /** |
| * Start section with a name and a specific anchor. |
| * |
| * @param anchor not null |
| * @param name not null |
| */ |
| // TODO Remove me when MSHARED-390 has been resolved |
| protected void startSection( String anchor, String name ) |
| { |
| section = section + 1; |
| |
| super.sink.anchor( HtmlTools.encodeId( anchor ) ); |
| super.sink.anchor_(); |
| |
| switch ( section ) |
| { |
| case 1: |
| sink.section1(); |
| sink.sectionTitle1(); |
| break; |
| case 2: |
| sink.section2(); |
| sink.sectionTitle2(); |
| break; |
| case 3: |
| sink.section3(); |
| sink.sectionTitle3(); |
| break; |
| case 4: |
| sink.section4(); |
| sink.sectionTitle4(); |
| break; |
| case 5: |
| sink.section5(); |
| sink.sectionTitle5(); |
| break; |
| |
| default: |
| // TODO: warning - just don't start a section |
| break; |
| } |
| |
| text( name ); |
| |
| switch ( section ) |
| { |
| case 1: |
| sink.sectionTitle1_(); |
| break; |
| case 2: |
| sink.sectionTitle2_(); |
| break; |
| case 3: |
| sink.sectionTitle3_(); |
| break; |
| case 4: |
| sink.sectionTitle4_(); |
| break; |
| case 5: |
| sink.sectionTitle5_(); |
| break; |
| |
| default: |
| // TODO: warning - just don't start a section |
| break; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| // workaround for MPIR-140 |
| // TODO Remove me when MSHARED-390 has been resolved |
| @Override |
| protected void endSection() |
| { |
| switch ( section ) |
| { |
| case 1: |
| sink.section1_(); |
| break; |
| case 2: |
| sink.section2_(); |
| break; |
| case 3: |
| sink.section3_(); |
| break; |
| case 4: |
| sink.section4_(); |
| break; |
| case 5: |
| sink.section5_(); |
| break; |
| |
| default: |
| // TODO: warning - just don't start a section |
| break; |
| } |
| |
| section = section - 1; |
| |
| if ( section < 0 ) |
| { |
| throw new IllegalStateException( "Too many closing sections" ); |
| } |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Private methods |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * @param withClassifier <code>true</code> to include the classifier column, <code>false</code> otherwise. |
| * @param withOptional <code>true</code> to include the optional column, <code>false</code> otherwise. |
| * @return the dependency table header with/without classifier/optional column |
| * @see #renderArtifactRow(Artifact, boolean, boolean) |
| */ |
| private String[] getDependencyTableHeader( boolean withClassifier, boolean withOptional ) |
| { |
| String groupId = getI18nString( "column.groupId" ); |
| String artifactId = getI18nString( "column.artifactId" ); |
| String version = getI18nString( "column.version" ); |
| String classifier = getI18nString( "column.classifier" ); |
| String type = getI18nString( "column.type" ); |
| String license = getI18nString( "column.licenses" ); |
| String optional = getI18nString( "column.optional" ); |
| |
| if ( withClassifier ) |
| { |
| if ( withOptional ) |
| { |
| return new String[] { groupId, artifactId, version, classifier, type, license, optional }; |
| } |
| |
| return new String[] { groupId, artifactId, version, classifier, type, license }; |
| } |
| |
| if ( withOptional ) |
| { |
| return new String[] { groupId, artifactId, version, type, license, optional }; |
| } |
| |
| return new String[] { groupId, artifactId, version, type, license }; |
| } |
| |
| private void renderSectionProjectDependencies() |
| { |
| startSection( getTitle() ); |
| |
| // collect dependencies by scope |
| Map<String, List<Artifact>> dependenciesByScope = dependencies.getDependenciesByScope( false ); |
| |
| renderDependenciesForAllScopes( dependenciesByScope, false ); |
| |
| endSection(); |
| } |
| |
| /** |
| * @param dependenciesByScope map with supported scopes as key and a list of <code>Artifact</code> as values. |
| * @param isTransitive <code>true</code> if it is transitive dependencies rendering. |
| * @see Artifact#SCOPE_COMPILE |
| * @see Artifact#SCOPE_PROVIDED |
| * @see Artifact#SCOPE_RUNTIME |
| * @see Artifact#SCOPE_SYSTEM |
| * @see Artifact#SCOPE_TEST |
| */ |
| private void renderDependenciesForAllScopes( Map<String, List<Artifact>> dependenciesByScope, boolean isTransitive ) |
| { |
| renderDependenciesForScope( Artifact.SCOPE_COMPILE, dependenciesByScope.get( Artifact.SCOPE_COMPILE ), |
| isTransitive ); |
| renderDependenciesForScope( Artifact.SCOPE_RUNTIME, dependenciesByScope.get( Artifact.SCOPE_RUNTIME ), |
| isTransitive ); |
| renderDependenciesForScope( Artifact.SCOPE_TEST, dependenciesByScope.get( Artifact.SCOPE_TEST ), isTransitive ); |
| renderDependenciesForScope( Artifact.SCOPE_PROVIDED, dependenciesByScope.get( Artifact.SCOPE_PROVIDED ), |
| isTransitive ); |
| renderDependenciesForScope( Artifact.SCOPE_SYSTEM, dependenciesByScope.get( Artifact.SCOPE_SYSTEM ), |
| isTransitive ); |
| } |
| |
| private void renderSectionProjectTransitiveDependencies() |
| { |
| Map<String, List<Artifact>> dependenciesByScope = dependencies.getDependenciesByScope( true ); |
| |
| startSection( getI18nString( "transitive.title" ) ); |
| |
| if ( dependenciesByScope.values().isEmpty() ) |
| { |
| paragraph( getI18nString( "transitive.nolist" ) ); |
| } |
| else |
| { |
| paragraph( getI18nString( "transitive.intro" ) ); |
| |
| renderDependenciesForAllScopes( dependenciesByScope, true ); |
| } |
| |
| endSection(); |
| } |
| |
| private void renderSectionProjectDependencyGraph() |
| { |
| startSection( getI18nString( "graph.title" ) ); |
| |
| // === SubSection: Dependency Tree |
| renderSectionDependencyTree(); |
| |
| endSection(); |
| } |
| |
| private void renderSectionDependencyTree() |
| { |
| StringWriter sw = new StringWriter(); |
| PrintWriter pw = new PrintWriter( sw ); |
| |
| pw.println( "" ); |
| pw.println( "<script language=\"javascript\" type=\"text/javascript\">" ); |
| pw.println( " function toggleDependencyDetails( divId, imgId )" ); |
| pw.println( " {" ); |
| pw.println( " var div = document.getElementById( divId );" ); |
| pw.println( " var img = document.getElementById( imgId );" ); |
| pw.println( " if( div.style.display == '' )" ); |
| pw.println( " {" ); |
| pw.println( " div.style.display = 'none';" ); |
| pw.printf( " img.src='%s';%n", IMG_INFO_URL ); |
| pw.printf( " img.alt='%s';%n", getI18nString( "graph.icon.information" ) ); |
| pw.println( " }" ); |
| pw.println( " else" ); |
| pw.println( " {" ); |
| pw.println( " div.style.display = '';" ); |
| pw.printf( " img.src='%s';%n", IMG_CLOSE_URL ); |
| pw.printf( " img.alt='%s';%n", getI18nString( "graph.icon.close" ) ); |
| pw.println( " }" ); |
| pw.println( " }" ); |
| pw.println( "</script>" ); |
| |
| sink.rawText( sw.toString() ); |
| |
| // for Dependencies Graph Tree |
| startSection( getI18nString( "graph.tree.title" ) ); |
| |
| sink.list(); |
| printDependencyListing( dependencyNode ); |
| sink.list_(); |
| |
| endSection(); |
| } |
| |
| private void renderSectionDependencyFileDetails() |
| { |
| startSection( getI18nString( "file.details.title" ) ); |
| |
| List<Artifact> alldeps = dependencies.getAllDependencies(); |
| Collections.sort( alldeps, getArtifactComparator() ); |
| |
| resolveAtrifacts( alldeps ); |
| |
| // i18n |
| String filename = getI18nString( "file.details.column.file" ); |
| String size = getI18nString( "file.details.column.size" ); |
| String entries = getI18nString( "file.details.column.entries" ); |
| String classes = getI18nString( "file.details.column.classes" ); |
| String packages = getI18nString( "file.details.column.packages" ); |
| String javaVersion = getI18nString( "file.details.column.javaVersion" ); |
| String debugInformation = getI18nString( "file.details.column.debuginformation" ); |
| String debugInformationTitle = getI18nString( "file.details.columntitle.debuginformation" ); |
| String debugInformationCellYes = getI18nString( "file.details.cell.debuginformation.yes" ); |
| String debugInformationCellNo = getI18nString( "file.details.cell.debuginformation.no" ); |
| String sealed = getI18nString( "file.details.column.sealed" ); |
| String sealedCellYes = getI18nString( "file.details.cell.sealed.yes" ); |
| String sealedCellNo = getI18nString( "file.details.cell.sealed.no" ); |
| |
| int[] justification = |
| new int[] { Sink.JUSTIFY_LEFT, Sink.JUSTIFY_RIGHT, Sink.JUSTIFY_RIGHT, Sink.JUSTIFY_RIGHT, |
| Sink.JUSTIFY_RIGHT, Sink.JUSTIFY_CENTER, Sink.JUSTIFY_CENTER, Sink.JUSTIFY_CENTER }; |
| |
| startTable( justification, false ); |
| |
| TotalCell totaldeps = new TotalCell( DEFAULT_DECIMAL_FORMAT ); |
| TotalCell totaldepsize = new TotalCell( fileLengthDecimalFormat ); |
| TotalCell totalentries = new TotalCell( DEFAULT_DECIMAL_FORMAT ); |
| TotalCell totalclasses = new TotalCell( DEFAULT_DECIMAL_FORMAT ); |
| TotalCell totalpackages = new TotalCell( DEFAULT_DECIMAL_FORMAT ); |
| double highestJavaVersion = 0.0; |
| TotalCell totalDebugInformation = new TotalCell( DEFAULT_DECIMAL_FORMAT ); |
| TotalCell totalsealed = new TotalCell( DEFAULT_DECIMAL_FORMAT ); |
| |
| boolean hasSealed = hasSealed( alldeps ); |
| |
| // Table header |
| String[] tableHeader; |
| String[] tableHeaderTitles; |
| if ( hasSealed ) |
| { |
| tableHeader = new String[] { filename, size, entries, classes, packages, javaVersion, debugInformation, |
| sealed }; |
| tableHeaderTitles = new String[] { null, null, null, null, null, null, debugInformationTitle, null }; |
| } |
| else |
| { |
| tableHeader = new String[] { filename, size, entries, classes, packages, javaVersion, debugInformation }; |
| tableHeaderTitles = new String[] { null, null, null, null, null, null, debugInformationTitle }; |
| } |
| tableHeader( tableHeader, tableHeaderTitles ); |
| |
| // Table rows |
| for ( Artifact artifact : alldeps ) |
| { |
| if ( artifact.getFile() == null ) |
| { |
| log.warn( "Artifact " + artifact.getId() + " has no file" |
| + " and won't be listed in dependency files details." ); |
| continue; |
| } |
| |
| File artifactFile = dependencies.getFile( artifact ); |
| |
| totaldeps.incrementTotal( artifact.getScope() ); |
| totaldepsize.addTotal( artifactFile.length(), artifact.getScope() ); |
| |
| if ( JAR_SUBTYPE.contains( artifact.getType().toLowerCase() ) ) |
| { |
| try |
| { |
| JarData jarDetails = dependencies.getJarDependencyDetails( artifact ); |
| |
| String debugInformationCellValue = debugInformationCellNo; |
| if ( jarDetails.isDebugPresent() ) |
| { |
| debugInformationCellValue = debugInformationCellYes; |
| totalDebugInformation.incrementTotal( artifact.getScope() ); |
| } |
| |
| totalentries.addTotal( jarDetails.getNumEntries(), artifact.getScope() ); |
| totalclasses.addTotal( jarDetails.getNumClasses(), artifact.getScope() ); |
| totalpackages.addTotal( jarDetails.getNumPackages(), artifact.getScope() ); |
| |
| try |
| { |
| if ( jarDetails.getJdkRevision() != null ) |
| { |
| highestJavaVersion = Math.max( highestJavaVersion, |
| Double.parseDouble( jarDetails.getJdkRevision() ) ); |
| } |
| } |
| catch ( NumberFormatException e ) |
| { |
| // ignore |
| } |
| |
| String sealedCellValue = sealedCellNo; |
| if ( jarDetails.isSealed() ) |
| { |
| sealedCellValue = sealedCellYes; |
| totalsealed.incrementTotal( artifact.getScope() ); |
| } |
| |
| String name = artifactFile.getName(); |
| String fileLength = fileLengthDecimalFormat.format( artifactFile.length() ); |
| |
| if ( artifactFile.isDirectory() ) |
| { |
| File parent = artifactFile.getParentFile(); |
| name = parent.getParentFile().getName() + '/' + parent.getName() + '/' + artifactFile.getName(); |
| fileLength = "-"; |
| } |
| |
| tableRow( hasSealed, |
| new String[] { name, fileLength, |
| DEFAULT_DECIMAL_FORMAT.format( jarDetails.getNumEntries() ), |
| DEFAULT_DECIMAL_FORMAT.format( jarDetails.getNumClasses() ), |
| DEFAULT_DECIMAL_FORMAT.format( jarDetails.getNumPackages() ), |
| jarDetails.getJdkRevision(), debugInformationCellValue, sealedCellValue } ); |
| } |
| catch ( IOException e ) |
| { |
| createExceptionInfoTableRow( artifact, artifactFile, e, hasSealed ); |
| } |
| } |
| else |
| { |
| tableRow( hasSealed, |
| new String[] { artifactFile.getName(), |
| fileLengthDecimalFormat.format( artifactFile.length() ), "", "", "", "", "", "" } ); |
| } |
| } |
| |
| // Total raws |
| tableHeader[0] = getI18nString( "file.details.total" ); |
| tableHeader( tableHeader ); |
| |
| justification[0] = Sink.JUSTIFY_RIGHT; |
| justification[6] = Sink.JUSTIFY_RIGHT; |
| |
| for ( int i = -1; i < TotalCell.SCOPES_COUNT; i++ ) |
| { |
| if ( totaldeps.getTotal( i ) > 0 ) |
| { |
| tableRow( hasSealed, |
| new String[] { totaldeps.getTotalString( i ), totaldepsize.getTotalString( i ), |
| totalentries.getTotalString( i ), totalclasses.getTotalString( i ), |
| totalpackages.getTotalString( i ), ( i < 0 ) ? String.valueOf( highestJavaVersion ) : "", |
| totalDebugInformation.getTotalString( i ), totalsealed.getTotalString( i ) } ); |
| } |
| } |
| |
| endTable(); |
| endSection(); |
| } |
| |
| // Almost as same as in the abstract class but includes the title attribute |
| private void tableHeader( String[] content, String[] titles ) |
| { |
| sink.tableRow(); |
| |
| if ( content != null ) |
| { |
| if ( titles != null && content.length != titles.length ) |
| { |
| // CHECKSTYLE_OFF: LineLength |
| throw new IllegalArgumentException( "Length of title array must equal the length of the content array" ); |
| // CHECKSTYLE_ON: LineLength |
| } |
| |
| for ( int i = 0; i < content.length; i++ ) |
| { |
| if ( titles != null ) |
| { |
| tableHeaderCell( content[i], titles[i] ); |
| } |
| else |
| { |
| tableHeaderCell( content[i] ); |
| } |
| } |
| } |
| |
| sink.tableRow_(); |
| } |
| |
| private void tableHeaderCell( String text, String title ) |
| { |
| if ( title != null ) |
| { |
| sink.tableHeaderCell( new SinkEventAttributeSet( SinkEventAttributes.TITLE, title ) ); |
| } |
| else |
| { |
| sink.tableHeaderCell(); |
| } |
| |
| text( text ); |
| |
| sink.tableHeaderCell_(); |
| } |
| |
| private void tableRow( boolean fullRow, String[] content ) |
| { |
| sink.tableRow(); |
| |
| int count = fullRow ? content.length : ( content.length - 1 ); |
| |
| for ( int i = 0; i < count; i++ ) |
| { |
| tableCell( content[i] ); |
| } |
| |
| sink.tableRow_(); |
| } |
| |
| private void createExceptionInfoTableRow( Artifact artifact, File artifactFile, Exception e, boolean hasSealed ) |
| { |
| tableRow( hasSealed, new String[] { artifact.getId(), artifactFile.getAbsolutePath(), e.getMessage(), "", "", |
| "", "", "" } ); |
| } |
| |
| private void renderSectionDependencyLicenseListing() |
| { |
| startSection( getI18nString( "graph.tables.licenses" ) ); |
| printGroupedLicenses(); |
| endSection(); |
| } |
| |
| private void renderDependenciesForScope( String scope, List<Artifact> artifacts, boolean isTransitive ) |
| { |
| if ( artifacts != null ) |
| { |
| boolean withClassifier = hasClassifier( artifacts ); |
| boolean withOptional = hasOptional( artifacts ); |
| String[] tableHeader = getDependencyTableHeader( withClassifier, withOptional ); |
| |
| // can't use straight artifact comparison because we want optional last |
| Collections.sort( artifacts, getArtifactComparator() ); |
| |
| String anchorByScope = |
| ( isTransitive ? getI18nString( "transitive.title" ) + "_" + scope : getI18nString( "title" ) + "_" |
| + scope ); |
| startSection( anchorByScope, scope ); |
| |
| paragraph( getI18nString( "intro." + scope ) ); |
| |
| startTable(); |
| tableHeader( tableHeader ); |
| for ( Artifact artifact : artifacts ) |
| { |
| renderArtifactRow( artifact, withClassifier, withOptional ); |
| } |
| endTable(); |
| |
| endSection(); |
| } |
| } |
| |
| private Comparator<Artifact> getArtifactComparator() |
| { |
| return new Comparator<Artifact>() |
| { |
| public int compare( Artifact a1, Artifact a2 ) |
| { |
| // put optional last |
| if ( a1.isOptional() && !a2.isOptional() ) |
| { |
| return +1; |
| } |
| else if ( !a1.isOptional() && a2.isOptional() ) |
| { |
| return -1; |
| } |
| else |
| { |
| return a1.compareTo( a2 ); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * @param artifact not null |
| * @param withClassifier <code>true</code> to include the classifier column, <code>false</code> otherwise. |
| * @param withOptional <code>true</code> to include the optional column, <code>false</code> otherwise. |
| * @see #getDependencyTableHeader(boolean, boolean) |
| */ |
| private void renderArtifactRow( Artifact artifact, boolean withClassifier, boolean withOptional ) |
| { |
| String isOptional = |
| artifact.isOptional() ? getI18nString( "column.isOptional" ) : getI18nString( "column.isNotOptional" ); |
| |
| String url = |
| ProjectInfoReportUtils.getArtifactUrl( repositorySystem, artifact, projectBuilder, buildingRequest ); |
| String artifactIdCell = ProjectInfoReportUtils.getArtifactIdCell( artifact.getArtifactId(), url ); |
| |
| MavenProject artifactProject; |
| StringBuilder sb = new StringBuilder(); |
| try |
| { |
| artifactProject = repoUtils.getMavenProjectFromRepository( artifact ); |
| |
| List<License> licenses = artifactProject.getLicenses(); |
| for ( License license : licenses ) |
| { |
| sb.append( ProjectInfoReportUtils.getArtifactIdCell( license.getName(), license.getUrl() ) ); |
| } |
| } |
| catch ( ProjectBuildingException e ) |
| { |
| log.warn( "Unable to create Maven project from repository.", e ); |
| } |
| |
| String[] content; |
| if ( withClassifier ) |
| { |
| content = |
| new String[] { artifact.getGroupId(), artifactIdCell, artifact.getVersion(), artifact.getClassifier(), |
| artifact.getType(), sb.toString(), isOptional }; |
| } |
| else |
| { |
| content = |
| new String[] { artifact.getGroupId(), artifactIdCell, artifact.getVersion(), artifact.getType(), |
| sb.toString(), isOptional }; |
| } |
| |
| tableRow( withOptional, content ); |
| } |
| |
| private void printDependencyListing( DependencyNode node ) |
| { |
| Artifact artifact = node.getArtifact(); |
| String id = artifact.getId(); |
| String dependencyDetailId = "_dep" + idCounter++; |
| String imgId = "_img" + idCounter++; |
| |
| sink.listItem(); |
| |
| sink.text( id + ( StringUtils.isNotEmpty( artifact.getScope() ) ? " (" + artifact.getScope() + ") " : " " ) ); |
| |
| String javascript = String.format( "<img id=\"%s\" src=\"%s\" alt=\"%s\"" |
| + " onclick=\"toggleDependencyDetails( '%s', '%s' );\"" |
| + " style=\"cursor: pointer; vertical-align: text-bottom;\"></img>", |
| imgId, IMG_INFO_URL, getI18nString( "graph.icon.information" ), dependencyDetailId, imgId ); |
| |
| sink.rawText( javascript ); |
| |
| printDescriptionsAndURLs( node, dependencyDetailId ); |
| |
| if ( !node.getChildren().isEmpty() ) |
| { |
| boolean toBeIncluded = false; |
| List<DependencyNode> subList = new ArrayList<DependencyNode>(); |
| for ( DependencyNode dep : node.getChildren() ) |
| { |
| if ( dependencies.getAllDependencies().contains( dep.getArtifact() ) ) |
| { |
| subList.add( dep ); |
| toBeIncluded = true; |
| } |
| } |
| |
| if ( toBeIncluded ) |
| { |
| sink.list(); |
| for ( DependencyNode dep : subList ) |
| { |
| printDependencyListing( dep ); |
| } |
| sink.list_(); |
| } |
| } |
| |
| sink.listItem_(); |
| } |
| |
| private void printDescriptionsAndURLs( DependencyNode node, String uid ) |
| { |
| Artifact artifact = node.getArtifact(); |
| String id = artifact.getId(); |
| String unknownLicenseMessage = getI18nString( "graph.tables.unknown" ); |
| |
| sink.rawText( "<div id=\"" + uid + "\" style=\"display:none\">" ); |
| |
| sink.table(); |
| |
| if ( !Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) ) |
| { |
| try |
| { |
| MavenProject artifactProject = repoUtils.getMavenProjectFromRepository( artifact ); |
| String artifactDescription = artifactProject.getDescription(); |
| String artifactUrl = artifactProject.getUrl(); |
| String artifactName = artifactProject.getName(); |
| |
| List<License> licenses = artifactProject.getLicenses(); |
| |
| sink.tableRow(); |
| sink.tableHeaderCell(); |
| sink.text( artifactName ); |
| sink.tableHeaderCell_(); |
| sink.tableRow_(); |
| |
| sink.tableRow(); |
| sink.tableCell(); |
| |
| sink.paragraph(); |
| sink.bold(); |
| sink.text( getI18nString( "column.description" ) + ": " ); |
| sink.bold_(); |
| if ( StringUtils.isNotEmpty( artifactDescription ) ) |
| { |
| sink.text( artifactDescription ); |
| } |
| else |
| { |
| sink.text( getI18nString( "index", "nodescription" ) ); |
| } |
| sink.paragraph_(); |
| |
| if ( StringUtils.isNotEmpty( artifactUrl ) ) |
| { |
| sink.paragraph(); |
| sink.bold(); |
| sink.text( getI18nString( "column.url" ) + ": " ); |
| sink.bold_(); |
| if ( ProjectInfoReportUtils.isArtifactUrlValid( artifactUrl ) ) |
| { |
| sink.link( artifactUrl ); |
| sink.text( artifactUrl ); |
| sink.link_(); |
| } |
| else |
| { |
| sink.text( artifactUrl ); |
| } |
| sink.paragraph_(); |
| } |
| |
| sink.paragraph(); |
| sink.bold(); |
| sink.text( getI18nString( "licenses", "title" ) + ": " ); |
| sink.bold_(); |
| if ( !licenses.isEmpty() ) |
| { |
| |
| for ( Iterator<License> it = licenses.iterator(); it.hasNext(); ) |
| { |
| License license = it.next(); |
| |
| String licenseName = license.getName(); |
| if ( StringUtils.isEmpty( licenseName ) ) |
| { |
| licenseName = getI18nString( "unnamed" ); |
| } |
| |
| String licenseUrl = license.getUrl(); |
| |
| if ( licenseUrl != null ) |
| { |
| sink.link( licenseUrl ); |
| } |
| sink.text( licenseName ); |
| |
| if ( licenseUrl != null ) |
| { |
| sink.link_(); |
| } |
| |
| if ( it.hasNext() ) |
| { |
| sink.text( ", " ); |
| } |
| |
| licenseMap.put( licenseName, artifactName ); |
| } |
| } |
| else |
| { |
| sink.text( getI18nString( "licenses", "nolicense" ) ); |
| |
| licenseMap.put( unknownLicenseMessage, artifactName ); |
| } |
| sink.paragraph_(); |
| } |
| catch ( ProjectBuildingException e ) |
| { |
| log.warn( "Unable to create Maven project from repository for artifact " + artifact.getId(), e ); |
| } |
| } |
| else |
| { |
| sink.tableRow(); |
| sink.tableHeaderCell(); |
| sink.text( id ); |
| sink.tableHeaderCell_(); |
| sink.tableRow_(); |
| |
| sink.tableRow(); |
| sink.tableCell(); |
| |
| sink.paragraph(); |
| sink.bold(); |
| sink.text( getI18nString( "column.description" ) + ": " ); |
| sink.bold_(); |
| sink.text( getI18nString( "index", "nodescription" ) ); |
| sink.paragraph_(); |
| |
| if ( artifact.getFile() != null ) |
| { |
| sink.paragraph(); |
| sink.bold(); |
| sink.text( getI18nString( "column.url" ) + ": " ); |
| sink.bold_(); |
| sink.text( artifact.getFile().getAbsolutePath() ); |
| sink.paragraph_(); |
| } |
| } |
| |
| sink.tableCell_(); |
| sink.tableRow_(); |
| |
| sink.table_(); |
| |
| sink.rawText( "</div>" ); |
| } |
| |
| private void printGroupedLicenses() |
| { |
| for ( Map.Entry<String, Object> entry : licenseMap.entrySet() ) |
| { |
| String licenseName = entry.getKey(); |
| if ( StringUtils.isEmpty( licenseName ) ) |
| { |
| licenseName = getI18nString( "unnamed" ); |
| } |
| |
| sink.paragraph(); |
| sink.bold(); |
| sink.text( licenseName ); |
| sink.text( ": " ); |
| sink.bold_(); |
| |
| @SuppressWarnings( "unchecked" ) |
| SortedSet<String> projects = (SortedSet<String>) entry.getValue(); |
| |
| for ( Iterator<String> iterator = projects.iterator(); iterator.hasNext(); ) |
| { |
| String projectName = iterator.next(); |
| sink.text( projectName ); |
| if ( iterator.hasNext() ) |
| { |
| sink.text( ", " ); |
| } |
| } |
| |
| sink.paragraph_(); |
| } |
| } |
| |
| /** |
| * Resolves all given artifacts with {@link RepositoryUtils}. |
| * |
| ** @param artifacts not null |
| */ |
| private void resolveAtrifacts( List<Artifact> artifacts ) |
| { |
| for ( Artifact artifact : artifacts ) |
| { |
| // TODO site:run Why do we need to resolve this... |
| if ( artifact.getFile() == null ) |
| { |
| if ( Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) ) |
| { |
| // can not resolve system scope artifact file |
| continue; |
| } |
| |
| try |
| { |
| repoUtils.resolve( artifact ); |
| } |
| catch ( ArtifactResolverException e ) |
| { |
| log.error( "Artifact " + artifact.getId() + " can't be resolved.", e ); |
| continue; |
| } |
| |
| if ( artifact.getFile() == null ) |
| { |
| log.error( "Artifact " + artifact.getId() + " has no file, even after resolution." ); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param artifacts not null |
| * @return <code>true</code> if one artifact in the list has a classifier, <code>false</code> otherwise. |
| */ |
| private boolean hasClassifier( List<Artifact> artifacts ) |
| { |
| for ( Artifact artifact : artifacts ) |
| { |
| if ( StringUtils.isNotEmpty( artifact.getClassifier() ) ) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @param artifacts not null |
| * @return <code>true</code> if one artifact in the list is optional, <code>false</code> otherwise. |
| */ |
| private boolean hasOptional( List<Artifact> artifacts ) |
| { |
| for ( Artifact artifact : artifacts ) |
| { |
| if ( artifact.isOptional() ) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @param artifacts not null |
| * @return <code>true</code> if one artifact in the list is sealed, <code>false</code> otherwise. |
| */ |
| private boolean hasSealed( List<Artifact> artifacts ) |
| { |
| for ( Artifact artifact : artifacts ) |
| { |
| if ( artifact.getFile() != null && JAR_SUBTYPE.contains( artifact.getType().toLowerCase() ) ) |
| { |
| try |
| { |
| JarData jarDetails = dependencies.getJarDependencyDetails( artifact ); |
| if ( jarDetails.isSealed() ) |
| { |
| return true; |
| } |
| } |
| catch ( IOException e ) |
| { |
| log.error( "Artifact " + artifact.getId() + " caused IOException: " + e.getMessage(), e ); |
| } |
| } |
| } |
| return false; |
| } |
| |
| // CHECKSTYLE_OFF: LineLength |
| /** |
| * Formats file length with the associated <a href="https://en.wikipedia.org/wiki/Metric_prefix">SI</a> prefix |
| * (GB, MB, kB) and using the pattern <code>###0.#</code> by default. |
| * |
| * @see <a href="https://en.wikipedia.org/wiki/Metric_prefix">https://en.wikipedia.org/wiki/Metric_prefix</a> |
| * @see <a href="https://en.wikipedia.org/wiki/Binary_prefix">https://en.wikipedia.org/wiki/Binary_prefix</a> |
| * @see <a |
| * href="https://en.wikipedia.org/wiki/Octet_%28computing%29">https://en.wikipedia.org/wiki/Octet_(computing)</a> |
| */ |
| // CHECKSTYLE_ON: LineLength |
| static class FileDecimalFormat |
| extends DecimalFormat |
| { |
| private static final long serialVersionUID = 4062503546523610081L; |
| |
| private final I18N i18n; |
| |
| private final Locale locale; |
| |
| /** |
| * Default constructor |
| * |
| * @param i18n |
| * @param locale |
| */ |
| FileDecimalFormat( I18N i18n, Locale locale ) |
| { |
| super( "###0.#" ); |
| |
| this.i18n = i18n; |
| this.locale = locale; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public StringBuffer format( long fs, StringBuffer result, FieldPosition fieldPosition ) |
| { |
| if ( fs > 1000 * 1000 * 1000 ) |
| { |
| result = super.format( (float) fs / ( 1000 * 1000 * 1000 ), result, fieldPosition ); |
| result.append( " " ).append( getString( "report.dependencies.file.details.column.size.gb" ) ); |
| return result; |
| } |
| |
| if ( fs > 1000 * 1000 ) |
| { |
| result = super.format( (float) fs / ( 1000 * 1000 ), result, fieldPosition ); |
| result.append( " " ).append( getString( "report.dependencies.file.details.column.size.mb" ) ); |
| return result; |
| } |
| |
| result = super.format( (float) fs / ( 1000 ), result, fieldPosition ); |
| result.append( " " ).append( getString( "report.dependencies.file.details.column.size.kb" ) ); |
| return result; |
| } |
| |
| private String getString( String key ) |
| { |
| return i18n.getString( "project-info-reports", locale, key ); |
| } |
| } |
| |
| /** |
| * Combine total and total by scope in a cell. |
| */ |
| static class TotalCell |
| { |
| static final int SCOPES_COUNT = 5; |
| |
| final DecimalFormat decimalFormat; |
| |
| long total = 0; |
| |
| long totalCompileScope = 0; |
| |
| long totalTestScope = 0; |
| |
| long totalRuntimeScope = 0; |
| |
| long totalProvidedScope = 0; |
| |
| long totalSystemScope = 0; |
| |
| TotalCell( DecimalFormat decimalFormat ) |
| { |
| this.decimalFormat = decimalFormat; |
| } |
| |
| void incrementTotal( String scope ) |
| { |
| addTotal( 1, scope ); |
| } |
| |
| static String getScope( int index ) |
| { |
| switch ( index ) |
| { |
| case 0: |
| return Artifact.SCOPE_COMPILE; |
| case 1: |
| return Artifact.SCOPE_TEST; |
| case 2: |
| return Artifact.SCOPE_RUNTIME; |
| case 3: |
| return Artifact.SCOPE_PROVIDED; |
| case 4: |
| return Artifact.SCOPE_SYSTEM; |
| default: |
| return null; |
| } |
| } |
| |
| long getTotal( int index ) |
| { |
| switch ( index ) |
| { |
| case 0: |
| return totalCompileScope; |
| case 1: |
| return totalTestScope; |
| case 2: |
| return totalRuntimeScope; |
| case 3: |
| return totalProvidedScope; |
| case 4: |
| return totalSystemScope; |
| default: |
| return total; |
| } |
| } |
| |
| String getTotalString( int index ) |
| { |
| long totalString = getTotal( index ); |
| |
| if ( totalString <= 0 ) |
| { |
| return ""; |
| } |
| |
| StringBuilder sb = new StringBuilder(); |
| if ( index >= 0 ) |
| { |
| sb.append( getScope( index ) ).append( ": " ); |
| } |
| sb.append( decimalFormat.format( getTotal( index ) ) ); |
| return sb.toString(); |
| } |
| |
| void addTotal( long add, String scope ) |
| { |
| total += add; |
| |
| if ( Artifact.SCOPE_COMPILE.equals( scope ) ) |
| { |
| totalCompileScope += add; |
| } |
| else if ( Artifact.SCOPE_TEST.equals( scope ) ) |
| { |
| totalTestScope += add; |
| } |
| else if ( Artifact.SCOPE_RUNTIME.equals( scope ) ) |
| { |
| totalRuntimeScope += add; |
| } |
| else if ( Artifact.SCOPE_PROVIDED.equals( scope ) ) |
| { |
| totalProvidedScope += add; |
| } |
| else if ( Artifact.SCOPE_SYSTEM.equals( scope ) ) |
| { |
| totalSystemScope += add; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public String toString() |
| { |
| StringBuilder sb = new StringBuilder(); |
| sb.append( decimalFormat.format( total ) ); |
| sb.append( " (" ); |
| |
| boolean needSeparator = false; |
| for ( int i = 0; i < SCOPES_COUNT; i++ ) |
| { |
| if ( getTotal( i ) > 0 ) |
| { |
| if ( needSeparator ) |
| { |
| sb.append( ", " ); |
| } |
| sb.append( getTotalString( i ) ); |
| needSeparator = true; |
| } |
| } |
| |
| sb.append( ")" ); |
| |
| return sb.toString(); |
| } |
| } |
| } |