/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.amber.gen;
import com.caucho.amber.field.AmberField;
import com.caucho.amber.manager.AmberContainer;
import com.caucho.amber.type.*;
import com.caucho.bytecode.Analyzer;
import com.caucho.bytecode.CodeVisitor;
import com.caucho.bytecode.ConstantPool;
import com.caucho.bytecode.FieldRefConstant;
import com.caucho.bytecode.JClass;
import com.caucho.bytecode.JavaClass;
import com.caucho.bytecode.JavaMethod;
import com.caucho.bytecode.MethodRefConstant;
import com.caucho.config.ConfigException;
import com.caucho.java.JavaCompiler;
import com.caucho.java.WorkDir;
import com.caucho.java.gen.ClassComponent;
import com.caucho.java.gen.DependencyComponent;
import com.caucho.java.gen.GenClass;
import com.caucho.java.gen.JavaClassGenerator;
import com.caucho.loader.*;
import com.caucho.loader.enhancer.ClassEnhancer;
import com.caucho.loader.enhancer.EnhancerPrepare;
import com.caucho.util.L10N;
import com.caucho.vfs.Path;
import com.caucho.vfs.Vfs;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Enhancing the java objects for Amber mapping.
*/
public class AmberEnhancer implements AmberGenerator, ClassEnhancer {
private static final L10N L = new L10N(AmberEnhancer.class);
private static final Logger log
= Logger.getLogger(AmberEnhancer.class.getName());
private Path _configDirectory;
private boolean _useHibernateFiles;
private AmberContainer _amberContainer;
private EnhancerPrepare _prepare;
private Path _workDir;
private Path _postWorkDir;
private ArrayList<String> _pendingClassNames = new ArrayList<String>();
public AmberEnhancer(AmberContainer amberContainer)
{
_amberContainer = amberContainer;
_workDir = WorkDir.getLocalWorkDir().lookup("pre-enhance");
_postWorkDir = WorkDir.getLocalWorkDir().lookup("post-enhance");
_prepare = new EnhancerPrepare();
_prepare.setClassLoader(Thread.currentThread().getContextClassLoader());
_prepare.setWorkPath(WorkDir.getLocalWorkDir());
_prepare.addEnhancer(this);
}
/**
* Sets the config directory.
*/
public void setConfigDirectory(Path dir)
{
_configDirectory = dir;
}
/**
* Returns the work directory.
*/
public Path getWorkDir()
{
return _workDir;
}
/**
* Returns the work directory.
*/
public Path getPostWorkDir()
{
return _postWorkDir;
}
/**
* Initialize the enhancer.
*/
public void init()
throws Exception
{
}
/**
* Checks to see if the preloaded class is modified.
*/
protected boolean isModified(Class preloadedClass)
{
try {
Method init = preloadedClass.getMethod("_caucho_init",
new Class[] { Path.class });
if (_configDirectory != null)
init.invoke(null, new Object[] { _configDirectory });
else
init.invoke(null, new Object[] { Vfs.lookup() });
Method isModified = preloadedClass.getMethod("_caucho_is_modified",
new Class[0]);
Object value = isModified.invoke(null, new Object[0]);
if (Boolean.FALSE.equals(value)) {
loadEntityType(preloadedClass, preloadedClass.getClassLoader());
return false;
}
else
return true;
} catch (Throwable e) {
log.log(Level.FINER, e.toString(), e);
return true;
}
}
/**
* Returns true if the class should be enhanced.
*/
public boolean shouldEnhance(String className)
{
className = className.replace('/', '.');
int p = className.lastIndexOf('-');
if (p > 0)
className = className.substring(0, p);
p = className.lastIndexOf('$');
if (p > 0)
className = className.substring(0, p);
AbstractEnhancedType type;
type = _amberContainer.getEntity(className);
if (type != null && type.isEnhanced())
return true;
type = _amberContainer.getMappedSuperclass(className);
if (type != null && type.isEnhanced())
return true;
type = _amberContainer.getEmbeddable(className);
if (type != null && type.isEnhanced())
return true;
type = _amberContainer.getListener(className);
if (type != null && type.isEnhanced())
return true;
return false;
/*
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(getRawLoader());
Class baseClass = Class.forName(className, false, getRawLoader());
type = loadEntityType(baseClass, getRawLoader());
} catch (ClassNotFoundException e) {
return false;
} finally {
thread.setContextClassLoader(oldLoader);
}
if (type == null)
return false;
return className.equals(type.getName()) || type.isFieldAccess();
*/
}
/**
* Returns true if the class should be enhanced.
*/
private EntityType loadEntityType(Class cl, ClassLoader loader)
{
EntityType parentType = null;
for (; cl != null; cl = cl.getSuperclass()) {
java.net.URL url;
String className = cl.getName();
EntityType type = _amberContainer.getEntity(className);
if (parentType == null)
parentType = type;
if (type != null && ! type.startConfigure())
return type;
type = loadEntityTypeImpl(cl, loader);
if (type != null && ! type.startConfigure())
return type;
}
return parentType;
}
protected EntityType loadEntityTypeImpl(Class cl, ClassLoader rawLoader)
{
return null;
}
/**
* Enhances the class.
*/
public void preEnhance(JavaClass baseClass)
throws Exception
{
EntityType type = _amberContainer.getEntity(baseClass.getName());
if (type == null)
type = _amberContainer.getMappedSuperclass(baseClass.getName());
if (type != null && type.getParentType() != null) {
String parentClass = type.getParentType().getInstanceClassName();
baseClass.setSuperClass(parentClass.replace('.', '/'));
}
}
/**
* Enhances the class.
*/
public void enhance(GenClass genClass,
JClass baseClass,
String extClassName)
throws Exception
{
String className = baseClass.getName();
EntityType type = _amberContainer.getEntity(className);
if (type == null)
type = _amberContainer.getMappedSuperclass(className);
// Type can be null for subclasses and inner classes that need fixups
if (type != null) {
// type is EntityType or MappedSuperclassType
log.info("Amber enhancing class " + className);
// XXX: _amberContainerenceUnitenceUnit.configure();
type.init();
genClass.addInterfaceName(type.getComponentInterfaceName());
genClass.addImport("java.util.logging.*");
genClass.addImport("com.caucho.amber.manager.*");
genClass.addImport("com.caucho.amber.entity.*");
genClass.addImport("com.caucho.amber.type.*");
AmberMappedComponent componentGenerator
= (AmberMappedComponent) type.getComponentGenerator();
componentGenerator.setRelatedType((EntityType) type);
componentGenerator.setBaseClassName(baseClass.getName());
componentGenerator.setExtClassName(extClassName);
genClass.addComponent(componentGenerator);
DependencyComponent dependency = genClass.addDependencyComponent();
dependency.addDependencyList(type.getDependencies());
return;
//_amberContainerenceUnitenceUnit.generate();
// generate(type);
// compile();
// XXX: _amberContainerenceUnitenceUnit.initEntityHomes();
}
ListenerType listenerType = _amberContainer.getListener(className);
// Type can be null for subclasses and inner classes that need fixups
if (listenerType != null) {
if (log.isLoggable(Level.INFO))
log.log(Level.INFO, "Amber enhancing class " + className);
listenerType.init();
genClass.addInterfaceName("com.caucho.amber.entity.Listener");
ListenerComponent listener = new ListenerComponent();
listener.setListenerType(listenerType);
listener.setBaseClassName(baseClass.getName());
listener.setExtClassName(extClassName);
genClass.addComponent(listener);
}
EmbeddableType embeddableType = _amberContainer.getEmbeddable(className);
// Type can be null for subclasses and inner classes that need fixups
if (embeddableType != null) {
if (log.isLoggable(Level.INFO))
log.log(Level.INFO, "Amber enhancing class " + className);
embeddableType.init();
genClass.addInterfaceName("com.caucho.amber.entity.Embeddable");
EmbeddableComponent embeddable = new EmbeddableComponent();
embeddable.setEmbeddableType(embeddableType);
embeddable.setBaseClassName(baseClass.getName());
embeddable.setExtClassName(extClassName);
genClass.addComponent(embeddable);
}
}
/**
* Generates the type.
*/
public void generate(AbstractEnhancedType type)
throws Exception
{
String className = type.getBeanClass().getName();
if (! isModified(className))
return;
JavaClassGenerator javaGen = new JavaClassGenerator();
javaGen.setWorkDir(getWorkDir());
String extClassName = type.getBeanClass().getName() + "__ResinExt";
type.setInstanceClassName(extClassName);
type.setEnhanced(true);
_pendingClassNames.add(type.getInstanceClassName());
generateJava(javaGen, type);
}
/**
* Generates the type.
*/
public void generateJava(JavaClassGenerator javaGen,
AbstractEnhancedType type)
throws Exception
{
if (type.isGenerated())
return;
type.setGenerated(true);
_prepare.renameClass(type.getBeanClass().getName(),
type.getBeanClass().getName());
GenClass javaClass = new GenClass(type.getInstanceClassName());
javaClass.setSuperClassName(type.getBeanClass().getName());
javaClass.addImport("java.util.logging.*");
javaClass.addImport("com.caucho.amber.manager.*");
javaClass.addImport("com.caucho.amber.entity.*");
javaClass.addImport("com.caucho.amber.type.*");
ClassComponent componentGenerator
= type.getComponentGenerator();
if (componentGenerator instanceof AmberMappedComponent) {
AmberMappedComponent entityGenerator
= (AmberMappedComponent) componentGenerator;
// type is EntityType or MappedSuperclassType
javaClass.addInterfaceName(type.getComponentInterfaceName());
type.setEnhanced(true);
entityGenerator.setRelatedType((EntityType) type);
entityGenerator.setBaseClassName(type.getBeanClass().getName());
//String extClassName = gen.getBaseClassName() + "__ResinExt";
// type.setInstanceClassName(extClassName);
entityGenerator.setExtClassName(type.getInstanceClassName());
javaClass.addComponent(componentGenerator);
}
else if (type instanceof ListenerType) {
javaClass.addInterfaceName("com.caucho.amber.entity.Listener");
type.setEnhanced(true);
ListenerComponent listener = new ListenerComponent();
listener.setListenerType((ListenerType) type);
listener.setBaseClassName(type.getBeanClass().getName());
listener.setExtClassName(type.getInstanceClassName());
javaClass.addComponent(listener);
}
else if (componentGenerator instanceof EmbeddableComponent) {
EmbeddableComponent embeddable
= (EmbeddableComponent) componentGenerator;
javaClass.addInterfaceName("com.caucho.amber.entity.Embeddable");
type.setEnhanced(true);
embeddable.setEmbeddableType((EmbeddableType) type);
embeddable.setBaseClassName(type.getBeanClass().getName());
embeddable.setExtClassName(type.getInstanceClassName());
javaClass.addComponent(embeddable);
}
javaGen.generate(javaClass);
// _pendingClassNames.add(extClassName);
}
private boolean isModified(String className)
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
ClassLoader loader = _amberContainer.getParentClassLoader();
ClassLoader tempLoader
= ((DynamicClassLoader) loader).getNewTempClassLoader();
DynamicClassLoader workLoader
= SimpleLoader.create(tempLoader, getPostWorkDir());
workLoader.setServletHack(true);
Class cl = Class.forName(className.replace('/', '.'),
false,
workLoader);
thread.setContextClassLoader(tempLoader);
Method init = cl.getMethod("_caucho_init", new Class[] { Path.class });
Method modified = cl.getMethod("_caucho_is_modified", new Class[0]);
init.invoke(null, Vfs.lookup());
return (Boolean) modified.invoke(null);
} catch (ClassNotFoundException e) {
log.log(Level.FINEST, e.toString(), e);
} catch (NoSuchMethodException e) {
log.log(Level.FINEST, e.toString(), e);
} catch (Throwable e) {
log.log(Level.FINER, e.toString(), e);
} finally {
thread.setContextClassLoader(oldLoader);
}
return true;
}
/**
* Compiles the pending classes.
*/
public void compile()
throws Exception
{
if (_pendingClassNames.size() == 0)
return;
ArrayList<String> classNames = new ArrayList<String>(_pendingClassNames);
_pendingClassNames.clear();
String []javaFiles = new String[classNames.size()];
for (int i = 0; i < classNames.size(); i++) {
String className = classNames.get(i);
String javaName = className.replace('.', '/') + ".java";
javaFiles[i] = javaName;
}
EntityGenerator gen = new EntityGenerator();
gen.setSearchPath(_configDirectory);
// XXX:
// gen.setClassDir(getPath());
JavaCompiler compiler = gen.getCompiler();
compiler.setClassDir(getWorkDir());
compiler.compileBatch(javaFiles);
for (int i = 0; i < classNames.size(); i++) {
String extClassName = classNames.get(i);
int tail = extClassName.length() - "__ResinExt".length();
String baseClassName = extClassName.substring(0, tail);
// fixup(baseClassName, extClassName);
}
}
/**
* Enhances the class.
*/
public void postEnhance(JavaClass baseClass)
throws Exception
{
String className = baseClass.getThisClass();
ArrayList<FieldMap> fieldMaps = new ArrayList<FieldMap>();
Class thisClass = _amberContainer.loadTempClass(className.replace('/', '.'));
if (thisClass == null)
return;
// Cache entity JClass for next fixup.
Class entityClass = thisClass;
// Field-based fixup.
do {
BeanType type;
type = _amberContainer.getEntity(thisClass.getName());
if (type == null)
type = _amberContainer.getMappedSuperclass(thisClass.getName());
if (type == null)
type = _amberContainer.getEmbeddable(thisClass.getName());
if (type == null || ! type.isFieldAccess())
continue;
if (type instanceof EmbeddableType)
continue;
if (type instanceof EntityType) {
EntityType entityType = (EntityType) type;
/*
for (AmberField field : entityType.getId().getKeys()) {
fieldMaps.add(new FieldMap(baseClass, field.getName()));
}
*/
}
for (AmberField field : type.getFields()) {
fieldMaps.add(new FieldMap(baseClass, field.getName()));
}
} while ((thisClass = thisClass.getSuperclass()) != null);
if (fieldMaps.size() > 0) {
FieldFixupAnalyzer analyzer = new FieldFixupAnalyzer(fieldMaps);
for (JavaMethod javaMethod : baseClass.getMethodList()) {
if (javaMethod.getName().startsWith("__caucho_get_"))
continue;
else if (javaMethod.getName().startsWith("__caucho_set_"))
continue;
else if (javaMethod.getName().startsWith("__caucho_super_"))
continue;
CodeVisitor visitor = new CodeVisitor(baseClass, javaMethod.getCode());
visitor.analyze(analyzer, true);
}
}
}
/**
* Parses the configuration file.
*/
public void configure(AbstractEnhancedType type)
throws ConfigException, IOException
{
}
static class FieldMap {
private int _fieldRef = -1;
private int _getterRef;
private int _setterRef;
FieldMap(com.caucho.bytecode.JavaClass baseClass,
String fieldName)
{
ConstantPool pool = baseClass.getConstantPool();
FieldRefConstant fieldRef = pool.getFieldRef(fieldName);
if (fieldRef == null)
return;
_fieldRef = fieldRef.getIndex();
MethodRefConstant methodRef;
String getterName = "__caucho_get_" + fieldName;
methodRef = pool.addMethodRef(baseClass.getThisClass(),
getterName,
"()" + fieldRef.getType());
_getterRef = methodRef.getIndex();
String setterName = "__caucho_set_" + fieldName;
methodRef = pool.addMethodRef(baseClass.getThisClass(),
setterName,
"(" + fieldRef.getType() + ")V");
_setterRef = methodRef.getIndex();
}
int getFieldRef()
{
return _fieldRef;
}
int getGetterRef()
{
return _getterRef;
}
int getSetterRef()
{
return _setterRef;
}
}
static class FieldFixupAnalyzer extends Analyzer {
private ArrayList<FieldMap> _fieldMap;
FieldFixupAnalyzer(ArrayList<FieldMap> fieldMap)
{
_fieldMap = fieldMap;
}
int getGetter(int fieldRef)
{
for (int i = _fieldMap.size() - 1; i >= 0; i--) {
FieldMap fieldMap = _fieldMap.get(i);
if (fieldMap.getFieldRef() == fieldRef)
return fieldMap.getGetterRef();
}
return -1;
}
public void analyze(CodeVisitor visitor)
{
switch (visitor.getOpcode()) {
case CodeVisitor.GETFIELD:
int getter = getGetter(visitor.getShortArg());
if (getter > 0) {
visitor.setByteArg(0, CodeVisitor.INVOKEVIRTUAL);
visitor.setShortArg(1, getter);
}
break;
case CodeVisitor.PUTFIELD:
int setter = getSetter(visitor.getShortArg());
if (setter > 0) {
visitor.setByteArg(0, CodeVisitor.INVOKEVIRTUAL);
visitor.setShortArg(1, setter);
}
break;
}
}
int getSetter(int fieldRef)
{
for (int i = _fieldMap.size() - 1; i >= 0; i--) {
FieldMap fieldMap = _fieldMap.get(i);
if (fieldMap.getFieldRef() == fieldRef)
return fieldMap.getSetterRef();
}
return -1;
}
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + _configDirectory + "]";
}
}