io.takari.maven.plugins.compile.ProjectClasspathDigester.java Source code

Java tutorial

Introduction

Here is the source code for io.takari.maven.plugins.compile.ProjectClasspathDigester.java

Source

/**
 * Copyright (c) 2014 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
 * http://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 java.util.concurrent.TimeUnit;

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;

import com.google.common.base.Stopwatch;

@Named
@MojoExecutionScoped
public class ProjectClasspathDigester {
    private static final String ATTR_CLASSPATH_DIGEST = "compile.classpath.digest";
    private static final String ATTR_PROCESSORPATH_DIGEST = "compile.processorpath.digest";

    private final Logger log = LoggerFactory.getLogger(getClass());

    private static final Map<File, ArtifactFile> CACHE = new ConcurrentHashMap<File, ArtifactFile>();

    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<File> dependencies) throws IOException {
        return digest(ATTR_CLASSPATH_DIGEST, dependencies);
    }

    public boolean digestProcessorpath(List<File> dependencies) throws IOException {
        return digest(ATTR_PROCESSORPATH_DIGEST, dependencies);
    }

    private boolean digest(String key, List<File> dependencies) {
        Stopwatch stopwatch = Stopwatch.createStarted();

        Map<File, ArtifactFile> previousArtifacts = getPreviousDependencies(key);
        LinkedHashMap<File, ArtifactFile> 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.size(),
                stopwatch.elapsed(TimeUnit.MILLISECONDS));

        return changed;
    }

    private File normalize(File file) {
        try {
            return file.getCanonicalFile();
        } catch (IOException e) {
            return file.getAbsoluteFile();
        }
    }

    private boolean equals(Collection<ArtifactFile> a, Collection<ArtifactFile> b) {
        if (a.size() != b.size()) {
            return false;
        }
        Iterator<ArtifactFile> ia = a.iterator();
        Iterator<ArtifactFile> 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<File, ArtifactFile> getPreviousDependencies(String key) {
        LinkedHashMap<File, ArtifactFile> digest = new LinkedHashMap<>();
        ArrayList<ArtifactFile> 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();
    }
}