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

nl.topicus.plugins.maven.javassist.JavassistMojo Maven / Gradle / Ivy

package nl.topicus.plugins.maven.javassist;

import static java.lang.Thread.currentThread;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

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

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

@Mojo(name = "javassist", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class JavassistMojo extends AbstractMojo implements ILogger {

	private static final Class TRANSFORMER_TYPE = ClassTransformer.class;

	@Component
	private BuildContext buildContext;

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

	@Parameter(property = "transformerClass", required = true)
	private String transformerClass;

	@Parameter(property = "packageName", required = true)
	private String packageName;

	@Parameter(property = "outputDirectory", required = true, defaultValue = "${project.build.outputDirectory}")
	private String outputDirectory;

	public void execute() throws MojoExecutionException {
		final ClassLoader originalContextClassLoader = currentThread()
				.getContextClassLoader();
		try {
			final List classpathElements = getCompileClasspathElements();
			loadClassPath(originalContextClassLoader,
					generateClassPathUrls(classpathElements));
			transform(classpathElements);
		} catch (DependencyResolutionRequiredException e) {
			throw new MojoExecutionException(e.getMessage(), e);
		} finally {
			currentThread().setContextClassLoader(originalContextClassLoader);
		}
	}

	public final void transform(final List classPaths)
			throws MojoExecutionException {
		int errors = 0;
		if (classPaths.isEmpty())
			return;

		ClassTransformer transformer = instantiateTransformerClass();
		final ClassPool classPool = new ClassPool(ClassPool.getDefault());
		classPool.appendClassPath(new LoaderClassPath(Thread.currentThread()
				.getContextClassLoader()));

		final Iterator classPathIterator = classPaths.iterator();
		while (classPathIterator.hasNext()) {
			final String classPath = classPathIterator.next();
			getLog().debug("Processing " + classPath);
			final ClassFileIterator classNames = createClassNameIterator(classPath);
			while (classNames.hasNext()) {
				final String className = classNames.next();
				if (!transformer.filterClassName(className)) {
					getLog().debug("Skipping " + className);
					continue;
				}

				try {
					final CtClass candidateClass = classPool.get(className);
					if (transformer.filterCtClass(candidateClass)) {
						transformer.applyTransformations(classPool,
								candidateClass);
						writeFile(candidateClass, outputDirectory);
					}
				} catch (final TransformationException e) {
					errors++;
					addMessage(classNames.getLastFile(), 1, 1, e.getMessage(),
							null);
					continue;
				} catch (final NotFoundException e) {
					errors++;
					addMessage(classNames.getLastFile(), 1, 1, String.format(
							"Class %s could not be resolved due "
									+ "to dependencies not found on current "
									+ "classpath.", className), e);
					continue;
				} catch (final Exception e) {
					errors++;
					addMessage(classNames.getLastFile(), 1, 1, String.format(
							"Class %s could not be transformed.", className), e);
					continue;
				}
			}
		}
		if (errors > 0)
			throw new MojoExecutionException(errors
					+ " errors found during transformation.");
	}

	public void writeFile(CtClass candidateClass, String targetDirectory)
			throws Exception {
		candidateClass.getClassFile().compact();
		candidateClass.rebuildClassFile();

		String classname = candidateClass.getName();
		String filename = targetDirectory + File.separatorChar
				+ classname.replace('.', File.separatorChar) + ".class";
		int pos = filename.lastIndexOf(File.separatorChar);
		if (pos > 0) {
			String dir = filename.substring(0, pos);
			if (!dir.equals(".")) {
				File outputDir = new File(dir);
				outputDir.mkdirs();
				buildContext.refresh(outputDir);
			}
		}
		try (DataOutputStream out = new DataOutputStream(
				new BufferedOutputStream(
						buildContext.newFileOutputStream(new File(filename))))) {
			candidateClass.toBytecode(out);
		}
	}

	private ClassFileIterator createClassNameIterator(final String classPath) {
		if (new File(classPath).isDirectory()) {
			return new ClassNameDirectoryIterator(classPath, buildContext);
		} else {
			return new ClassNameJarIterator(classPath, buildContext);
		}
	}

	private List getCompileClasspathElements()
			throws DependencyResolutionRequiredException {
		List ret = project.getCompileClasspathElements();
		ret.remove(project.getBuild().getOutputDirectory());
		return Lists.newArrayList(Iterables.filter(ret, String.class));
	}

	protected ClassTransformer instantiateTransformerClass()
			throws MojoExecutionException {
		if (transformerClass == null || transformerClass.trim().isEmpty())
			throw new MojoExecutionException(
					"Invalid transformer class name passed");

		Class transformerClassInstance;
		try {
			transformerClassInstance = Class.forName(transformerClass.trim(),
					true, currentThread().getContextClassLoader());
		} catch (ClassNotFoundException e) {
			throw new MojoExecutionException(e.getMessage(), e);
		}
		ClassTransformer transformerInstance = null;

		if (TRANSFORMER_TYPE.isAssignableFrom(transformerClassInstance)) {
			try {
				transformerInstance = TRANSFORMER_TYPE
						.cast(transformerClassInstance.newInstance());
			} catch (InstantiationException | IllegalAccessException e) {
				throw new MojoExecutionException(e.getMessage(), e);
			}
			transformerInstance.setLogger(this);
			transformerInstance.setFilterPackageName(packageName);
		} else {
			throw new MojoExecutionException(
					"Transformer class must inherit from "
							+ TRANSFORMER_TYPE.getName());
		}

		return transformerInstance;
	}

	private List generateClassPathUrls(Iterable classpathElements) {
		final List classPath = new ArrayList();
		for (final String runtimeResource : classpathElements) {
			URL url = resolveUrl(runtimeResource);
			if (url != null) {
				classPath.add(url);
			}
		}

		return classPath;
	}

	private void loadClassPath(final ClassLoader contextClassLoader,
			final List urls) {
		if (urls.size() <= 0)
			return;

		final URLClassLoader pluginClassLoader = URLClassLoader.newInstance(
				urls.toArray(new URL[urls.size()]), contextClassLoader);
		currentThread().setContextClassLoader(pluginClassLoader);
	}

	private URL resolveUrl(final String resource) {
		try {
			return new File(resource).toURI().toURL();
		} catch (final MalformedURLException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	@Override
	public void addMessage(File file, int line, int pos, String message,
			Throwable e) {
		buildContext.addMessage(file, line, pos, message,
				BuildContext.SEVERITY_ERROR, e);
	}

	@Override
	public void debug(String message) {
		getLog().debug(message);
	}

	@Override
	public void debug(String message, Throwable throwable) {
		getLog().debug(message, throwable);
	}

	@Override
	public void info(String message) {
		getLog().info(message);
	}

	@Override
	public void info(String message, Throwable throwable) {
		getLog().info(message, throwable);
	}

	@Override
	public void warn(String message) {
		getLog().warn(message);
	}

	@Override
	public void warn(String message, Throwable throwable) {
		getLog().warn(message, throwable);
	}

	@Override
	public void error(String message) {
		getLog().error(message);
	}

	@Override
	public void error(String message, Throwable throwable) {
		getLog().error(message, throwable);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy