/**********************************************************************
Copyright (c) 2007 Andy Jefferson and others. All rights reserved.
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.
Contributors:
...
**********************************************************************/
package org.datanucleus.enhancer.asm;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.enhancer.AbstractClassEnhancer;
import org.datanucleus.enhancer.ClassField;
import org.datanucleus.enhancer.DataNucleusEnhancer;
import org.datanucleus.enhancer.asm.method.InitFieldFlags;
import org.datanucleus.enhancer.asm.method.InitFieldNames;
import org.datanucleus.enhancer.asm.method.InitFieldTypes;
import org.datanucleus.enhancer.asm.method.InitPersistenceCapableSuperclass;
import org.datanucleus.enhancer.asm.method.JdoCopyField;
import org.datanucleus.enhancer.asm.method.JdoCopyFields;
import org.datanucleus.enhancer.asm.method.JdoCopyKeyFieldsFromObjectId;
import org.datanucleus.enhancer.asm.method.JdoCopyKeyFieldsFromObjectId2;
import org.datanucleus.enhancer.asm.method.JdoCopyKeyFieldsToObjectId;
import org.datanucleus.enhancer.asm.method.JdoCopyKeyFieldsToObjectId2;
import org.datanucleus.enhancer.asm.method.JdoGetInheritedFieldCount;
import org.datanucleus.enhancer.asm.method.JdoGetManagedFieldCount;
import org.datanucleus.enhancer.asm.method.JdoGetObjectId;
import org.datanucleus.enhancer.asm.method.JdoGetPersistenceManager;
import org.datanucleus.enhancer.asm.method.JdoGetTransactionalObjectId;
import org.datanucleus.enhancer.asm.method.JdoGetVersion;
import org.datanucleus.enhancer.asm.method.JdoIsDeleted;
import org.datanucleus.enhancer.asm.method.JdoIsDetached;
import org.datanucleus.enhancer.asm.method.JdoIsDirty;
import org.datanucleus.enhancer.asm.method.JdoIsNew;
import org.datanucleus.enhancer.asm.method.JdoIsPersistent;
import org.datanucleus.enhancer.asm.method.JdoIsTransactional;
import org.datanucleus.enhancer.asm.method.JdoMakeDirty;
import org.datanucleus.enhancer.asm.method.JdoNewInstance1;
import org.datanucleus.enhancer.asm.method.JdoNewInstance2;
import org.datanucleus.enhancer.asm.method.JdoNewObjectIdInstance1;
import org.datanucleus.enhancer.asm.method.JdoNewObjectIdInstance2;
import org.datanucleus.enhancer.asm.method.JdoPreSerialize;
import org.datanucleus.enhancer.asm.method.JdoProvideField;
import org.datanucleus.enhancer.asm.method.JdoProvideFields;
import org.datanucleus.enhancer.asm.method.JdoReplaceDetachedState;
import org.datanucleus.enhancer.asm.method.JdoReplaceField;
import org.datanucleus.enhancer.asm.method.JdoReplaceFields;
import org.datanucleus.enhancer.asm.method.JdoReplaceFlags;
import org.datanucleus.enhancer.asm.method.JdoReplaceStateManager;
import org.datanucleus.enhancer.asm.method.JdoSuperClone;
import org.datanucleus.enhancer.asm.method.LoadClass;
import org.datanucleus.metadata.ClassMetaData;
import org.datanucleus.metadata.ClassPersistenceModifier;
import org.datanucleus.util.StringUtils;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* Class enhancer using ASM (http://asm.objectweb.org).
* Assumes that the version of ASM is 3.0 or above.
* ASM operates using a SAXParser-like "visitor-pattern". We utilise this as follows :-
* <ul>
* <li><b>enhance</b> : start with a ClassReader for the class to be enhanced, and create a JdoClassAdapter
* (attached to a ClassWriter) that will perform the modifications, and use that as a visitor for the reader
* so that the reader sends its events to the adapter. Within the JdoClassAdapter we also make use of a
* JdoMethodAdapter to update individual methods</li>
* <li><b>check</b> : take a ClassReader, and create a JdoClassChecker that performs the checks. We then set
* the checker as a visitor for the reader so that the reader sends its events to the checker.</li>
* </ul>
*/
public class ASMClassEnhancer extends AbstractClassEnhancer
{
/** Resource name of the input class (only when the class exists in a class file). */
protected String inputResourceName;
/** Bytes of the input class (only when enhancing generated classes with no class file). */
protected byte[] inputBytes;
/** Class that is being enhanced. */
protected final Class cls;
/** Bytes of the class (after enhancing). */
protected byte[] classBytes = null;
/** ASM Class name for this class (replace . with /). */
protected String asmClassName = null;
/** Class descriptor for this class. */
protected String classDescriptor = null;
/**
* Constructor.
* @param cmd MetaData for the class to be enhanced
* @param clr ClassLoader resolver
*/
public ASMClassEnhancer(ClassMetaData cmd, ClassLoaderResolver clr)
{
super(cmd, clr);
cls = clr.classForName(cmd.getFullClassName());
asmClassName = cmd.getFullClassName().replace('.', '/');
classDescriptor = Type.getDescriptor(cls);
inputResourceName = "/" + className.replace('.','/') + ".class";
}
/**
* Constructor.
* @param cmd MetaData for the class to be enhanced
* @param clr ClassLoader resolver
* @param classBytes Bytes of the class to enhance
*/
public ASMClassEnhancer(ClassMetaData cmd, ClassLoaderResolver clr, byte[] classBytes)
{
super(cmd, clr);
cls = clr.classForName(cmd.getFullClassName());
asmClassName = cmd.getFullClassName().replace('.', '/');
classDescriptor = Type.getDescriptor(cls);
inputBytes = classBytes;
}
/**
* Convenience accessor for the class name that is stored in a particular class.
* @param filename Name of the file
* @return The class name
*/
public static String getClassNameForFileName(String filename)
{
MyClassVisitor vis = new MyClassVisitor();
try
{
new ClassReader(new FileInputStream(filename)).accept(vis, 0);
return vis.getClassName();
}
catch (IOException ioe)
{
return null;
}
}
/** Convenience class to look up the class name for a file. */
public static class MyClassVisitor implements ClassVisitor
{
String className = null;
public String getClassName()
{
return className;
}
public void visitInnerClass(String name, String outerName, String innerName, int access) { }
public void visit(int version, int access, String name, String sig, String supername, String[] intfs)
{
className = name.replace('/', '.');
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible)
{
return null;
}
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
{
return null;
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] excpts)
{
return null;
}
public void visitAttribute(Attribute attr) { }
public void visitOuterClass(String owner, String name, String desc) { }
public void visitSource(String source, String debug) { }
public void visitEnd() { }
}
/**
* Accessor for the class being enhanced.
* @return Class being enhanced
*/
public Class getClassEnhanced()
{
return cls;
}
/**
* Accessor for the ASM class name
* @return ASM class name
*/
public String getASMClassName()
{
return asmClassName;
}
/**
* Accessor for the class descriptor for the class being enhanced
* @return class descriptor
*/
public String getClassDescriptor()
{
return classDescriptor;
}
/**
* Method to initialise the list of methods to add.
*/
protected void initialiseMethodsList()
{
if (cmd.getPersistenceCapableSuperclass() == null)
{
// Root persistent class methods
methodsToAdd.add(JdoCopyKeyFieldsFromObjectId.getInstance(this));
methodsToAdd.add(JdoCopyKeyFieldsFromObjectId2.getInstance(this));
methodsToAdd.add(JdoCopyKeyFieldsToObjectId.getInstance(this));
methodsToAdd.add(JdoCopyKeyFieldsToObjectId2.getInstance(this));
methodsToAdd.add(JdoGetObjectId.getInstance(this));
methodsToAdd.add(JdoGetVersion.getInstance(this));
methodsToAdd.add(JdoPreSerialize.getInstance(this));
methodsToAdd.add(JdoGetPersistenceManager.getInstance(this));
methodsToAdd.add(JdoGetTransactionalObjectId.getInstance(this));
methodsToAdd.add(JdoIsDeleted.getInstance(this));
methodsToAdd.add(JdoIsDirty.getInstance(this));
methodsToAdd.add(JdoIsNew.getInstance(this));
methodsToAdd.add(JdoIsPersistent.getInstance(this));
methodsToAdd.add(JdoIsTransactional.getInstance(this));
methodsToAdd.add(JdoMakeDirty.getInstance(this));
methodsToAdd.add(JdoNewObjectIdInstance1.getInstance(this));
methodsToAdd.add(JdoNewObjectIdInstance2.getInstance(this));
methodsToAdd.add(JdoProvideFields.getInstance(this));
methodsToAdd.add(JdoReplaceFields.getInstance(this));
methodsToAdd.add(JdoReplaceFlags.getInstance(this));
methodsToAdd.add(JdoReplaceStateManager.getInstance(this));
}
if (requiresDetachable())
{
methodsToAdd.add(JdoReplaceDetachedState.getInstance(this));
}
if (cmd.isDetachable() && cmd.getPersistenceCapableSuperclass() != null)
{
methodsToAdd.add(JdoMakeDirty.getInstance(this));
}
methodsToAdd.add(JdoIsDetached.getInstance(this));
methodsToAdd.add(JdoNewInstance1.getInstance(this));
methodsToAdd.add(JdoNewInstance2.getInstance(this));
methodsToAdd.add(JdoReplaceField.getInstance(this));
methodsToAdd.add(JdoProvideField.getInstance(this));
methodsToAdd.add(JdoCopyField.getInstance(this));
methodsToAdd.add(JdoCopyFields.getInstance(this));
methodsToAdd.add(InitFieldNames.getInstance(this));
methodsToAdd.add(InitFieldTypes.getInstance(this));
methodsToAdd.add(InitFieldFlags.getInstance(this));
methodsToAdd.add(JdoGetInheritedFieldCount.getInstance(this));
methodsToAdd.add(JdoGetManagedFieldCount.getInstance(this));
methodsToAdd.add(InitPersistenceCapableSuperclass.getInstance(this));
methodsToAdd.add(LoadClass.getInstance(this));
methodsToAdd.add(JdoSuperClone.getInstance(this));
}
/**
* Method to initialise the list of fields to add.
*/
protected void initialiseFieldsList()
{
if (cmd.getPersistenceCapableSuperclass() == null)
{
// Root persistent class fields
fieldsToAdd.add(new ClassField(this, FN_StateManager,
Opcodes.ACC_PROTECTED | Opcodes.ACC_TRANSIENT, javax.jdo.spi.StateManager.class));
fieldsToAdd.add(new ClassField(this, FN_Flag,
Opcodes.ACC_PROTECTED | Opcodes.ACC_TRANSIENT, byte.class));
}
if (requiresDetachable())
{
// Detachable fields
fieldsToAdd.add(new ClassField(this, FN_JdoDetachedState,
Opcodes.ACC_PROTECTED, Object[].class));
}
fieldsToAdd.add(new ClassField(this, FN_FieldFlags,
Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, byte[].class));
fieldsToAdd.add(new ClassField(this, FN_PersistenceCapableSuperclass,
Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, Class.class));
fieldsToAdd.add(new ClassField(this, FN_FieldTypes,
Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, Class[].class));
fieldsToAdd.add(new ClassField(this, FN_FieldNames,
Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, String[].class));
fieldsToAdd.add(new ClassField(this, FN_JdoInheritedFieldCount,
Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, int.class));
}
/**
* Method to enhance a classes definition.
* @return Whether it was enhanced with no errors
*/
public boolean enhance()
{
if (cmd.getPersistenceModifier() != ClassPersistenceModifier.PERSISTENCE_CAPABLE &&
cmd.getPersistenceModifier() != ClassPersistenceModifier.PERSISTENCE_AWARE)
{
return false;
}
initialise();
if (checkClassIsEnhanced(false))
{
// Already enhanced
DataNucleusEnhancer.LOGGER.info(LOCALISER.msg("Enhancer.ClassIsAlreadyEnhanced", className));
return true;
}
try
{
// Create an adapter using a writer
// [ASM Note : In 2.2 this should be "new ClassWriter(true, true);"]
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
JdoClassAdapter cv = new JdoClassAdapter(cw, this);
ClassReader cr = null;
InputStream classReaderInputStream = null;
try
{
// Create a reader for the class and tell it to visit the adapter, performing the changes
if (inputBytes != null)
{
cr = new ClassReader(inputBytes);
}
else
{
classReaderInputStream = clr.getResource(inputResourceName,null).openStream();
cr = new ClassReader(classReaderInputStream);
}
cr.accept(cv, 0); // [ASM Note : In 2.2 this should be "cr.accept(cv, false);"]
// Save the bytes
classBytes = cw.toByteArray();
}
finally
{
if (classReaderInputStream != null)
{
classReaderInputStream.close();
}
}
}
catch (Exception e)
{
DataNucleusEnhancer.LOGGER.error("Error thrown enhancing with ASMClassEnhancer", e);
return false;
}
update = true;
return true;
}
/**
* Accessor for the class bytes.
* Only has relevance to be called after enhance().
* @return The class bytes
*/
public byte[] getBytes()
{
return classBytes;
}
/**
* Method to save the class definition bytecode into a class file.
* If directoryName is specified it will be written to $directoryName/className.class
* else will overwrite the existing class.
* @param directoryName Name of a directory (or null to overwrite the class)
* @throws IOException If an I/O error occurs in the write.
*/
public void save(String directoryName)
throws IOException
{
if (!update)
{
// Not updated so nothing to do here
return;
}
File file = null;
if (directoryName != null)
{
File baseDir = new File(directoryName);
if (!baseDir.exists())
{
baseDir.mkdirs();
}
else if (!baseDir.isDirectory())
{
throw new RuntimeException("not directory " + directoryName);
}
String sep = System.getProperty("file.separator");
String name = cmd.getFullClassName();
name = name.replace('.', sep.charAt(0));
name = name + ".class";
file = new File(directoryName, name);
file.getParentFile().mkdirs();
DataNucleusEnhancer.LOGGER.info(LOCALISER.msg("Enhancer.UpdateClass", file.getCanonicalPath()));
}
else
{
URL classURL = clr.getResource(className.replace('.','/') + ".class", null);
DataNucleusEnhancer.LOGGER.info(LOCALISER.msg("Enhancer.UpdateClass", classURL));
URL convertedPath = this.cmd.getMetaDataManager().getOMFContext().getPluginManager().resolveURLAsFileURL(classURL);
if (!convertedPath.toString().equals(classURL.toString()))
{
DataNucleusEnhancer.LOGGER.info(LOCALISER.msg("Enhancer.UpdateClass", classURL));
}
file = StringUtils.getFileForFilename(convertedPath.getFile());
}
FileOutputStream out = null;
try
{
out = new FileOutputStream(file);
out.write(getBytes());
}
finally
{
try
{
out.close();
out = null;
}
catch (Exception ignore)
{
// ignore exception in closing the stream
}
}
}
/* (non-Javadoc)
* @see org.datanucleus.enhancer.ClassEnhancer#checkEnhanced()
*/
public boolean validate()
{
if (cmd.getPersistenceModifier() != ClassPersistenceModifier.PERSISTENCE_CAPABLE &&
cmd.getPersistenceModifier() != ClassPersistenceModifier.PERSISTENCE_AWARE)
{
return false;
}
initialise();
return checkClassIsEnhanced(true);
}
/**
* Convenience method to return if a class is enhanced.
* @param logErrors Whether to log any errors (missing methods etc) as errors (otherwise info/debug)
* @return Whether the class is enhanced
*/
protected boolean checkClassIsEnhanced(boolean logErrors)
{
try
{
// Create an adapter using a writer
JdoClassChecker checker = new JdoClassChecker(this, logErrors);
InputStream classReaderInputStream = null;
try
{
// Create a reader for the class and visit it using the checker
ClassReader cr = null;
if (inputBytes != null)
{
cr = new ClassReader(inputBytes);
}
else
{
classReaderInputStream = clr.getResource(inputResourceName,null).openStream();
cr = new ClassReader(classReaderInputStream);
}
cr.accept(checker, 0); // [ASM Note : In 2.2 this should be "cr.accept(checker, false);"]
}
finally
{
if (classReaderInputStream != null)
{
classReaderInputStream.close();
}
}
return checker.isEnhanced();
}
catch (Exception e)
{
DataNucleusEnhancer.LOGGER.error("Error thrown enhancing with ASMClassEnhancer", e);
}
return false;
}
}