mirror of https://github.com/apache/openjpa.git
OPENJPA-2911 move PCSubclassValidator to ASM
work in progress. For now we compare old and new mechanism to catch errors.
This commit is contained in:
parent
b51d003ed9
commit
31681982de
|
@ -52,6 +52,7 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.apache.openjpa.conf.OpenJPAConfiguration;
|
import org.apache.openjpa.conf.OpenJPAConfiguration;
|
||||||
import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
|
import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
|
||||||
|
@ -96,6 +97,14 @@ import org.apache.openjpa.util.StringId;
|
||||||
import org.apache.openjpa.util.UserException;
|
import org.apache.openjpa.util.UserException;
|
||||||
import org.apache.openjpa.util.asm.AsmHelper;
|
import org.apache.openjpa.util.asm.AsmHelper;
|
||||||
import org.apache.openjpa.util.asm.ClassWriterTracker;
|
import org.apache.openjpa.util.asm.ClassWriterTracker;
|
||||||
|
import org.apache.xbean.asm9.ClassReader;
|
||||||
|
import org.apache.xbean.asm9.Opcodes;
|
||||||
|
import org.apache.xbean.asm9.Type;
|
||||||
|
import org.apache.xbean.asm9.tree.AbstractInsnNode;
|
||||||
|
import org.apache.xbean.asm9.tree.ClassNode;
|
||||||
|
import org.apache.xbean.asm9.tree.FieldInsnNode;
|
||||||
|
import org.apache.xbean.asm9.tree.MethodNode;
|
||||||
|
import org.apache.xbean.asm9.tree.VarInsnNode;
|
||||||
|
|
||||||
import serp.bytecode.BCClass;
|
import serp.bytecode.BCClass;
|
||||||
import serp.bytecode.BCField;
|
import serp.bytecode.BCField;
|
||||||
|
@ -541,14 +550,15 @@ public class PCEnhancer {
|
||||||
Class<?> type = _managedType.getType();
|
Class<?> type = _managedType.getType();
|
||||||
try {
|
try {
|
||||||
// if enum, skip, no need of any meta
|
// if enum, skip, no need of any meta
|
||||||
if (_pc.isEnum())
|
if (type.isEnum())
|
||||||
return ENHANCE_NONE;
|
return ENHANCE_NONE;
|
||||||
|
|
||||||
// if managed interface, skip
|
// if managed interface, skip
|
||||||
if (_pc.isInterface())
|
if (type.isInterface())
|
||||||
return ENHANCE_INTERFACE;
|
return ENHANCE_INTERFACE;
|
||||||
|
|
||||||
// check if already enhanced
|
// check if already enhanced
|
||||||
|
// we cannot simply use instanceof or isAssignableFrom as we have a temp ClassLoader inbetween
|
||||||
ClassLoader loader = AccessController.doPrivileged(J2DoPrivHelper.getClassLoaderAction(type));
|
ClassLoader loader = AccessController.doPrivileged(J2DoPrivHelper.getClassLoaderAction(type));
|
||||||
for (String iface : _managedType.getDeclaredInterfaceNames()) {
|
for (String iface : _managedType.getDeclaredInterfaceNames()) {
|
||||||
if (iface.equals(PCTYPE.getName())) {
|
if (iface.equals(PCTYPE.getName())) {
|
||||||
|
@ -558,10 +568,10 @@ public class PCEnhancer {
|
||||||
return ENHANCE_NONE;
|
return ENHANCE_NONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_log.isTraceEnabled()) {
|
|
||||||
_log.trace(_loc.get("enhance-start", type, loader));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (_log.isTraceEnabled()) {
|
||||||
|
_log.trace(_loc.get("enhance-start", type));
|
||||||
|
}
|
||||||
|
|
||||||
configureBCs();
|
configureBCs();
|
||||||
|
|
||||||
|
@ -569,8 +579,9 @@ public class PCEnhancer {
|
||||||
// we build up a record of backing fields, etc
|
// we build up a record of backing fields, etc
|
||||||
if (isPropertyAccess(_meta)) {
|
if (isPropertyAccess(_meta)) {
|
||||||
validateProperties();
|
validateProperties();
|
||||||
if (getCreateSubclass())
|
if (getCreateSubclass()) {
|
||||||
addAttributeTranslation();
|
addAttributeTranslation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
replaceAndValidateFieldAccess();
|
replaceAndValidateFieldAccess();
|
||||||
processViolations();
|
processViolations();
|
||||||
|
@ -710,7 +721,7 @@ public class PCEnhancer {
|
||||||
true);
|
true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
returned = getReturnedField(getter);
|
returned = getReturnedField_old(getter);
|
||||||
if (returned != null)
|
if (returned != null)
|
||||||
registerBackingFieldInfo(fmd, getter, returned);
|
registerBackingFieldInfo(fmd, getter, returned);
|
||||||
|
|
||||||
|
@ -738,7 +749,7 @@ public class PCEnhancer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setter != null)
|
if (setter != null)
|
||||||
assigned = getAssignedField(setter);
|
assigned = getAssignedField_old(setter);
|
||||||
|
|
||||||
if (assigned != null) {
|
if (assigned != null) {
|
||||||
if (setter != null)
|
if (setter != null)
|
||||||
|
@ -848,28 +859,35 @@ public class PCEnhancer {
|
||||||
* Return the field returned by the given method, or null if none.
|
* Return the field returned by the given method, or null if none.
|
||||||
* Package-protected and static for testing.
|
* Package-protected and static for testing.
|
||||||
*/
|
*/
|
||||||
static BCField getReturnedField(BCMethod meth) {
|
static BCField getReturnedField_old(BCMethod meth) {
|
||||||
return findField(meth, (AccessController.doPrivileged(
|
return findField_old(meth, new Code().xreturn()
|
||||||
J2DoPrivHelper.newCodeAction())).xreturn()
|
|
||||||
.setType(meth.getReturnType()), false);
|
.setType(meth.getReturnType()), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Field getReturnedField(Method meth) {
|
||||||
|
return findField(meth, (ain) -> ain.getOpcode() == AsmHelper.getReturnInsn(meth.getReturnType()), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the field assigned in the given method, or null if none.
|
* Return the field assigned in the given method, or null if none.
|
||||||
* Package-protected and static for testing.
|
* Package-protected and static for testing.
|
||||||
*/
|
*/
|
||||||
static BCField getAssignedField(BCMethod meth) {
|
static BCField getAssignedField_old(BCMethod meth) {
|
||||||
return findField(meth, (AccessController.doPrivileged(
|
return findField_old(meth, (AccessController.doPrivileged(
|
||||||
J2DoPrivHelper.newCodeAction())).putfield(), true);
|
J2DoPrivHelper.newCodeAction())).putfield(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Field getAssignedField(Method meth) {
|
||||||
|
return findField(meth, (ain) -> ain.getOpcode() == Opcodes.PUTFIELD, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the field returned / assigned by <code>meth</code>. Returns
|
* Return the field returned / assigned by <code>meth</code>. Returns
|
||||||
* null if non-fields (methods, literals, parameters, variables) are
|
* null if non-fields (methods, literals, parameters, variables) are
|
||||||
* returned, or if non-parameters are assigned to fields.
|
* returned, or if non-parameters are assigned to fields.
|
||||||
*/
|
*/
|
||||||
private static BCField findField(BCMethod meth, Instruction template,
|
private static BCField findField_old(BCMethod meth, Instruction template, boolean findAccessed) {
|
||||||
boolean findAccessed) {
|
|
||||||
// ignore any static methods. OpenJPA only currently supports
|
// ignore any static methods. OpenJPA only currently supports
|
||||||
// non-static setters and getters
|
// non-static setters and getters
|
||||||
if (meth.isStatic())
|
if (meth.isStatic())
|
||||||
|
@ -918,8 +936,7 @@ public class PCEnhancer {
|
||||||
&& ((LoadInstruction) prevIns).getParam() == 0) {
|
&& ((LoadInstruction) prevIns).getParam() == 0) {
|
||||||
final FieldInstruction fTemplateIns =
|
final FieldInstruction fTemplateIns =
|
||||||
(FieldInstruction) templateIns;
|
(FieldInstruction) templateIns;
|
||||||
cur = AccessController.doPrivileged(J2DoPrivHelper
|
cur = fTemplateIns.getField();
|
||||||
.getFieldInstructionFieldAction(fTemplateIns));
|
|
||||||
} else
|
} else
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -936,6 +953,103 @@ public class PCEnhancer {
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Field findField(Method meth, Predicate<AbstractInsnNode> ain, boolean findAccessed) {
|
||||||
|
// ignore any static methods. OpenJPA only currently supports
|
||||||
|
// non-static setters and getters
|
||||||
|
if (Modifier.isStatic(meth.getModifiers())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassReader cr;
|
||||||
|
try {
|
||||||
|
cr = new ClassReader(meth.getDeclaringClass().getName());
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
ClassNode classNode = new ClassNode();
|
||||||
|
cr.accept(classNode, 0);
|
||||||
|
final MethodNode methodNode = classNode.methods.stream()
|
||||||
|
.filter(mn -> mn.name.equals(meth.getName()) && mn.desc.equals(Type.getMethodDescriptor(meth)))
|
||||||
|
.findAny().get();
|
||||||
|
|
||||||
|
Field field = null;
|
||||||
|
Field cur;
|
||||||
|
AbstractInsnNode prevInsn, earlierInsn;
|
||||||
|
for (AbstractInsnNode insn : methodNode.instructions) {
|
||||||
|
if (!ain.test(insn)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevInsn = insn.getPrevious();
|
||||||
|
if (prevInsn == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevInsn.getOpcode() == Opcodes.INSTANCEOF && prevInsn.getPrevious() != null ) {
|
||||||
|
prevInsn = prevInsn.getPrevious();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevInsn.getPrevious() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
earlierInsn = prevInsn.getPrevious();
|
||||||
|
|
||||||
|
// if the opcode two before the template was an aload_0, check
|
||||||
|
// against the middle instruction based on what type of find
|
||||||
|
// we're doing
|
||||||
|
if (!AsmHelper.isLoadInsn(earlierInsn)
|
||||||
|
|| !AsmHelper.isThisInsn(earlierInsn)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the middle instruction was a getfield, then it's the
|
||||||
|
// field that's being accessed
|
||||||
|
if (!findAccessed && prevInsn.getOpcode() == Opcodes.GETFIELD) {
|
||||||
|
final FieldInsnNode fieldInsn = (FieldInsnNode) prevInsn;
|
||||||
|
|
||||||
|
cur = getField(meth.getDeclaringClass(), fieldInsn);
|
||||||
|
|
||||||
|
// if the middle instruction was an xload_1, then the
|
||||||
|
// matched instruction is the field that's being set.
|
||||||
|
} else if (findAccessed && AsmHelper.isLoadInsn(prevInsn)
|
||||||
|
&& ((VarInsnNode) prevInsn).var == 1) {
|
||||||
|
final FieldInsnNode fieldInsn = (FieldInsnNode)insn;
|
||||||
|
cur = getField(meth.getDeclaringClass(), fieldInsn);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (field != null && cur != field) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
field = cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Field getField(Class<?> clazz, FieldInsnNode fieldInsn) {
|
||||||
|
try {
|
||||||
|
final Class<?> fieldOwner = clazz.getClassLoader().loadClass(fieldInsn.owner.replace("/", "."));
|
||||||
|
return fieldOwner.getDeclaredField(fieldInsn.name);
|
||||||
|
}
|
||||||
|
catch (NoSuchFieldException e) {
|
||||||
|
if (clazz.getSuperclass() == Object.class) {
|
||||||
|
throw new IllegalStateException("Cannot find field " + fieldInsn + " in Class " + clazz);
|
||||||
|
}
|
||||||
|
return getField(clazz.getSuperclass(), fieldInsn);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record a violation of the property access restrictions.
|
* Record a violation of the property access restrictions.
|
||||||
*/
|
*/
|
||||||
|
@ -4895,8 +5009,8 @@ public class PCEnhancer {
|
||||||
if (args == null || args.length == 0) {
|
if (args == null || args.length == 0) {
|
||||||
classes = repos.getPersistentTypeNames(true, loader);
|
classes = repos.getPersistentTypeNames(true, loader);
|
||||||
if (classes == null) {
|
if (classes == null) {
|
||||||
log.warn(_loc.get("no-class-to-enhance"));
|
log.warn(_loc.get("no-class-to-enhance"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ClassArgParser cap = conf.getMetaDataRepositoryInstance().
|
ClassArgParser cap = conf.getMetaDataRepositoryInstance().
|
||||||
|
@ -4923,8 +5037,9 @@ public class PCEnhancer {
|
||||||
else
|
else
|
||||||
bc = project.loadClass((Class) o);
|
bc = project.loadClass((Class) o);
|
||||||
enhancer = new PCEnhancer(conf, bc, repos, loader);
|
enhancer = new PCEnhancer(conf, bc, repos, loader);
|
||||||
if (writer != null)
|
if (writer != null) {
|
||||||
enhancer.setBytecodeWriter(writer);
|
enhancer.setBytecodeWriter(writer);
|
||||||
|
}
|
||||||
enhancer.setDirectory(flags.directory);
|
enhancer.setDirectory(flags.directory);
|
||||||
enhancer.setAddDefaultConstructor(flags.addDefaultConstructor);
|
enhancer.setAddDefaultConstructor(flags.addDefaultConstructor);
|
||||||
status = enhancer.run();
|
status = enhancer.run();
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.apache.openjpa.enhance;
|
||||||
import java.io.Externalizable;
|
import java.io.Externalizable;
|
||||||
import java.io.ObjectInput;
|
import java.io.ObjectInput;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Member;
|
import java.lang.reflect.Member;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
@ -34,7 +35,6 @@ import org.apache.openjpa.lib.util.StringUtil;
|
||||||
import org.apache.openjpa.meta.AccessCode;
|
import org.apache.openjpa.meta.AccessCode;
|
||||||
import org.apache.openjpa.meta.ClassMetaData;
|
import org.apache.openjpa.meta.ClassMetaData;
|
||||||
import org.apache.openjpa.meta.FieldMetaData;
|
import org.apache.openjpa.meta.FieldMetaData;
|
||||||
import org.apache.openjpa.util.InternalException;
|
|
||||||
import org.apache.openjpa.util.UserException;
|
import org.apache.openjpa.util.UserException;
|
||||||
|
|
||||||
import serp.bytecode.BCClass;
|
import serp.bytecode.BCClass;
|
||||||
|
@ -42,48 +42,48 @@ import serp.bytecode.BCField;
|
||||||
import serp.bytecode.BCMethod;
|
import serp.bytecode.BCMethod;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Validates that a given type meets the JPA contract, plus a few
|
* <p>Validates that a given type meets the JPA contract, plus a few
|
||||||
* OpenJPA-specific additions for subclassing / redefinition:
|
* OpenJPA-specific additions for subclassing / redefinition:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>must have an accessible no-args constructor</li>
|
* <li>must have an accessible no-args constructor</li>
|
||||||
* <li>must be a public or protected class</li>
|
* <li>must be a public or protected class</li>
|
||||||
* <li>must not be final</li>
|
* <li>must not be final</li>
|
||||||
* <li>must not extend an enhanced class</li>
|
* <li>must not extend an enhanced class</li>
|
||||||
* <li>all persistent data represented by accessible setter/getter
|
* <li>all persistent data represented by accessible setter/getter
|
||||||
* methods (persistent properties)</li>
|
* methods (persistent properties)</li>
|
||||||
* <li>if versioning is to be used, exactly one persistent property for
|
* <li>if versioning is to be used, exactly one persistent property for
|
||||||
* the numeric version data</li> <!-- ##### is this true? -->
|
* the numeric version data</li> <!-- ##### is this true? -->
|
||||||
*
|
*
|
||||||
* <li>When using property access, the backing field for a persistent
|
* <li>When using property access, the backing field for a persistent
|
||||||
* property must be:
|
* property must be:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <!-- ##### JPA validation of these needs to be tested -->
|
* <!-- ##### JPA validation of these needs to be tested -->
|
||||||
* <li>private</li>
|
* <li>private</li>
|
||||||
* <li>set only in the designated setter,
|
* <li>set only in the designated setter,
|
||||||
* in the constructor, or in {@link Object#clone()},
|
* in the constructor, or in {@link Object#clone()},
|
||||||
* <code>readObject(ObjectInputStream)</code>, or
|
* <code>readObject(ObjectInputStream)</code>, or
|
||||||
* {@link Externalizable#readExternal(ObjectInput)}.</li>
|
* {@link Externalizable#readExternal(ObjectInput)}.</li>
|
||||||
* <li>read only in the designated getter and the
|
* <li>read only in the designated getter and the
|
||||||
* constructor.</li>
|
* constructor.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>If you use this technique and use the <code>new</code> keyword instead
|
* <p>If you use this technique and use the <code>new</code> keyword instead
|
||||||
* of a OpenJPA-supplied construction routine, OpenJPA will need to do extra
|
* of a OpenJPA-supplied construction routine, OpenJPA will need to do extra
|
||||||
* work with persistent-new-flushed instances, since OpenJPA cannot in this
|
* work with persistent-new-flushed instances, since OpenJPA cannot in this
|
||||||
* case track what happens to such an instance.</p>
|
* case track what happens to such an instance.</p>
|
||||||
*
|
* <p>
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public class PCSubclassValidator {
|
public class PCSubclassValidator {
|
||||||
|
|
||||||
private static final Localizer loc =
|
private static final Localizer loc =
|
||||||
Localizer.forPackage(PCSubclassValidator.class);
|
Localizer.forPackage(PCSubclassValidator.class);
|
||||||
|
|
||||||
private final ClassMetaData meta;
|
private final ClassMetaData meta;
|
||||||
private final BCClass pc;
|
private final BCClass bc;
|
||||||
private final Log log;
|
private final Log log;
|
||||||
private final boolean failOnContractViolations;
|
private final boolean failOnContractViolations;
|
||||||
|
|
||||||
|
@ -91,52 +91,51 @@ public class PCSubclassValidator {
|
||||||
private Collection contractViolations;
|
private Collection contractViolations;
|
||||||
|
|
||||||
public PCSubclassValidator(ClassMetaData meta, BCClass bc, Log log,
|
public PCSubclassValidator(ClassMetaData meta, BCClass bc, Log log,
|
||||||
boolean enforceContractViolations) {
|
boolean enforceContractViolations) {
|
||||||
this.meta = meta;
|
this.meta = meta;
|
||||||
this.pc = bc;
|
this.bc = bc;
|
||||||
this.log = log;
|
this.log = log;
|
||||||
this.failOnContractViolations = enforceContractViolations;
|
this.failOnContractViolations = enforceContractViolations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assertCanSubclass() {
|
public void assertCanSubclass() {
|
||||||
Class superclass = meta.getDescribedType();
|
Class<?> superclass = meta.getDescribedType();
|
||||||
String name = superclass.getName();
|
String name = superclass.getName();
|
||||||
if (superclass.isInterface())
|
if (superclass.isInterface()) {
|
||||||
addError(loc.get("subclasser-no-ifaces", name), meta);
|
addError(loc.get("subclasser-no-ifaces", name), meta);
|
||||||
if (Modifier.isFinal(superclass.getModifiers()))
|
}
|
||||||
|
if (Modifier.isFinal(superclass.getModifiers())) {
|
||||||
addError(loc.get("subclasser-no-final-classes", name), meta);
|
addError(loc.get("subclasser-no-final-classes", name), meta);
|
||||||
if (Modifier.isPrivate(superclass.getModifiers()))
|
}
|
||||||
|
if (Modifier.isPrivate(superclass.getModifiers())) {
|
||||||
addError(loc.get("subclasser-no-private-classes", name), meta);
|
addError(loc.get("subclasser-no-private-classes", name), meta);
|
||||||
if (PersistenceCapable.class.isAssignableFrom(superclass))
|
}
|
||||||
|
if (PersistenceCapable.class.isAssignableFrom(superclass)) {
|
||||||
addError(loc.get("subclasser-super-already-pc", name), meta);
|
addError(loc.get("subclasser-super-already-pc", name), meta);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Constructor c = superclass.getDeclaredConstructor(new Class[0]);
|
Constructor c = superclass.getDeclaredConstructor();
|
||||||
if (!(Modifier.isProtected(c.getModifiers())
|
if (!(Modifier.isProtected(c.getModifiers())
|
||||||
|| Modifier.isPublic(c.getModifiers())))
|
|| Modifier.isPublic(c.getModifiers()))) {
|
||||||
addError(loc.get("subclasser-private-ctor", name), meta);
|
addError(loc.get("subclasser-private-ctor", name), meta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (NoSuchMethodException e) {
|
catch (NoSuchMethodException e) {
|
||||||
addError(loc.get("subclasser-no-void-ctor", name),
|
addError(loc.get("subclasser-no-void-ctor", name), meta);
|
||||||
meta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the BCClass we loaded is already pc and the superclass is not,
|
if (AccessCode.isProperty(meta.getAccessType())) {
|
||||||
// then we should never get here, so let's make sure that the
|
|
||||||
// calling context is caching correctly by throwing an exception.
|
|
||||||
if (pc.isInstanceOf(PersistenceCapable.class) &&
|
|
||||||
!PersistenceCapable.class.isAssignableFrom(superclass))
|
|
||||||
throw new InternalException(
|
|
||||||
loc.get("subclasser-class-already-pc", name));
|
|
||||||
|
|
||||||
if (AccessCode.isProperty(meta.getAccessType()))
|
|
||||||
checkPropertiesAreInterceptable();
|
checkPropertiesAreInterceptable();
|
||||||
|
}
|
||||||
|
|
||||||
if (errors != null && !errors.isEmpty())
|
if (errors != null && !errors.isEmpty()) {
|
||||||
throw new UserException(errors.toString());
|
throw new UserException(errors.toString());
|
||||||
|
}
|
||||||
else if (contractViolations != null &&
|
else if (contractViolations != null &&
|
||||||
!contractViolations.isEmpty() && log.isWarnEnabled())
|
!contractViolations.isEmpty() && log.isWarnEnabled()) {
|
||||||
log.warn(contractViolations.toString());
|
log.warn(contractViolations.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPropertiesAreInterceptable() {
|
private void checkPropertiesAreInterceptable() {
|
||||||
|
@ -146,7 +145,7 @@ public class PCSubclassValidator {
|
||||||
Method getter = getBackingMember(fmd);
|
Method getter = getBackingMember(fmd);
|
||||||
if (getter == null) {
|
if (getter == null) {
|
||||||
addError(loc.get("subclasser-no-getter",
|
addError(loc.get("subclasser-no-getter",
|
||||||
fmd.getName()), fmd);
|
fmd.getName()), fmd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
BCField returnedField = checkGetterIsSubclassable(getter, fmd);
|
BCField returnedField = checkGetterIsSubclassable(getter, fmd);
|
||||||
|
@ -154,18 +153,19 @@ public class PCSubclassValidator {
|
||||||
Method setter = setterForField(fmd);
|
Method setter = setterForField(fmd);
|
||||||
if (setter == null) {
|
if (setter == null) {
|
||||||
addError(loc.get("subclasser-no-setter", fmd.getName()),
|
addError(loc.get("subclasser-no-setter", fmd.getName()),
|
||||||
fmd);
|
fmd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
BCField assignedField = checkSetterIsSubclassable(setter, fmd);
|
BCField assignedField = checkSetterIsSubclassable(setter, fmd);
|
||||||
if (assignedField == null)
|
if (assignedField == null) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (assignedField != returnedField)
|
if (assignedField != returnedField) {
|
||||||
addContractViolation(loc.get
|
addContractViolation(loc.get("subclasser-setter-getter-field-mismatch",
|
||||||
("subclasser-setter-getter-field-mismatch",
|
fmd.getName(), returnedField, assignedField),
|
||||||
fmd.getName(), returnedField, assignedField),
|
fmd);
|
||||||
fmd);
|
}
|
||||||
|
|
||||||
// ### scan through all the rest of the class to make sure it
|
// ### scan through all the rest of the class to make sure it
|
||||||
// ### doesn't use the field.
|
// ### doesn't use the field.
|
||||||
|
@ -173,22 +173,21 @@ public class PCSubclassValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Method getBackingMember(FieldMetaData fmd) {
|
private Method getBackingMember(FieldMetaData fmd) {
|
||||||
Member back = fmd.getBackingMember();
|
Member back = fmd.getBackingMember();
|
||||||
if (Method.class.isInstance(back))
|
if (back instanceof Method) {
|
||||||
return (Method)back;
|
return (Method) back;
|
||||||
|
}
|
||||||
|
|
||||||
Method getter = Reflection.findGetter(meta.getDescribedType(),
|
Method getter = Reflection.findGetter(meta.getDescribedType(), fmd.getName(), false);
|
||||||
fmd.getName(), false);
|
if (getter != null) {
|
||||||
if (getter != null)
|
fmd.backingMember(getter);
|
||||||
fmd.backingMember(getter);
|
}
|
||||||
return getter;
|
return getter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Method setterForField(FieldMetaData fmd) {
|
private Method setterForField(FieldMetaData fmd) {
|
||||||
try {
|
try {
|
||||||
return fmd.getDeclaringType().getDeclaredMethod(
|
return fmd.getDeclaringType().getDeclaredMethod("set" + StringUtil.capitalize(fmd.getName()), fmd.getDeclaredType());
|
||||||
"set" + StringUtil.capitalize(fmd.getName()),
|
|
||||||
new Class[]{ fmd.getDeclaredType() });
|
|
||||||
}
|
}
|
||||||
catch (NoSuchMethodException e) {
|
catch (NoSuchMethodException e) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -197,93 +196,111 @@ public class PCSubclassValidator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the name of the field that is returned by <code>meth</code>, or
|
* @return the name of the field that is returned by <code>meth</code>, or
|
||||||
* <code>null</code> if something other than a single field is
|
* <code>null</code> if something other than a single field is
|
||||||
* returned, or if it cannot be determined what is returned.
|
* returned, or if it cannot be determined what is returned.
|
||||||
*/
|
*/
|
||||||
private BCField checkGetterIsSubclassable(Method meth, FieldMetaData fmd) {
|
private BCField checkGetterIsSubclassable(Method meth, FieldMetaData fmd) {
|
||||||
checkMethodIsSubclassable(meth, fmd);
|
checkMethodIsSubclassable(meth, fmd);
|
||||||
BCField field = PCEnhancer.getReturnedField(getBCMethod(meth));
|
BCField bcField = PCEnhancer.getReturnedField_old(getBCMethod(meth));
|
||||||
if (field == null) {
|
Field field = PCEnhancer.getReturnedField(meth);
|
||||||
addContractViolation(loc.get("subclasser-invalid-getter",
|
|
||||||
fmd.getName()), fmd);
|
//X TODO remove
|
||||||
|
if (field == null && bcField != null || field != null && bcField == null ||
|
||||||
|
field != null && bcField != null && !field.getName().equals(bcField.getName())) {
|
||||||
|
throw new IllegalStateException("MSX ASMTODO " + bcField + " " + field);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bcField == null) {
|
||||||
|
addContractViolation(loc.get("subclasser-invalid-getter", fmd.getName()), fmd);
|
||||||
return null;
|
return null;
|
||||||
} else {
|
}
|
||||||
return field;
|
else {
|
||||||
|
return bcField;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the field that is set in <code>meth</code>, or
|
* @return the field that is set in <code>meth</code>, or
|
||||||
* <code>null</code> if something other than a single field is
|
* <code>null</code> if something other than a single field is
|
||||||
* set, or if it cannot be determined what is set.
|
* set, or if it cannot be determined what is set.
|
||||||
*/
|
*/
|
||||||
private BCField checkSetterIsSubclassable(Method meth, FieldMetaData fmd) {
|
private BCField checkSetterIsSubclassable(Method meth, FieldMetaData fmd) {
|
||||||
checkMethodIsSubclassable(meth, fmd);
|
checkMethodIsSubclassable(meth, fmd);
|
||||||
BCField field = PCEnhancer.getAssignedField(getBCMethod(meth));
|
BCField bcField = PCEnhancer.getAssignedField_old(getBCMethod(meth));
|
||||||
if (field == null) {
|
Field field = PCEnhancer.getAssignedField(meth);
|
||||||
addContractViolation(loc.get("subclasser-invalid-setter",
|
|
||||||
fmd.getName()), fmd);
|
//X TODO remove
|
||||||
|
if (field == null && bcField != null || field != null && bcField == null ||
|
||||||
|
field != null && bcField != null && !field.getName().equals(bcField.getName())) {
|
||||||
|
throw new IllegalStateException("MSX ASMTODO " + bcField + " " + field);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bcField == null) {
|
||||||
|
addContractViolation(loc.get("subclasser-invalid-setter", fmd.getName()), fmd);
|
||||||
return null;
|
return null;
|
||||||
} else {
|
}
|
||||||
return field;
|
else {
|
||||||
|
return bcField;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BCMethod getBCMethod(Method meth) {
|
private BCMethod getBCMethod(Method meth) {
|
||||||
BCClass bc = pc.getProject().loadClass(meth.getDeclaringClass());
|
BCClass bc = this.bc.getProject().loadClass(meth.getDeclaringClass());
|
||||||
return bc.getDeclaredMethod(meth.getName(), meth.getParameterTypes());
|
return bc.getDeclaredMethod(meth.getName(), meth.getParameterTypes());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkMethodIsSubclassable(Method meth, FieldMetaData fmd) {
|
private void checkMethodIsSubclassable(Method meth, FieldMetaData fmd) {
|
||||||
String className = fmd.getDefiningMetaData().
|
String className = fmd.getDefiningMetaData().
|
||||||
getDescribedType().getName();
|
getDescribedType().getName();
|
||||||
if (!(Modifier.isProtected(meth.getModifiers())
|
if (!(Modifier.isProtected(meth.getModifiers())
|
||||||
|| Modifier.isPublic(meth.getModifiers())))
|
|| Modifier.isPublic(meth.getModifiers()))) {
|
||||||
addError(loc.get("subclasser-private-accessors-unsupported",
|
addError(loc.get("subclasser-private-accessors-unsupported", className, meth.getName()), fmd);
|
||||||
className, meth.getName()), fmd);
|
}
|
||||||
if (Modifier.isFinal(meth.getModifiers()))
|
if (Modifier.isFinal(meth.getModifiers())) {
|
||||||
addError(loc.get("subclasser-final-methods-not-allowed",
|
addError(loc.get("subclasser-final-methods-not-allowed", className, meth.getName()), fmd);
|
||||||
className, meth.getName()), fmd);
|
}
|
||||||
if (Modifier.isNative(meth.getModifiers()))
|
if (Modifier.isNative(meth.getModifiers())) {
|
||||||
addContractViolation(loc.get
|
addContractViolation(loc.get("subclasser-native-methods-not-allowed", className, meth.getName()), fmd);
|
||||||
("subclasser-native-methods-not-allowed", className,
|
}
|
||||||
meth.getName()),
|
if (Modifier.isStatic(meth.getModifiers())) {
|
||||||
fmd);
|
addError(loc.get("subclasser-static-methods-not-supported", className, meth.getName()), fmd);
|
||||||
if (Modifier.isStatic(meth.getModifiers()))
|
}
|
||||||
addError(loc.get("subclasser-static-methods-not-supported",
|
|
||||||
className, meth.getName()), fmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addError(Message s, ClassMetaData cls) {
|
private void addError(Message s, ClassMetaData cls) {
|
||||||
if (errors == null)
|
if (errors == null) {
|
||||||
errors = new ArrayList();
|
errors = new ArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
errors.add(loc.get("subclasser-error-meta", s,
|
errors.add(loc.get("subclasser-error-meta", s,
|
||||||
cls.getDescribedType().getName(),
|
cls.getDescribedType().getName(),
|
||||||
cls.getSourceFile()));
|
cls.getSourceFile()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addError(Message s, FieldMetaData fmd) {
|
private void addError(Message s, FieldMetaData fmd) {
|
||||||
if (errors == null)
|
if (errors == null) {
|
||||||
errors = new ArrayList();
|
errors = new ArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
errors.add(loc.get("subclasser-error-field", s,
|
errors.add(loc.get("subclasser-error-field", s,
|
||||||
fmd.getFullName(),
|
fmd.getFullName(),
|
||||||
fmd.getDeclaringMetaData().getSourceFile()));
|
fmd.getDeclaringMetaData().getSourceFile()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addContractViolation(Message m, FieldMetaData fmd) {
|
private void addContractViolation(Message m, FieldMetaData fmd) {
|
||||||
// add the violation as an error in case we're processing violations
|
// add the violation as an error in case we're processing violations
|
||||||
// as errors; this keeps them in the order that they were found rather
|
// as errors; this keeps them in the order that they were found rather
|
||||||
// than just adding the violations to the end of the list.
|
// than just adding the violations to the end of the list.
|
||||||
if (failOnContractViolations)
|
if (failOnContractViolations) {
|
||||||
addError(m, fmd);
|
addError(m, fmd);
|
||||||
|
}
|
||||||
|
|
||||||
if (contractViolations == null)
|
if (contractViolations == null) {
|
||||||
contractViolations = new ArrayList();
|
contractViolations = new ArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
contractViolations.add(loc.get
|
contractViolations.add(loc.get
|
||||||
("subclasser-contract-violation-field", m.getMessage(),
|
("subclasser-contract-violation-field", m.getMessage(),
|
||||||
fmd.getFullName(), fmd.getDeclaringMetaData().getSourceFile()));
|
fmd.getFullName(), fmd.getDeclaringMetaData().getSourceFile()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,11 +187,11 @@ public abstract class AbstractMetaDataDefaults
|
||||||
Member member;
|
Member member;
|
||||||
FieldMetaData fmd;
|
FieldMetaData fmd;
|
||||||
for (int i = 0; i < fieldNames.length; i ++) {
|
for (int i = 0; i < fieldNames.length; i ++) {
|
||||||
String property = fieldNames[i];
|
String property = fieldNames[i];
|
||||||
member = getMemberByProperty(meta, property,
|
member = getMemberByProperty(meta, property, AccessCode.UNKNOWN, true);
|
||||||
AccessCode.UNKNOWN, true);
|
if (member == null) { // transient or indeterminable access
|
||||||
if (member == null) // transient or indeterminable access
|
continue;
|
||||||
continue;
|
}
|
||||||
fmd = meta.addDeclaredField(property, fieldTypes[i]);
|
fmd = meta.addDeclaredField(property, fieldTypes[i]);
|
||||||
fmd.backingMember(member);
|
fmd.backingMember(member);
|
||||||
populate(fmd);
|
populate(fmd);
|
||||||
|
@ -285,21 +285,27 @@ public abstract class AbstractMetaDataDefaults
|
||||||
* the next letter lower-cased. For other methods, returns null.
|
* the next letter lower-cased. For other methods, returns null.
|
||||||
*/
|
*/
|
||||||
public static String getFieldName(Member member) {
|
public static String getFieldName(Member member) {
|
||||||
if (member instanceof Field)
|
if (member instanceof Field) {
|
||||||
return member.getName();
|
return member.getName();
|
||||||
if (!(member instanceof Method))
|
}
|
||||||
return null;
|
if (!(member instanceof Method)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Method method = (Method) member;
|
Method method = (Method) member;
|
||||||
String name = method.getName();
|
String name = method.getName();
|
||||||
if (isNormalGetter(method))
|
if (isNormalGetter(method)) {
|
||||||
name = name.substring("get".length());
|
name = name.substring("get".length());
|
||||||
else if (isBooleanGetter(method))
|
}
|
||||||
name = name.substring("is".length());
|
else if (isBooleanGetter(method)) {
|
||||||
else
|
name = name.substring("is".length());
|
||||||
|
}
|
||||||
|
else {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (name.length() == 1)
|
if (name.length() == 1) {
|
||||||
return name.toLowerCase();
|
return name.toLowerCase();
|
||||||
|
}
|
||||||
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +341,7 @@ public abstract class AbstractMetaDataDefaults
|
||||||
if (fmd == null)
|
if (fmd == null)
|
||||||
return null;
|
return null;
|
||||||
if (fmd.getBackingMember() != null)
|
if (fmd.getBackingMember() != null)
|
||||||
return fmd.getBackingMember();
|
return fmd.getBackingMember();
|
||||||
return getMemberByProperty(fmd.getDeclaringMetaData(), fmd.getName(),
|
return getMemberByProperty(fmd.getDeclaringMetaData(), fmd.getName(),
|
||||||
fmd.getAccessType(), true);
|
fmd.getAccessType(), true);
|
||||||
}
|
}
|
||||||
|
@ -362,10 +368,10 @@ public abstract class AbstractMetaDataDefaults
|
||||||
* where T is any non-void type.
|
* where T is any non-void type.
|
||||||
*/
|
*/
|
||||||
public static boolean isNormalGetter(Method method) {
|
public static boolean isNormalGetter(Method method) {
|
||||||
String methodName = method.getName();
|
String methodName = method.getName();
|
||||||
return startsWith(methodName, "get")
|
return startsWith(methodName, "get")
|
||||||
&& method.getParameterTypes().length == 0
|
&& method.getParameterTypes().length == 0
|
||||||
&& method.getReturnType() != void.class;
|
&& method.getReturnType() != void.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -374,10 +380,10 @@ public abstract class AbstractMetaDataDefaults
|
||||||
* <code> public Boolean isXXX() </code>
|
* <code> public Boolean isXXX() </code>
|
||||||
*/
|
*/
|
||||||
public static boolean isBooleanGetter(Method method) {
|
public static boolean isBooleanGetter(Method method) {
|
||||||
String methodName = method.getName();
|
String methodName = method.getName();
|
||||||
return startsWith(methodName, "is")
|
return startsWith(methodName, "is")
|
||||||
&& method.getParameterTypes().length == 0
|
&& method.getParameterTypes().length == 0
|
||||||
&& isBoolean(method.getReturnType());
|
&& isBoolean(method.getReturnType());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -388,16 +394,16 @@ public abstract class AbstractMetaDataDefaults
|
||||||
* <code> public T isXXX()</code> where T is boolean or Boolean.<br>
|
* <code> public T isXXX()</code> where T is boolean or Boolean.<br>
|
||||||
*/
|
*/
|
||||||
public static boolean isGetter(Method method, boolean includePrivate) {
|
public static boolean isGetter(Method method, boolean includePrivate) {
|
||||||
if (method == null)
|
if (method == null)
|
||||||
return false;
|
return false;
|
||||||
int mods = method.getModifiers();
|
int mods = method.getModifiers();
|
||||||
if (!(Modifier.isPublic(mods)
|
if (!(Modifier.isPublic(mods)
|
||||||
|| Modifier.isProtected(mods)
|
|| Modifier.isProtected(mods)
|
||||||
|| (Modifier.isPrivate(mods) && includePrivate))
|
|| (Modifier.isPrivate(mods) && includePrivate))
|
||||||
|| Modifier.isNative(mods)
|
|| Modifier.isNative(mods)
|
||||||
|| Modifier.isStatic(mods))
|
|| Modifier.isStatic(mods))
|
||||||
return false;
|
return false;
|
||||||
return isNormalGetter(method) || isBooleanGetter(method);
|
return isNormalGetter(method) || isBooleanGetter(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -409,14 +415,14 @@ public abstract class AbstractMetaDataDefaults
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isBoolean(Class<?> cls) {
|
public static boolean isBoolean(Class<?> cls) {
|
||||||
return cls == boolean.class || cls == Boolean.class;
|
return cls == boolean.class || cls == Boolean.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> toNames(List<? extends Member> members) {
|
public static List<String> toNames(List<? extends Member> members) {
|
||||||
List<String> result = new ArrayList<>();
|
List<String> result = new ArrayList<>();
|
||||||
for (Member m : members)
|
for (Member m : members)
|
||||||
result.add(m.getName());
|
result.add(m.getName());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ import org.apache.xbean.asm9.ClassReader;
|
||||||
import org.apache.xbean.asm9.ClassWriter;
|
import org.apache.xbean.asm9.ClassWriter;
|
||||||
import org.apache.xbean.asm9.Opcodes;
|
import org.apache.xbean.asm9.Opcodes;
|
||||||
import org.apache.xbean.asm9.Type;
|
import org.apache.xbean.asm9.Type;
|
||||||
|
import org.apache.xbean.asm9.tree.AbstractInsnNode;
|
||||||
|
import org.apache.xbean.asm9.tree.VarInsnNode;
|
||||||
|
|
||||||
import serp.bytecode.BCClass;
|
import serp.bytecode.BCClass;
|
||||||
import serp.bytecode.Project;
|
import serp.bytecode.Project;
|
||||||
|
@ -211,4 +213,32 @@ public final class AsmHelper {
|
||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the instruction is an LOAD instruction
|
||||||
|
*/
|
||||||
|
public static boolean isLoadInsn(AbstractInsnNode insn) {
|
||||||
|
return insn.getOpcode() == Opcodes.ALOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.ILOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.IALOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.LLOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.LALOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.FLOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.FALOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.DLOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.DALOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.BALOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.CALOAD
|
||||||
|
|| insn.getOpcode() == Opcodes.SALOAD;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the instruction is an ALOAD_0
|
||||||
|
*/
|
||||||
|
public static boolean isThisInsn(AbstractInsnNode insn) {
|
||||||
|
return insn instanceof VarInsnNode
|
||||||
|
&& insn.getOpcode() == Opcodes.ALOAD
|
||||||
|
&& ((VarInsnNode)insn).var == 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class TestPCEnhancerFindField
|
||||||
Project proj = new Project();
|
Project proj = new Project();
|
||||||
BCClass bc = proj.loadClass(getClass());
|
BCClass bc = proj.loadClass(getClass());
|
||||||
BCMethod meth = bc.getMethods("myMethod")[0];
|
BCMethod meth = bc.getMethods("myMethod")[0];
|
||||||
BCField field = PCEnhancer.getReturnedField(meth);
|
BCField field = PCEnhancer.getReturnedField_old(meth);
|
||||||
assertEquals("field", field.getName());
|
assertEquals("field", field.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You 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.
|
||||||
|
*/
|
||||||
|
package org.apache.openjpa.enhance;
|
||||||
|
|
||||||
|
import org.apache.openjpa.conf.OpenJPAConfiguration;
|
||||||
|
import org.apache.openjpa.lib.log.Log;
|
||||||
|
import org.apache.openjpa.lib.util.TemporaryClassLoader;
|
||||||
|
import org.apache.openjpa.meta.ClassMetaData;
|
||||||
|
import org.apache.openjpa.meta.MetaDataModes;
|
||||||
|
import org.apache.openjpa.meta.MetaDataRepository;
|
||||||
|
import org.apache.openjpa.persistence.common.apps.Department;
|
||||||
|
import org.apache.openjpa.persistence.common.apps.RuntimeTest2;
|
||||||
|
import org.apache.openjpa.enhance.UnenhancedPropertyAccess;
|
||||||
|
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
|
||||||
|
import org.apache.openjpa.util.UserException;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Access;
|
||||||
|
import jakarta.persistence.AccessType;
|
||||||
|
import jakarta.persistence.Basic;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import serp.bytecode.BCClass;
|
||||||
|
import serp.bytecode.Project;
|
||||||
|
|
||||||
|
public class TestSubclassValidator extends SingleEMFTestCase {
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
setUp("openjpa.RuntimeUnenhancedClasses", "supported",
|
||||||
|
Department.class,
|
||||||
|
RuntimeTest2.class,
|
||||||
|
EnhancableGetterEntity.class,
|
||||||
|
UnenhancedPropertyAccess.class,
|
||||||
|
CLEAR_TABLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBcSubclassValidator() throws Exception {
|
||||||
|
Project project = new Project();
|
||||||
|
TemporaryClassLoader tempCl = new TemporaryClassLoader(this.getClass().getClassLoader());
|
||||||
|
final OpenJPAConfiguration conf = emf.getConfiguration();
|
||||||
|
|
||||||
|
final MetaDataRepository repos = conf.newMetaDataRepositoryInstance();
|
||||||
|
repos.setSourceMode(MetaDataModes.MODE_META);
|
||||||
|
|
||||||
|
final Log log = conf.getLog(OpenJPAConfiguration.LOG_ENHANCE);
|
||||||
|
/*
|
||||||
|
|
||||||
|
{
|
||||||
|
final BCClass bcClass = project.loadClass(Department.class.getName(), tempCl);
|
||||||
|
final ClassMetaData meta = repos.getMetaData(tempCl.loadClass(Department.class.getName()), tempCl, false);
|
||||||
|
PCSubclassValidator subclassValidator = new PCSubclassValidator(meta, bcClass, log, false);
|
||||||
|
subclassValidator.assertCanSubclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final BCClass bcClass = project.loadClass(RuntimeTest2.class.getName(), tempCl);
|
||||||
|
final ClassMetaData meta = repos.getMetaData(tempCl.loadClass(RuntimeTest2.class.getName()), tempCl, false);
|
||||||
|
PCSubclassValidator subclassValidator = new PCSubclassValidator(meta, bcClass, log, true);
|
||||||
|
subclassValidator.assertCanSubclass();
|
||||||
|
fail("RuntimeTest2 validation should fail");
|
||||||
|
}
|
||||||
|
catch (UserException ue) {
|
||||||
|
// all fine
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
{
|
||||||
|
final BCClass bcClass = project.loadClass(EnhancableGetterEntity.class.getName(), tempCl);
|
||||||
|
final ClassMetaData meta = repos.getMetaData(tempCl.loadClass(EnhancableGetterEntity.class.getName()), tempCl, false);
|
||||||
|
PCSubclassValidator subclassValidator = new PCSubclassValidator(meta, bcClass, log, true);
|
||||||
|
subclassValidator.assertCanSubclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
final BCClass bcClass = project.loadClass(UnenhancedPropertyAccess.class.getName(), tempCl);
|
||||||
|
final ClassMetaData meta = repos.getMetaData(tempCl.loadClass(UnenhancedPropertyAccess.class.getName()), tempCl, false);
|
||||||
|
PCSubclassValidator subclassValidator = new PCSubclassValidator(meta, bcClass, log, true);
|
||||||
|
subclassValidator.assertCanSubclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Access(AccessType.PROPERTY)
|
||||||
|
public static class EnhancableGetterEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Basic
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Basic
|
||||||
|
private String another;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDifferent() {
|
||||||
|
return another;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDifferent(String another) {
|
||||||
|
this.another = another;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue