org.datanucleus.enhancer.EnhancerClassAdapter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of datanucleus-core Show documentation
Show all versions of datanucleus-core Show documentation
DataNucleus Core provides the primary components of a heterogenous Java persistence solution.
It supports persistence API's being layered on top of the core functionality.
/**********************************************************************
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;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Iterator;
import java.util.List;
import org.datanucleus.enhancement.Persistable;
import org.datanucleus.enhancer.asm.ClassVisitor;
import org.datanucleus.enhancer.asm.FieldVisitor;
import org.datanucleus.enhancer.asm.MethodVisitor;
import org.datanucleus.enhancer.asm.Opcodes;
import org.datanucleus.enhancer.asm.Type;
import org.datanucleus.enhancer.methods.Clone;
import org.datanucleus.enhancer.methods.DefaultConstructor;
import org.datanucleus.enhancer.methods.InitClass;
import org.datanucleus.enhancer.methods.GetNormal;
import org.datanucleus.enhancer.methods.GetViaCheck;
import org.datanucleus.enhancer.methods.GetViaMediate;
import org.datanucleus.enhancer.methods.SetNormal;
import org.datanucleus.enhancer.methods.SetViaCheck;
import org.datanucleus.enhancer.methods.SetViaMediate;
import org.datanucleus.enhancer.methods.WriteObject;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ClassPersistenceModifier;
import org.datanucleus.metadata.FieldPersistenceModifier;
import org.datanucleus.metadata.PropertyMetaData;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.StringUtils;
/**
* Adapter visitor class for providing enhancement of an existing class using ASM.
* Is created with its own ClassWriter, and is passed to a ClassReader to visit the class.
* All parts of the class to be enhanced are fed through the different visitXXX methods here
* allowing intervention to either enhance an existing method, or to add on new fields/methods/interfaces.
*/
public class EnhancerClassAdapter extends ClassVisitor
{
/** The underlying enhancer. */
protected ClassEnhancer enhancer;
/** Whether a default constructor is present. Set if found, and then processed in visitEnd. */
protected boolean hasDefaultConstructor = false;
/** Whether the field serialVersionUID is present. Set if found, and processed in visitEnd. */
protected boolean hasSerialVersionUID = false;
/** Whether the field xxxDetachedState is present. Set if found, and processed in visitEnd. */
protected boolean hasDetachedState = false;
/** Whether the method writeObject(ObjectOutputStream) is present. Set if found, and processed in visitEnd. */
protected boolean hasWriteObject = false;
/** Whether the class already has a static init block. Set if found, and processed in visitEnd. */
protected boolean hasStaticInitialisation = false;
protected boolean needsClone = false;
/**
* Constructor.
* If the writer is null it means we just have to check the enhancement status
* @param cv The writer visitor
* @param enhancer ClassEnhancer
*/
public EnhancerClassAdapter(ClassVisitor cv, ClassEnhancer enhancer)
{
super(ClassEnhancer.ASM_API_VERSION, cv);
this.enhancer = enhancer;
if (enhancer.getClassMetaData().getSuperAbstractClassMetaData() == null && Cloneable.class.isAssignableFrom(enhancer.getClassBeingEnhanced()))
{
// Root persistable class that is Cloneable, in principle, needs "clone()"
needsClone = true;
}
}
/**
* Method called to visit the header of the class.
* @param version Version of this class
* @param access Access for the class
* @param name name of the class
* @param signature Signature of the class
* @param superName Superclass name (if any)
* @param interfaces Interface(s) implemented
*/
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
if (enhancer.getClassMetaData().getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE)
{
// Check if the class already implements required interfaces
boolean alreadyPersistable = false;
boolean alreadyDetachable = false;
boolean needsPersistable = false;
boolean needsDetachable = false;
int numInterfaces = 0;
if (interfaces != null && interfaces.length > 0)
{
numInterfaces = interfaces.length;
for (int i=0;i 0)
{
for (int i=0;i") && desc != null && desc.equals("()V"))
{
// Check for default constructor
hasDefaultConstructor = true;
}
if (name.equals("writeObject") && desc != null && desc.equals("(Ljava/io/ObjectOutputStream;)V"))
{
// Has writeObject() for use in serialisation
hasWriteObject = true;
}
if (name.equals("") && desc != null && desc.equals("()V"))
{
hasStaticInitialisation = true;
}
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (mv == null)
{
return null;
}
if (name.equals("jdoPreClear") || name.equals("jdoPostLoad"))
{
// jdoPreClear/jdoPostLoad should not be enhanced (JDO spec [10.1, 10.3]
return mv;
}
else if (name.equals("readObject") && desc != null && (desc.equals("(Ljava/io/ObjectOutputStream;)V") || desc.equals("(Ljava/io/ObjectInputStream;)V")))
{
// readObject(ObjectInputStream), readObject(ObjectOutputStream) should not be enhanced (JDO spec [21.6])
return mv;
}
else if (name.equals("clone") && desc != null && (desc.equals("()Ljava/lang/Object;")))
{
// "Object clone()"
needsClone = false; // User has provided clone so this will move it to "dnClone" and add a "clone" to call it.
return new EnhancerCloneAdapter(mv, enhancer, cv);
}
String propGetterName = ClassUtils.getFieldNameForJavaBeanGetter(name);
String propSetterName = ClassUtils.getFieldNameForJavaBeanSetter(name);
if ((access & Opcodes.ACC_BRIDGE) != Opcodes.ACC_BRIDGE) // Ignore bridge methods and treat as normal methods
{
if (propGetterName != null)
{
AbstractMemberMetaData mmd = enhancer.getClassMetaData().getMetaDataForMember(propGetterName);
if (mmd != null && mmd instanceof PropertyMetaData && mmd.getPersistenceModifier() != FieldPersistenceModifier.NONE)
{
// Property getter method "getXXX" - generated dnGetXXX
return new EnhancerPropertyGetterAdapter(mv, enhancer, name, desc, mmd, cv);
}
}
else if (propSetterName != null)
{
AbstractMemberMetaData mmd = enhancer.getClassMetaData().getMetaDataForMember(propSetterName);
if (mmd != null && mmd instanceof PropertyMetaData && mmd.getPersistenceModifier() != FieldPersistenceModifier.NONE)
{
// Property setter method "setXXX" - generates dnSetXXX
return new EnhancerPropertySetterAdapter(mv, enhancer, name, desc, mmd, cv);
}
}
}
// normal method, so just enhance it
return new EnhancerMethodAdapter(mv, enhancer, name, desc);
}
/**
* Method called at the end of the class.
*/
public void visitEnd()
{
AbstractClassMetaData cmd = enhancer.getClassMetaData();
if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE)
{
// Add any new fields
List fields = enhancer.getFieldsList();
Iterator fieldsIter = fields.iterator();
while (fieldsIter.hasNext())
{
ClassField field = (ClassField)fieldsIter.next();
if (field.getName().equals(enhancer.getNamer().getDetachedStateFieldName()) && hasDetachedState)
{
// No need to add this field since exists
continue;
}
if (DataNucleusEnhancer.LOGGER.isDebugEnabled())
{
DataNucleusEnhancer.LOGGER.debug(Localiser.msg("005021", ((Class)field.getType()).getName() + " " + field.getName()));
}
cv.visitField(field.getAccess(), field.getName(), Type.getDescriptor((Class)field.getType()), null, null);
}
if (!hasStaticInitialisation)
{
// Add a static initialisation block for the class since nothing added yet
InitClass method = InitClass.getInstance(enhancer);
method.initialise(cv);
method.execute();
method.close();
}
if (!hasDefaultConstructor && enhancer.hasOption(ClassEnhancer.OPTION_GENERATE_DEFAULT_CONSTRUCTOR))
{
// Add a default constructor
DefaultConstructor ctr = DefaultConstructor.getInstance(enhancer);
ctr.initialise(cv);
ctr.execute();
ctr.close();
}
// Add any new methods
List methods = enhancer.getMethodsList();
if (needsClone)
{
// Add a default clone() method
methods.add(Clone.getInstance(enhancer));
}
Iterator methodsIter = methods.iterator();
while (methodsIter.hasNext())
{
ClassMethod method = methodsIter.next();
method.initialise(cv);
method.execute();
method.close();
}
if (Serializable.class.isAssignableFrom(enhancer.getClassBeingEnhanced()))
{
// Class is Serializable
if (!hasSerialVersionUID)
{
// Needs "serialVersionUID" field
Long uid = null;
try
{
uid = (Long) AccessController.doPrivileged(new PrivilegedAction()
{
public Object run()
{
return Long.valueOf(ObjectStreamClass.lookup(enhancer.getClassBeingEnhanced()).getSerialVersionUID());
}
});
}
catch (Throwable e)
{
DataNucleusEnhancer.LOGGER.warn(StringUtils.getStringFromStackTrace(e));
}
ClassField cf = new ClassField(enhancer, enhancer.getNamer().getSerialVersionUidFieldName(),
Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, long.class, uid);
if (DataNucleusEnhancer.LOGGER.isDebugEnabled())
{
DataNucleusEnhancer.LOGGER.debug(Localiser.msg("005021", ((Class)cf.getType()).getName() + " " + cf.getName()));
}
cv.visitField(cf.getAccess(), cf.getName(), Type.getDescriptor((Class)cf.getType()), null, cf.getInitialValue());
}
// The dnPreSerialize method need be called only once for a persistent instance.
// The writeObject method in the least-derived pc class that implements Serializable in the inheritance hierarchy needs to be modified or generated to call it.
if (cmd.getSuperAbstractClassMetaData() == null)
{
if (!hasWriteObject)
{
// User hasn't provided their own writeObject, so provide the default but with a call to dnPreSerialize first
ClassMethod method = WriteObject.getInstance(enhancer);
method.initialise(cv);
method.execute();
method.close();
}
}
}
// Add dnGetXXX, dnSetXXX for each of the (managed) fields/properties
for (AbstractMemberMetaData mmd : cmd.getManagedMembers())
{
if (mmd.getPersistenceModifier() == FieldPersistenceModifier.NONE)
{
// Field/Property is not persistent so ignore
continue;
}
ClassMethod getMethod = null;
ClassMethod setMethod = null;
if (mmd instanceof PropertyMetaData)
{
// dnGetXXX, dnSetXXX for property are typically generated when processing existing getXXX, setXXX methods
// TODO What if the user overrode the getter and not the setter? or vice-versa?
}
else
{
// Generate dnGetXXX, dnSetXXX for field
byte persistenceFlags = mmd.getPersistenceFlags();
if ((persistenceFlags & Persistable.MEDIATE_READ) == Persistable.MEDIATE_READ)
{
getMethod = new GetViaMediate(enhancer, mmd);
}
else if ((persistenceFlags & Persistable.CHECK_READ) == Persistable.CHECK_READ)
{
getMethod = new GetViaCheck(enhancer, mmd);
}
else
{
getMethod = new GetNormal(enhancer, mmd);
}
if ((persistenceFlags & Persistable.MEDIATE_WRITE) == Persistable.MEDIATE_WRITE)
{
setMethod = new SetViaMediate(enhancer, mmd);
}
else if ((persistenceFlags & Persistable.CHECK_WRITE) == Persistable.CHECK_WRITE)
{
setMethod = new SetViaCheck(enhancer, mmd);
}
else
{
setMethod = new SetNormal(enhancer, mmd);
}
}
if (getMethod != null)
{
getMethod.initialise(cv);
getMethod.execute();
getMethod.close();
}
if (setMethod != null)
{
setMethod.initialise(cv);
setMethod.execute();
setMethod.close();
}
}
}
cv.visitEnd();
}
}