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

org.jerkar.tool.JkBuild Maven / Gradle / Ivy

There is a newer version: 0.7.0.RELEASE
Show newest version
package org.jerkar.tool;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.jerkar.api.depmanagement.JkComputedDependency;
import org.jerkar.api.depmanagement.JkDependencyResolver;
import org.jerkar.api.file.JkFileTree;
import org.jerkar.api.file.JkPath;
import org.jerkar.api.system.JkLog;
import org.jerkar.api.utils.JkUtilsFile;
import org.jerkar.api.utils.JkUtilsIterable;
import org.jerkar.api.utils.JkUtilsObject;
import org.jerkar.api.utils.JkUtilsReflect;
import org.jerkar.api.utils.JkUtilsTime;

/**
 * Base class defining commons tasks and utilities necessary for building any
 * kind of project, regardless involved technologies.
 *
 * @author Jerome Angibaud
 */
public class JkBuild {

    private static final ThreadLocal> SUB_PROJECT_CONTEXT = new ThreadLocal>();

    private static final ThreadLocal BASE_DIR_CONTEXT = new ThreadLocal();

    static void baseDirContext(File baseDir) {
        BASE_DIR_CONTEXT.set(baseDir);
    }

    private final File baseDir;

    private final Date buildTime = JkUtilsTime.now();

    /** attached plugin instances to this build */
    protected final JkBuildPlugins plugins = new JkBuildPlugins(this);

    private JkDependencyResolver buildDefDependencyResolver;

    private final JkSlaveBuilds annotatedJkProjectSlaves;

    /**
     * Constructs a {@link JkBuild}
     */
    protected JkBuild() {
        final File baseDirContext = BASE_DIR_CONTEXT.get();
        this.baseDir = JkUtilsObject.firstNonNull(baseDirContext, JkUtilsFile.workingDir());
        final List subBuilds = populateJkProjectAnnotatedFields();
        this.annotatedJkProjectSlaves = JkSlaveBuilds.of(this.baseDir().root(), subBuilds);
    }

    /**
     * This method is invoked right after the option values has been injected to instance fields
     * of this object.
     */
    public void init() {
        // Do nothing by default
    }

    void setBuildDefDependencyResolver(JkDependencyResolver scriptDependencyResolver) {
        this.buildDefDependencyResolver = scriptDependencyResolver;
    }

    /**
     * Returns the dependency resolver used to compile/run scripts of this
     * project.
     */
    public JkDependencyResolver buildDefDependencyResolver() {
        return this.buildDefDependencyResolver;
    }

    /**
     * Returns the classes accepted as template for plugins. If you override it,
     * do not forget to add the ones from the super class.
     */
    protected List> pluginTemplateClasses() {
        return Collections.emptyList();
    }

    /**
     * Set the plugins to activate for this build. This method should be invoked
     * after the {@link #setBaseDir(File)} method, so plugins can be configured
     * using the proper base dir.
     */
    protected void setPlugins(Iterable plugins) {
        // Do nothing as no plugin extension as been defined at this level.
    }

    /**
     * Returns the time-stamp this build has been initiated. Default is the time
     * stamp (formatted as 'yyyyMMdd-HHmmss') this build has been instantiated.
     */
    public String buildTimestamp() {
        return JkUtilsTime.iso(buildTime);
    }

    /**
     * Returns the time the build was started.
     */
    protected Date buildTime() {
        return (Date) buildTime.clone();
    }

    /**
     * Returns the specified relative path to this project as a {@link JkPath} instance.
     */
    protected final JkPath toPath(String pathAsString) {
        if (pathAsString == null) {
            return JkPath.of();
        }
        return JkPath.of(baseDir().root(), pathAsString);
    }

    /**
     * Returns the base directory for this project. All file/directory path are
     * resolved from this directory.
     */
    public final JkFileTree baseDir() {
        return JkFileTree.of(baseDir);
    }


    /**
     * Invokes the specified method in this build.
     */
    private final void invoke(String methodName, File fromDir) {
        final Method method;
        try {
            method = this.getClass().getMethod(methodName);
        } catch (final NoSuchMethodException e) {
            JkLog.warn("No zero-arg method '" + methodName + "' found in class '" + this.getClass()
                    + "'. Skip.");
            JkLog.warnStream().flush();
            return;
        }
        final String context;
        if (fromDir != null) {
            final String path = JkUtilsFile.getRelativePath(fromDir, this.baseDir).replace(
                    File.separator, "/");
            context = " from project " + path + ", class " + this.getClass().getName();
        } else {
            context = "";
        }
        JkLog.infoUnderlined("Method : " + methodName + context);
        final long time = System.nanoTime();
        try {
            JkUtilsReflect.invoke(this, method);
            JkLog.info("Method " + methodName + " success in "
                    + JkUtilsTime.durationInSeconds(time) + " seconds.");
        } catch (final RuntimeException e) {
            JkLog.info("Method " + methodName + " failed in " + JkUtilsTime.durationInSeconds(time)
                    + " seconds.");
            throw e;
        }
    }

    /**
     * Executes the specified methods given the fromDir as working directory.
     */
    public void execute(Iterable methods, File fromDir) {
        for (final JkModelMethod method : methods) {
            this.invoke(method, fromDir);
        }
    }

    /**
     * Returns a file located at the specified path relative to the base
     * directory.
     */
    public final File file(String relativePath) {
        if (relativePath.isEmpty()) {
            return baseDir().root();
        }
        return baseDir().file(relativePath);
    }

    /**
     * The output directory where all the final and intermediate artifacts are
     * generated.
     */
    public JkFileTree ouputDir() {
        return baseDir().from(JkConstants.BUILD_OUTPUT_PATH).createIfNotExist();
    }

    /**
     * Returns the file located at the specified path relative to the output
     * directory.
     */
    public File ouputDir(String relativePath) {
        return ouputDir().file(relativePath);
    }

    // ------------ Jerkar methods ------------



    /**
     * Creates the project structure (mainly project folder layout, build class code and IDE metadata) at the root
     * of the current project.
     */
    @JkDoc("Create the project structure")
    public final void scaffold() {
        scaffolder().run();
        JkBuildPlugin.applyScaffold(this.plugins.getActives());
    }

    /**
     * Returns the scaffolder object in charge of doing the scaffolding for this build.
     * Override this method if you write a template class that need to do custom action for scaffolding.
     */
    protected JkScaffolder scaffolder() {
        return new JkScaffolder(this);
    }

    /** Clean the output directory. */
    @JkDoc("Clean the output directory.")
    public void clean() {
        JkLog.start("Cleaning output directory " + ouputDir().root().getPath());
        ouputDir().exclude(JkConstants.BUILD_DEF_BIN_DIR_NAME + "/**").deleteAll();
        JkLog.done();
    }

    /** Conventional method standing for the default operations to perform. */
    @JkDoc("Conventional method standing for the default operations to perform.")
    public void doDefault() {
        clean();
    }

    /** Run checks to verify the package is valid and meets quality criteria. */
    @JkDoc("Run checks to verify the package is valid and meets quality criteria.")
    public void verify() {
        JkBuildPlugin.applyVerify(this.plugins.getActives());
    }

    /** Display all available methods defined in this build. */
    @JkDoc("Display all available methods defined in this build.")
    public void help() {
        HelpDisplayer.help(this);
    }

    /** Display details on all available plugins. */
    @JkDoc("Display details on all available plugins.")
    public void helpPlugins() {
        HelpDisplayer.helpPlugins();
    }

    /**
     * Invokes the specified method in this build but from the w
     *
     * @param jkModelMethod
     * @param from
     */
    private void invoke(JkModelMethod jkModelMethod, File fromDir) {
        if (jkModelMethod.isMethodPlugin()) {
            this.plugins.invoke(jkModelMethod.pluginClass(), jkModelMethod.name());
        } else {
            this.invoke(jkModelMethod.name(), fromDir);
        }
    }

    /**
     * Returns plugins attached to this build and extending the specified class.
     */
    public  T pluginOf(Class pluginClass) {
        return this.plugins.findInstanceOf(pluginClass);
    }

    @Override
    public String toString() {
        return this.baseDir.getPath();
    }

    /**
     * Returns a {@link JkComputedDependency} on this project and specified
     * files. The 'doDefault' method will be invoked to compute the dependee
     * files.
     */
    public JkComputedDependency asDependency(Iterable files) {
        return BuildDependency.of(this, JkUtilsIterable.setOf(files));
    }

    /**
     * Returns a {@link JkComputedDependency} on this project and specified
     * files. The 'doDefault' method will be invoked to compute the dependee
     * files.
     */
    public JkComputedDependency asDependency(File... files) {
        return BuildDependency.of(this, files);
    }

    /**
     * Returns a {@link JkComputedDependency} on this project and specified
     * files and methods to execute.
     */
    public JkComputedDependency asDependency(String methods, File... files) {
        return BuildDependency.of(this, methods, files);
    }

    /**
     * Returns slave builds (potentially on other projects).
     */
    public final JkSlaveBuilds slaves() {
        return this.annotatedJkProjectSlaves;
    }

    /**
     * Returns the slave project declared with annotation JkProject in this build.
     * @return
     */
    protected final JkSlaveBuilds annotatedJkProjectSlaves() {
        return this.annotatedJkProjectSlaves;
    }

    @SuppressWarnings("unchecked")
    private List populateJkProjectAnnotatedFields() {
        final List result = new LinkedList();
        final List fields = JkUtilsReflect.getAllDeclaredField(this.getClass(),
                JkProject.class);
        for (final Field field : fields) {
            final JkProject jkProject = field.getAnnotation(JkProject.class);
            final JkBuild subBuild = relativeProject(this,
                    (Class) field.getType(), jkProject.value());
            JkUtilsReflect.setFieldValue(this, field, subBuild);
            result.add(subBuild);
        }
        return result;
    }

    /**
     * Returns the build of the specified slave project. Slave projects are expressed with relative path to this project.
     */
    public JkBuild relativeProject(String relativePath) {
        return relativeProject(this, null, relativePath);
    }

    private static final JkBuild relativeProject(JkBuild mainBuild, Class clazz,
            String relativePath) {
        final JkBuild build = mainBuild.relativeProjectBuild(clazz, relativePath);
        return build;
    }

    /**
     * Creates an instance of JkBuild for the given project and
     * build class. The instance field annotated with JkOption are
     * populated as usual.
     */
    @SuppressWarnings("unchecked")
    private final  T relativeProjectBuild(Class clazz, String relativePath) {
        final File projectDir = this.file(relativePath);
        final SubProjectRef projectRef = new SubProjectRef(projectDir, clazz);
        Map map = SUB_PROJECT_CONTEXT.get();
        if (map == null) {
            map = new HashMap();
            SUB_PROJECT_CONTEXT.set(map);
        }
        final T cachedResult = (T) SUB_PROJECT_CONTEXT.get().get(projectRef);
        if (cachedResult != null) {
            return cachedResult;
        }
        final Project project = new Project(projectDir);
        final T result = project.getBuild(clazz);
        JkOptions.populateFields(result);
        SUB_PROJECT_CONTEXT.get().put(projectRef, result);
        return result;
    }

    private static class SubProjectRef {

        final String canonicalFileName;

        final Class clazz;

        SubProjectRef(File projectDir, Class clazz) {
            super();
            this.canonicalFileName = JkUtilsFile.canonicalPath(projectDir);
            this.clazz = clazz;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result
                    + ((canonicalFileName == null) ? 0 : canonicalFileName.hashCode());
            result = prime * result + ((clazz == null) ? 0 : clazz.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final SubProjectRef other = (SubProjectRef) obj;
            if (canonicalFileName == null) {
                if (other.canonicalFileName != null) {
                    return false;
                }
            } else if (!canonicalFileName.equals(other.canonicalFileName)) {
                return false;
            }
            if (clazz == null) {
                if (other.clazz != null) {
                    return false;
                }
            } else if (!clazz.equals(other.clazz)) {
                return false;
            }
            return true;
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy