
io.takari.maven.plugins.compile.ProjectClasspathDigester Maven / Gradle / Ivy
/*
* Copyright (c) 2014-2024 Takari, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*/
package io.takari.maven.plugins.compile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.maven.execution.scope.MojoExecutionScoped;
import org.codehaus.plexus.util.DirectoryScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Named
@MojoExecutionScoped
public class ProjectClasspathDigester {
private static final String ATTR_CLASSPATH_DIGEST = "compile.classpath.digest";
private static final String ATTR_SOURCEPATH_DIGEST = "compile.sourcepath.digest";
private static final String ATTR_PROCESSORPATH_DIGEST = "compile.processorpath.digest";
private final Logger log = LoggerFactory.getLogger(getClass());
private static final Map CACHE = new ConcurrentHashMap();
private final CompilerBuildContext context;
@Inject
public ProjectClasspathDigester(CompilerBuildContext context) {
this.context = context;
}
/**
* Detects if classpath dependencies changed compared to the previous build or not.
*/
public boolean digestClasspath(List dependencies) throws IOException {
return digest(ATTR_CLASSPATH_DIGEST, dependencies);
}
public boolean digestSourcepath(List dependencies) throws IOException {
return digest(ATTR_SOURCEPATH_DIGEST, dependencies);
}
public boolean digestProcessorpath(List dependencies) throws IOException {
return digest(ATTR_PROCESSORPATH_DIGEST, dependencies);
}
private boolean digest(String key, List dependencies) {
long started = System.currentTimeMillis();
Map previousArtifacts = getPreviousDependencies(key);
LinkedHashMap digest = new LinkedHashMap<>();
if (dependencies != null) {
for (final File dependency : dependencies) {
File normalized = normalize(dependency);
ArtifactFile previousArtifact = previousArtifacts.get(normalized);
ArtifactFile artifact = CACHE.get(normalized);
if (artifact == null) {
if (normalized.isFile()) {
artifact = newFileArtifact(normalized, previousArtifact);
} else if (normalized.isDirectory()) {
artifact = newDirectoryArtifact(normalized, previousArtifact);
} else {
// happens with reactor dependencies with empty source folders
continue;
}
CACHE.put(normalized, artifact);
}
digest.put(normalized, artifact);
if (!equals(artifact, previousArtifact)) {
log.debug("New or changed classpath entry {}", normalized);
}
}
}
for (File reviousDependency : previousArtifacts.keySet()) {
if (!digest.containsKey(reviousDependency)) {
log.debug("Removed classpath entry {}", reviousDependency);
}
}
boolean changed = !equals(digest.values(), previousArtifacts.values());
context.setAttribute(key, new ArrayList<>(digest.values()));
log.debug(
"Analyzed {} classpath dependencies ({} ms)",
dependencies != null ? dependencies.size() : 0,
System.currentTimeMillis() - started);
return changed;
}
private File normalize(File file) {
try {
return file.getCanonicalFile();
} catch (IOException e) {
return file.getAbsoluteFile();
}
}
private boolean equals(Collection a, Collection b) {
if (a.size() != b.size()) {
return false;
}
Iterator ia = a.iterator();
Iterator ib = b.iterator();
while (ia.hasNext()) {
if (!equals(ia.next(), ib.next())) {
return false;
}
}
return true;
}
private boolean equals(ArtifactFile a, ArtifactFile b) {
if (a == null) {
return b == null;
}
if (b == null) {
return false;
}
return a.file.equals(b.file)
&& a.isFile == b.isFile
&& a.lastModified == b.lastModified
&& a.length == b.length;
}
private ArtifactFile newDirectoryArtifact(File directory, ArtifactFile previousArtifact) {
StringBuilder msg = new StringBuilder();
DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir(directory);
scanner.setIncludes(new String[] {"**/*"});
scanner.scan();
long maxLastModified = 0, fileCount = 0;
for (String path : scanner.getIncludedFiles()) {
File file = new File(directory, path);
long lastModified = file.lastModified();
maxLastModified = Math.max(maxLastModified, lastModified);
fileCount++;
if (previousArtifact != null && previousArtifact.lastModified < lastModified) {
msg.append("\n new or modfied class folder member ").append(file);
}
}
if (previousArtifact != null && previousArtifact.length != fileCount) {
msg.append("\n classfolder member count changed (new ")
.append(fileCount)
.append(" previous ")
.append(previousArtifact.length)
.append(')');
}
if (msg.length() > 0) {
log.debug("Changed dependency class folder {}: {}", directory, msg.toString());
}
return new ArtifactFile(directory, false, fileCount, maxLastModified);
}
private ArtifactFile newFileArtifact(File file, ArtifactFile previousArtifact) {
return new ArtifactFile(file, true, file.length(), file.lastModified());
}
@SuppressWarnings("unchecked")
private Map getPreviousDependencies(String key) {
LinkedHashMap digest = new LinkedHashMap<>();
ArrayList artifacts = context.getAttribute(key, true, ArrayList.class);
if (artifacts == null) {
return Collections.emptyMap();
}
for (ArtifactFile artifact : artifacts) {
digest.put(artifact.file, artifact);
}
return digest;
}
/**
* @noreference this method is public for test purposes only
*/
public static void flush() {
CACHE.clear();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy