icon-systemhaus-gmbh / javassist-maven-plugin Goto Github PK
View Code? Open in Web Editor NEWMaven plugin that will apply Javassist bytecode transformations during build time.
License: Apache License 2.0
Maven plugin that will apply Javassist bytecode transformations during build time.
License: Apache License 2.0
There any warnings generated by
mvn javadoc:javadoc
Add package-info.java
into packages and describe the intention of this package.
There are currently no tests at all :(
I suggest we have both :
I can take the unit tests in charge based on http://maven.apache.org/plugin-testing/maven-plugin-testing-harness/getting-started/index.html.
Does someone volunteer to setup the integration test structure and first test ?
@stephanenicolas Clarified the inner classes support of javassist (see: jboss-javassist/javassist#6 (comment)).
This plugin always working on class files and should not exclude the inner/nested classes.
Hi there,
I was approached by @stephanenicolas to attempt some sort of interop between the Gradle and Maven plugins. I was wondering if you could turn filter into shouldTransform() instead, as
Kind Regards,
Daryl
Hey there,
i'm trying to enhance a class with some methods and fields.
For this i have two transformer classes:
<plugin> <groupId>de.icongmbh.oss.maven.plugins</groupId> <artifactId>javassist-maven-plugin</artifactId> <version>1.1.0</version> <configuration> <includeTestClasses>false</includeTestClasses> <transformerClasses> <transformerClass> <className>com.dvag.example.javassist.transformers.PropertyTransformer</className> </transformerClass> <transformerClass> <className>com.dvag.example.javassist.transformers.ToStringTransformer</className> </transformerClass> </transformerClasses> </configuration> <executions> <execution> <phase>process-classes</phase> <goals> <goal>javassist</goal> </goals> </execution> </executions> <dependencies> ... </dependencies> </plugin>
Running my unit - tests with maven ends up as expected, but not when compiling via eclipse..
is it possible to integrate the byte code transformations with eclipse? maybe show generated methods in eclipse outline?
regards,
patrick
During a build, a class might be already compiled (if it wasn't modified, it's not compiled again) and already instrumented (transformed), so it's instrumented again. Usually it leads to errors because you might end with an duplicated method or field added before, for example.
possible solutions:
The plugin was unable to load my ClassTransformer and a ClassNotFoundException was raised.
I noticed that the classPath variable within the JavassistMojo class contained the correct urls, but these urls were not attached to the currentThread().getContextClassLoader() until after setTransformerClasses was called.
Calling loadAdditionalClassPath(classPath) before executing the code below solves this problem:
executor.setTransformerClasses(instantiateTransformerClasses(
currentThread().getContextClassLoader(), transformerClasses));
TBD: add test project.
I would really appreciate to get this library on central. Please, can we shorten the path to go there quickly ?
Is it possible to prune the milestone 1.1 related issues, clean them up and only keep the essential, and to provide an arrival date ?
The only alternative option left is to fork and publish it by myself to get artifacts public.
Hi,
I'm generating inner classes so I need to write the new classes to file.
inner.writeFile("./target/classes");
just works but it would be nicer to add a method that could save the class using the build path and mark the new class as processed too.
I can implement the feature for you.
It would be great to stop the build, if the transformation of a class file fails.
This is not possible in the current implementation. In a further step, javassist-maven-plugin could be configured for stopping on exceptions, or not.
For a work around to stop build on exceptions, I have created my own abstract class, which inherits from ClassTransformer. It can be used by extending AbstractClassTransformer instead of ClassTransformer.
package de.icongmbh.oss.maven.plugin.javassist;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Iterator;
import org.apache.maven.MavenExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import javassist.bytecode.ClassFile;
public abstract class AbstractClassTransformer extends ClassTransformer {
private static final Logger LOG = LoggerFactory.getLogger(AbstractClassTransformer.class);
@Override
public void transform(final String inputDir, final String outputDir) {
if (null == inputDir || inputDir.trim().isEmpty()) {
return;
}
final String outDirectory = outputDir != null
&& !outputDir.trim().isEmpty() ? outputDir : inputDir;
try {
transformWithBuildErrors(inputDir, outDirectory,
iterateClassnames(inputDir));
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
/**
* Use the passed className iterator, load each one as {@link CtClass},
* filter the valid candidates (using {@link #filter(CtClass)}) and apply
* transformation to each one ({@link #applyTransformations(CtClass)}).
* <ol>
* <li><strong>Limitation:</strong> do not search inside .jar files yet.</li>
* <li>This implementation is the same as the original method
* {@link #transform(String, String, Iterator)} except, that exceptions can
* be thrown by the called abstract method
* {@link #applyTransformations(CtClass)} to stop the Maven build, if the
* transformation fails</li>
* </ol>
*
* @param inputDir
* root directory - required - if <code>null</code> or empty
* nothing will be transformed
* @param outputDir
* must be not <code>null</code>
* @throws Exception
* @see #applyTransformations(CtClass)
*/
public void transformWithBuildErrors(final String inputDir,
final String outputDir, final Iterator<String> classNames) throws Exception {
if (null == classNames || !classNames.hasNext()) {
return;
}
// create new class pool for transform; don't blow up the default
final ClassPool classPool = new ClassPool(ClassPool.getDefault());
classPool.childFirstLookup = true;
classPool.appendClassPath(inputDir);
classPool.appendClassPath(new LoaderClassPath(Thread
.currentThread().getContextClassLoader()));
classPool.appendSystemPath();
debugClassLoader(classPool);
int i = 0;
while (classNames.hasNext()) {
final String className = classNames.next();
if (null == className) {
continue;
}
try {
LOG.debug("Got class name {}", className);
classPool.importPackage(className);
final CtClass candidateClass = classPool.get(className);
initializeClass(candidateClass);
if (!hasStamp(candidateClass)
&& shouldTransform(candidateClass)) {
applyTransformations(candidateClass);
applyStamp(candidateClass);
candidateClass.writeFile(outputDir);
LOG.debug("Class {} instrumented by {}", className,
getClass().getName());
++i;
}
} catch (final NotFoundException e) {
throw new MavenExecutionException(String.format("Class %s has not been found on class path.", className), e);
} catch (final Exception ex) {
throw new MavenExecutionException(String.format("Instrumenting class %s failed.", className), ex);
}
}
LOG.info("#{} classes instrumented by {}", i, getClass()
.getName());
}
private void initializeClass(final CtClass candidateClass)
throws NotFoundException {
debugClassFile(candidateClass.getClassFile2());
// TODO hack to initialize class to avoid further NotFoundException
// (what's the right way of doing this?)
candidateClass.subtypeOf(ClassPool.getDefault().get(
Object.class.getName()));
}
private void debugClassLoader(final ClassPool classPool) {
if (!LOG.isDebugEnabled()) {
return;
}
LOG.debug(" - classPool: {}", classPool.toString());
ClassLoader classLoader = classPool.getClassLoader();
while (classLoader != null) {
LOG.debug(" -- {}: {}", classLoader.getClass().getName(),
classLoader.toString());
if (classLoader instanceof URLClassLoader) {
LOG.debug(" --- urls: {}", Arrays
.deepToString(((URLClassLoader) classLoader).getURLs()));
}
classLoader = classLoader.getParent();
}
}
private void debugClassFile(final ClassFile classFile) {
if (!LOG.isDebugEnabled()) {
return;
}
LOG.debug(" - class: {}", classFile.getName());
LOG.debug(" -- Java version: {}.{}", classFile.getMajorVersion(),
classFile.getMinorVersion());
LOG.debug(" -- interface: {} abstract: {} final: {}",
classFile.isInterface(), classFile.isAbstract(),
classFile.isFinal());
LOG.debug(" -- extends class: {}", classFile.getSuperclass());
LOG.debug(" -- implements interfaces: {}",
Arrays.deepToString(classFile.getInterfaces()));
}
}
The context classloader is adjusted to add the input directory the local build output directory and the dependencies before calling the transformation methods.
I think the context classloader should be set around all interactions with the ClassTransformer. This would allow configure() to access classes in the local project, and also allow the ClassTransformer implementations to be loaded from the local project.
If you agree, I'm happy to do the work. I'd pull the context classloader management up from JavassistTransformerExecutor.execute() to JavassistMojo.execute().
Add the skip property like other maven plugins does.
Based on suggestion of @stephanenicolas set up a public CI infrastructure on travis-ci.org.
Hi @drochetti,
do you think it would be easy to use an alternative implementation on javassist with your plugin ?
I would like to make it work on Android and it looks like someone already has a github project to make javassist compatible with Android :
https://github.com/crimsonwoods/javassist-android
The plugin is really nice, a little more documentation and a release on central would be nice.
Stéphane
Change the package names before/after the first release.
[...]
As I've been very busy, I think a transfer will work best for you, so you'll don't need
to wait for pull request merges anymore. You're free to change package name too,
since is using my GitHub username on it (lack of inspiration at the time).
I'll be happy to see this project improved and to fork it to contribute anytime.Cheers!
Daniel (@drochetti)On Tue, Mar 11, 2014 at 12:48 PM, @barthel wrote:
Hi Barthel! Thanks for your contact.
I've been super busy with other projects and I would love to have you
guys maintaing this plugin, that helped me a lot once.I'll be happy to transfer the repository to you guys if you want, just
let me know to which user should I transfer.Cheers!
Daniel (@drochetti)
The major version number of class files created from scratch. The default value is 47 (JDK 1.3).
javassist.bytecode.ClassFile#MAJOR_VERSION
The major version numbers of class files for JDK 1.1 - 1.8
javassist.bytecode.ClassFile#JAVA_1
javassist.bytecode.ClassFile#JAVA_2
javassist.bytecode.ClassFile#JAVA_3
javassist.bytecode.ClassFile#JAVA_4
javassist.bytecode.ClassFile#JAVA_5
javassist.bytecode.ClassFile#JAVA_6
javassist.bytecode.ClassFile#JAVA_7
javassist.bytecode.ClassFile#JAVA_8
would you agree to define a simple common java interface
for ClassTransformers
, it is already the case de facto, but a real common interface would be cleaner and it could be provided inside a simple jar artifact on maven central.
If you agree, I suggest that I host the project on GH and add you 2 as full contributors for the repo and on maven central.
It could be as simple as :
public interface ClassTransformer {
void applyTransformations(CtClass classToTransform);
boolean shouldTransform(final CtClass candidateClass)
;
}
On gradle repo, same issue :
darylteo/gradle-plugins#4
Add/modify information within pom:
It would be nice to ignore and/or remove the stampField to re-transform the class again.
To prevent something like:
[ERROR] Parameter 'directory' is not a directory
java.lang.IllegalArgumentException: Parameter 'directory' is not a directory
at org.apache.commons.io.FileUtils.validateListFilesParameters(FileUtils.java:545)
at org.apache.commons.io.FileUtils.listFiles(FileUtils.java:521)
at org.apache.commons.io.FileUtils.iterateFiles(FileUtils.java:628)
at de.icongmbh.oss.maven.plugin.javassist.ClassTransformer.iterateClassnames(ClassTransformer.java:198)
at de.icongmbh.oss.maven.plugin.javassist.ClassTransformer.transform(ClassTransformer.java:131)
at de.icongmbh.oss.maven.plugin.javassist.JavassistTransformerExecutor.execute(JavassistTransformerExecutor.java:107)
at de.icongmbh.oss.maven.plugin.javassist.JavassistTransformerExecutor.execute(JavassistTransformerExecutor.java:98)
at de.icongmbh.oss.maven.plugin.javassist.JavassistMojo.execute(JavassistMojo.java:118)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:106)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:208)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:317)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:152)
at org.jvnet.hudson.maven3.launcher.Maven31Launcher.main(Maven31Launcher.java:132)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchStandard(Launcher.java:330)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:238)
at jenkins.maven3.agent.Maven31Main.launch(Maven31Main.java:181)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at hudson.maven.Maven3Builder.call(Maven3Builder.java:134)
at hudson.maven.Maven3Builder.call(Maven3Builder.java:69)
at hudson.remoting.UserRequest.perform(UserRequest.java:118)
at hudson.remoting.UserRequest.perform(UserRequest.java:48)
at hudson.remoting.Request$2.run(Request.java:328)
at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:72)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
For example in maven assembly projects where no target/classes
(buildDir
) exists and the plugin is defined in parent POM.
if (!classToTransform.isInterface()) {
stampField.setModifiers(AccessFlag.PRIVATE | AccessFlag.STATIC | AccessFlag.FINAL);
} else {
stampField.setModifiers(AccessFlag.PUBLIC | AccessFlag.STATIC | AccessFlag.FINAL);
}
classToTransform.addField(stampField, Initializer.constant(true));
Add and configure maven site and provide via gh-pages.
Using the master version of the repository one can only apply one transformer.
This seems to be introduced by 1edfc58 because now the stamp field's name is __TRANSFORMED_BY_JAVASSIST_MAVEN_PLUGIN__de_icongmbh_oss_maven_plugin_javassist_JavassistTransformerExecutor
for all transformers.
Suggested fix is to not use the JavassistTransformerExecutor
's class name as suffix but the class of the transformer.
I'm using enforcer-plugin and it detects followings version conflicts, regarding :
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
and
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-classworlds</artifactId>
How come did you push a release into maven central without checking version conflicts ??
The plugin must be published to a public Maven Repo. AFAIK the Sonatype OSS Repo pushes to the Maven Central and it's easy to publish to.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.