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

org.joda.beans.maven.AbstractJodaBeansMojo Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright 2013-present, Stephen Colebourne
 *
 *  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.joda.beans.maven;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.sonatype.plexus.build.incremental.BuildContext;

/**
 * Abstract Joda-Beans Mojo.
 */
public abstract class AbstractJodaBeansMojo extends AbstractMojo {

    /**
     * Key for clearing messages.
     */
    private static final String JODA_BEANS_MESSAGE_FILE = "joda-beans.message.file";
    /**
     * Key for clearing messages.
     */
    static final Pattern MESSAGE_PATTERN =
            Pattern.compile("Error in bean[:] (.*?)[,] Line[:] ([0-9]+)[,] Message[:] (.*)");

    @Parameter(alias = "skip", property = "joda.beans.skip", defaultValue = "false")
    private boolean skip;

    @Parameter(alias = "indent", property = "joda.beans.indent")
    private String indent;

    @Parameter(alias = "prefix", property = "joda.beans.prefix")
    private String prefix;

    @Parameter(alias = "eol", property = "joda.beans.eol")
    private String eol;

    @Parameter(alias = "config", property = "joda.beans.config")
    private String config;

    @Parameter(alias = "generatedAnnotation", property = "joda.beans.generatedAnnotation", defaultValue = "false")
    private boolean generatedAnnotation;

    @Parameter(alias = "verbose", property = "joda.beans.verbose")
    private Integer verbose;

    @Parameter(alias = "eclipse", property = "joda.beans.eclipse", defaultValue = "false")
    private boolean eclipse;

    @Parameter(alias = "sourceDir", property = "joda.beans.source.dir", defaultValue = "${project.build.sourceDirectory}", required = true)
    private String sourceDir;

    @Parameter(alias = "classesDir", property = "joda.beans.classes.dir", defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
    private String classesDir;

    @Parameter(alias = "testSourceDir", property = "joda.beans.test.source.dir", defaultValue = "${project.build.testSourceDirectory}", required = true, readonly = true)
    private String testSourceDir;

    @Parameter(alias = "testClassesDir", property = "joda.beans.test.classes.dir", defaultValue = "${project.build.testOutputDirectory}", required = true, readonly = true)
    private String testClassesDir;

    @Parameter(alias = "project", defaultValue = "${project}", required = true, readonly = true)
    private MavenProject project;

    @Component
    private BuildContext buildContext;

    //-----------------------------------------------------------------------
    /**
     * Gets the source directory.
     * 
     * @return the source directory, not null
     */
    protected String getSourceDir() {
        return (sourceDir == null ? "" : sourceDir.trim());
    }

    /**
     * Gets the classes directory.
     * 
     * @return the classes directory, not null
     */
    protected String getClassesDir() {
        return (classesDir == null ? "" : classesDir.trim());
    }

    /**
     * Gets the test source directory.
     * 
     * @return the test source directory, not null
     */
    protected String getTestSourceDir() {
        return (testSourceDir == null ? "" : testSourceDir.trim());
    }

    /**
     * Gets the test classes directory.
     * 
     * @return the test classes directory, not null
     */
    protected String getTestClassesDir() {
        return (testClassesDir == null ? "" : testClassesDir.trim());
    }

    //-----------------------------------------------------------------------
    /**
     * Executes the Joda-Beans generator.
     * @throws MojoExecutionException if an error occurs
     * @throws MojoFailureException if an error occurs
     */
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        long start = System.nanoTime();
        try {
            if (skip) {
                return;
            }
            if (getSourceDir().length() == 0) {
                throw new MojoExecutionException("Source directory must be specified");
            }
            ClassLoader classLoader = obtainClassLoader();
            Class toolClass = null;
            try {
                toolClass = classLoader.loadClass("org.joda.beans.gen.BeanCodeGen");
            } catch (Exception ex) {
                logInfo("Skipping as joda-beans is not in the project compile classpath");
                return;
            }
    
            List argsList = buildArgs();
            runTool(toolClass, argsList, buildContext);
        } finally {
            long end = System.nanoTime();
            logDebug("Took: " + ((end - start) / 1000000L) + "ms");
        }
    }

    /**
     * Builds the arguments to the tool.
     * 
     * @return the arguments, not null
     */
    protected List buildArgs() {
        List argsList = new ArrayList<>();
        argsList.add("-R");
        if (indent != null) {
            argsList.add("-indent=" + indent);
        }
        if (prefix != null) {
            argsList.add("-prefix=" + prefix);
        }
        if (eol != null) {
            argsList.add("-eol=" + eol);
        }
        if (config != null) {
            argsList.add("-config=" + config);
        }
        if (generatedAnnotation) {
            argsList.add("-generated");
        }
        if (verbose != null) {
            argsList.add("-verbose=" + verbose);
        }
        return argsList;
    }

    // runs the tool
    abstract void runTool(Class toolClass, List argsList, BuildContext buildContext) throws MojoExecutionException, MojoFailureException;

