/**********************************************************************
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 org.datanucleus.enhancer.ClassEnhancer;
import org.datanucleus.enhancer.DataNucleusEnhancer;
import org.datanucleus.enhancer.asm.method.InitClass;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ClassPersistenceModifier;
import org.datanucleus.metadata.FieldMetaData;
import org.datanucleus.metadata.FieldPersistenceModifier;
import org.datanucleus.util.Localiser;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* Adapter for methods in persistence-enabled classes allowing enhancement of direct access to user fields.
* Currently performs the following updates
* <ul>
* <li>Any GETFIELD on a field of a Persistable class is replaced by a call to aaaGetXXX()</li>
* <li>Any PUTFIELD on a field of a Persistable class is replaced by a call to aaaSetXXX()</li>
* <li>Any clone() method that has no superclass but calls clone() is changed to call aaaSuperClone()</li>
* <li>Any static class initialisation adds on the "InitClass" instructions</li>
* </ul>
*/
public class JdoMethodAdapter extends MethodAdapter
{
/** Localisation of messages. */
protected static final Localiser LOCALISER = Localiser.getInstance("org.datanucleus.enhancer.Localisation",
ClassEnhancer.class.getClassLoader());
/** The enhancer for this class. */
protected ASMClassEnhancer enhancer;
/** Name for the method being adapted. */
protected String methodName;
/** Descriptor for the method being adapted. */
protected String methodDescriptor;
/**
* Constructor for the method adapter.
* @param mv MethodVisitor
* @param enhancer ClassEnhancer for the class with the method
*/
public JdoMethodAdapter(MethodVisitor mv, ASMClassEnhancer enhancer, String methodName, String methodDesc)
{
super(mv);
this.enhancer = enhancer;
this.methodName = methodName;
this.methodDescriptor = methodDesc;
}
/**
* Method to intercept any calls to fields.
* @param opcode Operation
* @param owner Owner class
* @param name Name of the field
* @param desc Descriptor for the field
*/
public void visitFieldInsn(int opcode, String owner, String name, String desc)
{
String ownerName = owner.replace('/', '.');
if (enhancer.isPersistenceCapable(ownerName))
{
AbstractClassMetaData cmd = null;
boolean fieldInThisClass = true;
if (enhancer.getClassMetaData().getFullClassName().equals(ownerName))
{
// Same class so use the input MetaData
cmd = enhancer.getClassMetaData();
}
else
{
fieldInThisClass = false;
cmd = enhancer.getMetaDataManager().getMetaDataForClass(ownerName,
enhancer.getClassLoaderResolver());
}
// If the field access is in this class and this is the constructor then don't enhance it.
// This is because this object is not connected to a StateManager nor is it detached.
// Also languages like Scala don't necessarily initialise superclasses first and so
// enhancing here would cause issues
if (!fieldInThisClass || !(methodName.equals("<init>")))
{
AbstractMemberMetaData fmd = cmd.getMetaDataForMember(name);
if (fmd != null && !fmd.isStatic() && !fmd.isFinal() &&
fmd.getPersistenceModifier() != FieldPersistenceModifier.NONE &&
fmd.getPersistenceFlags() != 0 && fmd instanceof FieldMetaData)
{
// Field being accessed has its access mediated by the enhancer, so intercept it
// Make sure we address the field being in the class it is actually in
String fieldOwner = fmd.getClassName(true).replace('.', '/');
if (opcode == Opcodes.GETFIELD)
{
// Read of a field of a PC class, so replace with jdoGetXXX() call
mv.visitMethodInsn(Opcodes.INVOKESTATIC, fieldOwner, enhancer.getGetMethodPrefixMethodName() + name,
"(L" + fieldOwner + ";)" + desc);
if (DataNucleusEnhancer.LOGGER.isDebugEnabled())
{
DataNucleusEnhancer.LOGGER.debug(LOCALISER.msg("Enhancer.EnhanceOriginalMethodField",
enhancer.className + "." + methodName, (fmd.getClassName(true) + "." + name),
enhancer.getGetMethodPrefixMethodName() + name + "()"));
}
return;
}
else if (opcode == Opcodes.PUTFIELD)
{
// Write of a field of a PC class, so replace with jdoSetXXX() call
mv.visitMethodInsn(Opcodes.INVOKESTATIC, fieldOwner, enhancer.getSetMethodPrefixMethodName() + name,
"(L" + fieldOwner + ";" + desc + ")V");
if (DataNucleusEnhancer.LOGGER.isDebugEnabled())
{
DataNucleusEnhancer.LOGGER.debug(LOCALISER.msg("Enhancer.EnhanceOriginalMethodField",
enhancer.className + "." + methodName, (fmd.getClassName(true) + "." + name),
enhancer.getSetMethodPrefixMethodName() + name + "()"));
}
return;
}
}
}
else
{
DataNucleusEnhancer.LOGGER.debug(LOCALISER.msg("Enhancer.EnhanceOriginalMethodFieldOmit",
enhancer.className + "." + methodName, (opcode == Opcodes.GETFIELD ? "get" : "set"),
(ownerName + "." + name)));
}
}
super.visitFieldInsn(opcode, owner, name, desc);
}
/**
* Method to intercept any calls to methods.
* @param opcode Operation
* @param owner Owner class
* @param name Name of the field
* @param desc Descriptor for the field
*/
public void visitMethodInsn(int opcode, String owner, String name, String desc)
{
if (methodName.equals("clone") && methodDescriptor.equals("()Ljava/lang/Object;") &&
enhancer.getClassMetaData().getPersistenceCapableSuperclass() == null &&
opcode == Opcodes.INVOKESPECIAL && name.equals("clone") && desc.equals("()Ljava/lang/Object;"))
{
// clone() method calls super.clone() so change to use JdoSuperClone()
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, enhancer.getASMClassName(), "jdoSuperClone", "()Ljava/lang/Object;");
return;
}
super.visitMethodInsn(opcode, owner, name, desc);
}
/**
* Method to intercept any general instructions.
* We use it to intercept any RETURN on a static initialisation block so we can append to it.
* @param opcode Operation
*/
public void visitInsn(int opcode)
{
if (enhancer.getClassMetaData().getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE &&
methodName.equals("<clinit>") && methodDescriptor.equals("()V") && opcode == Opcodes.RETURN)
{
// Add the initialise instructions to the existing block
InitClass initMethod = InitClass.getInstance(enhancer);
initMethod.addInitialiseInstructions(mv);
// Add the RETURN
mv.visitInsn(Opcodes.RETURN);
return;
}
super.visitInsn(opcode);
}
}