org.joda.beans.maven.AbstractJodaBeansMojo Maven / Gradle / Ivy
/*
* Copyright 2013 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.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.project.MavenProject;
import org.sonatype.plexus.build.incremental.BuildContext;
/**
* Abstract Joda-Beans Mojo.
*/
public 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[:] (.*)");
/**
* Skips the mojo.
* @parameter alias="skip" property="joda.beans.skip"
*/
private boolean _skip;
/**
* @parameter default-value="${project.build.sourceDirectory}" property="joda.beans.source.dir"
* @required
* @readonly
*/
private String sourceDir;
/**
* @parameter default-value="${project.build.outputDirectory}" property="joda.beans.classes.dir"
* @required
* @readonly
*/
private String classesDir;
/**
* @parameter default-value="${project.build.testSourceDirectory}" property="joda.beans.test.source.dir"
* @required
* @readonly
*/
private String testSourceDir;
/**
* @parameter default-value="${project.build.testOutputDirectory}" property="joda.beans.test.classes.dir"
* @required
* @readonly
*/
private String testClassesDir;
/**
* @parameter alias="indent" property="joda.beans.indent"
*/
private String indent;
/**
* @parameter alias="prefix" property="joda.beans.prefix"
*/
private String prefix;
/**
* @parameter alias="verbose" property="joda.beans.verbose"
*/
private Integer verbose;
/**
* @parameter alias="eclipse" property="joda.beans.eclipse"
*/
private boolean eclipse;
/**
* The Maven project.
* @parameter default-value="${project}"
* @required
* @readonly
*/
private MavenProject _project;
/**
* Better support for IDEs.
* @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.
*/
public void execute() throws MojoExecutionException, MojoFailureException {
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) {
getLog().info("Skipping as joda-beans is not in the project compile classpath");
return;
}
List argsList = buildArgs();
runTool(toolClass, argsList);
}
/**
* 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 (verbose != null) {
argsList.add("-verbose=" + verbose);
}
return argsList;
}
/**
* Runs the tool.
*
* @param toolClass the tool class, not null
* @param argsList the argument flags, not null
* @return the number of changes
* @throws MojoExecutionException if an error occurs
* @throws MojoFailureException if a failure occurs
*/
protected int runTool(Class> toolClass, List argsList) throws MojoExecutionException, MojoFailureException {
// cleanup from last run
File errorFile = (File) buildContext.getValue(JODA_BEANS_MESSAGE_FILE);
if (errorFile != null) {
buildContext.removeMessages(errorFile);
}
// invoke main source
argsList.add(getSourceDir());
int changedFileCount = runToolHandleChanges(toolClass, argsList, new File(getSourceDir()), new File(getClassesDir()));
// optionally invoke test source
if (getTestSourceDir().length() > 0) {
argsList.set(argsList.size() - 1, getTestSourceDir());
changedFileCount += runToolHandleChanges(toolClass, argsList, new File(getTestSourceDir()), new File(getTestClassesDir()));
}
return changedFileCount;
}
private int runToolHandleChanges(Class> toolClass, List argsList, File baseDir, File classesDir)
throws MojoExecutionException, MojoFailureException {
try {
String baseStr = baseDir.getCanonicalPath();
List changedFiles = invoke(toolClass, argsList);
// mark each file as being in need of a refresh
if (changedFiles.size() > 0) {
if (changedFiles.get(0) == null) {
buildContext.refresh(baseDir);
} else {
for (File file : changedFiles) {
getLog().debug("Refreshed: " + file);
buildContext.refresh(file);
// 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()) {
getLog().debug("Deleted: " + classFile);
} else {
getLog().debug("Failed to delete: " + classFile);
}
}
}
}
}
}
return changedFiles.size();
} catch (IOException ex) {
throw new MojoExecutionException("IO problem", ex);
} catch (MojoFailureException ex) {
if (eclipse && buildContext.getValue(JODA_BEANS_MESSAGE_FILE) != null) {
return 0; // avoid showing error in Eclipse pom that is reported in a file
} else {
throw ex;
}
}
}
private List invoke(Class> toolClass, List argsList) throws MojoExecutionException, MojoFailureException {
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);
}
}
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;
}
private Method findProcessMethod(Class> toolClass) throws MojoExecutionException {
Method processMethod = null;
try {
processMethod = toolClass.getMethod("processFiles");
getLog().debug("Using Joda-Beans v1.5 or later - processFiles()");
} catch (Exception ex) {
try {
processMethod = toolClass.getMethod("process");
getLog().debug("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;
}
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());
}
}
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);
}
}
@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);
}
}
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.
*
* @return the classloader, not null
*/
private URLClassLoader obtainClassLoader() throws MojoExecutionException {
getLog().debug("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 {
getLog().debug("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.
*
* @return the classpath, not null
* @throws MojoExecutionException
*/
@SuppressWarnings("unchecked")
private List obtainClasspath() throws MojoExecutionException {
try {
return _project.getCompileClasspathElements();
} catch (DependencyResolutionRequiredException ex) {
throw new MojoExecutionException("Error obtaining dependencies", ex);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy