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

de.icongmbh.oss.maven.plugin.javassist.ClassTransformer Maven / Gradle / Ivy

Go to download

A maven plugin based on Javassist to manipulate bytecode of project classes at post-compile time.

There is a newer version: 2.0.2
Show newest version
/*
 * Copyright 2012 http://github.com/drochetti/
 *
 * 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;

import java.io.File;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Properties;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtField.Initializer;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.ClassFile;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base class for class transformation logic.
 * @author Daniel Rochetti
 */
public abstract class ClassTransformer {

    private static final String STAMP_FIELD_NAME = "__TRANSFORMED_BY_JAVASSIST_MAVEN_PLUGIN__";
    
    private static Logger logger = LoggerFactory.getLogger(ClassTransformer.class);

    /**
     * 

Concrete implementations must implement all transformations on this method. * You can use Javassist API to add/remove/replace methods, attributes and more. * Only classes approved by {@link #filter(CtClass)} are considered.

* * @param classToTransform The class to transform. * @see #filter(CtClass) * @throws Exception if any error occur during the transformation. */ protected abstract void applyTransformations(CtClass classToTransform) throws Exception; /** *

Test if the given class is suitable for applying transformations or not.

*

For example, if the class is a specific type:

*
     * CtClass myInterface = ClassPool.getDefault().get(MyInterface.class.getName());
     * return candidateClass.subtypeOf(myInterface);
     * 
* Override this method to boost class transformations and discard classes you don't want * to transform. * * @param candidateClass * @return {@code true} if the Class should be transformed; {@code false} otherwise. * @throws Exception */ protected boolean shouldTransform(final CtClass candidateClass) throws Exception { return true; } /** *

Configure this instance by passing {@link Properties}.

* @param properties maybe null or empty * @throws Exception */ public void configure(final Properties properties) throws Exception { return; } /** *

* Search for class files on the passed directory, load each one as {@link CtClass}, filter * the valid candidates (using {@link #filter(CtClass)}) and apply transformation to each one * ({@link #applyTransformations(CtClass)}). *

*

* Limitation: do not search inside .jar files yet. *

* @param dir root directory -input and output directory are the same. * @see #iterateClassnames(String) * @see #transform(Iterator, String) * @see #applyTransformations(CtClass) * */ public final void transform(final String dir) { if( null == dir || dir.trim().isEmpty()) { return; } transform(dir,dir,iterateClassnames(dir)); } /** *

* Search for class files on the passed input directory, load each one as {@link CtClass}, filter * the valid candidates (using {@link #filter(CtClass)}) and apply transformation to each one * ({@link #applyTransformations(CtClass)}). *

*

* Limitation: do not search inside .jar files yet. *

* @param inputDir root directory - required - if null or empty nothing will be transformed * @param outputDir if null or empty the inputDir will be used * @see #iterateClassnames(String) * @see #transform(Iterator, String) * @see #applyTransformations(CtClass) */ 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; transform(inputDir, outDirectory,iterateClassnames(inputDir)); } /** *

* 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)}). *

*

* Limitation: do not search inside .jar files yet. *

* @param inputDir root directory - required - if null or empty nothing will be transformed * @param outputDir must be not null * @see #applyTransformations(CtClass) */ public final void transform(final String inputDir,final String outputDir, final Iterator classNames) { if( null == classNames || !classNames.hasNext()) { return; } try { // create new classpool 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 { logger.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); logger.debug("Class {} instrumented by {}", className, getClass().getName()); ++i; } } catch (final NotFoundException e) { logger.warn("Class {} could not not be resolved due to dependencies not found on " + "current classpath (usually your class depends on \"provided\" scoped dependencies).", className); continue; } catch ( final Exception ex) { // EOFException ... logger.error("Class {} could not not be instrumented due to initialize FAILED.",className, ex); continue; } } logger.info("#{} classes instrumented by {}",i,getClass().getName()); } catch (final Exception e) { throw new RuntimeException(e.getMessage(), e); } } protected Iterator iterateClassnames(final String dir) { final String[] extensions = { ".class" }; final File directory = new File(dir); IOFileFilter fileFilter = new SuffixFileFilter(extensions); final IOFileFilter dirFilter = TrueFileFilter.INSTANCE; return ClassnameExtractor.iterateClassnames(directory, FileUtils.iterateFiles(directory, fileFilter, dirFilter)); } protected static Logger getLogger() { return logger; } /** * Apply a "stamp" to a class to indicate it has been modified. * By default, this method uses a boolean field named * {@value #STAMP_FIELD_NAME} as the stamp. * Any class overriding this method should also override {@link #hasStamp(CtClass)}. * @param candidateClass the class to mark/stamp. * @throws CannotCompileException * @see {@link #hasStamp(CtClass)} */ protected void applyStamp(CtClass candidateClass) throws CannotCompileException { candidateClass.addField(createStampField(candidateClass),Initializer.constant(true)); } /** * Remove a "stamp" from a class if the "stamp" field is available. * By default, this method removes a boolean field named {@value #STAMP_FIELD_NAME}. * Any class overriding this method should also override {@link #hasStamp(CtClass)}. * @param candidateClass the class to remove the mark/stamp from. * @throws CannotCompileException * @see {@link #hasStamp(CtClass)} * @see {@link #applyStamp(CtClass)} */ protected void removeStamp(CtClass candidateClass) throws CannotCompileException { try { candidateClass.removeField(createStampField(candidateClass)); } catch (final NotFoundException e) { // ignore; } } /** * Indicates whether a class holds a stamp or not. * By default, this method uses a boolean field named * {@value #STAMP_FIELD_NAME} as the stamp. * Any class overriding this method should also override {@link #applyStamp(CtClass)}. * @param candidateClass the class to check. * @return true if the class owns the stamp, otherwise false. * @see #applyStamp(CtClass) */ protected boolean hasStamp(CtClass candidateClass) { boolean hasStamp = false; try { hasStamp = null != candidateClass.getDeclaredField(createStampFieldName()); } catch (NotFoundException e) { hasStamp = false; } if( logger.isDebugEnabled() ) { logger.debug("Stamp {}{} found in class {}", createStampFieldName(),(hasStamp?"":" NOT"),candidateClass.getName()); } return hasStamp; } private String createStampFieldName() { return STAMP_FIELD_NAME+getClass().getName().replaceAll("\\W", "_"); } private CtField createStampField(CtClass candidateClass) throws CannotCompileException { int stampModifiers = AccessFlag.STATIC | AccessFlag.FINAL; if (!candidateClass.isInterface()) { stampModifiers |= AccessFlag.PRIVATE; } else { stampModifiers |= AccessFlag.PUBLIC; } final CtField stampField = new CtField(CtClass.booleanType, createStampFieldName(),candidateClass); stampField.setModifiers(stampModifiers); return stampField; } 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 debugClassFile(final ClassFile classFile) { if (!logger.isDebugEnabled()) { return; } logger.debug(" - class: {}",classFile.getName()); logger.debug(" -- Java version: {}.{}", classFile.getMajorVersion(), classFile.getMinorVersion()); logger.debug(" -- interface: {} abstract: {} final: {}", classFile.isInterface(), classFile.isAbstract(), classFile.isFinal()); logger.debug(" -- extends class: {}",classFile.getSuperclass()); logger.debug(" -- implements interfaces: {}", Arrays.deepToString(classFile.getInterfaces())); } private void debugClassLoader(final ClassPool classPool) { if (!logger.isDebugEnabled()) { return; } logger.debug(" - classPool: {}", classPool.toString()); ClassLoader classLoader = classPool.getClassLoader(); while (classLoader != null) { logger.debug(" -- {}: {}", classLoader.getClass().getName(), classLoader.toString()); if (classLoader instanceof URLClassLoader) { logger.debug(" --- urls: {}", Arrays.deepToString(((URLClassLoader) classLoader).getURLs())); } classLoader = classLoader.getParent(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy