package de.is24.deadcode4j.plugin; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.cache.LoadingCache; import de.is24.deadcode4j.Module; import de.is24.deadcode4j.Repository; import de.is24.deadcode4j.Resource; import de.is24.deadcode4j.plugin.packaginghandler.DefaultPackagingHandler; import de.is24.deadcode4j.plugin.packaginghandler.PackagingHandler; import de.is24.deadcode4j.plugin.packaginghandler.PomPackagingHandler; import de.is24.deadcode4j.plugin.packaginghandler.WarPackagingHandler; import de.is24.guava.NonNullFunction; import de.is24.guava.NonNullFunctions; import de.is24.guava.SequentialLoadingCache; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; import org.apache.maven.artifact.resolver.ArtifactResolutionResult; import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.apache.maven.repository.RepositorySystem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.File; import java.util.Collection; import java.util.List; import java.util.Map; import static com.google.common.base.Optional.absent; import static com.google.common.base.Optional.of; import static com.google.common.base.Strings.emptyToNull; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newHashMap; import static de.is24.deadcode4j.Utils.*; /** * Calculates the modules for the given maven projects. * * @see #getModulesFor(Iterable) * @since 2.0.0 */ class ModuleGenerator { @Nonnull private final Logger logger = LoggerFactory.getLogger(getClass()); @Nonnull private final PackagingHandler defaultPackagingHandler = new DefaultPackagingHandler(); @Nonnull private final Map<String, PackagingHandler> packagingHandlers = newHashMap(); @Nonnull private final LoadingCache<Artifact, Optional<File>> artifactResolverCache; /** * Creates a new <code>ModuleGenerator</code>. * * @param repositorySystem the given <code>RepositorySystem</code> is required to resolve the class path of the * examined maven projects * @since 2.0.0 */ public ModuleGenerator(@Nonnull final RepositorySystem repositorySystem) { packagingHandlers.put("pom", new PomPackagingHandler()); packagingHandlers.put("war", new WarPackagingHandler()); artifactResolverCache = new SequentialLoadingCache<Artifact, File>(NonNullFunctions.toFunction(new NonNullFunction<Artifact, Optional<File>>() { @Nonnull @Override public Optional<File> apply(@Nonnull Artifact input) { if (!input.isResolved()) { ArtifactResolutionRequest request = new ArtifactResolutionRequest(); request.setResolveRoot(true); request.setResolveTransitively(false); request.setArtifact(input); ArtifactResolutionResult artifactResolutionResult = repositorySystem.resolve(request); if (!artifactResolutionResult.isSuccess()) { logger.warn(" Failed to resolve [{}]; some analyzers may not work properly.", getVersionedKeyFor(input)); return absent(); } } File classPathElement = input.getFile(); if (classPathElement == null) { logger.warn(" No valid path to [{}] found; some analyzers may not work properly.", getVersionedKeyFor(input)); return absent(); } return of(classPathElement); } })); } /** * Attempts to create a <code>Module</code> for each given maven project. A project providing no repository will be * silently ignored. * * @param projects the <code>MavenProject</code>s to transform; the projects MUST be ordered such that project * <i>A</i> is listed before project <i>B</i> if <i>B</i> depends on <i>A</i> * @throws MojoExecutionException if calculating the repositories fails * @since 2.0.0 */ @Nonnull public Iterable<Module> getModulesFor(@Nonnull Iterable<MavenProject> projects) throws MojoExecutionException { Map<String, Module> knownModules = newHashMap(); for (MavenProject project : projects) { Module module = getModuleFor(project, knownModules); knownModules.put(module.getModuleId(), module); logger.debug("Added [{}] for [{}].", module, project); } return knownModules.values(); } @Nonnull private Module getModuleFor( @Nonnull MavenProject project, @Nonnull Map<String, Module> knownModules) throws MojoExecutionException { String projectId = getKeyFor(project); String encoding = emptyToNull(project.getProperties().getProperty("project.build.sourceEncoding")); if (encoding == null) { logger.warn("No encoding set for [{}]! Parsing source files may cause issues.", projectId); } PackagingHandler packagingHandler = getValueOrDefault(this.packagingHandlers, project.getPackaging(), this.defaultPackagingHandler); Repository outputRepository = packagingHandler.getOutputRepositoryFor(project); Iterable<Repository> additionalRepositories = packagingHandler.getAdditionalRepositoriesFor(project); Collection<Resource> dependencies = computeDependencies(project, knownModules); return new Module(projectId, encoding, dependencies, outputRepository, additionalRepositories); } @Nonnull protected Collection<Resource> computeDependencies( @Nonnull MavenProject project, @Nonnull Map<String, Module> knownModules) { logger.debug("Gathering dependencies for project [{}]...", getKeyFor(project)); List<Resource> dependencies = newArrayList(); for (Artifact dependency : filter(project.getArtifacts(), artifactsWithCompileScope())) { if (addKnownArtifact(dependencies, dependency, knownModules)) { continue; } final Optional<File> artifactPath = this.artifactResolverCache.getUnchecked(dependency); if (artifactPath.isPresent()) { final File classPathElement = artifactPath.get(); dependencies.add(Resource.of(classPathElement)); logger.debug(" Added artifact: [{}]", classPathElement); } } logger.debug("[{}] dependencies found for [{}].", dependencies.size(), getKeyFor(project)); return dependencies; } private Predicate<Artifact> artifactsWithCompileScope() { return new Predicate<Artifact>() { private ScopeArtifactFilter artifactFilter = new ScopeArtifactFilter(Artifact.SCOPE_COMPILE); @Override public boolean apply(@Nullable Artifact input) { return input != null && artifactFilter.include(input); } }; } private boolean addKnownArtifact( @Nonnull List<Resource> resources, @Nonnull Artifact artifact, @Nonnull Map<String, Module> knownModules) { String dependencyKey = getKeyFor(artifact); Module knownModule = knownModules.get(dependencyKey); if (knownModule == null) { return false; } resources.add(Resource.of(knownModule)); logger.debug(" Added project: [{}]", knownModule); return true; } }