MethodCallClassTransformer.java
/*
* Copyright 2013 https://github.com/barthel
*
* 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 de.icongmbh.oss.maven.plugin.javassist.example.transformer;
import java.util.Properties;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtField.Initializer;
import javassist.NotFoundException;
import javassist.build.JavassistBuildException;
import javassist.bytecode.AccessFlag;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
import de.icongmbh.oss.maven.plugin.javassist.ClassTransformer;
/**
* Example of {@link ClassTransformer} implementation.
*
* <p>
* This example shows a full functional implementation of an {@link ClassTransformer}.
* <br>
* The transformer inject bytecode around configurable method calls.
* </p>
*
* <pre>
* {@code
*
* <transformerClass>
* <className>
* de.icongmbh.oss.maven.plugin.javassist.example.transformer.MethodCallClassTransformer
* </className>
* <properties>
* <property>
* <name>my.example.App#doSomthing</name>
* <value>{ $2="injected value for sec. parameter"; $_ = $proceed($$); }</value>
* </property>
* </properties>
* </transformerClass>
* }
* </pre>
*
* @see <a href="https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html#before">
* https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html#before</a>
*
* @since 1.1.0
*/
public class MethodCallClassTransformer extends ClassTransformer {
/**
* This token separates the class and the method name in {@code className}.
* <p>
* {@code my.example.App}<b>{@code #}</b>{@code doSomthing}
* </p>
*/
public static final char METHOD_TOKEN = '#';
/**
* Start identifier for the Javassist statement.
*/
public static final char JAVASSIST_STATEMENT_START_TOKEN = '{';
/**
* End identifier for the Javassist statement.
*/
public static final char JAVASSIST_STATEMENT_END_TOKEN = '}';
/**
* The, so called, "stamp" to mark already transformed classes.
*/
public static final String ALREADY_INTROSPECTED_FIELD_NAME = "__introspected__"
+ MethodCallClassTransformer.class
.getSimpleName();
private Properties properties;
/**
* <p>
* {@link Properties} entries like the following.
* </p>
* <ul>
* <li>name: full qualified class name and method name separated by '{@link #METHOD_TOKEN #}'
* <li>value: Javassist statement - starts with '{@link #JAVASSIST_STATEMENT_START_TOKEN {}'
* and ends with '{@link #JAVASSIST_STATEMENT_END_TOKEN }}'
* </ul>
*
* <pre>
* {@code
* my.example.App#doSomthing={ $2="injected value for sec. parameter"; $_ = $proceed($$); }
* }
* </pre>
*
* @param properties maybe {@code null}
* @throws Exception provided by interface
*
* @see <a href="https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html#before">
* https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html#before</a>
*/
@Override
public void configure(final Properties properties) throws Exception {
this.properties = (null == properties) ? new Properties() : (Properties)properties.clone();
}
@Override
public boolean shouldTransform(final CtClass candidateClass) throws JavassistBuildException {
return candidateClass != null && !isIntrospected(candidateClass);
}
@Override
public void applyTransformations(final CtClass classToTransform) throws JavassistBuildException {
if (null == classToTransform) {
return;
}
try {
classToTransform.instrument(new ExprEditor() {
@Override
public void edit(final MethodCall method) throws CannotCompileException {
final String statement = getStatement(method.getClassName(), method.getMethodName());
if (statement != null) {
try {
method.replace(statement);
} catch (final CannotCompileException e) {
throw new CannotCompileException(String
.format("Compile statement '%1$s' FAILED with: %2$s", statement, e.getMessage()),
e);
}
}
}
});
// insert internal introspection state field
final CtField introspectedField = new CtField(CtClass.booleanType,
ALREADY_INTROSPECTED_FIELD_NAME,
classToTransform);
introspectedField.setModifiers(AccessFlag.PUBLIC | AccessFlag.STATIC | AccessFlag.FINAL);
classToTransform.addField(introspectedField, Initializer.constant(true));
} catch (CannotCompileException e) {
throw new JavassistBuildException(e);
}
}
private boolean isIntrospected(final CtClass candidateClass) {
try {
candidateClass.getField(ALREADY_INTROSPECTED_FIELD_NAME);
return true;
} catch (final NotFoundException e) {
return false;
}
}
// TODO: find better implementation
private String getStatement(final String className, final String methodName) {
if (null == properties || (null == className && null == methodName)) {
return null;
}
String statement = this.properties.getProperty(className + METHOD_TOKEN + methodName);
if (null == statement) {
statement = this.properties.getProperty(className + METHOD_TOKEN);
}
return null == statement ? this.properties.getProperty(METHOD_TOKEN + methodName) : statement;
}
}