This is an automated email from the ASF dual-hosted git repository.

struberg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openjpa.git

commit 92bb3b055ae4f882b10a197b012c0a1a3449a4cf
Author: Mark Struberg <strub...@apache.org>
AuthorDate: Sat Jun 17 20:29:00 2023 +0200

    OPENJPA-2911 replace Field access via ASM
---
 .../org/apache/openjpa/enhance/PCEnhancer.java     | 234 +++++++++++++--------
 .../org/apache/openjpa/util/asm/AsmHelper.java     | 168 +++++++++++++++
 .../enhance/stats/FetchStatisticsAuxEnhancer.java  |   6 +
 3 files changed, 315 insertions(+), 93 deletions(-)

diff --git 
a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java 
b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java
index 3b26af905..057627a5b 100644
--- a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java
@@ -109,16 +109,12 @@ import serp.bytecode.BCMethod;
 import serp.bytecode.Code;
 import serp.bytecode.Constants;
 import serp.bytecode.Exceptions;
-import serp.bytecode.FieldInstruction;
 import serp.bytecode.IfInstruction;
 import serp.bytecode.Instruction;
 import serp.bytecode.JumpInstruction;
 import serp.bytecode.LoadInstruction;
-import serp.bytecode.LookupSwitchInstruction;
-import serp.bytecode.MethodInstruction;
 import serp.bytecode.Project;
 import serp.bytecode.PutFieldInstruction;
-import serp.bytecode.TableSwitchInstruction;
 
 /**
  * Bytecode enhancer used to enhance persistent classes from metadata. The
@@ -594,13 +590,11 @@ public class PCEnhancer {
                 if (getCreateSubclass()) {
                     addAttributeTranslation();
                 }
-                AsmHelper.readIntoBCClass(pc, _pc);
             }
             replaceAndValidateFieldAccess();
             processViolations();
 
             if (_meta != null) {
-                pc = AsmHelper.toClassNode(_pc);
                 enhanceClass(pc);
                 addFields(pc);
                 //X TODO finish addStaticInitializer(classNodeTracker);
@@ -1098,25 +1092,12 @@ public class PCEnhancer {
      * pcGet/pcSet method. Note that this includes access to fields
      * owned by PersistenceCapable classes other than this one.
      */
-    private void replaceAndValidateFieldAccess() throws NoSuchMethodException {
-        // create template putfield/getfield instructions to search for
-        Code template = AccessController.doPrivileged(
-                J2DoPrivHelper.newCodeAction());
-        Instruction put = template.putfield();
-        Instruction get = template.getfield();
-        Instruction stat = template.invokestatic();
-
-        // look through all methods; this is done before any methods are added
-        // so we don't need to worry about excluding synthetic methods.
-        BCMethod[] methods = _managedType.getDeclaredMethods();
-        Code code;
-        for (BCMethod method : methods) {
-            code = method.getCode(false);
-
-            // don't modify the methods specified by the auxiliary enhancers
-            if (code != null && !skipEnhance(method)) {
-                replaceAndValidateFieldAccess(code, get, true, stat);
-                replaceAndValidateFieldAccess(code, put, false, stat);
+    private void replaceAndValidateFieldAccess() throws NoSuchMethodException, 
ClassNotFoundException {
+        final ClassNode classNode = pc.getClassNode();
+        for (MethodNode methodNode : classNode.methods) {
+            if (methodNode.instructions.size() > 0 && 
!skipEnhance(methodNode)) {
+                replaceAndValidateFieldAccess(classNode, methodNode, (a)-> 
a.getOpcode() == Opcodes.GETFIELD, true);
+                replaceAndValidateFieldAccess(classNode, methodNode, (a)-> 
a.getOpcode() == Opcodes.PUTFIELD, false);
             }
         }
     }
@@ -1125,28 +1106,27 @@ public class PCEnhancer {
      * Replaces all instructions matching the given template in the given
      * code block with calls to the appropriate generated getter/setter.
      *
-     * @param code the code block to modify; the code iterator will
+     * @param methodNode the code block to modify; the code iterator will
      *             be placed before the first instruction on method start,
      *             and will be after the last instruction on method completion
-     * @param ins  the template instruction to search for; either a
+     * @param insnCheck  the template instruction to search for; either a
      *             getfield or putfield instruction
      * @param get  boolean indicating if this is a get instruction
-     * @param stat template invokestatic instruction to replace with
      */
-    private void replaceAndValidateFieldAccess(Code code, Instruction ins,
-                                               boolean get, Instruction stat) 
throws NoSuchMethodException {
-        code.beforeFirst();
+    private void replaceAndValidateFieldAccess(ClassNode classNode, MethodNode 
methodNode, Predicate<AbstractInsnNode> insnCheck,
+                                               boolean get) throws 
NoSuchMethodException, ClassNotFoundException {
+        AbstractInsnNode currentInsn = methodNode.instructions.getFirst();
 
-        FieldInstruction fi;
-        MethodInstruction mi;
-        ClassMetaData owner;
-        String name, typeName, methodName;
-        while (code.searchForward(ins)) {
-            // back up to the matched instruction
-            fi = (FieldInstruction) code.previous();
-            name = fi.getFieldName();
-            typeName = fi.getFieldTypeName();
-            owner = getPersistenceCapableOwner(name, 
fi.getFieldDeclarerType());
+        // skip to the next instruction we are looking for
+        while ((currentInsn = searchNextInstruction(currentInsn, insnCheck)) 
!= null) {
+            FieldInsnNode fi = (FieldInsnNode) currentInsn;
+            String name = fi.name;
+
+            ClassMetaData owner = null;
+            if (fi.owner != null) {
+                final Class<?> declarerType = 
AsmHelper.getDescribedClass(managedType.getClassLoader(), fi.owner);
+                owner = getPersistenceCapableOwner(name, declarerType);
+            }
             FieldMetaData fmd = owner == null ? null : owner.getField(name);
             if (isPropertyAccess(fmd)) {
                 // if we're directly accessing a field in another class
@@ -1155,74 +1135,99 @@ public class PCEnhancer {
                         _meta != null && !owner.getDescribedType()
                         .isAssignableFrom(_meta.getDescribedType()))
                     throw new UserException(_loc.get("property-field-access",
-                                                     new Object[]{_meta, 
owner, name,
-                                                             
code.getMethod().getName()}));
+                                                     new Object[]{_meta, 
owner, name, methodNode.name}));
 
                 // if we're directly accessing a property-backing field outside
                 // the property in our own class, notify user
-                if (isBackingFieldOfAnotherProperty(name, code))
-                    addViolation("property-field-access", new Object[]{_meta,
-                            owner, name, code.getMethod().getName()}, false);
+                if (isBackingFieldOfAnotherProperty(methodNode, name)) {
+                    addViolation("property-field-access", new Object[]{_meta, 
owner, name, methodNode.name}, false);
+                }
             }
 
-            if (owner == null ||
-                    owner.getDeclaredField(fromBackingFieldName(name)) == 
null) {
-                // not persistent field?
-                code.next();
-                continue;
+            if (owner == null || 
owner.getDeclaredField(fromBackingFieldName(name)) == null) {
+                // not a persistent field?
             }
-            else if (!getRedefine() && !getCreateSubclass()
-                    && isFieldAccess(fmd)) {
-                // replace the instruction with a call to the generated access
-                // method
-                mi = (MethodInstruction) code.set(stat);
-
-                // invoke the proper access method, whether getter or setter
-                String prefix = (get) ? PRE + "Get" : PRE + "Set";
-                methodName = prefix + name;
+            else if (!getRedefine() && !getCreateSubclass() && 
isFieldAccess(fmd)) {
+                // replace the instruction with a call to the generated access 
method
+                Type ownerType = Type.getType(getType(owner));
+                MethodInsnNode pcCall;
                 if (get) {
-                    mi.setMethod(getType(owner).getName(),
-                                 methodName, typeName, new String[]
-                                         {getType(owner).getName()});
+                    pcCall = new MethodInsnNode(Opcodes.INVOKESTATIC,
+                                                ownerType.getInternalName(),
+                                                PRE + "Get" + name,
+                                                
Type.getMethodDescriptor(Type.getType(fi.desc), ownerType));
                 }
                 else {
-                    mi.setMethod(getType(owner).getName(),
-                                 methodName, "void", new String[]
-                                         {getType(owner).getName(), typeName});
+                    pcCall = new MethodInsnNode(Opcodes.INVOKESTATIC,
+                                                ownerType.getInternalName(),
+                                                PRE + "Set" + name,
+                                                
Type.getMethodDescriptor(Type.VOID_TYPE, ownerType, Type.getType(fi.desc)));
                 }
-                code.next();
+                methodNode.instructions.insertBefore(currentInsn, pcCall);
+                // and now delete the direct field access
+                methodNode.instructions.remove(currentInsn);
+
+                // next iteration will be started here.
+                currentInsn = pcCall;
             }
             else if (getRedefine()) {
                 name = fromBackingFieldName(name);
                 if (get) {
-                    addNotifyAccess(code, owner.getField(name));
-                    code.next();
+                    addNotifyAccess(methodNode, currentInsn, 
owner.getField(name));
                 }
                 else {
                     // insert the set operations after the field mutation, but
                     // first load the old value for use in the
                     // StateManager.settingXXX method.
-                    loadManagedInstance(code, false);
-                    final FieldInstruction fFi = fi;
-                    code.getfield().setField(
-                            AccessController.doPrivileged(J2DoPrivHelper
-                                                                  
.getFieldInstructionFieldAction(fFi)));
-                    int val = code.getNextLocalsIndex();
-                    code.xstore().setLocal(val).setType(fi.getFieldType());
-
-                    // move past the putfield
-                    code.next();
-                    addNotifyMutation(code, owner.getField(name), val, -1);
+
+                    InsnList insns = new InsnList();
+                    insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
+
+                    int valVarPos = methodNode.maxLocals++;
+                    insns.add(new 
VarInsnNode(AsmHelper.getCorrespondingLoadInsn(fi.getOpcode()), valVarPos));
+
+                    currentInsn = addNotifyMutation(classNode, methodNode, 
currentInsn, owner.getField(name), valVarPos, -1);
                 }
+
             }
-            else {
-                code.next();
-            }
-            code.calculateMaxLocals();
-            code.calculateMaxStack();
+
+            currentInsn = currentInsn.getNext();
         }
     }
 
+    /**
+     * Scan the instructions until you found any which fits the predicate.
+     * @param currentInsn the instruction to start searching from
+     * @param insnCheck the condition which has to be met
+     * @return the instruction node we did search for or {@code null} if there 
is no such instruction.
+     */
+    private AbstractInsnNode searchNextInstruction(AbstractInsnNode 
currentInsn, Predicate<AbstractInsnNode> insnCheck) {
+        while (currentInsn != null && !insnCheck.test(currentInsn)) {
+            currentInsn = currentInsn.getNext();
+        }
+
+        return currentInsn;
+    }
+
+    /**
+     * Add the following code to the code:
+     * <code>
+     * PCHelper.accessingField(this, <absolute-index>);
+     * </code>
+     */
+    private void addNotifyAccess(MethodNode methodNode, AbstractInsnNode 
currentInsn, FieldMetaData fmd) {
+        InsnList insns = new InsnList();
+
+        insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
+        insns.add(new LdcInsnNode(fmd.getIndex()));
+        insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
+                                     
Type.getInternalName(RedefinitionHelper.class),
+                                     "accessingField",
+                                     Type.getMethodDescriptor(Type.VOID_TYPE, 
TYPE_OBJECT, Type.INT_TYPE)));
+
+        methodNode.instructions.insertBefore(currentInsn, insns);
+    }
+
     private void addNotifyAccess(Code code, FieldMetaData fmd) {
         // PCHelper.accessingField(this, <absolute-index>);
         code.aload().setThis();
@@ -1232,6 +1237,45 @@ public class PCEnhancer {
                                       new Class[]{Object.class, int.class});
     }
 
+    /**
+     * This must be called after setting the value in the object.
+     *
+     * @param valVarPos   the position in the local variable table where the
+     *              old value is stored
+     * @param param the parameter position containing the new value, or
+     *              -1 if the new value is unavailable and should therefore be 
looked
+     *              up.
+     * @return the last inserted InsnNode
+     */
+    private AbstractInsnNode addNotifyMutation(ClassNode classNode, MethodNode 
methodNode, AbstractInsnNode currentInsn,
+                                   FieldMetaData fmd, int valVarPos, int 
param) {
+        // PCHelper.settingField(this, <absolute-index>, old, new);
+        InsnList insns = new InsnList();
+
+        insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
+        insns.add(new LdcInsnNode(fmd.getIndex()));
+
+        Class type = fmd.getDeclaredType();
+        // we only have special signatures for primitives and Strings
+        if (!type.isPrimitive() && type != String.class) {
+            type = Object.class;
+        }
+        insns.add(new VarInsnNode(AsmHelper.getLoadInsn(type), valVarPos));
+        if (param == -1) {
+            insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
+            addGetManagedValueCode(classNode, insns, fmd, true);
+        }
+        else {
+            insns.add(new VarInsnNode(AsmHelper.getLoadInsn(type), param + 1));
+        }
+        insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
+                                     
Type.getInternalName(RedefinitionHelper.class),
+                                     "settingField",
+                                     Type.getMethodDescriptor(Type.VOID_TYPE, 
TYPE_OBJECT, Type.INT_TYPE, Type.getType(type))));
+
+        methodNode.instructions.insert(currentInsn, insns);
+        return insns.getLast();
+    }
     /**
      * This must be called after setting the value in the object.
      *
@@ -1240,10 +1284,8 @@ public class PCEnhancer {
      * @param param the parameter position containing the new value, or
      *              -1 if the new value is unavailable and should therefore be 
looked
      *              up.
-     * @throws NoSuchMethodException
      */
-    private void addNotifyMutation(Code code, FieldMetaData fmd, int val,
-                                   int param)
+    private void addNotifyMutation(Code code, FieldMetaData fmd, int val, int 
param)
             throws NoSuchMethodException {
         // PCHelper.settingField(this, <absolute-index>, old, new);
         code.aload().setThis();
@@ -1270,8 +1312,8 @@ public class PCEnhancer {
      * Return true if the given instruction accesses a field that is a backing
      * field of another property in this property-access class.
      */
-    private boolean isBackingFieldOfAnotherProperty(String name, Code code) {
-        String methName = code.getMethod().getName();
+    private boolean isBackingFieldOfAnotherProperty(MethodNode methodNode, 
String name) {
+        String methName = methodNode.name;
         return !"<init>".equals(methName)
                 && _backingFields != null
                 && !name.equals(_backingFields.get(methName))
@@ -4093,13 +4135,15 @@ public class PCEnhancer {
      * @return true if any of the auxiliary enhancers skips the given method,
      * or if the method is a constructor
      */
-    private boolean skipEnhance(BCMethod method) {
-        if ("<init>".equals(method.getName()))
+    private boolean skipEnhance(MethodNode method) {
+        if ("<init>".equals(method.name) || "<clinit>".equals(method.name)) {
             return true;
+        }
 
         for (AuxiliaryEnhancer auxEnhancer : _auxEnhancers)
-            if (auxEnhancer.skipEnhance(method))
+            if (auxEnhancer.skipEnhance(method)) {
                 return true;
+            }
 
         return false;
     }
@@ -5606,7 +5650,11 @@ public class PCEnhancer {
     public interface AuxiliaryEnhancer
     {
         void run (BCClass bc, ClassMetaData meta);
+
+        @Deprecated
         boolean skipEnhance(BCMethod m);
+
+        boolean skipEnhance(MethodNode m);
     }
 
     private void addGetIDOwningClass()  {
diff --git 
a/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java 
b/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java
index 7e3298806..2a23a01c0 100644
--- a/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java
@@ -19,6 +19,7 @@ package org.apache.openjpa.util.asm;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
@@ -28,6 +29,7 @@ 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.VarInsnNode;
 
 import serp.bytecode.BCClass;
@@ -39,6 +41,8 @@ import serp.bytecode.Project;
  * @author <a href="mailto:strub...@apache.org";>Mark Struberg</a>
  */
 public final class AsmHelper {
+    private static final char[] PRIMITIVE_DESCRIPTORS = 
{'V','Z','C','B','S','I','F','J','D'};
+
     private AsmHelper() {
         // utility class ct
     }
@@ -313,4 +317,168 @@ public final class AsmHelper {
                 && insn.getOpcode() == Opcodes.ALOAD
                 && ((VarInsnNode)insn).var == 0;
     }
+
+    /**
+     * Get the corresponding LOAD instruction for the given STORE instruction.
+     * 
+     * @param storeInsnOpcode e.g. {@link Opcodes#ISTORE}
+     * @throws IllegalArgumentException if the given opcode is not a STORE 
instruction
+     */
+    public static int getCorrespondingLoadInsn(int storeInsnOpcode) {
+        switch (storeInsnOpcode) {
+            case Opcodes.ISTORE:
+                return Opcodes.ILOAD;
+            case Opcodes.LSTORE:
+                return Opcodes.LLOAD;
+            case Opcodes.FSTORE:
+                return Opcodes.FLOAD;
+            case Opcodes.DSTORE:
+                return Opcodes.DLOAD;
+            case Opcodes.ASTORE:
+                return Opcodes.ALOAD;
+            case Opcodes.IASTORE:
+                return Opcodes.IALOAD;
+            case Opcodes.LASTORE:
+                return Opcodes.LALOAD;
+            case Opcodes.FASTORE:
+                return Opcodes.FALOAD;
+            case Opcodes.DASTORE:
+                return Opcodes.DALOAD;
+            case Opcodes.AASTORE:
+                return Opcodes.AALOAD;
+            case Opcodes.BASTORE:
+                return Opcodes.BALOAD;
+            case Opcodes.CASTORE:
+                return Opcodes.CALOAD;
+            case Opcodes.SASTORE:
+                return Opcodes.SALOAD;
+            default:
+                throw new IllegalArgumentException("Not a STORE instruction: " 
+ storeInsnOpcode);
+        }
+    }
+
+    /**
+     * Get the corresponding STORE instruction for the given LOAD instruction.
+     * 
+     * @param loadInsnOpcode e.g. {@link Opcodes#FLOAD}
+     * @throws IllegalArgumentException if the given opcode is not a STORE 
instruction
+     */
+    public static int getCorrespondingStoreInsn(int loadInsnOpcode) {
+        switch (loadInsnOpcode) {
+            case Opcodes.ILOAD:
+                return Opcodes.ISTORE;
+            case Opcodes.LLOAD:
+                return Opcodes.LSTORE;
+            case Opcodes.FLOAD:
+                return Opcodes.FSTORE;
+            case Opcodes.DLOAD:
+                return Opcodes.DSTORE;
+            case Opcodes.ALOAD:
+                return Opcodes.ASTORE;
+            case Opcodes.IALOAD:
+                return Opcodes.IASTORE;
+            case Opcodes.LALOAD:
+                return Opcodes.LASTORE;
+            case Opcodes.FALOAD:
+                return Opcodes.FASTORE;
+            case Opcodes.DALOAD:
+                return Opcodes.DASTORE;
+            case Opcodes.AALOAD:
+                return Opcodes.AASTORE;
+            case Opcodes.BALOAD:
+                return Opcodes.BASTORE;
+            case Opcodes.CALOAD:
+                return Opcodes.CASTORE;
+            case Opcodes.SALOAD:
+                return Opcodes.SASTORE;
+            default:
+                throw new IllegalArgumentException("Not a LOAD instruction: " 
+ loadInsnOpcode);
+        }
+    }
+
+    /**
+     * @param typeDesc the internal type descriptor from the bytecode. See 
{@link ClassNode#name}
+     * @param includeVoid if the Void.class type also counts as primitive
+     */
+    public static boolean isPrimitive(String typeDesc, boolean includeVoid) {
+        if (typeDesc != null && typeDesc.length() == 1) {
+            char typeChar = typeDesc.charAt(0);
+            for (int i = includeVoid ? 0: 1; i<PRIMITIVE_DESCRIPTORS.length; 
i++) {
+                if (PRIMITIVE_DESCRIPTORS[i] == typeChar) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get the class from the described type
+     * @param typeDesc
+     * @return described class or {@code null} if it could not be loaded
+     */
+    public static Class<?> getDescribedClass(ClassLoader classLoader, String 
typeDesc) {
+        if (typeDesc == null || typeDesc.isEmpty()) {
+            return null;
+        }
+        if (typeDesc.charAt(0) == '[') {
+            switch (typeDesc.charAt(1)) {
+                case 'V':
+                    throw new IllegalArgumentException("There is no such thing 
as a void array");
+                case 'Z':
+                    return boolean[].class;
+                case 'C':
+                    return char[].class;
+                case 'B':
+                    return byte[].class;
+                case 'S':
+                    return short[].class;
+                case 'I':
+                    return int[].class;
+                case 'F':
+                    return float[].class;
+                case 'J':
+                    return long[].class;
+                case 'D':
+                    return double[].class;
+                default:
+                    // some object array
+                    Class<?> clazz = getClass(classLoader, 
typeDesc.substring(1));
+                    return Array.newInstance(clazz, 0).getClass();
+            }
+        }
+
+        switch (typeDesc.charAt(0)) {
+            case 'V':
+                return void.class;
+            case 'Z':
+                return boolean.class;
+            case 'C':
+                return char.class;
+            case 'B':
+                return byte.class;
+            case 'S':
+                return short.class;
+            case 'I':
+                return int.class;
+            case 'F':
+                return float.class;
+            case 'J':
+                return long.class;
+            case 'D':
+                return double.class;
+            default:
+                // some kind of class
+                return getClass(classLoader, typeDesc);
+        }
+    }
+
+    private static Class<?> getClass(ClassLoader classLoader, String typeName) 
{
+        try {
+            return Class.forName(typeName.replace("/", "."), false, 
classLoader);
+        }
+        catch (NoClassDefFoundError | ClassNotFoundException e) {
+            return null;
+        }
+    }
 }
diff --git 
a/openjpa-tools/openjpa-fetch-statistics/src/main/java/org/apache/openjpa/enhance/stats/FetchStatisticsAuxEnhancer.java
 
b/openjpa-tools/openjpa-fetch-statistics/src/main/java/org/apache/openjpa/enhance/stats/FetchStatisticsAuxEnhancer.java
index 7ec6af585..07989ea61 100644
--- 
a/openjpa-tools/openjpa-fetch-statistics/src/main/java/org/apache/openjpa/enhance/stats/FetchStatisticsAuxEnhancer.java
+++ 
b/openjpa-tools/openjpa-fetch-statistics/src/main/java/org/apache/openjpa/enhance/stats/FetchStatisticsAuxEnhancer.java
@@ -28,6 +28,7 @@ import org.apache.openjpa.meta.AccessCode;
 import org.apache.openjpa.meta.ClassMetaData;
 import org.apache.openjpa.meta.FieldMetaData;
 
+import org.apache.xbean.asm9.tree.MethodNode;
 import serp.bytecode.BCClass;
 import serp.bytecode.BCMethod;
 import serp.bytecode.Code;
@@ -51,6 +52,11 @@ public class FetchStatisticsAuxEnhancer implements 
AuxiliaryEnhancer {
         return false;
     }
 
+    @Override
+    public boolean skipEnhance(MethodNode m) {
+        return false;
+    }
+
     private void addEnhancement(BCClass bcc, ClassMetaData cmd) {
         Log log = 
cmd.getRepository().getConfiguration().getLog(OpenJPAConfiguration.LOG_RUNTIME);
         FetchStatsCollector.setlogger(log);

Reply via email to