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;
}
}