/*
* This software is distributed under the terms of the FSF
* Gnu Lesser General Public License (see lgpl.txt).
*
* This program is distributed WITHOUT ANY WARRANTY. See the
* GNU General Public License for more details.
*/
package com.scooterframework.autoloader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.DuplicateMemberException;
import com.scooterframework.admin.ApplicationConfig;
import com.scooterframework.common.logging.LogUtil;
import com.scooterframework.common.util.StringUtil;
import com.scooterframework.orm.activerecord.ActiveRecord;
/**
* ClassWork class knows how to modify class byte codes.
*
* @author (Fei) John Chen
*/
public class ClassWork {
protected LogUtil log = LogUtil.getLogger(this.getClass().getName());
private ClassPool pool;
private ClassLoader cl;
public ClassWork(ClassLoader cl) {
this.cl = cl;
pool = new ClassPool();
pool.appendSystemPath();
try {
pool.appendPathList(System.getProperty("java.class.path"));
pool.appendClassPath(ApplicationConfig.getInstance().getClassFileLocationPath());
} catch (NotFoundException ex) {
log.error("Error finding classpath: " + ex.getMessage());
}
}
public Class<?> changeClass(String className) throws ClassNotFoundException {
Class<?> result = null;
try {
CtClass cc = pool.get(className);
boolean transformed = true;
if (cc.subclassOf(pool.get(ActiveRecord.class.getName())) && furtherCheckAllowedToChange(cc)) {
addMethods(cc, ClassWorkSource.arMethods);
}
else {
transformed = false;
}
if (ApplicationConfig.getInstance().isWebApp()) {
result = cc.toClass(cl, this.getClass().getProtectionDomain());
} else {
if (transformed) {
result = cc.toClass();
}
}
cc.defrost();
} catch (Exception ex) {
throw new ClassNotFoundException("classWork failed on " + className + ": " + ex.getMessage(), ex);
}
return result;
}
public byte[] changeClassBytes(String className, byte[] bytes)
throws ClassNotFoundException {
byte[] result = null;
CtClass cc = null;
try {
cc = pool.makeClass(new ByteArrayInputStream(bytes));
if (cc.subtypeOf(pool.get(ActiveRecord.class.getName())) && furtherCheckAllowedToChange(cc)) {
addMethods(cc, ClassWorkSource.arMethods);
result = cc.toBytecode();
}
else {
result = bytes;
}
} catch (Exception ex) {
throw new ClassNotFoundException("classWork failed on " + className + ": " + ex.getMessage(), ex);
}
return result;
}
private boolean furtherCheckAllowedToChange(CtClass cc) throws NotFoundException {
boolean change = true;
String className = cc.getName();
String superClassName = cc.getSuperclass().getName();
int lastDot = className.lastIndexOf('.');
String tmpHelper = "";
String pkgName = "";
String modelName = className;
if (lastDot != -1) {
pkgName = className.substring(0, lastDot);
modelName = className.substring(lastDot + 1);
tmpHelper = pkgName + "."
+ AutoLoaderConfig.GENERATED_MODEL_CLASS_PREFIX + modelName
+ AutoLoaderConfig.GENERATED_MODEL_CLASS_SUFFIX;
}
else {
tmpHelper = AutoLoaderConfig.GENERATED_MODEL_CLASS_PREFIX + modelName
+ AutoLoaderConfig.GENERATED_MODEL_CLASS_SUFFIX;
}
if (superClassName.equals(tmpHelper)) change = false;
return change;
}
private void addMethods(CtClass cc, List<String> methods) throws CannotCompileException, IOException {
cc.defrost();
CtMethod m = null;
Iterator<String> it = methods.iterator();
while (it.hasNext()) {
String method = it.next();
m = CtMethod.make(filterSourceCode(method, cc.getName()), cc);
try {
cc.addMethod(m);
}
catch(DuplicateMemberException dme) {
}
}
cc.writeFile(ApplicationConfig.getInstance().getClassFileLocationPath());
}
private String filterSourceCode(String src, String className) {
if (src.indexOf("@@") != -1) {
src = StringUtil.replace(src, "@@", className);
}
return src;
}
}