Package org.opentripplanner.customize

Source Code of org.opentripplanner.customize.ClassCustomizer

/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>. */

package org.opentripplanner.customize;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtNewConstructor;
import javassist.Modifier;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.Bytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.DuplicateMemberException;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.util.proxy.FactoryHelper;

/**
* This creates a subclass of a given class by adding a new interface to it, and allows users to add
* any necessary fields
*
* The intended use is to allow optimizations or extensions that require per-edge data to massage an
* existing graph to meet their needs, without adding additional fields to the core.
*
* @author novalis
*
*/
public class ClassCustomizer {

    private ClassFile classFile;

    private CtClass ctClass;

    private File extraClassPath;

    public void setClassPath(File file) {
        this.extraClassPath = file;
    }

    /**
     *
     * @param iface The interface the new class should implement
     * @param oldlassName The class to be extended
     * @param newClassName the name of the new class to be created
     */
    public ClassCustomizer(Class<?> iface, String oldlassName, String newClassName) {

        try {
            ClassPool pool = ClassPool.getDefault();
            ctClass = pool.makeClass(newClassName);
            classFile = ctClass.getClassFile();
            classFile.setSuperclass(oldlassName);

            classFile.setName(newClassName);

            classFile.setInterfaces(new String[] { iface.getName() });

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /** Adds a new field of type double to the customized class */
    public void addDoubleField(String fieldName) {
        // FIXME: this should support default values but does not

        ClassFile classFile = ctClass.getClassFile();
        ConstPool constPool = classFile.getConstPool();
        try {
            // add field
            FieldInfo fieldInfo = new FieldInfo(constPool, fieldName, "D");
            classFile.addField(fieldInfo);

            CtConstructor ctor = CtNewConstructor.defaultConstructor(ctClass);
            ctClass.addConstructor(ctor);

            addDoubleSetter(classFile, fieldName);
            addDoubleGetter(classFile, fieldName);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /** Writes the class file to the classpath and returns a class object */
    public Class<?> saveClass() {
        ClassFile classFile = ctClass.getClassFile();

        try {
            if (!extraClassPath.exists()) {
                extraClassPath.mkdirs();
            }
            FactoryHelper.writeFile(classFile, extraClassPath.getPath());
            ClassLoader loader = getClass().getClassLoader();

            Class<?> cls = FactoryHelper.toClass(classFile, loader);
            return cls;
            // load class
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /** Creates a clone of original but with the class NewClass (which extends original's class) */
    public static <T> T reclass(T original, Class<? extends T> newClass) {
        Class<?> origClass = original.getClass();

        T newObj;
        try {
            Constructor<? extends T> ctor = newClass.getConstructor();
            newObj = ctor.newInstance();

            while (origClass != null) {
                Field[] fields = origClass.getDeclaredFields();
                for (Field field : fields) {
                    field.setAccessible(true);
                    int modifiers = field.getModifiers();
                    if (Modifier.isStatic(modifiers)) {
                        continue;
                    }
                    Object value = field.get(original);
                    field.set(newObj, value);
                }
                origClass = origClass.getSuperclass();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return newObj;
    }

    /**
     * capitalize the first letter of the string
     *
     * @param str
     * @return
     */
    private String ucfirst(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }

    /**
     * Add a simple getter with signature "double getFoo()" to the class, which simply returns the value of the
     * field fieldName
     * @param ctClass
     * @param classFile
     * @param fieldName
     * @throws DuplicateMemberException
     */
    private void addDoubleGetter(ClassFile classFile, String fieldName)
            throws DuplicateMemberException {
        ConstPool constPool = classFile.getConstPool();

        // double getFoo()
        MethodInfo getter = new MethodInfo(constPool, "get" + ucfirst(fieldName), "()D");

        Bytecode code = new Bytecode(constPool, 2, 1);

        // load this
        code.addAload(0);

        code.addGetfield(ctClass, fieldName, "D");

        // return with value
        code.addOpcode(Opcode.DRETURN);
        getter.setCodeAttribute(code.toCodeAttribute());

        getter.setAccessFlags(AccessFlag.PUBLIC);
        classFile.addMethod(getter);
    }

    private void addDoubleSetter(ClassFile classFile, String fieldName)
            throws DuplicateMemberException {
        ConstPool constPool = classFile.getConstPool();

        // void setFoo(double)
        MethodInfo setter = new MethodInfo(constPool, "set" + ucfirst(fieldName), "(D)V");
        Bytecode code = new Bytecode(constPool, 3, 3);

        // load this
        code.addAload(0);

        // load param
        code.addDload(1);
        code.addPutfield(ctClass, fieldName, "D");

        code.addOpcode(Opcode.RETURN);

        setter.setCodeAttribute(code.toCodeAttribute());
        setter.setAccessFlags(AccessFlag.PUBLIC);
        classFile.addMethod(setter);

    }
}
TOP

Related Classes of org.opentripplanner.customize.ClassCustomizer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.