    // remove any error markers leftover from the last run
    void cleanupLastRun() {
        File errorFile = (File) buildContext.getValue(JODA_BEANS_MESSAGE_FILE);
        if (errorFile != null) {
            buildContext.removeMessages(errorFile);
        }
    }

    // actually run the tool using the specified args
    List runToolHandleChanges(Class toolClass, List argsList, File baseDir, File classesDir)
            throws MojoExecutionException, MojoFailureException {
        try {
            String baseStr = baseDir.getCanonicalPath();
            List changedFiles = invoke(toolClass, argsList);
            Set filesToRefresh = new LinkedHashSet<>();
            // mark each file as being in need of a refresh
            if (changedFiles.size() > 0) {
                if (changedFiles.get(0) == null) {
                    filesToRefresh.add(baseDir.toString());
                } else {
                    for (File file : changedFiles) {
                        filesToRefresh.add(file.toString());
                        // when running in Eclipse (determined by the eclipse flag) apply a hack
                        // the hack deleted the class file associated with the java file
                        // this triggers Eclipse to recompile the edited source file
                        // this provides an Eclipse plugin for Joda-Beans just via m2e mechanisms
                        if (eclipse) {
                            String fileStr = file.getCanonicalPath();
                            if (fileStr.length() > baseStr.length() && fileStr.startsWith(baseStr)) {
                                String relative = fileStr.substring(baseStr.length());
                                if (relative.startsWith("/") || relative.startsWith("\\")) {
                                    relative = relative.substring(1);
                                }
                                relative = relative.replace(".java", ".class");
                                File classFile = new File(classesDir, relative);
                                if (classFile.delete()) {
                                    logDebug("Deleted: " + classFile);
                                } else {
                                    logDebug("Failed to delete: " + classFile);
                                }
                                filesToRefresh.add(classFile.toString());
                            }
                        }
                    }
                }
            }
            // refresh once all other changes made
            for (String fileStr : filesToRefresh) {
                logDebug("Refreshed: " + fileStr);
                buildContext.refresh(new File(fileStr));
            }
            return changedFiles;
        } catch (IOException ex) {
            throw new MojoExecutionException("IO problem: " + ex.toString(), ex);
        } catch (MojoFailureException ex) {
            if (eclipse && buildContext.getValue(JODA_BEANS_MESSAGE_FILE) != null) {
                return Collections.emptyList();  // avoid showing error in Eclipse pom that is reported in a file
            } else {
                throw ex;
            }
        }
    }

    // invokes the generator by reflection
    private List invoke(Class toolClass, List argsList) throws MojoExecutionException, MojoFailureException {
        long start = System.nanoTime();
        try {
            Method createFromArgsMethod = findCreateFromArgsMethod(toolClass);
            Method processMethod = findProcessMethod(toolClass);
            Object beanCodeGen = createBuilder(argsList, createFromArgsMethod);
            if (processMethod.getReturnType() == Integer.TYPE) {
                int count = invokeBuilderCountChanges(processMethod, beanCodeGen);
                return Collections.nCopies(count, null);
            } else {
                return invokeBuilderListChanges(processMethod, beanCodeGen);
            }
        } finally {
            long end = System.nanoTime();
            logDebug("Invoke: " + ((end - start) / 1000000L) + "ms");
        }
    }

    // finds the method to call by reflection
    private Method findCreateFromArgsMethod(Class toolClass) throws MojoExecutionException {
        Method createFromArgsMethod = null;
        try {
            createFromArgsMethod = toolClass.getMethod("createFromArgs", String[].class);
        } catch (Exception ex) {
            throw new MojoExecutionException("Unable to find method BeanCodeGen.createFromArgs()");
        }
        return createFromArgsMethod;
    }

    // finds the method to call by reflection
    private Method findProcessMethod(Class toolClass) throws MojoExecutionException {
        Method processMethod = null;
        try {
            processMethod = toolClass.getMethod("processFiles");
            logDebug("Using Joda-Beans v1.5 or later - processFiles()");
        } catch (Exception ex) {
            try {
                processMethod = toolClass.getMethod("process");
                logDebug("Using Joda-Beans v1.4 or earlier - process()");
            } catch (Exception ex2) {
                throw new MojoExecutionException("Unable to find method BeanCodeGen.processFiles() or BeanCodeGen.process()");
            }
        }
        return processMethod;
    }

    // creates the generator by reflection
    private Object createBuilder(List argsList, Method createFromArgsMethod) throws MojoExecutionException, MojoFailureException {
        String[] args = argsList.toArray(new String[argsList.size()]);
        try {
            return createFromArgsMethod.invoke(null, new Object[] { args });
        } catch (IllegalArgumentException ex) {
            throw new MojoExecutionException("Error invoking BeanCodeGen.createFromArgs()");
        } catch (IllegalAccessException ex) {
            throw new MojoExecutionException("Error invoking BeanCodeGen.createFromArgs()");
        } catch (InvocationTargetException ex) {
            throw new MojoFailureException("Invalid Joda-Beans Mojo configuration: " + ex.getCause().getMessage(), ex.getCause());
        }
    }

    // invokes the builder
    private int invokeBuilderCountChanges(Method processMethod, Object beanCodeGen) throws MojoExecutionException, MojoFailureException {
        try {
            return (Integer) processMethod.invoke(beanCodeGen);
        } catch (IllegalArgumentException ex) {
            throw new MojoExecutionException("Error invoking BeanCodeGen.process()");
        } catch (IllegalAccessException ex) {
            throw new MojoExecutionException("Error invoking BeanCodeGen.process()");
        } catch (InvocationTargetException ex) {
            throw handleFailure(ex);
        }
    }

    // invokes the builder
    @SuppressWarnings("unchecked")
    private List invokeBuilderListChanges(Method processMethod, Object beanCodeGen) throws MojoExecutionException, MojoFailureException {
        try {
            return (List) processMethod.invoke(beanCodeGen);
        } catch (IllegalArgumentException ex) {
            throw new MojoExecutionException("Error invoking BeanCodeGen.process()");
        } catch (IllegalAccessException ex) {
            throw new MojoExecutionException("Error invoking BeanCodeGen.process()");
        } catch (InvocationTargetException ex) {
            throw handleFailure(ex);
        }
    }

    // handles failure in reflection
    private MojoFailureException handleFailure(InvocationTargetException ex) throws MojoFailureException {
        String msg = ex.getCause().getMessage();
        File file = new File(getSourceDir());
        int line = 1;
        try {
            if (msg.startsWith("Error in bean: ")) {
                Matcher matcher = MESSAGE_PATTERN.matcher(msg);
                if (matcher.matches()) {
                    // Joda-Beans v1.5 messages
                    file = new File(matcher.group(1));
                    line = Integer.parseInt(matcher.group(2));
                    msg = matcher.group(3);
                } else {
                    // Joda-Beans v1.4 messages
                    File sourceFile = new File(msg.substring("Error in bean: ".length()));
                    if (sourceFile.exists()) {
                        file = sourceFile;
                        if (ex.getCause().getCause() != null) {
                            msg = ex.getCause().getCause().getMessage();
                            if (ex.getCause().getCause().getCause() != null) {
                                msg += ": " + ex.getCause().getCause().getCause().getMessage();
                            }
                        }
                    }
                }
            } else if (ex.getCause().getCause() != null) {
                msg += ": " + ex.getCause().getCause().getMessage();
                if (ex.getCause().getCause().getCause() != null) {
                    msg += ": " + ex.getCause().getCause().getCause().getMessage();
                }
            }
        } catch (Exception unexpected) {
            // ignore and use standard messages
        }
        buildContext.setValue(JODA_BEANS_MESSAGE_FILE, file);
        buildContext.addMessage(
                        file.getAbsoluteFile(), line + 1, 1,
                        msg, BuildContext.SEVERITY_ERROR, ex.getCause());
        return new MojoFailureException("Error while running Joda-Beans tool: " + msg, ex.getCause());
    }

    // obtains the classloader from a set of file paths
    ClassLoader obtainClassLoader() throws MojoExecutionException {
        logDebug("Finding joda-beans in classpath");
        List compileClasspath = obtainClasspath();
        Set classpathUrlSet = new HashSet<>();
        for (String classpathEntry : compileClasspath) {
            File f = new File(classpathEntry);
            if (f.exists() && f.getPath().contains("joda")) {
                try {
                    logDebug("Found classpath: " + f);
                    classpathUrlSet.add(f.toURI().toURL());
                } catch (MalformedURLException ex) {
                    throw new RuntimeException("Error interpreting classpath entry as URL: " + classpathEntry, ex);
                }
            }
        }
        URL[] classpathUrls = classpathUrlSet.toArray(new URL[classpathUrlSet.size()]);
        return new URLClassLoader(classpathUrls, AbstractJodaBeansMojo.class.getClassLoader());
    }

    // obtains the resolved classpath of dependencies
    private List obtainClasspath() throws MojoExecutionException {
        try {
            return project.getCompileClasspathElements();
        } catch (DependencyResolutionRequiredException ex) {
            throw new MojoExecutionException("Error obtaining dependencies", ex);
        }
    }

    // log to info
    void logInfo(String msg) throws MojoExecutionException {
        getLog().info(msg);
        localLog(msg);
    }

    // log to debug
    void logDebug(String msg) throws MojoExecutionException {
        getLog().debug(msg);
        localLog(msg);
    }

    // used for debugging plugin within M2E
    private void localLog(String msg) throws MojoExecutionException {
//        Path path = Paths.get("/dev/a.txt");
//        try {
//            BufferedWriter out = Files.newBufferedWriter(
//                    path, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
//            Date now = new Date();
//            out.write(now + " " + now.getTime() % 1000 + " ");
//            out.write(msg);
//            out.newLine();
//            out.flush();
//            out.close();
//        } catch (IOException ex) {
//            throw new MojoExecutionException("Died" + ex.toString(), ex);
//        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy