All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kantega.reststop.development.DevelopmentClassloader Maven / Gradle / Ivy

/*
 * Copyright 2018 Kantega AS
 *
 * Licensed 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.
 */

package org.kantega.reststop.development;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.kantega.reststop.classloaderutils.Artifact;
import org.kantega.reststop.classloaderutils.PluginClassLoader;
import org.kantega.reststop.classloaderutils.PluginInfo;

import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Predicate;

import static java.util.Arrays.asList;

/**
 *
 */
public class DevelopmentClassloader extends PluginClassLoader{
    private final long created;
    private final File basedir;
    private final File jarFile;
    private final List compileClasspath;
    private final List runtimeClasspath;
    private final int version;
    private final List testClasspath;

    public final static JavaCompiler compiler;

    static {
        compiler = ToolProvider.getSystemJavaCompiler();

    }

    private long lastTestCompile;
    private volatile boolean testsFailed = false;
    private final List> loadedClasses = new CopyOnWriteArrayList<>();
    private Set usedUrls = new CopyOnWriteArraySet<>();
    private volatile boolean failed;

    public DevelopmentClassloader(PluginInfo info, File baseDir, File jarFile, List compileClasspath, List runtimeClasspath, List testClasspath, ClassLoader parent, int version) {
        super(info, new URL[0], parent);
        this.basedir = baseDir;
        this.jarFile = jarFile;
        this.compileClasspath = compileClasspath;
        this.runtimeClasspath = runtimeClasspath;
        this.version = version;
        this.testClasspath = new ArrayList<>(testClasspath);
        try {
            if(baseDir != null && baseDir.exists()) {
                this.testClasspath.add(new File(baseDir, "target/classes"));

                addURL(new File(baseDir, "target/classes").toURI().toURL());
            } else if(jarFile != null && jarFile.exists()) {
                addURL(jarFile.toURI().toURL());
                this.testClasspath.add(jarFile);
            }
            for (File file : runtimeClasspath) {
                addURL(file.toURI().toURL());
            }
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }

        this.created = System.currentTimeMillis();
        this.lastTestCompile = this.created;

    }

    public DevelopmentClassloader(DevelopmentClassloader other, ClassLoader parentClassLoader) {
        this(other.getPluginInfo(), other.getBasedir(), other.jarFile, other.compileClasspath, other.runtimeClasspath, other.testClasspath, parentClassLoader, other.version +1);
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        Class clazz = super.findClass(name);
        if(clazz.getClassLoader() == this) {
            this.loadedClasses.add(clazz);
            ProtectionDomain protectionDomain = clazz.getProtectionDomain();
            if(protectionDomain != null) {
                CodeSource codeSource = protectionDomain.getCodeSource();
                if(codeSource != null) {
                    URL location = codeSource.getLocation();
                    if(location != null) {
                        this.usedUrls.add(location.toString());
                    }
                }
            }

        }
        return clazz;
    }

    public List> getLoadedClasses() {
        return loadedClasses;
    }

    public Set getUsedUrls() {
        return usedUrls;
    }

    public List getUnusedArtifacts() {

        List artifacts = new ArrayList<>();

        for (Artifact artifact : artifacts) {
            if(isUnused(artifact)) {
                artifacts.add(artifact);
            }
        }
        return artifacts;
    }

    public boolean isUnused(Artifact artifact) {
        try {
            return !usedUrls.contains(artifact.getFile().toURI().toURL().toString());
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public URL getResource(String name) {
        if(basedir != null) {
            File resources = new File(basedir, "src/main/resources");
            File resource = new File(resources, name);
            if (resource.exists()) {
                try {
                    return resource.toURI().toURL();
                } catch (MalformedURLException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return super.getResource(name);
    }

    public boolean isStaleSources() {

        if(jarFile.lastModified() > created) {
            return true;
        }
        for (File file : runtimeClasspath) {
            if(file.lastModified() > created) {
                return true;
            }
        }
        if(basedir == null) {
            return false;
        }

        File sourceDir = new File(basedir, "src/main/java");
        if(!sourceDir.exists()) {
            return false;
        }
        File target = new File(basedir, "target/classes");

        if(!target.exists()) {
            return false;
        }
        long newestSource = newest(sourceDir, p -> p.getFileName().toString().endsWith(".java"));
        long newestClass = newest(target, p -> p.getFileName().toString().endsWith(".class"));
        return newestSource > newestClass || newestClass > created;
    }

    public boolean isStaleTests() {
        File sources = new File(basedir, "src/test/java");
        return sources.exists() && newest(sources, p -> p.getFileName().toString().endsWith(".java")) > lastTestCompile;
    }

    private long newest(File directory, Predicate filter) {
        if(! directory.exists()) {
            return 0;
        }
        NewestFileVisitor visitor = new NewestFileVisitor(filter);
        try {
            Files.walkFileTree(directory.toPath(), visitor);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return visitor.getNewest();
    }

    public List getTestClasses() {
        File testSources = new File(basedir, "src/test/java");
        if(!testSources.exists()) {
            return Collections.emptyList();
        }

        List urls = new ArrayList<>();


        try {
            for (File file : testClasspath) {

                urls.add(file.toURI().toURL());

            }
            urls.add(new File(basedir, "target/test-classes").toURI().toURL());
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }

        URLClassLoader loader = new URLClassLoader(urls.toArray(new URL[urls.size()]), getClass().getClassLoader());

        final List classNames = new ArrayList<>();
        try {
            final Path root = testSources.toPath();
            Files.walkFileTree(root, new SimpleFileVisitor() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    String name = file.toFile().getName();
                    if(name.endsWith("Test.java") || name.endsWith("IT.java")) {
                        String sourceFile = root.relativize(file).toString();
                        classNames.add(sourceFile.replace(File.separatorChar, '.').substring(0, sourceFile.length()-".java".length()));
                    }
                   return  FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        List classes = new ArrayList<>();
        for (String className : classNames) {
            try {
                Class clazz = loader.loadClass(className);
                if(isJunit4Class(clazz)) {
                    classes.add(clazz);
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        return classes;
    }

    private boolean isJunit4Class(Class clazz) {
        if(clazz.getAnnotation(RunWith.class) != null) {
            return true;
        }
        for (Method method : clazz.getMethods()) {
            if(method.getAnnotation(Test.class) != null) {
                return true;
            }
        }
        return false;
    }

    public File getBasedir() {
        return basedir;
    }

    public void testsFailed() {
        testsFailed = true;
    }

    public void testsPassed() {
        testsFailed = false;
    }

    public boolean hasFailingTests() {
        return testsFailed;
    }

    public void setFailed(boolean failed) {
        this.failed = failed;
    }

    public boolean isFailed() {
        return failed;
    }

    private class NewestFileVisitor extends SimpleFileVisitor {

        private final Predicate matcher;
        private long newest;

        public NewestFileVisitor(Predicate matcher) {
            this.matcher = matcher;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            if(matcher.test(file)) {
                newest = Math.max(file.toFile().lastModified(), newest);
            }
            return FileVisitResult.CONTINUE;
        }

        private long getNewest() {
            return newest;
        }
    }

    public void compileSources(StandardJavaFileManager standardFileManager) {

        if(basedir == null) {
            return;
        }
        File sourceDirectory = new File(basedir, "src/main/java");
        File outputDirectory = new File(basedir, "target/classes");
        List classpath = compileClasspath;


        compileJava(sourceDirectory, outputDirectory, classpath, standardFileManager);

    }

    public void compileJavaTests(StandardJavaFileManager standardFileManager) {
        if(basedir == null) {
            return;
        }
        File sourceDirectory = new File(basedir, "src/test/java");
        if(sourceDirectory.exists()) {
            File outputDirectory = new File(basedir, "target/test-classes");
            List classpath = testClasspath;

            compileJava(sourceDirectory, outputDirectory, classpath, standardFileManager);

            lastTestCompile = System.currentTimeMillis();
        }
    }


    private void compileJava(File sourceDirectory, File outputDirectory, List classpath, StandardJavaFileManager fileManager) {
        List sourceFiles = getCompilationUnits(sourceDirectory, newest(outputDirectory, p -> p.getFileName().toString().endsWith(".class")));

        if (!sourceFiles.isEmpty()) {

            DiagnosticCollector diagnostics = new DiagnosticCollector<>();


            String cp = toPath(classpath) + File.pathSeparator + outputDirectory.getAbsolutePath();

            outputDirectory.mkdirs();

            List options = new ArrayList<>(asList("-g", "-classpath", cp, "-d", outputDirectory.getAbsolutePath()));

            if(!"1.8".equals(System.getProperty("java.specification.version"))) {
                options.add("--add-modules=java.xml.ws");
            }
            JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, fileManager.getJavaFileObjectsFromFiles(sourceFiles));


            boolean success = task.call();
            if (!success) {
                throw new JavaCompilationException(diagnostics.getDiagnostics());
            }
        }
    }

    private String toPath(List compileClasspath) {
        StringBuilder sb = new StringBuilder();
        for (File file : compileClasspath) {
            if(sb.length() != 0) {
                sb.append(File.pathSeparator);
            }
            sb.append(file.getAbsolutePath());
        }
        return sb.toString();
    }

    public void copySourceResorces() {
        final Path fromDirectory = new File(basedir, "src/main/resources/").toPath();
        final Path toDirectory = new File(basedir, "target/classes").toPath();

        copyResources(fromDirectory, toDirectory);
    }

    public void copyTestResources() {
        final Path fromDirectory = new File(basedir, "src/test/resources/").toPath();
        final Path toDirectory = new File(basedir, "target/test-classes").toPath();

        copyResources(fromDirectory, toDirectory);
    }

    private void copyResources(final Path fromDirectory, final Path toDirectory) {
        if(! Files.exists(fromDirectory)) {
            return;
        }
        try {
            Files.walkFileTree(fromDirectory, new SimpleFileVisitor() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    Path target = toDirectory.resolve(fromDirectory.relativize(file));
                    target.toFile().getParentFile().mkdirs();
                    Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING);
                    return FileVisitResult.CONTINUE;
                }
            });

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List getCompilationUnits(File sourceDirectory, final long newestClass) {

        final List compilationUnits = new ArrayList<>();

        try {
            Files.walkFileTree(sourceDirectory.toPath(), new SimpleFileVisitor() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    if (file.toFile().getName().endsWith(".java") && file.toFile().lastModified() > newestClass) {
                        compilationUnits.add(file.toFile());
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
            return compilationUnits;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public int getVersion() {
        return version;
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + " for " + getPluginInfo().getPluginId();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy