/* 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);
}
}