All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.openjpa.enhance.PCEnhancer Maven / Gradle / Ivy

The newest version!
/*
 * 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 java.io.Externalizable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLDecoder;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.meta.ClassArgParser;
import org.apache.openjpa.util.asm.BytecodeWriter;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.Files;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Localizer.Message;
import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.lib.util.Services;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.lib.util.git.GitUtils;
import org.apache.openjpa.meta.AccessCode;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.MetaDataModes;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.meta.ValueStrategies;
import org.apache.openjpa.util.ApplicationIds;
import org.apache.openjpa.util.BigDecimalId;
import org.apache.openjpa.util.BigIntegerId;
import org.apache.openjpa.util.ByteId;
import org.apache.openjpa.util.CharId;
import org.apache.openjpa.util.DateId;
import org.apache.openjpa.util.DoubleId;
import org.apache.openjpa.util.FloatId;
import org.apache.openjpa.util.GeneralException;
import org.apache.openjpa.util.Id;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.IntId;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.LongId;
import org.apache.openjpa.util.ObjectId;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.ShortId;
import org.apache.openjpa.util.StringId;
import org.apache.openjpa.util.UserException;
import org.apache.openjpa.util.asm.AsmHelper;
import org.apache.openjpa.util.asm.ClassNodeTracker;
import org.apache.openjpa.util.asm.EnhancementProject;
import org.apache.openjpa.util.asm.RedefinedAttribute;
import org.apache.xbean.asm9.Opcodes;
import org.apache.xbean.asm9.Type;
import org.apache.xbean.asm9.tree.*;


/**
 * Bytecode enhancer used to enhance persistent classes from metadata. The
 * enhancer must be invoked on all persistence-capable and persistence aware
 * classes.
 *
 * @author Abe White
 * @author Mark Struberg
 */
public class PCEnhancer {
    // Designates a version for maintaining compatbility when PCEnhancer
    // modifies enhancement that can break serialization or other contracts
    // Each enhanced class will return the value of this field via
    // public int getEnhancementContractVersion()
    public static final int ENHANCER_VERSION;

    public static final int ENHANCE_NONE = 0;
    public static final int ENHANCE_AWARE = 2 << 0;
    public static final int ENHANCE_INTERFACE = 2 << 1;
    public static final int ENHANCE_PC = 2 << 2;

    public static final String PRE = "pc";
    public static final String ISDETACHEDSTATEDEFINITIVE = PRE + "isDetachedStateDefinitive";

    private static final Class PCTYPE = PersistenceCapable.class;
    private static final Type TYPE_PCTYPE = Type.getType(PersistenceCapable.class);
    private static final String SM = PRE + "StateManager";
    private static final Class SMTYPE = StateManager.class;
    private static final String INHERIT = PRE + "InheritedFieldCount";
    private static final String CONTEXTNAME = "GenericContext";
    private static final Class USEREXCEP = UserException.class;
    private static final Class INTERNEXCEP = InternalException.class;
    private static final Class HELPERTYPE = PCRegistry.class;
    private static final String SUPER = PRE + "PCSuperclass";
    private static final Class OIDFSTYPE = FieldSupplier.class;
    private static final Class OIDFCTYPE = FieldConsumer.class;

    private static final String VERSION_INIT_STR = PRE + "VersionInit";

    private static final Localizer _loc = Localizer.forPackage(PCEnhancer.class);

    private static final AuxiliaryEnhancer[] _auxEnhancers;

    private static final Method LONG_VALUE_OF = Stream.of(Long.class.getDeclaredMethods())
            .filter(m -> "valueOf".equals(m.getName()) && long.class == m.getParameterTypes()[0])
            .findAny().get();

    static {
        Class[] classes = Services.getImplementorClasses(
                AuxiliaryEnhancer.class,
                AccessController.doPrivileged(
                        J2DoPrivHelper.getClassLoaderAction(AuxiliaryEnhancer.class)));
        List auxEnhancers = new ArrayList(classes.length);
        for (Class aClass : classes) {
            try {
                auxEnhancers.add(AccessController.doPrivileged(
                        J2DoPrivHelper.newInstanceAction(aClass)));
            }
            catch (Throwable t) {
                // aux enhancer may rely on non-existant spec classes, etc
            }
        }
        _auxEnhancers = (AuxiliaryEnhancer[]) auxEnhancers.toArray
                (new AuxiliaryEnhancer[0]);

        int rev = 0;
        Properties revisionProps = new Properties();
        try {
            InputStream in = PCEnhancer.class.getResourceAsStream("/META-INF/org.apache.openjpa.revision.properties");
            if (in != null) {
                try (in) {
                    revisionProps.load(in);
                }
            }
            rev = GitUtils.convertGitInfoToPCEnhancerVersion(revisionProps.getProperty("openjpa.enhancer.revision"));
        }
        catch (Exception e) {
        }
        if (rev > 0) {
            ENHANCER_VERSION = rev;
        }
        else {
            // Something bad happened and we couldn't load from the properties file. We need to default to using the
            // value of 2 because that is the value that was the value as of rev.511998.
            ENHANCER_VERSION = 2;
        }
    }

    private final MetaDataRepository _repos;
    private final ClassMetaData _meta;
    private final Log _log;

    boolean _addVersionInitFlag = true;


    private final EnhancementProject project;

    /**
     * represents the managed type.
     */
    private final ClassNodeTracker managedType;

    /**
     * represents the persistent class.
     * This might be the same as {@link #managedType}
     * but also a subclass.
     */
    private ClassNodeTracker pc;

    private boolean _defCons = true;
    private boolean _redefine = false;
    private boolean _subclass = false;
    private boolean _fail = false;
    private Set _violations = null;
    private File _dir = null;
    private BytecodeWriter _writer = null;
    private Map _backingFields = null; // map of set / get names => field names
    private Map _attrsToFields = null; // map of attr names => field names
    private Map _fieldsToAttrs = null; // map of field names => attr names
    private boolean _isAlreadyRedefined = false;
    private boolean _isAlreadySubclassed = false;
    private boolean _bcsConfigured = false;

    private boolean _optimizeIdCopy = false; // whether to attempt optimizing id copy

    /**
     * Constructor. Supply configuration and type to enhance. This will look
     * up the metadata for type from conf's
     * repository.
     */
    public PCEnhancer(OpenJPAConfiguration conf, Class type) {
        this(conf, new EnhancementProject().loadClass(type), (MetaDataRepository) null);
    }

    /**
     * Constructor. Supply configuration and type to enhance. This will look
     * up the metadata for meta by converting back to a class
     * and then loading from conf's repository.
     */
    public PCEnhancer(OpenJPAConfiguration conf, ClassMetaData meta) {
        this(conf, new EnhancementProject().loadClass(meta.getDescribedType()), meta.getRepository());
    }

    /**
     * Constructor. Supply configuration.
     *
     * @param type  the bytecode representation fo the type to
     *              enhance; this can be created from any stream or file
     * @param repos a metadata repository to use for metadata access,
     *              or null to create a new reporitory; the repository
     *              from the given configuration isn't used by default
     *              because the configuration might be an
     *              implementation-specific subclass whose metadata
     *              required more than just base metadata files
     * @deprecated use {@link #PCEnhancer(OpenJPAConfiguration, ClassNodeTracker,
     * MetaDataRepository, ClassLoader)} instead.
     */
    @Deprecated
    public PCEnhancer(OpenJPAConfiguration conf, ClassNodeTracker type, MetaDataRepository repos) {
        this(conf, type, repos, null);
    }

    /**
     * Constructor. Supply configuration.
     *
     * @param type   the bytecode representation fo the type to
     *               enhance; this can be created from any stream or file
     * @param repos  a metadata repository to use for metadata access,
     *               or null to create a new reporitory; the repository
     *               from the given configuration isn't used by default
     *               because the configuration might be an
     *               implementation-specific subclass whose metadata
     *               required more than just base metadata files
     * @param loader the environment classloader to use for loading
     *               classes and resources.
     */
    public PCEnhancer(OpenJPAConfiguration conf, ClassNodeTracker type, MetaDataRepository repos, ClassLoader loader) {

        // we assume that the original class and the enhanced class is the same
        project = type.getProject();
        managedType = type;
        pc = managedType;

        _log = conf.getLog(OpenJPAConfiguration.LOG_ENHANCE);

        if (repos == null) {
            _repos = conf.newMetaDataRepositoryInstance();
            _repos.setSourceMode(MetaDataModes.MODE_META);
        }
        else {
            _repos = repos;
        }

        _meta = _repos.getMetaData(type.getType(), loader, false);

        configureOptimizeIdCopy();
    }

    /**
     * Constructor. Supply repository. The repository's configuration will
     * be used, and the metadata passed in will be used as-is without doing
     * any additional lookups. This is useful when running the enhancer
     * during metadata load.
     *
     * @param repos a metadata repository to use for metadata access,
     *              or null to create a new reporitory; the repository
     *              from the given configuration isn't used by default
     *              because the configuration might be an
     *              implementation-specific subclass whose metadata
     *              required more than just base metadata files
     * @param type  the bytecode representation fo the type to
     *              enhance; this can be created from any stream or file
     * @param meta  the metadata to use for processing this type.
     * @since 1.1.0
     */
    public PCEnhancer(MetaDataRepository repos, ClassNodeTracker type, ClassMetaData meta) {
        // we assume that the original class and the enhanced class is the same
        project = type.getProject();
        managedType = type;
        pc = managedType;

        _log = repos.getConfiguration()
                .getLog(OpenJPAConfiguration.LOG_ENHANCE);

        _repos = repos;
        _meta = meta;
    }

    static String toPCSubclassName(ClassNodeTracker cnt) {
        return ClassUtil.getPackageName(PCEnhancer.class) + "."
                + cnt.getClassNode().name.replace('/', '$') + "$pcsubclass";
    }

    @Deprecated
    static String toPCSubclassName(Class cls) {
        return ClassUtil.getPackageName(PCEnhancer.class) + "."
                + cls.getName().replace('.', '$') + "$pcsubclass";
    }

    /**
     * Whether className is the name for a
     * dynamically-created persistence-capable subclass.
     *
     * @since 1.1.0
     */
    public static boolean isPCSubclassName(String className) {
        return className.startsWith(ClassUtil.getPackageName(PCEnhancer.class))
                && className.endsWith("$pcsubclass");
    }

    /**
     * If className is a dynamically-created persistence-capable
     * subclass name, returns the name of the class that it subclasses.
     * Otherwise, returns className.
     *
     * @since 1.1.0
     */
    public static String toManagedTypeName(String className) {
        if (isPCSubclassName(className)) {
            className = className.substring(
                    ClassUtil.getPackageName(PCEnhancer.class).length() + 1);
            className = className.substring(0, className.lastIndexOf("$"));
            // this is not correct for nested PCs
            className = className.replace('$', '.');
        }

        return className;
    }

    /**
     * Return the bytecode representation of the persistence-capable class
     * being manipulated.
     */
    public ClassNodeTracker getPCBytecode() {
        return pc;
    }

    /**
     * Return the bytecode representation of the managed class being
     * manipulated. This is usually the same as {@link #getPCBytecode},
     * except when running the enhancer to redefine and subclass
     * existing persistent types.
     */
    public ClassNodeTracker getManagedTypeBytecode() {
        return managedType;
    }

    /**
     * Return the metadata for the class being manipulated, or null if not
     * a persistent type.
     */
    public ClassMetaData getMetaData() {
        return _meta;
    }

    /**
     * A boolean indicating whether the enhancer should add a no-args
     * constructor if one is not already present in the class. OpenJPA
     * requires that a no-arg constructor (whether created by the compiler
     * or by the user) be present in a PC.
     */
    public boolean getAddDefaultConstructor() {
        return _defCons;
    }

    /**
     * A boolean indicating whether the enhancer should add a no-args
     * constructor if one is not already present in the class. OpenJPA
     * requires that a no-arg constructor (whether created by the compiler
     * or by the user) be present in a PC.
     */
    public void setAddDefaultConstructor(boolean addDefaultConstructor) {
        _defCons = addDefaultConstructor;
    }

    /**
     * Whether the enhancer should mutate its arguments, or just run validation
     * and optional subclassing logic on them. Usually used in conjunction with
     * setCreateSubclass(true).
     *
     * @since 1.0.0
     */
    public boolean getRedefine() {
        return _redefine;
    }

    /**
     * Whether the enhancer should mutate its arguments, or just run validation
     * and optional subclassing logic on them. Usually used in conjunction with
     * setCreateSubclass(true).
     *
     * @since 1.0.0
     */
    public void setRedefine(boolean redefine) {
        _redefine = redefine;
    }

    /**
     * Whether the type that this instance is enhancing has already been
     * redefined.
     *
     * @since 1.0.0
     */
    public boolean isAlreadyRedefined() {
        return _isAlreadyRedefined;
    }

    /**
     * Whether the type that this instance is enhancing has already been
     * subclassed in this instance's environment classloader.
     *
     * @since 1.0.0
     */
    public boolean isAlreadySubclassed() {
        return _isAlreadySubclassed;
    }

    /**
     * Whether the enhancer should make its arguments persistence-capable,
     * or generate a persistence-capable subclass.
     *
     * @since 1.0.0
     */
    public boolean getCreateSubclass() {
        return _subclass;
    }

    /**
     * Whether the enhancer should make its arguments persistence-capable,
     * or generate a persistence-capable subclass.
     *
     * @since 1.0.0
     */
    public void setCreateSubclass(boolean subclass) {
        _subclass = subclass;
        _addVersionInitFlag = false;
    }

    /**
     * Whether to fail if the persistent type uses property access and
     * bytecode analysis shows that it may be violating OpenJPA's property
     * access restrictions.
     */
    public boolean getEnforcePropertyRestrictions() {
        return _fail;
    }

    /**
     * Whether to fail if the persistent type uses property access and
     * bytecode analysis shows that it may be violating OpenJPA's property
     * access restrictions.
     */
    public void setEnforcePropertyRestrictions(boolean fail) {
        _fail = fail;
    }

    /**
     * The base build directory to generate code to. The proper package
     * structure will be created beneath this directory. Defaults to
     * overwriting the existing class file if null.
     */
    public File getDirectory() {
        return _dir;
    }

    /**
     * The base build directory to generate code to. The proper package
     * structure will be creaed beneath this directory. Defaults to
     * overwriting the existing class file if null.
     */
    public void setDirectory(File dir) {
        _dir = dir;
    }

    /**
     * Return the current {@link BytecodeWriter} to write to or null if none.
     */
    public BytecodeWriter getBytecodeWriter() {
        return _writer;
    }

    /**
     * Set the {@link BytecodeWriter} to write the bytecode to or null if none.
     */
    public void setBytecodeWriter(BytecodeWriter writer) {
        _writer = writer;
    }

    /**
     * Perform bytecode enhancements.
     *
     * @return ENHANCE_* constant
     */
    public int run() {
        try {
            // if enum, skip, no need of any meta
            if ((managedType.getClassNode().access & Opcodes.ACC_ENUM) > 0) {
                return ENHANCE_NONE;
            }

            // if managed interface, skip
            if ((managedType.getClassNode().access & Opcodes.ACC_INTERFACE) > 0) {
                return ENHANCE_INTERFACE;
            }

            // check if already enhanced
            // we cannot simply use instanceof or isAssignableFrom as we have a temp ClassLoader inbetween
            ClassLoader loader = managedType.getClassLoader();
            for (String iface : managedType.getClassNode().interfaces) {
                final String pctypeInternalName = TYPE_PCTYPE.getInternalName();
                if (iface.equals(pctypeInternalName)) {
                    if (_log.isTraceEnabled()) {
                        _log.trace(_loc.get("pc-type", managedType.getClassNode().name, loader));
                    }
                    return ENHANCE_NONE;
                }
            }

            if (_log.isTraceEnabled()) {
                _log.trace(_loc.get("enhance-start", managedType.getClassNode().name));
            }


            configureBCs();

            // validate properties before replacing field access so that
            // we build up a record of backing fields, etc
            if (isPropertyAccess(_meta)) {
                validateProperties();
                if (getCreateSubclass()) {
                    addAttributeTranslation();
                }
            }
            replaceAndValidateFieldAccess();
            processViolations();

            if (_meta != null) {
                enhanceClass(pc);
                addFields(pc);
                addStaticInitializer(pc);
                addPCMethods();
                addAccessors(pc);
                addAttachDetachCode();
                addSerializationCode();
                addCloningCode();
                runAuxiliaryEnhancers();

                return ENHANCE_PC;
            }
            return ENHANCE_AWARE;
        }
        catch (OpenJPAException ke) {
            throw ke;
        }
        catch (Exception e) {
            throw new GeneralException(_loc.get("enhance-error",
                                                managedType.getClassNode().name, e.getMessage()), e);
        }
    }

    private void configureBCs() {
        if (!_bcsConfigured) {
            if (getRedefine()) {
                final boolean isRedefined = managedType.getClassNode().attrs != null &&
                        managedType.getClassNode().attrs.stream().anyMatch(a -> a.isUnknown() && a.type.equals(RedefinedAttribute.ATTR_TYPE));

                if (!isRedefined) {
                    if (managedType.getClassNode().attrs == null) {
                        managedType.getClassNode().attrs = new ArrayList<>();
                    }
                    managedType.getClassNode().attrs.add(new RedefinedAttribute());
                }
                else {
                    _isAlreadyRedefined = true;
                }
            }

            if (getCreateSubclass()) {
                PCSubclassValidator val = new PCSubclassValidator(_meta, managedType.getClassNode(), _log, _fail);
                val.assertCanSubclass();
                pc = project.loadClass(toPCSubclassName(managedType));
                if (pc.getClassNode().superName.equals("java/lang/Object")) {
                    // set the parent class
                    pc.getClassNode().superName = managedType.getClassNode().name;
                    if ((managedType.getClassNode().access & Opcodes.ACC_ABSTRACT) > 0) {
                        pc.getClassNode().access |= Opcodes.ACC_ABSTRACT;
                    }

                    pc.declareInterface(DynamicPersistenceCapable.class);
                }
                else {
                    _isAlreadySubclassed = true;
                }
            }

            _bcsConfigured = true;
        }
    }

    /**
     * Write the generated bytecode.
     */
    public void record() throws IOException {
        if (managedType != pc && getRedefine()) {
            record(managedType);
        }

        record(pc);
    }

    /**
     * Write the given class.
     */
    private void record(ClassNodeTracker cnt)
            throws IOException {
        if (_writer != null) {
            _writer.write(cnt);
        }
        else if (_dir == null) {
            String name = cnt.getClassNode().name.replace(".", "/");
            ClassLoader cl = cnt.getClassLoader();
            if (cl == null) {
                cl = Thread.currentThread().getContextClassLoader();
            }
            final URL resource = cl.getResource(name + ".class");
            try (OutputStream out = new FileOutputStream(URLDecoder.decode(resource.getFile()))) {
                out.write(AsmHelper.toByteArray(cnt));
                out.flush();
            }
        }
        else {
            String name = cnt.getClassNode().name.replace(".", "/") + ".class";
            File targetFile = new File(_dir, name);
            if (!targetFile.getParentFile().exists()) {
                targetFile.getParentFile().mkdirs();
            }
            java.nio.file.Files.write(targetFile.toPath(), AsmHelper.toByteArray(cnt));
        }
    }

    /**
     * Validate that the methods that use a property-access instance are
     * written correctly. This method also gathers information on each
     * property's backing field.
     */
    private void validateProperties() {
        final ClassNode classNode = managedType.getClassNode();
        FieldMetaData[] fmds;
        if (getCreateSubclass()) {
            fmds = _meta.getFields();
        }
        else {
            fmds = _meta.getDeclaredFields();
        }

        Method getter, setter;
        Field returned, assigned = null;

        for (FieldMetaData fmd : fmds) {
            if (!(fmd.getBackingMember() instanceof Method)) {
                // If not mixed access is not defined, flag the field members,
                // otherwise do not process them because they are valid
                // persistent attributes.
                if (!_meta.isMixedAccess()) {
                    addViolation("property-bad-member",
                                 new Object[]{fmd, fmd.getBackingMember()},
                                 true);
                }
                continue;
            }

            getter = (Method) fmd.getBackingMember();

            if (getter == null) {
                addViolation("property-no-getter", new Object[]{fmd},
                             true);
                continue;
            }
            returned = getReturnedField(classNode, getter);


            if (returned != null) {
                registerBackingFieldInfo(fmd, getter, returned);
            }

            setter = getMethod(getter.getDeclaringClass(), getSetterName(fmd), fmd.getDeclaredType());

            if (setter == null) {
                if (returned == null) {
                    addViolation("property-no-setter",
                                 new Object[]{fmd}, true);
                    continue;
                }
                else if (!getRedefine()) {
                    // create synthetic setter
                    MethodNode setterNode = new MethodNode(Opcodes.ACC_PRIVATE,
                                                           getSetterName(fmd),
                                                           Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(fmd.getDeclaredType())),
                                                           null, null);
                    //X TODO: pc or managedType?
                    pc.getClassNode().methods.add(setterNode);
                    InsnList instructions = setterNode.instructions;
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                    instructions.add(new VarInsnNode(AsmHelper.getLoadInsn(fmd.getDeclaredType()), 1)); // param1
                    instructions.add(new FieldInsnNode(Opcodes.PUTFIELD,
                                                       Type.getInternalName(returned.getDeclaringClass()),
                                                       returned.getName(),
                                                       Type.getDescriptor(fmd.getDeclaredType())));
                    instructions.add(new InsnNode(Opcodes.RETURN));
                }
            }

            if (setter != null) {
                assigned = getAssignedField(classNode, getMethod(fmd.getDeclaringType(), fmd.getSetterName(), new Class[]{fmd.getDeclaredType()}));
            }

            if (assigned != null) {
                if (setter != null) {
                    registerBackingFieldInfo(fmd, setter, assigned);
                }

                if (!assigned.equals(returned)) {
                    addViolation("property-setter-getter-mismatch", new Object[]
                            {fmd, assigned.getName(), (returned == null)
                                    ? null : returned.getName()}, false);
                }
            }
        }
    }

    private void registerBackingFieldInfo(FieldMetaData fmd, Method method, Field field) {
        if (_backingFields == null) {
            _backingFields = new HashMap();
        }
        _backingFields.put(method.getName(), field.getName());

        if (_attrsToFields == null) {
            _attrsToFields = new HashMap();
        }
        _attrsToFields.put(fmd.getName(), field.getName());

        if (_fieldsToAttrs == null) {
            _fieldsToAttrs = new HashMap();
        }
        _fieldsToAttrs.put(field.getName(), fmd.getName());
    }


    private void addAttributeTranslation() {

        // Get all field metadata
        ArrayList propFmds = new ArrayList<>();
        FieldMetaData[] fmds = _meta.getFields();

        if (_meta.isMixedAccess()) {
            // Stores indexes of property access fields to be used in
            //
            propFmds = new ArrayList<>();

            // Determine which fields have property access and save their
            // indexes
            for (int i = 0; i < fmds.length; i++) {
                if (isPropertyAccess(fmds[i])) {
                    propFmds.add(i);
                }
            }

            // if no fields have property access do not do attribute translation
            if (propFmds.size() == 0) {
                return;
            }
        }

        ClassNode classNode = pc.getClassNode();
        classNode.interfaces.add(Type.getInternalName(AttributeTranslator.class));

        MethodNode attrIdxMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                PRE + "AttributeIndexToFieldName",
                                                Type.getMethodDescriptor(Type.getType(String.class), Type.INT_TYPE),
                                                null, null);
        classNode.methods.add(attrIdxMeth);

        InsnList instructions = attrIdxMeth.instructions;

        // switch (val)
        instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); // int param of the method
        if (!_meta.isMixedAccess()) {
            // if not mixed access use a table switch on all property-based fmd.
            // a table switch is more efficient with +1 incremental operations

            LabelNode defLbl = new LabelNode();
            TableSwitchInsnNode switchNd = new TableSwitchInsnNode(0, fmds.length - 1, defLbl);
            instructions.add(switchNd);

            // case i:
            //     return <_attrsToFields.get(fmds[i].getName())>
            for (FieldMetaData fmd : fmds) {
                LabelNode caseLabel = new LabelNode();
                switchNd.labels.add(caseLabel);
                instructions.add(caseLabel);
                instructions.add(AsmHelper.getLoadConstantInsn(_attrsToFields.get(fmd.getName())));
                instructions.add(new InsnNode(Opcodes.ARETURN));
            }

            // default: throw new IllegalArgumentException ()
            instructions.add(defLbl);
            instructions.add(AsmHelper.throwException(IllegalArgumentException.class));
        }
        else {
            // In mixed access mode, property indexes are not +1 incremental
            // a lookup switch must be used to do indexed lookup.
            LabelNode defLbl = new LabelNode();
            LookupSwitchInsnNode switchNd = new LookupSwitchInsnNode(defLbl, null, null);
            instructions.add(switchNd);
            for (Integer i : propFmds) {
                LabelNode caseLabel = new LabelNode();
                instructions.add(caseLabel);
                switchNd.labels.add(caseLabel);
                switchNd.keys.add(propFmds.get(i));
                instructions.add(AsmHelper.getLoadConstantInsn(_attrsToFields.get(fmds[i].getName())));
                instructions.add(new InsnNode(Opcodes.ARETURN));
            }
        }
    }

    /**
     * Return the name of the setter method for the given field.
     */
    private static String getSetterName(FieldMetaData fmd) {
        return fmd.getSetterName();
    }

    /**
     * Return the field returned by the given method, or null if none.
     * Package-protected and static for testing.
     */
    static Field getReturnedField(ClassNode classNode, Method meth) {
        return findField(classNode, meth, (ain) -> ain.getOpcode() == AsmHelper.getReturnInsn(meth.getReturnType()), false);
    }


    /**
     * Return the field assigned in the given method, or null if none.
     * Package-protected and static for testing.
     */
    static Field getAssignedField(ClassNode classNode, Method meth) {
        return findField(classNode, meth, (ain) -> ain.getOpcode() == Opcodes.PUTFIELD, true);
    }

    /**
     * Return the field returned / assigned by meth. Returns
     * null if non-fields (methods, literals, parameters, variables) are
     * returned, or if non-parameters are assigned to fields.
     */
    private static Field findField(ClassNode classNode, Method meth, Predicate ain, boolean findAccessed) {
        // ignore any static methods. OpenJPA only currently supports
        // non-static setters and getters
        if (Modifier.isStatic(meth.getModifiers())) {
            return null;
        }

        if (meth.getDeclaringClass().isInterface()) {
            return null;
        }

        final MethodNode methodNode = findMethodNode(classNode, meth);

        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;
            }

            // skip a few non-functional ops like instanceof and checkcast
            if ((prevInsn.getOpcode() == Opcodes.INSTANCEOF || prevInsn.getOpcode() == Opcodes.CHECKCAST)
                    && 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.name);

                // 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.name);
            }
            else {
                return null;
            }


            if (field != null && !cur.equals(field)) {
                return null;
            }
            field = cur;
        }


        return field;
    }

    private static MethodNode findMethodNode(ClassNode classNode, Method meth) {
        return AsmHelper.getMethodNode(classNode, meth).get();
    }


    private static Field getField(Class clazz, String fieldName) {
        try {
            return clazz.getDeclaredField(fieldName);
        }
        catch (NoSuchFieldException e) {
            if (clazz.getSuperclass() == Object.class) {
                throw new IllegalStateException("Cannot find field " + fieldName + " in Class " + clazz);
            }
            return getField(clazz.getSuperclass(), fieldName);
        }
    }

    private static Method getMethod(Class clazz, String methodName, Class... paramTypes) {
        try {
            return clazz.getDeclaredMethod(methodName, paramTypes);
        }
        catch (NoSuchMethodException e) {
            if (clazz.getSuperclass() == Object.class) {
                throw new IllegalStateException("Cannot find method " + methodName + " in Class " + clazz);
            }
            return getMethod(clazz.getSuperclass(), methodName);
        }
    }

    /**
     * Record a violation of the property access restrictions.
     */
    private void addViolation(String key, Object[] args, boolean fatal) {
        if (_violations == null) {
            _violations = new HashSet();
        }
        _violations.add(_loc.get(key, args));
        _fail |= fatal;
    }

    /**
     * Log / throw recorded property access violations.
     */
    private void processViolations() {
        if (_violations == null) {
            return;
        }

        String sep = J2DoPrivHelper.getLineSeparator();
        StringBuilder buf = new StringBuilder();
        for (Iterator itr = _violations.iterator(); itr.hasNext(); ) {
            buf.append(itr.next());
            if (itr.hasNext()) {
                buf.append(sep);
            }
        }
        Message msg = _loc.get("property-violations", buf);

        if (_fail) {
            throw new UserException(msg);
        }
        if (_log.isWarnEnabled()) {
            _log.warn(msg);
        }
    }

    /**
     * Replaced all direct access to managed fields with the appropriate
     * pcGet/pcSet method. Note that this includes access to fields
     * owned by PersistenceCapable classes other than this one.
     */
    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);
            }
        }
    }

    /**
     * Replaces all instructions matching the given template in the given
     * code block with calls to the appropriate generated getter/setter.
     *
     * @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 insnCheck  the template instruction to search for; either a
     *                   getfield or putfield instruction
     * @param get        boolean indicating if this is a get instruction
     */
    private void replaceAndValidateFieldAccess(ClassNode classNode, MethodNode methodNode, Predicate insnCheck,
                                               boolean get) throws NoSuchMethodException, ClassNotFoundException {
        AbstractInsnNode currentInsn = methodNode.instructions.getFirst();

        // 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
                // hierarchy that uses property access, something is wrong
                if (owner != _meta && owner.getDeclaredField(name) != null &&
                        _meta != null && !owner.getDescribedType()
                        .isAssignableFrom(_meta.getDescribedType())) {
                    throw new UserException(_loc.get("property-field-access",
                                                     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(methodNode, name)) {
                    addViolation("property-field-access", new Object[]{_meta, owner, name, methodNode.name}, false);
                }
            }

            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
                Type ownerType = Type.getType(getType(owner));
                MethodInsnNode pcCall;
                if (get) {
                    pcCall = new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                ownerType.getInternalName(),
                                                PRE + "Get" + name,
                                                Type.getMethodDescriptor(Type.getType(fi.desc), ownerType));
                }
                else {
                    pcCall = new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                ownerType.getInternalName(),
                                                PRE + "Set" + name,
                                                Type.getMethodDescriptor(Type.VOID_TYPE, ownerType, Type.getType(fi.desc)));
                }
                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(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.

                    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);
                }

            }

            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 insnCheck) {
        while (currentInsn != null && !insnCheck.test(currentInsn)) {
            currentInsn = currentInsn.getNext();
        }

        return currentInsn;
    }

    /**
     * Add the following code to the code:
     * 
     * PCHelper.accessingField(this, );
     * 
     */
    private void addNotifyAccess(MethodNode methodNode, AbstractInsnNode currentInsn, FieldMetaData fmd) {
        InsnList insns = new InsnList();

        insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
        insns.add(AsmHelper.getLoadConstantInsn(fmd.getIndex()));
        insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                     Type.getInternalName(RedefinitionHelper.class),
                                     "accessingField",
                                     Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT, Type.INT_TYPE)));

        if (methodNode.instructions.size() == 0) {
            methodNode.instructions.add(insns);
        }
        else {
            methodNode.instructions.insertBefore(currentInsn, insns);
        }
    }

    /**
     * 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, , old, new);
        InsnList insns = new InsnList();

        insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
        insns.add(AsmHelper.getLoadConstantInsn(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 {
            // the method parameter
            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,
                                                              AsmHelper.TYPE_OBJECT, Type.INT_TYPE, Type.getType(type), Type.getType(type))));

        methodNode.instructions.insert(currentInsn, insns);
        return insns.getLast();
    }


    /**
     * 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(MethodNode methodNode, String name) {
        String methName = methodNode.name;
        return !"".equals(methName)
                && _backingFields != null
                && !name.equals(_backingFields.get(methName))
                && _backingFields.containsValue(name);
    }

    /**
     * Helper method to return the declaring PersistenceCapable class of
     * the given field.
     *
     * @param fieldName the name of the field
     * @param owner     the nominal owner of the field
     * @return the metadata for the PersistenceCapable type that
     * declares the field (and therefore has the static method), or null if none
     */
    private ClassMetaData getPersistenceCapableOwner(String fieldName,
                                                     Class owner) {
        // find the actual ancestor class that declares the field, then
        // check if the class is persistent, and if the field is managed
        Field f = Reflection.findField(owner, fieldName, false);
        if (f == null) {
            return null;
        }

        // managed interface
        if (_meta != null && _meta.getDescribedType().isInterface()) {
            return _meta;
        }

        return _repos.getMetaData(f.getDeclaringClass(), null, false);
    }

    /**
     * Adds all synthetic methods to the bytecode by delegating to
     * the various addXXXMethods () functions in this class. Includes
     * all static field access methods.
     * Note that the 'stock' methods like pcIsTransactional,
     * pcFetchObjectId, etc are defined only in the
     * least-derived PersistenceCapable type.
     */
    private void addPCMethods() throws NoSuchMethodException {
        addClearFieldsMethod(pc.getClassNode());

        addNewInstanceMethod(pc.getClassNode(), true);
        addNewInstanceMethod(pc.getClassNode(), false);

        addManagedFieldCountMethod(pc.getClassNode());
        addReplaceFieldsMethods(pc.getClassNode());
        addProvideFieldsMethods(pc.getClassNode());

        addCopyFieldsMethod(pc.getClassNode());

        if (_meta.getPCSuperclass() == null || getCreateSubclass()) {
            addStockMethods();
            addGetVersionMethod();
            addReplaceStateManagerMethod();

            if (_meta.getIdentityType() != ClassMetaData.ID_APPLICATION) {
                addNoOpApplicationIdentityMethods();
            }
        }

        // add the app id methods to each subclass rather
        // than just the superclass, since it is possible to have
        // a subclass with an app id hierarchy that matches the
        // persistent class inheritance hierarchy
        if (_meta.getIdentityType() == ClassMetaData.ID_APPLICATION
                && (_meta.getPCSuperclass() == null || getCreateSubclass() ||
                _meta.getObjectIdType() != _meta.getPCSuperclassMetaData().getObjectIdType())) {

            addCopyKeyFieldsToObjectIdMethod(true);
            addCopyKeyFieldsToObjectIdMethod(false);
            addCopyKeyFieldsFromObjectIdMethod(true);
            addCopyKeyFieldsFromObjectIdMethod(false);

            if (_meta.hasAbstractPKField()) {
                addGetIDOwningClass();
            }

            if (_meta.isEmbeddable() && _meta.getIdentityType() == ClassMetaData.ID_APPLICATION) {
                _log.warn(_loc.get("ID-field-in-embeddable-unsupported", _meta.toString()));
            }

            addNewObjectIdInstanceMethod(true);
            addNewObjectIdInstanceMethod(false);
        }
        else if (_meta.hasPKFieldsFromAbstractClass()) {
            addGetIDOwningClass();
        }
    }

    /**
     * Add a method to clear all persistent fields; we'll call this from
     * the new instance method to ensure that unloaded fields have
     * default values.
     */
    private void addClearFieldsMethod(ClassNode classNode) throws NoSuchMethodException {
        // protected void pcClearFields ()
        MethodNode clearFieldMethod = new MethodNode(Opcodes.ACC_PROTECTED,
                                                     PRE + "ClearFields",
                                                     Type.getMethodDescriptor(Type.VOID_TYPE),
                                                     null, null);
        classNode.methods.add(clearFieldMethod);
        final InsnList instructions = clearFieldMethod.instructions;

        if (_meta.getPCSuperclass() != null && !getCreateSubclass()) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                Type.getInternalName(getType(_meta.getPCSuperclassMetaData())),
                                                PRE + "ClearFields",
                                                Type.getMethodDescriptor(Type.VOID_TYPE)));
        }

        FieldMetaData[] fmds = _meta.getDeclaredFields();
        for (FieldMetaData fmd : fmds) {
            if (fmd.getManagement() != FieldMetaData.MANAGE_PERSISTENT) {
                continue;
            }
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            switch (fmd.getDeclaredTypeCode()) {
                case JavaTypes.BOOLEAN:
                case JavaTypes.BYTE:
                case JavaTypes.CHAR:
                case JavaTypes.INT:
                case JavaTypes.SHORT:
                    instructions.add(getSetValueInsns(classNode, fmd, 0));
                    break;
                case JavaTypes.DOUBLE:
                    instructions.add(getSetValueInsns(classNode, fmd, 0D));
                    break;
                case JavaTypes.FLOAT:
                    instructions.add(getSetValueInsns(classNode, fmd, 0F));
                    break;
                case JavaTypes.LONG:
                    instructions.add(getSetValueInsns(classNode, fmd, 0L));
                    break;
                default:
                    instructions.add(getSetValueInsns(classNode, fmd, null));
                    break;
            }
        }
        instructions.add(new InsnNode(Opcodes.RETURN));
    }

    /**
     * Adds the pcNewInstance method to the bytecode.
     * These methods are used by the impl helper to create new
     * managed instances efficiently without reflection.
     *
     * @param oid set to true to mimic the method version that takes
     *            an oid value as well as a state manager
     */
    private void addNewInstanceMethod(ClassNode classNode, boolean oid) {
        // public PersistenceCapable pcNewInstance (...)
        String desc = oid
                ? Type.getMethodDescriptor(Type.getType(PCTYPE), Type.getType(SMTYPE), AsmHelper.TYPE_OBJECT, Type.BOOLEAN_TYPE)
                : Type.getMethodDescriptor(Type.getType(PCTYPE), Type.getType(SMTYPE), Type.BOOLEAN_TYPE);
        MethodNode newInstance = new MethodNode(Opcodes.ACC_PUBLIC,
                                                PRE + "NewInstance",
                                                desc,
                                                null, null);
        classNode.methods.add(newInstance);
        final InsnList instructions = newInstance.instructions;

        if ((pc.getClassNode().access & Opcodes.ACC_ABSTRACT) > 0) {
            instructions.add(AsmHelper.throwException(USEREXCEP));
            return;
        }

        // XXX pc = new XXX ();
        instructions.add(new TypeInsnNode(Opcodes.NEW, classNode.name));
        instructions.add(new InsnNode(Opcodes.DUP));
        instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                            classNode.name,
                                            "",
                                            Type.getMethodDescriptor(Type.VOID_TYPE)));

        int newPcVarPos = (oid) ? 4 : 3; // number of params +1
        instructions.add(new VarInsnNode(Opcodes.ASTORE, newPcVarPos));

        // if (clear)
        //   pc.pcClearFields ();
        instructions.add(new VarInsnNode(Opcodes.ILOAD, (oid) ? 3 : 2));
        LabelNode labelAfterClearFields = new LabelNode();
        instructions.add(new JumpInsnNode(Opcodes.IFEQ, labelAfterClearFields));

        // inside the if
        instructions.add(new VarInsnNode(Opcodes.ALOAD, newPcVarPos));
        instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                            classNode.name,
                                            PRE + "ClearFields",
                                            Type.getMethodDescriptor(Type.VOID_TYPE)));

        instructions.add(labelAfterClearFields);

        // pc.pcStateManager = sm;
        instructions.add(new VarInsnNode(Opcodes.ALOAD, newPcVarPos));
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // the 1st method param
        instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

        // copy key fields from oid
        if (oid) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, newPcVarPos));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); // the 2nd method param, Object
            instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                classNode.name,
                                                PRE + "CopyKeyFieldsFromObjectId",
                                                Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT)));
        }

        instructions.add(new VarInsnNode(Opcodes.ALOAD, newPcVarPos));
        instructions.add(new InsnNode(Opcodes.ARETURN));
    }

    /**
     * Adds the protected static int pcGetManagedFieldCount ()
     * method to the bytecode, returning the inherited field count added
     * to the number of managed fields in the current PersistenceCapable class.
     */
    private void addManagedFieldCountMethod(ClassNode classNode) {
        MethodNode getFieldCountMeth = new MethodNode(Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC,
                                                      PRE + "GetManagedFieldCount",
                                                      Type.getMethodDescriptor(Type.INT_TYPE),
                                                      null, null);
        classNode.methods.add(getFieldCountMeth);

        // return  + pcInheritedFieldCount
        // awhite: the above should work, but I'm seeing a messed up situation
        // all of a sudden where when a subclass calls this method, it somehow
        // happens before  is ever invoked, and so our
        // pcInheritedFieldCount field isn't initialized!  so instead,
        // return  + .pcGetManagedFieldCount ()
        final InsnList instructions = getFieldCountMeth.instructions;
        instructions.add(AsmHelper.getLoadConstantInsn(_meta.getDeclaredFields().length));
        if (_meta.getPCSuperclass() != null) {
            Class superClass = getType(_meta.getPCSuperclassMetaData());
            String superName = getCreateSubclass() ?
                    PCEnhancer.toPCSubclassName(superClass).replace(".", "/") :
                    Type.getInternalName(superClass);
            instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                superName,
                                                PRE + "GetManagedFieldCount",
                                                Type.getMethodDescriptor(Type.INT_TYPE)));
            instructions.add(new InsnNode(Opcodes.IADD));
        }

        instructions.add(new InsnNode(Opcodes.IRETURN));
    }

    /**
     * Adds the {@link PersistenceCapable#pcProvideField} and
     * {@link PersistenceCapable#pcProvideFields} methods to the bytecode.
     */
    private void addProvideFieldsMethods(ClassNode classNode) throws NoSuchMethodException {
        MethodNode provideFieldsMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                      PRE + "ProvideField",
                                                      Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE),
                                                      null, null);
        classNode.methods.add(provideFieldsMeth);
        final InsnList instructions = provideFieldsMeth.instructions;

        final int relLocal = beginSwitchMethod(classNode, PRE + "ProvideField", instructions, false);

        // if no fields in this inst, just throw exception
        FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
                : _meta.getDeclaredFields();
        if (fmds.length == 0) {
            instructions.add(AsmHelper.throwException(IllegalArgumentException.class));
        }
        else {
            // switch (val)
            instructions.add(new VarInsnNode(Opcodes.ILOAD, relLocal));

            LabelNode defaultCase = new LabelNode();
            TableSwitchInsnNode ts = new TableSwitchInsnNode(0, fmds.length - 1, defaultCase);
            instructions.add(ts);

            //  = pcStateManager.providedField(this, fieldNumber);
            for (FieldMetaData fmd : fmds) {
                // case xxx:
                LabelNode caseLabel = new LabelNode();
                instructions.add(caseLabel);
                ts.labels.add(caseLabel);

                // load pcStateManager to stack
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

                // invoke StateManager#provided
                final Method smProvidedMeth = getStateManagerMethod(fmd.getDeclaredType(), "provided", false, false);

                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); // fieldNr int

                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this for the getfield
                addGetManagedValueCode(classNode, instructions, fmd, true);

                instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                                    Type.getInternalName(SMTYPE),
                                                    smProvidedMeth.getName(),
                                                    Type.getMethodDescriptor(smProvidedMeth)));

                instructions.add(new InsnNode(Opcodes.RETURN));
            }

            instructions.add(defaultCase);
            instructions.add(AsmHelper.throwException(IllegalArgumentException.class));
        }

        addMultipleFieldsMethodVersion(classNode, provideFieldsMeth, false);
    }

    /**
     * Adds the {@link PersistenceCapable#pcReplaceField} and
     * {@link PersistenceCapable#pcReplaceFields} methods to the bytecode.
     */
    private void addReplaceFieldsMethods(ClassNode classNode) throws NoSuchMethodException {
        MethodNode replaceFieldMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                     PRE + "ReplaceField",
                                                     Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE),
                                                     null, null);
        classNode.methods.add(replaceFieldMeth);
        final InsnList instructions = replaceFieldMeth.instructions;
        final int relLocal = beginSwitchMethod(classNode, PRE + "ReplaceField", instructions, false);

        // if no fields in this inst, just throw exception
        FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
                : _meta.getDeclaredFields();
        if (fmds.length == 0) {
            instructions.add(AsmHelper.throwException(IllegalArgumentException.class));
        }
        else {
            // switch (val)
            instructions.add(new VarInsnNode(Opcodes.ILOAD, relLocal));

            LabelNode defaultCase = new LabelNode();
            TableSwitchInsnNode ts = new TableSwitchInsnNode(0, fmds.length - 1, defaultCase);
            instructions.add(ts);

            //  = pcStateManager.replaceField(this, fieldNumber);
            for (FieldMetaData fmd : fmds) {
                // case xxx:
                LabelNode caseLabel = new LabelNode();
                instructions.add(caseLabel);
                ts.labels.add(caseLabel);

                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this

                // load pcStateManager to stack
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

                // invoke StateManager#replace
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); // fieldNr int
                final Method rmReplaceMeth = getStateManagerMethod(fmd.getDeclaredType(), "replace", true, false);
                instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                                    Type.getInternalName(SMTYPE),
                                                    rmReplaceMeth.getName(),
                                                    Type.getMethodDescriptor(rmReplaceMeth)));
                if (!fmd.getDeclaredType().isPrimitive()) {
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(fmd.getDeclaredType())));
                }

                addSetManagedValueCode(classNode, instructions, fmd);

                if (_addVersionInitFlag && fmd.isVersion()) {
                    // If this case is setting the version field
                    // pcVersionInit = true;
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                    instructions.add(new InsnNode(Opcodes.ICONST_1));
                    putfield(classNode, instructions, getType(_meta), VERSION_INIT_STR, boolean.class);
                }

                instructions.add(new InsnNode(Opcodes.RETURN));
            }

            instructions.add(defaultCase);
            instructions.add(AsmHelper.throwException(IllegalArgumentException.class));
        }

        addMultipleFieldsMethodVersion(classNode, replaceFieldMeth, false);
    }


    /**
     * Adds the {@link PersistenceCapable#pcCopyFields} method to the bytecode.
     */
    private void addCopyFieldsMethod(ClassNode classNode) {
        MethodNode copyFieldMeth = new MethodNode(Opcodes.ACC_PROTECTED,
                                                  PRE + "CopyField",
                                                  Type.getMethodDescriptor(Type.VOID_TYPE,
                                                                           Type.getObjectType(managedType.getClassNode().name),
                                                                           Type.INT_TYPE),
                                                  null, null);
        classNode.methods.add(copyFieldMeth);
        final InsnList instructions = copyFieldMeth.instructions;
        final int relLocal = beginSwitchMethod(classNode, PRE + "CopyField", instructions, true);

        // if no fields in this inst, just throw exception
        FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
                : _meta.getDeclaredFields();
        if (fmds.length == 0) {
            instructions.add(AsmHelper.throwException(IllegalArgumentException.class));
        }
        else {
            instructions.add(new VarInsnNode(Opcodes.ILOAD, relLocal));

            LabelNode defaultCase = new LabelNode();
            TableSwitchInsnNode ts = new TableSwitchInsnNode(0, fmds.length - 1, defaultCase);
            instructions.add(ts);

            //  = other.;
            // or set (other.get);
            for (FieldMetaData fmd : fmds) {
                // case xxx:
                LabelNode caseLabel = new LabelNode();
                instructions.add(caseLabel);
                ts.labels.add(caseLabel);

                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // other instance

                addGetManagedValueCode(classNode, instructions, fmd, false);
                addSetManagedValueCode(classNode, instructions, fmd);

                instructions.add(new InsnNode(Opcodes.RETURN));
            }

            instructions.add(defaultCase);
            instructions.add(AsmHelper.throwException(IllegalArgumentException.class));
        }

        addMultipleFieldsMethodVersion(classNode, copyFieldMeth, true);
    }

    /**
     * Helper method to add the code common to the beginning of both the
     * pcReplaceField method and the pcProvideField method. This includes
     * calculating the relative field number of the desired field and calling
     * the superclass if necessary.
     *
     * @return the index in which the local variable holding the relative
     * field number is stored
     */
    private int beginSwitchMethod(ClassNode classNode, String name, InsnList instructions, boolean copy) {
        int fieldNumber = (copy) ? 2 : 1;
        int relLocal = fieldNumber + 1;
        if (getCreateSubclass()) {
            instructions.add(new VarInsnNode(Opcodes.ILOAD, fieldNumber));
            instructions.add(new VarInsnNode(Opcodes.ISTORE, relLocal));
            return relLocal;
        }

        // int rel = fieldNumber - pcInheritedFieldCount
        instructions.add(new VarInsnNode(Opcodes.ILOAD, fieldNumber));
        instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, INHERIT, Type.INT_TYPE.getDescriptor()));
        instructions.add(new InsnNode(Opcodes.ISUB));
        instructions.add(new VarInsnNode(Opcodes.ISTORE, relLocal));

        // super: if (rel < 0) super.pcReplaceField (fieldNumber); return;
        // no super: if (rel < 0) throw new IllegalArgumentException ();
        LabelNode afterRelCheck = new LabelNode();

        instructions.add(new VarInsnNode(Opcodes.ILOAD, relLocal));
        instructions.add(new JumpInsnNode(Opcodes.IFGE, afterRelCheck));
        if (_meta.getPCSuperclass() != null) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            final Class pcSuperClass = getType(_meta.getPCSuperclassMetaData());
            String mDesc = copy
                    ? Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(pcSuperClass), Type.INT_TYPE)
                    : Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE);
            if (copy) {
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // the instance to copy into
            }
            instructions.add(new VarInsnNode(Opcodes.ILOAD, fieldNumber));
            instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                Type.getInternalName(pcSuperClass),
                                                name,
                                                mDesc));
            instructions.add(new InsnNode(Opcodes.RETURN));
        }
        else {
            instructions.add(AsmHelper.throwException(IllegalArgumentException.class));
        }
        instructions.add(afterRelCheck);

        return relLocal;
    }

    /**
     * This helper method, given the pcReplaceField or pcProvideField
     * method, adds the bytecode for the corresponding 'plural' version
     * of the method -- the version that takes an int[] of fields to
     * access rather than a single field. The multiple fields version
     * simply loops through the provided indexes and delegates to the
     * singular version for each one.
     */
    private void addMultipleFieldsMethodVersion(ClassNode classNode, MethodNode single, boolean copy) {
        String desc = copy
                ? Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT, Type.getType(int[].class))
                : Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class));

        MethodNode multiMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                              single.name + "s",
                                              desc,
                                              null, null);
        final InsnList instructions = multiMeth.instructions;
        classNode.methods.add(multiMeth);

        int instVarPos = 0;
        if (copy) {
            instVarPos = 3;
            if (getCreateSubclass()) {
                // get the managed instance into the local variable table

                // (EntityType)ImplHelper.getManagedInstance(other_param1) to Stack
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // other instance
                instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                    Type.getInternalName(ImplHelper.class),
                                                    "getManagedInstance",
                                                    Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT, AsmHelper.TYPE_OBJECT)));
                instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, managedType.getClassNode().name));
                instructions.add(new VarInsnNode(Opcodes.ASTORE, instVarPos));

                // there might be a difference between the classes of 'this'
                // vs 'other' in this context; use the PC methods to get the SM
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // other_param1 object

                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

                instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                    Type.getInternalName(ImplHelper.class),
                                                    "toPersistenceCapable",
                                                    Type.getMethodDescriptor(Type.getType(PersistenceCapable.class),
                                                                             AsmHelper.TYPE_OBJECT,
                                                                             AsmHelper.TYPE_OBJECT)));

                // now we get the StateManager from the other instance
                instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                                    Type.getInternalName(PersistenceCapable.class),
                                                    "pcGetStateManager",
                                                    Type.getMethodDescriptor(Type.getType(StateManager.class))));
            }
            else {
                // XXX other = (XXX) pc;
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // other object
                instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, pc.getClassNode().name));
                instructions.add(new VarInsnNode(Opcodes.ASTORE, instVarPos));

                // access the other's sm field directly
                instructions.add(new VarInsnNode(Opcodes.ALOAD, instVarPos));
                instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));
            }

            // if (other.pcStateManager != pcStateManager)
            //    throw new IllegalArgumentException
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
            instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));
            LabelNode toEndSmCmp = new LabelNode();
            instructions.add(new JumpInsnNode(Opcodes.IF_ACMPEQ, toEndSmCmp));
            instructions.add(AsmHelper.throwException(IllegalArgumentException.class));
            instructions.add(toEndSmCmp);

            // if (pcStateManager == null)
            //  throw new IllegalStateException
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
            instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));
            LabelNode toEndSmNull = new LabelNode();
            instructions.add(new JumpInsnNode(Opcodes.IFNONNULL, toEndSmNull));
            instructions.add(AsmHelper.throwException(IllegalStateException.class));
            instructions.add(toEndSmNull);
        }

        // for (int i = 0;
        int iVarPos = copy ? 4 : 2;
        instructions.add(new InsnNode(Opcodes.ICONST_0));
        instructions.add(new VarInsnNode(Opcodes.ISTORE, iVarPos));
        LabelNode toI = new LabelNode();
        instructions.add(toI);

        int fieldNumbersPos = copy ? 2 : 1;

        instructions.add(new VarInsnNode(Opcodes.ILOAD, iVarPos));
        instructions.add(new VarInsnNode(Opcodes.ALOAD, fieldNumbersPos)); // the int[]
        instructions.add(new InsnNode(Opcodes.ARRAYLENGTH)); // int[] parameter variable.length
        LabelNode toEnd = new LabelNode();
        instructions.add(new JumpInsnNode(Opcodes.IF_ICMPGE, toEnd)); // if i >= int[].length

        // otherwise call the single method
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));        // this
        if (copy) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, instVarPos));  // instance
        }
        instructions.add(new VarInsnNode(Opcodes.ALOAD, fieldNumbersPos));        // the int[] param
        instructions.add(new VarInsnNode(Opcodes.ILOAD, iVarPos));  // int[ i ]
        instructions.add(new InsnNode(Opcodes.IALOAD));             // load the value at that position

        // now invoke the single method
        instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                            classNode.name,
                                            single.name,
                                            single.desc));

        instructions.add(new IincInsnNode(iVarPos, 1));
        instructions.add(new JumpInsnNode(Opcodes.GOTO, toI));

        instructions.add(toEnd);        // end of loop

        instructions.add(new InsnNode(Opcodes.RETURN));
    }

    /**
     * Adds the 'stock' methods to the bytecode; these include methods
     * like {@link PersistenceCapable#pcFetchObjectId}
     * and {@link PersistenceCapable#pcIsTransactional}.
     */
    private void addStockMethods() throws NoSuchMethodException {
        // pcGetGenericContext
        translateFromStateManagerMethod(SMTYPE.getDeclaredMethod("get" + CONTEXTNAME), false);

        // pcFetchObjectId
        translateFromStateManagerMethod(SMTYPE.getDeclaredMethod("fetchObjectId"), false);

        // pcIsDeleted
        translateFromStateManagerMethod(SMTYPE.getDeclaredMethod("isDeleted"), false);

        // pcIsDirty
        translateFromStateManagerMethod(SMTYPE.getDeclaredMethod("isDirty"), true);

        // pcIsNew
        translateFromStateManagerMethod(SMTYPE.getDeclaredMethod("isNew"), false);

        // pcIsPersistent
        translateFromStateManagerMethod(SMTYPE.getDeclaredMethod("isPersistent"), false);

        // pcIsTransactional
        translateFromStateManagerMethod(SMTYPE.getDeclaredMethod("isTransactional"), false);

        // pcSerializing
        translateFromStateManagerMethod(SMTYPE.getDeclaredMethod("serializing"), false);

        // pcDirty
        translateFromStateManagerMethod(SMTYPE.getDeclaredMethod("dirty", String.class), false);

        // pcGetStateManager
        MethodNode getSmMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                              PRE + "GetStateManager",
                                              Type.getMethodDescriptor(Type.getType(SMTYPE)),
                                              null, null);
        pc.getClassNode().methods.add(getSmMeth);

        InsnList instructions = getSmMeth.instructions;

        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
        instructions.add(new FieldInsnNode(Opcodes.GETFIELD, pc.getClassNode().name, SM, Type.getDescriptor(SMTYPE)));
        instructions.add(new InsnNode(Opcodes.ARETURN));
    }


    /**
     * Helper method to add a stock method to the bytecode. Each
     * stock method simply delegates to a corresponding StateManager method.
     * Given the StateManager method, then, this function translates it into
     * the wrapper method that should be added to the bytecode.
     */
    private void translateFromStateManagerMethod(Method m, boolean isDirtyCheckMethod) {
        // form the name of the method by prepending 'pc' to the sm method
        String name = PRE + StringUtil.capitalize(m.getName());
        Class[] params = m.getParameterTypes();
        Type[] paramTypes = Arrays.stream(params)
                .map(Type::getType)
                .toArray(Type[]::new);
        Class returnType = m.getReturnType();

        final ClassNode classNode = pc.getClassNode();

        // add the method to the pc
        MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC,
                                               name,
                                               Type.getMethodDescriptor(Type.getType(returnType), paramTypes),
                                               null, null);
        InsnList instructions = methodNode.instructions;
        classNode.methods.add(methodNode);

        // if (pcStateManager == null) return ;
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
        instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

        LabelNode lblAfterIf = new LabelNode();
        instructions.add(new JumpInsnNode(Opcodes.IFNONNULL, lblAfterIf));
        if (returnType.equals(boolean.class)) {
            instructions.add(new InsnNode(Opcodes.ICONST_0)); // false
        }
        else if (!returnType.equals(void.class)) {
            instructions.add(new InsnNode(Opcodes.ACONST_NULL));
        }
        instructions.add(new InsnNode(AsmHelper.getReturnInsn(returnType)));
        instructions.add(lblAfterIf);

        // load the StateManager onto the stack
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
        instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

        // if this is the dirty-check method and we're subclassing but not
        // redefining, hook into PCHelper to do the dirty check
        if (isDirtyCheckMethod && !getRedefine()) {
            // RedefinitionHelper.dirtyCheck(sm);
            instructions.add(new InsnNode(Opcodes.DUP)); // duplicate the StateManager for the return statement below
            instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                Type.getInternalName(RedefinitionHelper.class),
                                                "dirtyCheck",
                                                Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(SMTYPE))));
        }


        // return pcStateManager. ();
        // managed instance loaded above in if-else block
        for (int i = 0; i < params.length; i++) {
            instructions.add(new VarInsnNode(AsmHelper.getLoadInsn(params[i]), i + 1));
        }
        instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                            Type.getInternalName(SMTYPE),
                                            m.getName(),
                                            Type.getMethodDescriptor(m)));

        instructions.add(new InsnNode(AsmHelper.getReturnInsn(returnType)));
    }

    /**
     * Adds the {@link PersistenceCapable#pcGetVersion} method to the bytecode.
     */
    private void addGetVersionMethod() throws NoSuchMethodException {
        final ClassNode classNode = pc.getClassNode();
        MethodNode getVersionMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                   PRE + "GetVersion",
                                                   Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT),
                                                   null, null);
        classNode.methods.add(getVersionMeth);
        InsnList instructions = getVersionMeth.instructions;

        // if (pcStateManager == null)
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
        instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));
        LabelNode lblAfterIf = new LabelNode();
        instructions.add(new JumpInsnNode(Opcodes.IFNONNULL, lblAfterIf));

        FieldMetaData versionField = _meta.getVersionField();
        if (versionField == null) {
            instructions.add(new InsnNode(Opcodes.ACONST_NULL));
        }
        else {
            // return ;
            Class wrapper = toPrimitiveWrapper(versionField);
            if (wrapper != versionField.getDeclaredType()) {
                instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(wrapper)));
                instructions.add(new InsnNode(Opcodes.DUP));
            }
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
            addGetManagedValueCode(classNode, instructions, versionField, true);
            if (wrapper != versionField.getDeclaredType()) {
                instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                    Type.getInternalName(wrapper),
                                                    "",
                                                    Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(versionField.getDeclaredType()))));
            }
        }
        instructions.add(new InsnNode(Opcodes.ARETURN));
        instructions.add(lblAfterIf);

        // return pcStateManager.getVersion ();
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
        instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

        instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                            Type.getInternalName(SMTYPE),
                                            "getVersion",
                                            Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT)));

        instructions.add(new InsnNode(Opcodes.ARETURN));
    }

    /**
     * Return the version field type as a primitive wrapper, or null if
     * the version field is not primitive.
     */
    private Class toPrimitiveWrapper(FieldMetaData fmd) {
        switch (fmd.getDeclaredTypeCode()) {
            case JavaTypes.BOOLEAN:
                return Boolean.class;
            case JavaTypes.BYTE:
                return Byte.class;
            case JavaTypes.CHAR:
                return Character.class;
            case JavaTypes.DOUBLE:
                return Double.class;
            case JavaTypes.FLOAT:
                return Float.class;
            case JavaTypes.INT:
                return Integer.class;
            case JavaTypes.LONG:
                return Long.class;
            case JavaTypes.SHORT:
                return Short.class;
        }
        return fmd.getDeclaredType();
    }

    /**
     * Adds the {@link PersistenceCapable#pcReplaceStateManager}
     * method to the bytecode.
     */
    private void addReplaceStateManagerMethod() {
        // public void pcReplaceStateManager (StateManager sm)
        MethodNode replaceSmMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                  PRE + "ReplaceStateManager",
                                                  Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(SMTYPE)),
                                                  null, new String[]{Type.getInternalName(SecurityException.class)});
        final ClassNode classNode = pc.getClassNode();
        classNode.methods.add(replaceSmMeth);
        InsnList instructions = replaceSmMeth.instructions;

        // if (pcStateManager != null)
        //    pcStateManager = pcStateManager.replaceStateManager(sm);
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
        instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

        LabelNode lblEndIfNull = new LabelNode();
        instructions.add(new JumpInsnNode(Opcodes.IFNULL, lblEndIfNull));
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this

        instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

        instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st method param, the new StateManager
        instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                            Type.getInternalName(SMTYPE),
                                            "replaceStateManager",
                                            Type.getMethodDescriptor(Type.getType(SMTYPE), Type.getType(SMTYPE))));
        instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));
        instructions.add(new InsnNode(Opcodes.RETURN));

        instructions.add(lblEndIfNull);

        // pcStateManager = sm;
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
        instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st method param, the new StateManager
        instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

        instructions.add(new InsnNode(Opcodes.RETURN));
    }

    /**
     * Creates the PersistenceCapable methods dealing with application
     * identity and gives them no-op implementations.
     */
    private void addNoOpApplicationIdentityMethods() {
        ClassNode classNode = pc.getClassNode();
        {
            // public void pcCopyKeyFieldsToObjectId (ObjectIdFieldSupplier fs, Object oid)
            MethodNode copyKeyMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                    PRE + "CopyKeyFieldsToObjectId",
                                                    Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OIDFSTYPE), AsmHelper.TYPE_OBJECT),
                                                    null, null);
            classNode.methods.add(copyKeyMeth);
            copyKeyMeth.instructions.add(new InsnNode(Opcodes.RETURN));
        }

        {
            // public void pcCopyKeyFieldsToObjectId (Object oid)
            MethodNode copyKeyMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                    PRE + "CopyKeyFieldsToObjectId",
                                                    Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT),
                                                    null, null);
            classNode.methods.add(copyKeyMeth);
            copyKeyMeth.instructions.add(new InsnNode(Opcodes.RETURN));
        }

        {
            // public void pcCopyKeyFieldsFromObjectId (ObjectIdFieldConsumer fc, Object oid)
            MethodNode copyKeyMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                    PRE + "CopyKeyFieldsFromObjectId",
                                                    Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OIDFCTYPE), AsmHelper.TYPE_OBJECT),
                                                    null, null);
            classNode.methods.add(copyKeyMeth);
            copyKeyMeth.instructions.add(new InsnNode(Opcodes.RETURN));
        }

        {
            // public void pcCopyKeyFieldsFromObjectId (Object oid)
            MethodNode copyKeyMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                    PRE + "CopyKeyFieldsFromObjectId",
                                                    Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT),
                                                    null, null);
            classNode.methods.add(copyKeyMeth);
            copyKeyMeth.instructions.add(new InsnNode(Opcodes.RETURN));
        }

        {
            // public Object pcNewObjectIdInstance ()
            MethodNode copyKeyMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                    PRE + "NewObjectIdInstance",
                                                    Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT),
                                                    null, null);
            classNode.methods.add(copyKeyMeth);
            copyKeyMeth.instructions.add(new InsnNode(Opcodes.ACONST_NULL));
            copyKeyMeth.instructions.add(new InsnNode(Opcodes.ARETURN));
        }

        {
            // public Object pcNewObjectIdInstance (Object obj)
            MethodNode copyKeyMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                                    PRE + "NewObjectIdInstance",
                                                    Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT, AsmHelper.TYPE_OBJECT),
                                                    null, null);
            classNode.methods.add(copyKeyMeth);
            copyKeyMeth.instructions.add(new InsnNode(Opcodes.ACONST_NULL));
            copyKeyMeth.instructions.add(new InsnNode(Opcodes.ARETURN));

        }
    }

    /**
     * Adds the pcCopyKeyFieldsToObjectId methods
     * to classes using application identity.
     */
    private void addCopyKeyFieldsToObjectIdMethod(boolean fieldManager) throws NoSuchMethodException {

        // public void pcCopyKeyFieldsToObjectId (ObjectIdFieldSupplier fs, Object oid)
        String mDesc = fieldManager
                ? Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OIDFSTYPE), AsmHelper.TYPE_OBJECT)
                : Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT);
        MethodNode copyKFMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                               PRE + "CopyKeyFieldsToObjectId",
                                               mDesc,
                                               null, null);
        final ClassNode classNode = pc.getClassNode();
        classNode.methods.add(copyKFMeth);
        InsnList instructions = copyKFMeth.instructions;

        // single field identity always throws exception
        if (_meta.isOpenJPAIdentity()) {
            instructions.add(AsmHelper.throwException(INTERNEXCEP));
            return;
        }

        // call superclass method
        if (_meta.getPCSuperclass() != null && !getCreateSubclass()) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st parameter object
            if (fieldManager) {
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); // 2nd parameter object
            }
            instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                Type.getInternalName(getType(_meta.getPCSuperclassMetaData())),
                                                PRE + "CopyKeyFieldsToObjectId",
                                                mDesc));
        }

        // Object id = oid;
        if (fieldManager) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); // 2nd parameter object
        }
        else {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st parameter object
        }

        if (_meta.isObjectIdTypeShared()) {
            // oid = ((ObjectId) id).getId ();
            instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(ObjectId.class)));
            instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                Type.getInternalName(ObjectId.class),
                                                "getId",
                                                Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT)));
        }

        //  id = () oid;
        int nextFreeVarPos = (fieldManager) ? 3 : 2;
        int idVarPos = nextFreeVarPos++;

        Class oidType = _meta.getObjectIdType();
        instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(oidType)));
        instructions.add(new VarInsnNode(Opcodes.ASTORE, idVarPos));

        // int inherited = pcInheritedFieldCount;
        int inherited = 0;
        if (fieldManager) {
            instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, INHERIT, Type.INT_TYPE.getDescriptor()));
            inherited = nextFreeVarPos++;
            instructions.add(new VarInsnNode(Opcodes.ISTORE, inherited));
        }

        // id. = fs.fetchField (); or...
        // id. = pc.;
        FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
                : _meta.getDeclaredFields();

        // If optimizeIdCopy is enabled and not a field manager method, try to
        // optimize the copyTo by using a public constructor instead of reflection
        if (_optimizeIdCopy) {
            ArrayList pkfields = optimizeIdCopy(oidType, fmds);
            if (pkfields != null) {
                // search for a constructor on the IdClass that can be used
                // to construct the IdClass
                int[] parmOrder = getIdClassConstructorParmOrder(oidType, pkfields, fmds);
                if (parmOrder != null) {
                    // If using a field manager, values must be loaded into locals so they can be properly ordered
                    // as constructor parameters.
                    int[] localIndexes = new int[fmds.length];

                    if (fieldManager) {
                        for (int k = 0; k < fmds.length; k++) {
                            if (!fmds[k].isPrimaryKey()) {
                                continue;
                            }
                            instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param
                            instructions.add(AsmHelper.getLoadConstantInsn(k));
                            instructions.add(new VarInsnNode(Opcodes.ILOAD, inherited));
                            instructions.add(new InsnNode(Opcodes.IADD));

                            final Method fieldSupplierMethod = getFieldSupplierMethod(fmds[k].getObjectIdFieldType());
                            instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                                                Type.getInternalName(fieldSupplierMethod.getDeclaringClass()),
                                                                fieldSupplierMethod.getName(),
                                                                Type.getMethodDescriptor(fieldSupplierMethod)));
                            localIndexes[k] = nextFreeVarPos++;
                            instructions.add(new VarInsnNode(AsmHelper.getStoreInsn(fmds[k].getObjectIdFieldType()), localIndexes[k]));
                        }
                    }

                    // found a matching constructor.  parm array is constructor parm order
                    instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(oidType)));
                    instructions.add(new InsnNode(Opcodes.DUP));

                    // build the parm list in order
                    Class[] clsArgs = new Class[parmOrder.length];
                    for (int i = 0; i < clsArgs.length; i++) {
                        int parmIndex = parmOrder[i];
                        clsArgs[i] = fmds[parmIndex].getObjectIdFieldType();
                        if (!fieldManager) {
                            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                            addGetManagedValueCode(classNode, instructions, fmds[parmIndex], true);
                        }
                        else {
                            // Load constructor parameters in appropriate order
                            instructions.add(new VarInsnNode(AsmHelper.getLoadInsn(fmds[parmIndex].getObjectIdFieldType()), localIndexes[parmIndex]));

                            if (fmds[parmIndex].getObjectIdFieldTypeCode() == JavaTypes.OBJECT &&
                                    !fmds[parmIndex].getDeclaredType().isEnum()) {
                                instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(ObjectId.class)));
                                instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                                    Type.getInternalName(ObjectId.class),
                                                                    "getId",
                                                                    Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT)));
                            }

                            // if the type of this field meta data is
                            // non-primitive and non-string, be sure to cast
                            // to the appropriate type.
                            if (!clsArgs[i].isPrimitive() && !clsArgs[i].getName().equals(String.class.getName())) {
                                instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(clsArgs[i])));
                            }
                        }
                    }

                    // invoke the public constructor to create a new local id
                    Type[] parms = Arrays.stream(clsArgs)
                            .map(Type::getType)
                            .toArray(Type[]::new);

                    instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                        Type.getInternalName(oidType),
                                                        "",
                                                        Type.getMethodDescriptor(Type.VOID_TYPE, parms)));


                    int retVarPos = inherited + fmds.length;
                    instructions.add(new VarInsnNode(Opcodes.ASTORE, retVarPos));

                    // swap out the app id with the new one
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, fieldManager ? 2 : 1));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(ObjectId.class)));
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, retVarPos));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                        Type.getInternalName(ApplicationIds.class),
                                                        "setAppId",
                                                        Type.getMethodDescriptor(Type.VOID_TYPE,
                                                                                 Type.getType(ObjectId.class), AsmHelper.TYPE_OBJECT)));
                    instructions.add(new InsnNode(Opcodes.RETURN));
                    return;
                }
            }
        }

        Field field = null;
        Method setter = null;
        for (int i = 0; i < fmds.length; i++) {
            if (!fmds[i].isPrimaryKey()) {
                continue;
            }

            instructions.add(new VarInsnNode(Opcodes.ALOAD, idVarPos));

            String name = fmds[i].getName();
            Class type = fmds[i].getObjectIdFieldType();
            boolean reflect = false;

            if (isFieldAccess(fmds[i])) {
                field = Reflection.findField(oidType, name, true);
                reflect = !Modifier.isPublic(field.getModifiers());
                if (reflect) {
                    instructions.add(AsmHelper.getLoadConstantInsn(oidType));
                    instructions.add(AsmHelper.getLoadConstantInsn(name));
                    instructions.add(AsmHelper.getLoadConstantInsn(true));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                        Type.getInternalName(Reflection.class),
                                                        "findField",
                                                        Type.getMethodDescriptor(Type.getType(Field.class), Type.getType(Class.class),
                                                                                 Type.getType(String.class), Type.BOOLEAN_TYPE)));
                }
            }
            else {
                setter = Reflection.findSetter(oidType, name, type, true);
                reflect = !Modifier.isPublic(setter.getModifiers());
                if (reflect) {
                    instructions.add(AsmHelper.getLoadConstantInsn(oidType));
                    instructions.add(AsmHelper.getLoadConstantInsn(name));
                    instructions.add(AsmHelper.getLoadConstantInsn(type));
                    instructions.add(AsmHelper.getLoadConstantInsn(true));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                        Type.getInternalName(Reflection.class),
                                                        "findSetter",
                                                        Type.getMethodDescriptor(Type.getType(Method.class),
                                                                                 Type.getType(Class.class),
                                                                                 Type.getType(String.class),
                                                                                 Type.getType(Class.class),
                                                                                 Type.BOOLEAN_TYPE)));
                }
            }

            if (fieldManager) {
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 1));  // 1st param
                instructions.add(AsmHelper.getLoadConstantInsn(i));
                instructions.add(new VarInsnNode(Opcodes.ILOAD, inherited));
                instructions.add(new InsnNode(Opcodes.IADD));

                final Method fieldSupplierMethod = getFieldSupplierMethod(type);
                instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                                    Type.getInternalName(fieldSupplierMethod.getDeclaringClass()),
                                                    fieldSupplierMethod.getName(),
                                                    Type.getMethodDescriptor(fieldSupplierMethod)));


                if (fmds[i].getObjectIdFieldTypeCode() == JavaTypes.OBJECT && !fmds[i].getDeclaredType().isEnum()) {
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(ObjectId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(ObjectId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT)));
                }

                // if the type of this field meta data is
                // non-primitive and non-string, be sure to cast
                // to the appropriate type.
                if (!reflect && !type.isPrimitive() && !type.getName().equals(String.class.getName())) {
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(type)));
                }
            }
            else {
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                addGetManagedValueCode(classNode, instructions, fmds[i], true);

                // get id/pk from pc instance
                if (fmds[i].getDeclaredTypeCode() == JavaTypes.PC) {
                    addExtractObjectIdFieldValueCode(classNode, instructions, fmds[i], nextFreeVarPos++);
                }
            }

            if (reflect && field != null) {
                instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                    Type.getInternalName(Reflection.class),
                                                    "set",
                                                    Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT, Type.getType(Field.class),
                                                                             (type.isPrimitive()) ? Type.getType(type) : AsmHelper.TYPE_OBJECT)));

            }
            else if (reflect) {
                instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                    Type.getInternalName(Reflection.class),
                                                    "set",
                                                    Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT, Type.getType(Method.class),
                                                                             (type.isPrimitive()) ? Type.getType(type) : AsmHelper.TYPE_OBJECT)));
            }
            else if (field != null) {
                instructions.add(new FieldInsnNode(Opcodes.PUTFIELD,
                                                   Type.getInternalName(field.getDeclaringClass()),
                                                   field.getName(),
                                                   Type.getDescriptor(field.getType())));
            }
            else {
                instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                    Type.getInternalName(setter.getDeclaringClass()),
                                                    setter.getName(),
                                                    Type.getMethodDescriptor(setter)));
            }

        }
        instructions.add(new InsnNode(Opcodes.RETURN));
    }

    /**
     * Add code to extract the id of the given primary key relation field for
     * setting into an objectid instance.
     */
    private void addExtractObjectIdFieldValueCode(ClassNode classNode, InsnList instructions, FieldMetaData pk, int nextFreeVarPos) {
        // if (val != null) {
        int pcVarPos = nextFreeVarPos++;
        instructions.add(new VarInsnNode(Opcodes.ASTORE, pcVarPos));
        instructions.add(new VarInsnNode(Opcodes.ALOAD, pcVarPos));

        LabelNode lblAfterIfNull = new LabelNode();
        instructions.add(new JumpInsnNode(Opcodes.IFNULL, lblAfterIfNull));
        instructions.add(new VarInsnNode(Opcodes.ALOAD, pcVarPos));
        instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(PersistenceCapable.class)));

        //  val = ((PersistenceCapable) val).pcFetchObjectId(); or pcNewObjectIdInstance()
        if (!pk.getTypeMetaData().isOpenJPAIdentity()) {
            instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                                Type.getInternalName(PersistenceCapable.class),
                                                PRE + "FetchObjectId",
                                                Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT)));
        }
        else {
            instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                                Type.getInternalName(PersistenceCapable.class),
                                                PRE + "NewObjectIdInstance",
                                                Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT)));
        }

        int oidVarPos = nextFreeVarPos++;
        instructions.add(new VarInsnNode(Opcodes.ASTORE, oidVarPos));
        instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));

        LabelNode lblAfterIfNull2 = new LabelNode();
        instructions.add(new JumpInsnNode(Opcodes.IFNULL, lblAfterIfNull2));

        // for datastore / single-field identity:
        // if (val != null)
        //   val = ((OpenJPAId) val).getId();
        ClassMetaData pkmeta = pk.getDeclaredTypeMetaData();
        int pkcode = pk.getObjectIdFieldTypeCode();
        Class pktype = pk.getObjectIdFieldType();
        if (pkmeta.getIdentityType() == ClassMetaData.ID_DATASTORE && pkcode == JavaTypes.LONG) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
            instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(Id.class)));
            instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                Type.getInternalName(Id.class),
                                                "getId",
                                                Type.getMethodDescriptor(Type.LONG_TYPE)));
        }
        else if (pkmeta.getIdentityType() == ClassMetaData.ID_DATASTORE) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
        }
        else if (pkmeta.isOpenJPAIdentity()) {
            switch (pkcode) {
                case JavaTypes.BYTE_OBJ:
                    instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(Byte.class)));
                    instructions.add(new InsnNode(Opcodes.DUP));
                    // no break
                case JavaTypes.BYTE:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(ByteId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(ByteId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.BYTE_TYPE)));
                    if (pkcode == JavaTypes.BYTE_OBJ) {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                            Type.getInternalName(Byte.class),
                                                            "",
                                                            Type.getMethodDescriptor(Type.VOID_TYPE, Type.BYTE_TYPE)));
                    }
                    break;
                case JavaTypes.CHAR_OBJ:
                    instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(Character.class)));
                    instructions.add(new InsnNode(Opcodes.DUP));
                    // no break
                case JavaTypes.CHAR:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(CharId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(CharId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.CHAR_TYPE)));
                    if (pkcode == JavaTypes.CHAR_OBJ) {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                            Type.getInternalName(Character.class),
                                                            "",
                                                            Type.getMethodDescriptor(Type.VOID_TYPE, Type.CHAR_TYPE)));
                    }
                    break;
                case JavaTypes.DOUBLE_OBJ:
                    instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(Double.class)));
                    instructions.add(new InsnNode(Opcodes.DUP));
                    // no break
                case JavaTypes.DOUBLE:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(DoubleId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(DoubleId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.DOUBLE_TYPE)));
                    if (pkcode == JavaTypes.DOUBLE_OBJ) {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                            Type.getInternalName(Character.class),
                                                            "",
                                                            Type.getMethodDescriptor(Type.VOID_TYPE, Type.CHAR_TYPE)));
                    }
                    break;
                case JavaTypes.FLOAT_OBJ:
                    instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(Float.class)));
                    instructions.add(new InsnNode(Opcodes.DUP));
                    // no break
                case JavaTypes.FLOAT:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(FloatId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(FloatId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.FLOAT_TYPE)));
                    if (pkcode == JavaTypes.FLOAT_OBJ) {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                            Type.getInternalName(Float.class),
                                                            "",
                                                            Type.getMethodDescriptor(Type.VOID_TYPE, Type.FLOAT_TYPE)));
                    }
                    break;
                case JavaTypes.INT_OBJ:
                    instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(Integer.class)));
                    instructions.add(new InsnNode(Opcodes.DUP));
                    // no break
                case JavaTypes.INT:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(IntId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(IntId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.INT_TYPE)));
                    if (pkcode == JavaTypes.INT_OBJ) {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                            Type.getInternalName(Integer.class),
                                                            "",
                                                            Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE)));
                    }
                    break;
                case JavaTypes.LONG_OBJ:
                    instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(Long.class)));
                    instructions.add(new InsnNode(Opcodes.DUP));
                    // no break
                case JavaTypes.LONG:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(LongId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(LongId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.LONG_TYPE)));
                    if (pkcode == JavaTypes.LONG_OBJ) {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                            Type.getInternalName(Long.class),
                                                            "",
                                                            Type.getMethodDescriptor(Type.VOID_TYPE, Type.LONG_TYPE)));
                    }
                    break;
                case JavaTypes.SHORT_OBJ:
                    instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(Short.class)));
                    instructions.add(new InsnNode(Opcodes.DUP));
                    // no break
                case JavaTypes.SHORT:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(ShortId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(ShortId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.SHORT_TYPE)));
                    if (pkcode == JavaTypes.SHORT_OBJ) {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                            Type.getInternalName(Short.class),
                                                            "",
                                                            Type.getMethodDescriptor(Type.VOID_TYPE, Type.SHORT_TYPE)));
                    }
                    break;
                case JavaTypes.DATE:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(DateId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(DateId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.getType(Date.class))));
                    if (pktype != Date.class) {
                        // java.sql.Date.class
                        instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(pktype)));
                    }
                    break;
                case JavaTypes.STRING:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(StringId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(StringId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.getType(String.class))));
                    break;
                case JavaTypes.BIGDECIMAL:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(BigDecimalId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(BigDecimalId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.getType(BigDecimal.class))));
                    break;
                case JavaTypes.BIGINTEGER:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(BigIntegerId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(BigIntegerId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.getType(BigInteger.class))));
                    break;
                default:
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
                    instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(ObjectId.class)));
                    instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                        Type.getInternalName(ObjectId.class),
                                                        "getId",
                                                        Type.getMethodDescriptor(Type.getType(Object.class))));
            }
        }
        else if (pkmeta.getObjectIdType() != null) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
            if (pkcode == JavaTypes.OBJECT) {
                instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(ObjectId.class)));
                instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                    Type.getInternalName(ObjectId.class),
                                                    "getId",
                                                    Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT)));
            }
            instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(pktype)));
        }
        else {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, oidVarPos));
        }

        // jump from here to the end
        LabelNode lblGo2End = new LabelNode();
        instructions.add(new JumpInsnNode(Opcodes.GOTO, lblGo2End));

        // elses from above to define the defaults
        instructions.add(lblAfterIfNull);
        instructions.add(lblAfterIfNull2);

        switch (pkcode) {
            case JavaTypes.BOOLEAN:
                instructions.add(AsmHelper.getLoadConstantInsn(false));
                break;
            case JavaTypes.BYTE:
                instructions.add(AsmHelper.getLoadConstantInsn(0));
                break;
            case JavaTypes.CHAR:
                instructions.add(AsmHelper.getLoadConstantInsn(0));
                break;
            case JavaTypes.DOUBLE:
                instructions.add(AsmHelper.getLoadConstantInsn(0D));
                break;
            case JavaTypes.FLOAT:
                instructions.add(AsmHelper.getLoadConstantInsn(0F));
                break;
            case JavaTypes.INT:
                instructions.add(AsmHelper.getLoadConstantInsn(0));
                break;
            case JavaTypes.LONG:
                instructions.add(AsmHelper.getLoadConstantInsn(0L));
                break;
            case JavaTypes.SHORT:
                instructions.add(AsmHelper.getLoadConstantInsn((short) 0));
                break;
            default:
                instructions.add(AsmHelper.getLoadConstantInsn(null));
        }


        instructions.add(lblGo2End);
    }

    /**
     * Adds the pcCopyKeyFieldsFromObjectId methods
     * to classes using application identity.
     */
    private void addCopyKeyFieldsFromObjectIdMethod(boolean fieldManager) throws NoSuchMethodException {
        // public void pcCopyKeyFieldsFromObjectId (ObjectIdFieldConsumer fc, Object oid)
        String mDesc = fieldManager
                ? Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OIDFCTYPE), AsmHelper.TYPE_OBJECT)
                : Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT);
        MethodNode copyKFMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                               PRE + "CopyKeyFieldsFromObjectId",
                                               mDesc,
                                               null, null);
        final ClassNode classNode = pc.getClassNode();
        classNode.methods.add(copyKFMeth);
        InsnList instructions = copyKFMeth.instructions;


        // call superclass method
        if (_meta.getPCSuperclass() != null && !getCreateSubclass()) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st parameter object
            if (fieldManager) {
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); // 2nd parameter object
            }
            instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                Type.getInternalName(getType(_meta.getPCSuperclassMetaData())),
                                                PRE + "CopyKeyFieldsFromObjectId",
                                                mDesc));
        }

        // Object id = oid;
        if (fieldManager) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); // 2nd parameter object
        }
        else {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st parameter object
        }

        if (!_meta.isOpenJPAIdentity() && _meta.isObjectIdTypeShared()) {
            // oid = ((ObjectId) id).getId ();
            instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(ObjectId.class)));
            instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                Type.getInternalName(ObjectId.class),
                                                "getId",
                                                Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT)));
        }

        //  id = () oid;
        int nextFreeVarPos = (fieldManager) ? 3 : 2;
        int idVarPos = nextFreeVarPos++;

        Class oidType = _meta.getObjectIdType();
        instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(oidType)));
        instructions.add(new VarInsnNode(Opcodes.ASTORE, idVarPos));

        // fs.storeField (, id.); or...
        // this. = id.
        // or for single field identity: id.getId ()
        FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
                : _meta.getDeclaredFields();
        for (int i = 0; i < fmds.length; i++) {
            if (!fmds[i].isPrimaryKey()) {
                continue;
            }

            String name = fmds[i].getName();
            Class type = fmds[i].getObjectIdFieldType();

            if (!fieldManager && fmds[i].getDeclaredTypeCode() == JavaTypes.PC) {
                // if (sm == null) return;
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));

                LabelNode lblEndIfNotNull = new LabelNode();
                instructions.add(new JumpInsnNode(Opcodes.IFNONNULL, lblEndIfNotNull));
                instructions.add(new InsnNode(Opcodes.RETURN));

                instructions.add(lblEndIfNotNull);

                // sm.getPCPrimaryKey(oid, i + pcInheritedFieldCount);
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                instructions.add(new InsnNode(Opcodes.DUP)); // leave orig on stack to set value into
                instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));
                instructions.add(new VarInsnNode(Opcodes.ALOAD, idVarPos));
                instructions.add(AsmHelper.getLoadConstantInsn(i));
                instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, INHERIT, Type.INT_TYPE.getDescriptor()));
                instructions.add(new InsnNode(Opcodes.IADD));
                instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                                    Type.getInternalName(SMTYPE),
                                                    "getPCPrimaryKey",
                                                    Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT, AsmHelper.TYPE_OBJECT, Type.INT_TYPE)));
                instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(fmds[i].getDeclaredType())));
            }
            else {
                Class unwrapped = (fmds[i].getDeclaredTypeCode() == JavaTypes.PC) ? type : unwrapSingleFieldIdentity(fmds[i]);
                if (fieldManager) {
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param
                    instructions.add(AsmHelper.getLoadConstantInsn(i));
                    instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, INHERIT, Type.INT_TYPE.getDescriptor()));
                    instructions.add(new InsnNode(Opcodes.IADD));
                }
                else {
                    instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                }

                if (unwrapped != type && type != Long.class) {
                    instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(type)));
                    instructions.add(new InsnNode(Opcodes.DUP));
                }

                instructions.add(new VarInsnNode(Opcodes.ALOAD, idVarPos));
                if (_meta.isOpenJPAIdentity()) {
                    if (oidType == ObjectId.class) {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                            Type.getInternalName(oidType),
                                                            "getId",
                                                            Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT)));
                        if (!fieldManager && type != Object.class) {
                            instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(fmds[i].getDeclaredType())));
                        }
                    }
                    else if (oidType == DateId.class) {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                            Type.getInternalName(oidType),
                                                            "getId",
                                                            Type.getMethodDescriptor(Type.getType(Date.class))));
                        if (!fieldManager && type != Date.class) {
                            instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(fmds[i].getDeclaredType())));
                        }
                    }
                    else {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                            Type.getInternalName(oidType),
                                                            "getId",
                                                            Type.getMethodDescriptor(Type.getType(unwrapped))));
                        if (unwrapped != type) {
                            if (type == Long.class) {
                                instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                                    Type.getInternalName(type),
                                                                    LONG_VALUE_OF.getName(),
                                                                    Type.getMethodDescriptor(LONG_VALUE_OF)));
                            } else {
                                instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                                    Type.getInternalName(type),
                                                                    "",
                                                                    Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(unwrapped))));
                            }
                        }
                    }
                }
                else if (isFieldAccess(fmds[i])) {
                    Field field = Reflection.findField(oidType, name, true);
                    if (Modifier.isPublic(field.getModifiers())) {
                        instructions.add(new FieldInsnNode(Opcodes.GETFIELD,
                                                           Type.getInternalName(field.getDeclaringClass()),
                                                           field.getName(),
                                                           Type.getDescriptor(field.getType())));
                    }
                    else {
                        boolean usedFastOid = false;
                        if (_optimizeIdCopy) {
                            // If fastOids, ignore access type and try to use a public getter
                            Method getter = Reflection.findGetter(oidType, name, false);
                            if (getter != null && Modifier.isPublic(getter.getModifiers())) {
                                usedFastOid = true;
                                instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                                    Type.getInternalName(getter.getDeclaringClass()),
                                                                    getter.getName(),
                                                                    Type.getMethodDescriptor(getter)));
                            }
                        }
                        if (!usedFastOid) {
                            // Reflection.getXXX(oid, Reflection.findField(...));
                            instructions.add(AsmHelper.getLoadConstantInsn(oidType));
                            instructions.add(AsmHelper.getLoadConstantInsn(name));
                            instructions.add(AsmHelper.getLoadConstantInsn(true));
                            instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                                Type.getInternalName(Reflection.class),
                                                                "findField",
                                                                Type.getMethodDescriptor(Type.getType(Field.class), Type.getType(Class.class),
                                                                                         Type.getType(String.class), Type.BOOLEAN_TYPE)));

                            final Method reflectionGetterMethod = getReflectionGetterMethod(type, Field.class);
                            instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                                Type.getInternalName(reflectionGetterMethod.getDeclaringClass()),
                                                                reflectionGetterMethod.getName(),
                                                                Type.getMethodDescriptor(reflectionGetterMethod)));
                            if (!type.isPrimitive() && type != Object.class) {
                                instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(type)));
                            }
                        }
                    }
                }
                else {
                    Method getter = Reflection.findGetter(oidType, name, true);
                    if (Modifier.isPublic(getter.getModifiers())) {
                        instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                            Type.getInternalName(getter.getDeclaringClass()),
                                                            getter.getName(),
                                                            Type.getMethodDescriptor(getter)));
                    }
                    else {
                        // Reflection.getXXX(oid, Reflection.findGetter(...));
                        instructions.add(AsmHelper.getLoadConstantInsn(oidType));
                        instructions.add(AsmHelper.getLoadConstantInsn(name));
                        instructions.add(AsmHelper.getLoadConstantInsn(true));
                        instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                            Type.getInternalName(Reflection.class),
                                                            "findGetter",
                                                            Type.getMethodDescriptor(Type.getType(Method.class), Type.getType(Class.class),
                                                                                     Type.getType(String.class), Type.BOOLEAN_TYPE)));

                        final Method reflectionGetterMethod = getReflectionGetterMethod(type, Method.class);
                        instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                                            Type.getInternalName(reflectionGetterMethod.getDeclaringClass()),
                                                            reflectionGetterMethod.getName(),
                                                            Type.getMethodDescriptor(reflectionGetterMethod)));
                        if (!type.isPrimitive() && type != Object.class) {
                            instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(type)));
                        }
                    }
                }
            }

            if (fieldManager) {
                final Method fieldConsumerMethod = getFieldConsumerMethod(type);
                instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                                    Type.getInternalName(fieldConsumerMethod.getDeclaringClass()),
                                                    fieldConsumerMethod.getName(),
                                                    Type.getMethodDescriptor(fieldConsumerMethod)));
            }
            else {
                addSetManagedValueCode(classNode, instructions, fmds[i]);
            }
        }

        instructions.add(new InsnNode(Opcodes.RETURN));
    }

    /**
     * Return if the class uses the Class/String constructor
     * instead of just String.
     */
    private Boolean usesClassStringIdConstructor() {
        if (_meta.getIdentityType() != ClassMetaData.ID_APPLICATION) {
            return Boolean.FALSE;
        }

        if (_meta.isOpenJPAIdentity()) {
            if (_meta.getObjectIdType() == ObjectId.class) {
                return null;
            }
            return Boolean.TRUE;
        }

        Class oidType = _meta.getObjectIdType();
        try {
            oidType.getConstructor(new Class[]{Class.class, String.class});
            return Boolean.TRUE;
        }
        catch (Throwable t) {
        }
        try {
            oidType.getConstructor(new Class[]{String.class});
            return Boolean.FALSE;
        }
        catch (Throwable t) {
        }
        return null;
    }

    /**
     * If the given field is a wrapper-type single field identity primary key,
     * return its corresponding primitive class. Else return the field type.
     */
    private Class unwrapSingleFieldIdentity(FieldMetaData fmd) {
        if (!fmd.getDefiningMetaData().isOpenJPAIdentity()) {
            return fmd.getDeclaredType();
        }

        switch (fmd.getDeclaredTypeCode()) {
            case JavaTypes.BYTE_OBJ:
                return byte.class;
            case JavaTypes.CHAR_OBJ:
                return char.class;
            case JavaTypes.DOUBLE_OBJ:
                return double.class;
            case JavaTypes.FLOAT_OBJ:
                return float.class;
            case JavaTypes.INT_OBJ:
                return int.class;
            case JavaTypes.SHORT_OBJ:
                return short.class;
            case JavaTypes.LONG_OBJ:
                return long.class;
            default:
                return fmd.getDeclaredType();
        }
    }

    /**
     * Return the proper getter method of the {@link Reflection} helper for
     * a field or getter method of the given type.
     */
    private Method getReflectionGetterMethod(Class type, Class argType)
            throws NoSuchMethodException {
        String name = "get";
        if (type.isPrimitive()) {
            name += StringUtil.capitalize(type.getName());
        }
        return Reflection.class.getMethod(name, new Class[]{Object.class,
                argType});
    }

    /**
     * Return the proper fetch method of the ObjectIdFieldSupplier for
     * a field of the given type.
     */
    private Method getFieldSupplierMethod(Class type)
            throws NoSuchMethodException {
        return getMethod(OIDFSTYPE, type, "fetch", true, false, false);
    }

    /**
     * Return the proper fetch method of the ObjectIdFieldConsumer for
     * a field of the given type.
     */
    private Method getFieldConsumerMethod(Class type)
            throws NoSuchMethodException {
        return getMethod(OIDFCTYPE, type, "store", false, false, false);
    }

    /**
     * Adds the pcNewObjectIdInstance method to classes using
     * application identity.
     */
    private void addNewObjectIdInstanceMethod(boolean obj) throws NoSuchMethodException {
        // public Object pcNewObjectIdInstance ()
        String mDesc = obj
                ? Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT, AsmHelper.TYPE_OBJECT)
                : Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT);

        MethodNode newOidMeth = new MethodNode(Opcodes.ACC_PUBLIC,
                                               PRE + "NewObjectIdInstance",
                                               mDesc,
                                               null, null);
        final ClassNode classNode = pc.getClassNode();
        classNode.methods.add(newOidMeth);
        InsnList instructions = newOidMeth.instructions;

        Boolean usesClsString = usesClassStringIdConstructor();
        Class oidType = _meta.getObjectIdType();
        if (obj && usesClsString == null) {
            // throw new IllegalArgumentException (...);
            String msg = _loc.get("str-cons", oidType, _meta.getDescribedType()).getMessage();

            instructions.add(AsmHelper.throwException(IllegalArgumentException.class, msg));
            return;
        }

        if (!_meta.isOpenJPAIdentity() && _meta.isObjectIdTypeShared()) {
            // new ObjectId (cls, oid)
            instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(ObjectId.class)));
            instructions.add(new InsnNode(Opcodes.DUP));

            if (_meta.isEmbeddedOnly() || _meta.hasAbstractPKField()) {
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                    classNode.name,
                                                    PRE + "GetIDOwningClass",
                                                    Type.getMethodDescriptor(Type.getType(Class.class))));
            }
            else {
                instructions.add(AsmHelper.getLoadConstantInsn(getType(_meta)));
            }
        }

        // new  ();
        instructions.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(oidType)));
        instructions.add(new InsnNode(Opcodes.DUP));
        if (_meta.isOpenJPAIdentity() || (obj && usesClsString == Boolean.TRUE)) {
            if ((_meta.isEmbeddedOnly()
                    && !(_meta.isEmbeddable() && _meta.getIdentityType() == ClassMetaData.ID_APPLICATION))
                    || _meta.hasAbstractPKField()) {
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
                instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                                    classNode.name,
                                                    PRE + "GetIDOwningClass",
                                                    Type.getMethodDescriptor(Type.getType(Class.class))));
            }
            else {
                instructions.add(AsmHelper.getLoadConstantInsn(getType(_meta)));
            }
        }

        String mDescInit = Type.getMethodDescriptor(Type.VOID_TYPE);
        if (obj) {
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param
            instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(String.class)));

            if (usesClsString == Boolean.TRUE) {
                mDescInit = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class), Type.getType(String.class));
            }
            else if (usesClsString == Boolean.FALSE) {
                mDescInit = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class));
            }
        }
        else if (_meta.isOpenJPAIdentity()) {
            // new Identity (XXX.class, );
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
            FieldMetaData pk = _meta.getPrimaryKeyFields()[0];
            addGetManagedValueCode(classNode, instructions, pk, true);
            if (pk.getDeclaredTypeCode() == JavaTypes.PC) {
                int nextFreeVarPos = 1;
                addExtractObjectIdFieldValueCode(classNode, instructions, pk, nextFreeVarPos);
            }

            if (_meta.getObjectIdType() == ObjectId.class) {
                mDescInit = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class), Type.getType(Object.class));
            }
            else if (_meta.getObjectIdType() == Date.class) {
                mDescInit = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class), Type.getType(Date.class));
            }
            else {
                mDescInit = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class), Type.getType(pk.getObjectIdFieldType()));
            }
        }

        instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                            Type.getInternalName(oidType),
                                            "",
                                            mDescInit));

        if (!_meta.isOpenJPAIdentity() && _meta.isObjectIdTypeShared()) {
            instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                Type.getInternalName(ObjectId.class),
                                                "",
                                                Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class), Type.getType(Object.class))));

        }

        instructions.add(new InsnNode(Opcodes.ARETURN));
    }

    /**
     * When communicating with the StateManager, many methods are used
     * depending on the class of state being passed. This method,
     * given the type of information being passed and the prefix
     * ('provided', 'replace', etc) of the method to
     * call, returns the StateManager method that should be used.
     *
     * @param type     the type of state being passed
     * @param prefix   the prefix of the method to call; all methods
     *                 end in '[state type]Field'; only the prefix varies
     * @param get      true if receiving information from the
     *                 StateManager, false if passing it to the SM
     * @param curValue true if the current state value is passed to
     *                 the StateManager as an extra argument
     */
    private Method getStateManagerMethod(Class type, String prefix,
                                         boolean get, boolean curValue)
            throws NoSuchMethodException {
        return getMethod(SMTYPE, type, prefix, get, true, curValue);
    }

    /**
     * Return the method of the given owner type matching the given criteria.
     *
     * @param type     the type of state being passed
     * @param prefix   the prefix of the method to call; all methods
     *                 end in '[state type]Field'; only the prefix varies
     * @param get      true if receiving information from the
     *                 owner, false if passing it to the owner
     * @param haspc    true if the pc is passed as an extra argument
     * @param curValue true if the current state value is passed to
     *                 the owner as an extra argument
     */
    private Method getMethod(Class owner, Class type, String prefix,
                             boolean get, boolean haspc, boolean curValue)
            throws NoSuchMethodException {
        // all methods end in [field type]Field, where the field type
        // can be any of the primitve types (but capitalized), 'String',
        // or 'Object'; figure out what type to use
        String typeName = type.getName();
        if (type.isPrimitive()) {
            typeName = typeName.substring(0, 1).toUpperCase(Locale.ENGLISH)
                    + typeName.substring(1);
        }
        else if (type.equals(String.class)) {
            typeName = "String";
        }
        else {
            typeName = "Object";
            type = Object.class;
        }

        // the field index is always passed as an arg; the pc instance and
        // the current value may be passed; if setting the new value is
        // also passed
        List plist = new ArrayList(4);
        if (haspc) {
            plist.add(PCTYPE);
        }
        plist.add(int.class);
        if (!get || curValue) {
            plist.add(type);
        }
        if (!get && curValue) {
            plist.add(type);
            plist.add(int.class);
        }

        // use reflection to return the right method
        String name = prefix + typeName + "Field";
        Class[] params = (Class[]) plist.toArray(new Class[0]);

        try {
            return AccessController.doPrivileged(
                    J2DoPrivHelper.getDeclaredMethodAction(owner, name, params));
        }
        catch (PrivilegedActionException pae) {
            throw (NoSuchMethodException) pae.getException();
        }
    }

    /**
     * Adds the PersistenceCapable interface to the class being
     * enhanced, and adds a default constructor for use by OpenJPA
     * if it is not already present.
     */
    private void enhanceClass(final ClassNodeTracker classNodeTracker) {

        // make the class implement PersistenceCapable
        final ClassNode classNode = classNodeTracker.getClassNode();
        classNode.interfaces.add(Type.getInternalName(PCTYPE));

        // add a version stamp
        addGetEnhancementContractVersionMethod(classNodeTracker);

        // find the default constructor
        final boolean hasDefaultCt = classNode.methods.stream()
                .anyMatch(m -> m.name.equals("") && m.desc.equals("()V"));
        if (!hasDefaultCt) {
            if (!_defCons) {
                throw new UserException(_loc.get("enhance-defaultconst", classNode.name));
            }

            int accessMode;
            String access;
            if (_meta.isDetachable()) {
                // externalizable requires that the constructor
                // be public, so make the added constructor public
                accessMode = Opcodes.ACC_PUBLIC;
                access = "public";
            }
            else if ((pc.getClassNode().access & Opcodes.ACC_FINAL) > 0) {
                accessMode = Opcodes.ACC_PRIVATE;
                access = "private";
            }
            else {
                accessMode = Opcodes.ACC_PROTECTED;
                access = "protected";
            }

            MethodNode ctNode = new MethodNode(accessMode,
                                               "",
                                               Type.getMethodDescriptor(Type.VOID_TYPE),
                                               null, null);
            ctNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            ctNode.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, classNode.superName,
                                                       "", "()V"));
            ctNode.instructions.add(new InsnNode(Opcodes.RETURN));
            classNode.methods.add(ctNode);

            if (!(_meta.getDescribedType().isInterface() || getCreateSubclass()) && _log.isWarnEnabled()) {
                _log.warn(_loc.get("enhance-adddefaultconst", classNode.name, access));
            }

        }
    }

    /**
     * Adds the following fields to the PersistenceCapable instance:
     * 
    *
  • private static int pcInheritedFieldCount
  • *
  • private static Class pcPCSuperclass *
  • *
  • private static String[] pcFieldNames
  • *
  • private static Class[] pcFieldTypes
  • *
  • private static byte[] pcFieldFlags
  • *
  • protected transient StateManager pcStateManager * if no PersistenceCapable superclass present)
  • *
*/ private void addFields(ClassNodeTracker classNodeTracker) { final ClassNode classNode = classNodeTracker.getClassNode(); classNode.fields.add(new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, INHERIT, Type.getDescriptor(int.class), null, null)); classNode.fields.add(new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, PRE + "FieldNames", Type.getDescriptor(String[].class), null, null)); classNode.fields.add(new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, PRE + "FieldTypes", Type.getDescriptor(Class[].class), null, null)); classNode.fields.add(new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, PRE + "FieldFlags", Type.getDescriptor(byte[].class), null, null)); classNode.fields.add(new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, SUPER, Type.getDescriptor(Class.class), null, null)); if (_addVersionInitFlag && _meta.getVersionField() != null) { classNode.fields.add(new FieldNode(Opcodes.ACC_PROTECTED | Opcodes.ACC_TRANSIENT, VERSION_INIT_STR, Type.getDescriptor(boolean.class), null, null)); } if (_meta.getPCSuperclass() == null || getCreateSubclass()) { classNode.fields.add(new FieldNode(Opcodes.ACC_PROTECTED | Opcodes.ACC_TRANSIENT, SM, Type.getDescriptor(SMTYPE), null, null)); } } /** * Modifies the class initialization method (creating one if necessary) * to initialize the static fields of the PersistenceCapable instance and * to register it with the impl helper. */ private void addStaticInitializer(ClassNodeTracker classNodeTracker) { final ClassNode classNode = classNodeTracker.getClassNode(); InsnList instructions = new InsnList(); if (_meta.getPCSuperclass() != null) { if (getCreateSubclass()) { instructions.add(AsmHelper.getLoadConstantInsn(0)); instructions.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, INHERIT, Type.INT_TYPE.getDescriptor())); } else { // pcInheritedFieldCount = .pcGetManagedFieldCount() instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, classNode.superName, PRE + "GetManagedFieldCount", Type.getMethodDescriptor(Type.INT_TYPE))); instructions.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, INHERIT, Type.INT_TYPE.getDescriptor())); } // pcPCSuperclass = ; // this intentionally calls getDescribedType() directly // instead of PCEnhancer.getType() instructions.add(AsmHelper.getLoadConstantInsn(_meta.getPCSuperclassMetaData().getDescribedType())); instructions.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, SUPER, Type.getDescriptor(Class.class))); } FieldMetaData[] fmds = _meta.getDeclaredFields(); // pcFieldNames = new String[] { "", "", ... }; instructions.add(AsmHelper.getLoadConstantInsn(fmds.length)); instructions.add(new TypeInsnNode(Opcodes.ANEWARRAY, Type.getInternalName(String.class))); for (int i = 0; i < fmds.length; i++) { instructions.add(new InsnNode(Opcodes.DUP)); instructions.add(AsmHelper.getLoadConstantInsn(i)); instructions.add(AsmHelper.getLoadConstantInsn(fmds[i].getName())); instructions.add(new InsnNode(Opcodes.AASTORE)); } instructions.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, PRE + "FieldNames", Type.getDescriptor(String[].class))); // pcFieldTypes = new Class[] { .class, .class, ... }; instructions.add(AsmHelper.getLoadConstantInsn(fmds.length)); instructions.add(new TypeInsnNode(Opcodes.ANEWARRAY, Type.getInternalName(Class.class))); for (int i = 0; i < fmds.length; i++) { instructions.add(new InsnNode(Opcodes.DUP)); instructions.add(AsmHelper.getLoadConstantInsn(i)); instructions.add(AsmHelper.getLoadConstantInsn(fmds[i].getDeclaredType())); instructions.add(new InsnNode(Opcodes.AASTORE)); } instructions.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, PRE + "FieldTypes", Type.getDescriptor(Class[].class))); // pcFieldFlags = new byte[] { , , ... }; instructions.add(AsmHelper.getLoadConstantInsn(fmds.length)); instructions.add(new IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_BYTE)); for (int i = 0; i < fmds.length; i++) { instructions.add(new InsnNode(Opcodes.DUP)); instructions.add(AsmHelper.getLoadConstantInsn(i)); instructions.add(AsmHelper.getLoadConstantInsn(getFieldFlag(fmds[i]))); instructions.add(new InsnNode(Opcodes.BASTORE)); } instructions.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, PRE + "FieldFlags", Type.getDescriptor(byte[].class))); // PCRegistry.register (cls, // pcFieldNames, pcFieldTypes, pcFieldFlags, // pcPCSuperclass, alias, new XXX ()); instructions.add(AsmHelper.getLoadConstantInsn(_meta.getDescribedType())); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, PRE + "FieldNames", Type.getDescriptor(String[].class))); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, PRE + "FieldTypes", Type.getDescriptor(Class[].class))); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, PRE + "FieldFlags", Type.getDescriptor(byte[].class))); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, SUPER, Type.getDescriptor(Class.class))); if (_meta.isMapped() || _meta.isAbstract()) { instructions.add(AsmHelper.getLoadConstantInsn(_meta.getTypeAlias())); } else { instructions.add(new InsnNode(Opcodes.ACONST_NULL)); } if ((pc.getClassNode().access & Opcodes.ACC_ABSTRACT) > 0) { instructions.add(new InsnNode(Opcodes.ACONST_NULL)); } else { instructions.add(new TypeInsnNode(Opcodes.NEW, classNode.name)); instructions.add(new InsnNode(Opcodes.DUP)); instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, classNode.name, "", Type.getMethodDescriptor(Type.VOID_TYPE))); } instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(HELPERTYPE), "register", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class), Type.getType(String[].class), Type.getType(Class[].class), Type.getType(byte[].class), Type.getType(Class.class), Type.getType(String.class), Type.getType(PersistenceCapable.class)))); // now add those instructions to the method MethodNode clinit = getOrCreateClassInitMethod(classNode); final AbstractInsnNode retInsn = clinit.instructions.getLast(); if (retInsn.getOpcode() != Opcodes.RETURN) { throw new IllegalStateException("Problem with parsing instructions. RETURN expected"); } clinit.instructions.insertBefore(retInsn, instructions); } /** * Return the flag for the given field. */ private static byte getFieldFlag(FieldMetaData fmd) { if (fmd.getManagement() == FieldMetaData.MANAGE_NONE) { return -1; } byte flags = 0; if (fmd.getDeclaredType().isPrimitive() || Serializable.class.isAssignableFrom(fmd.getDeclaredType())) { flags = PersistenceCapable.SERIALIZABLE; } if (fmd.getManagement() == FieldMetaData.MANAGE_TRANSACTIONAL) { flags |= PersistenceCapable.CHECK_WRITE; } else if (!fmd.isPrimaryKey() && !fmd.isInDefaultFetchGroup()) { flags |= PersistenceCapable.CHECK_WRITE | PersistenceCapable.CHECK_READ; } else { flags |= PersistenceCapable.MEDIATE_WRITE | PersistenceCapable.MEDIATE_READ; } return flags; } /** * Adds the code to properly handle PersistenceCapable serialization * to the bytecode. This includes creating and initializing the * static serialVersionUID constant if not already defined, * as well as creating a custom writeObject method if the * class is Serializable and does not define them. */ private void addSerializationCode() { if (externalizeDetached() || !Serializable.class.isAssignableFrom(_meta.getDescribedType())) { return; } if (getCreateSubclass()) { // ##### what should happen if a type is Externalizable? It looks // ##### like Externalizable classes will not be serialized as PCs // ##### based on this logic. if (!Externalizable.class.isAssignableFrom(_meta.getDescribedType())) { addSubclassSerializationCode(); } return; } // if not already present, add a serialVersionUID field; if the instance // is detachable and uses detached state without a declared field, // can't add a serial version UID because we'll be adding extra fields // to the enhanced version final Optional serialVersionUIDNode = pc.getClassNode().fields.stream() .filter(f -> f.name.equals("serialVersionUID")) .findFirst(); if (serialVersionUIDNode.isEmpty()) { Long uid = null; try { uid = ObjectStreamClass.lookup(_meta.getDescribedType()).getSerialVersionUID(); } catch (Throwable t) { // last-chance catch for bug #283 (which can happen // in a variety of ClassLoading environments) if (_log.isTraceEnabled()) { _log.warn(_loc.get("enhance-uid-access", _meta), t); } else { _log.warn(_loc.get("enhance-uid-access", _meta)); } } // if we couldn't access the serialVersionUID, we will have to // skip the override of that field and not be serialization // compatible with non-enhanced classes if (uid != null) { FieldNode serVersField = new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "serialVersionUID", Type.LONG_TYPE.getDescriptor(), null, uid); pc.getClassNode().fields.add(serVersField); } } MethodNode writeObjectMeth = AsmHelper.getMethodNode(pc.getClassNode(), "writeObject", void.class, ObjectOutputStream.class) .orElse(null); boolean full = writeObjectMeth == null; // add write object method if (full) { // private void writeObject (ObjectOutputStream out) writeObjectMeth = new MethodNode(Opcodes.ACC_PRIVATE, "writeObject", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectOutputStream.class)), null, new String[]{Type.getInternalName(IOException.class)}); pc.getClassNode().methods.add(writeObjectMeth); } modifyWriteObjectMethod(pc.getClassNode(), writeObjectMeth, full); // and read object MethodNode readObjectMeth = AsmHelper.getMethodNode(pc.getClassNode(), "readObject", void.class, ObjectInputStream.class) .orElse(null); full = readObjectMeth == null; if (full) { // private void readObject (ObjectInputStream in) readObjectMeth = new MethodNode(Opcodes.ACC_PRIVATE, "readObject", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectInputStream.class)), null, new String[]{Type.getInternalName(IOException.class), Type.getInternalName(ClassNotFoundException.class)}); pc.getClassNode().methods.add(readObjectMeth); } modifyReadObjectMethod(pc.getClassNode(), readObjectMeth, full); } private void addSubclassSerializationCode() { // for generated subclasses, serialization must write an instance of // the superclass instead of the subclass, so that the client VM can // deserialize successfully. // private Object writeReplace() throws ObjectStreamException MethodNode writeReplaceMeth = new MethodNode(Opcodes.ACC_PRIVATE, "writeReplace", Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT), null, new String[]{Type.getInternalName(ObjectStreamException.class)}); final ClassNode classNode = pc.getClassNode(); classNode.methods.add(writeReplaceMeth); InsnList instructions = writeReplaceMeth.instructions; // Object o = new () instructions.add(new TypeInsnNode(Opcodes.NEW, managedType.getClassNode().name)); instructions.add(new InsnNode(Opcodes.DUP)); // for post- work instructions.add(new InsnNode(Opcodes.DUP)); // for instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, managedType.getClassNode().name, "", Type.getMethodDescriptor(Type.VOID_TYPE))); // copy all the fields. // ##### limiting to JPA @Transient limitations FieldMetaData[] fmds = _meta.getFields(); for (FieldMetaData fmd : fmds) { if (fmd.isTransient()) { continue; } // o. = this. (or reflective analog) instructions.add(new InsnNode(Opcodes.DUP)); // for putfield instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this for getfield getfield(classNode, instructions, _meta.getDescribedType(), fmd.getName(), fmd.getDeclaredType()); putfield(classNode, instructions, _meta.getDescribedType(), fmd.getName(), fmd.getDeclaredType()); } instructions.add(new InsnNode(Opcodes.ARETURN)); } /** * Whether the class being enhanced should externalize to a detached * instance rather than serialize. */ private boolean externalizeDetached() { return ClassMetaData.SYNTHETIC.equals(_meta.getDetachedState()) && Serializable.class.isAssignableFrom(_meta.getDescribedType()) && !_repos.getConfiguration().getDetachStateInstance(). isDetachedStateTransient(); } /** * Adds a custom writeObject method that delegates to the * {@link ObjectOutputStream#defaultWriteObject} method, * but only after calling the internal pcSerializing method. */ private void modifyWriteObjectMethod(ClassNode classNode, MethodNode method, boolean full) { InsnList instructions = new InsnList(); // bool clear = pcSerializing (); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + "Serializing", Type.getMethodDescriptor(Type.BOOLEAN_TYPE))); int clearVarPos = full ? 2 : method.maxLocals + 1; instructions.add(new VarInsnNode(Opcodes.ISTORE, clearVarPos)); if (full) { // out.defaultWriteObject (); instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(ObjectOutputStream.class), "defaultWriteObject", Type.getMethodDescriptor(Type.VOID_TYPE))); instructions.add(new InsnNode(Opcodes.RETURN)); method.instructions.insert(instructions); instructions.clear(); } AbstractInsnNode insn = method.instructions.getFirst(); // skip to the next RETURN instruction while ((insn = searchNextInstruction(insn, i -> i.getOpcode() == Opcodes.RETURN)) != null) { InsnList insns = new InsnList(); insns.add(new VarInsnNode(Opcodes.ILOAD, clearVarPos)); LabelNode lblEndIf = new LabelNode(); insns.add(new JumpInsnNode(Opcodes.IFEQ, lblEndIf)); insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this insns.add(new InsnNode(Opcodes.ACONST_NULL)); insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + "SetDetachedState", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class)))); insns.add(lblEndIf); method.instructions.insertBefore(insn, insns); insn = insn.getNext(); } method.instructions.insert(instructions); } /** * Adds a custom readObject method that delegates to the * {@link ObjectInputStream#readObject()} method. */ private void modifyReadObjectMethod(ClassNode classNode, MethodNode method, boolean full) { InsnList instructions = new InsnList(); // if this instance uses synthetic detached state, note that it has // been deserialized if (ClassMetaData.SYNTHETIC.equals(_meta.getDetachedState())) { instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(PersistenceCapable.class), "DESERIALIZED", Type.getDescriptor(Object.class))); instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + "SetDetachedState", Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT))); } if (full) { // in.defaultReadObject (); instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(ObjectInputStream.class), "defaultReadObject", Type.getMethodDescriptor(Type.VOID_TYPE))); instructions.add(new InsnNode(Opcodes.RETURN)); } method.instructions.insert(instructions); } /** * Creates the pcIsDetached() method to determine if an instance * is detached. */ private void addIsDetachedMethod(ClassNode classNode) throws NoSuchMethodException { // public boolean pcIsDetached() MethodNode isDetachedMeth = new MethodNode(Opcodes.ACC_PUBLIC, PRE + "IsDetached", Type.getMethodDescriptor(Type.getType(Boolean.class)), null, null); classNode.methods.add(isDetachedMeth); boolean needsDefinitiveMethod = writeIsDetachedMethod(classNode, isDetachedMeth); if (!needsDefinitiveMethod) { return; } // private boolean pcIsDetachedStateDefinitive() // return false; // auxilliary enhancers may change the return value of this method // if their specs consider detached state definitive MethodNode isDetachedStateDefinitiveMeth = new MethodNode(Opcodes.ACC_PRIVATE, ISDETACHEDSTATEDEFINITIVE, Type.getMethodDescriptor(Type.BOOLEAN_TYPE), null, null); classNode.methods.add(isDetachedStateDefinitiveMeth); isDetachedStateDefinitiveMeth.instructions.add(AsmHelper.getLoadConstantInsn(false)); isDetachedStateDefinitiveMeth.instructions.add(new InsnNode(Opcodes.IRETURN)); } /** * Creates the body of the pcIsDetached() method to determine if an * instance is detached. * * @return true if we need a pcIsDetachedStateDefinitive method, false * otherwise */ private boolean writeIsDetachedMethod(ClassNode classNode, MethodNode meth) throws NoSuchMethodException { InsnList instructions = meth.instructions; // not detachable: return Boolean.FALSE if (!_meta.isDetachable()) { instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "FALSE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); return false; } // if (sm != null) // return (sm.isDetached ()) ? Boolean.TRUE : Boolean.FALSE; instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); LabelNode lblEndIfNull = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFNULL, lblEndIfNull)); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(SMTYPE), "isDetached", Type.getMethodDescriptor(Type.BOOLEAN_TYPE))); LabelNode lblEndIfFalse = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFEQ, lblEndIfFalse)); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "TRUE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); instructions.add(lblEndIfFalse); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "FALSE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); // END - if (sm != null) // if we use detached state: // if (pcGetDetachedState () != null // && pcGetDetachedState != DESERIALIZED) // return Boolean.TRUE; Boolean state = _meta.usesDetachedState(); LabelNode lblNotDeser = null; if (state != Boolean.FALSE) { instructions.add(lblEndIfNull); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + "GetDetachedState", Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT))); lblEndIfNull = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFNULL, lblEndIfNull)); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + "GetDetachedState", Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT))); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(PersistenceCapable.class), "DESERIALIZED", AsmHelper.TYPE_OBJECT.getDescriptor())); lblNotDeser = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IF_ACMPEQ, lblNotDeser)); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "TRUE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); if (state == Boolean.TRUE) { // if we have to use detached state: // return Boolean.FALSE; instructions.add(lblEndIfNull); instructions.add(lblNotDeser); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "FALSE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); return false; } } instructions.add(lblEndIfNull); if (lblNotDeser != null) { instructions.add(lblNotDeser); } // allow users with version or auto-assigned pk fields to manually // construct a "detached" instance, so check these before taking into // account non-existent detached state // consider detached if version is non-default FieldMetaData version = _meta.getVersionField(); if (state != Boolean.TRUE && version != null) { // if ( != ) instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this addGetManagedValueCode(classNode, instructions, version, true); LabelNode lblAfterDefault = ifDefaultValue(instructions, version); // return true instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "TRUE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); instructions.add(lblAfterDefault); if (!_addVersionInitFlag) { // else return false; instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "FALSE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); } else { // if (pcVersionInit != false) // return true // else return null; // (returning null because we don't know the correct answer) instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this getfield(classNode, instructions, null, VERSION_INIT_STR, boolean.class); LabelNode lblAfterEq = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFEQ, lblAfterEq)); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "TRUE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); instructions.add(lblAfterEq); instructions.add(AsmHelper.getLoadConstantInsn(null)); instructions.add(new InsnNode(Opcodes.ARETURN)); } return false; } // consider detached if auto-genned primary keys are non-default LabelNode ifIns = null; LabelNode ifIns2 = null; if (state != Boolean.TRUE && _meta.getIdentityType() == ClassMetaData.ID_APPLICATION) { // for each pk field: // if ( != [&& !"".equals ()]) // return Boolean.TRUE; FieldMetaData[] pks = _meta.getPrimaryKeyFields(); for (FieldMetaData pk : pks) { if (pk.getValueStrategy() == ValueStrategies.NONE) { continue; } if (ifIns != null) { instructions.add(ifIns); } if (ifIns2 != null) { instructions.add(ifIns2); } ifIns2 = null; instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this addGetManagedValueCode(classNode, instructions, pk, true); ifIns = ifDefaultValue(instructions, pk); if (pk.getDeclaredTypeCode() == JavaTypes.STRING) { instructions.add(AsmHelper.getLoadConstantInsn("")); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this addGetManagedValueCode(classNode, instructions, pk, true); instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(String.class), "equals", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, AsmHelper.TYPE_OBJECT))); ifIns2 = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFNE, ifIns2)); } instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "TRUE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); } } if (ifIns != null) { instructions.add(ifIns); } if (ifIns2 != null) { instructions.add(ifIns2); } // if detached state is not definitive, just give up now and return // null so that the runtime will perform a DB lookup to determine // whether we're detached or new instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, classNode.name, ISDETACHEDSTATEDEFINITIVE, Type.getMethodDescriptor(Type.BOOLEAN_TYPE))); LabelNode lblAfterNe = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFNE, lblAfterNe)); instructions.add(AsmHelper.getLoadConstantInsn(null)); instructions.add(new InsnNode(Opcodes.ARETURN)); instructions.add(lblAfterNe); // no detached state: if instance uses detached state and it's not // synthetic or the instance is not serializable or the state isn't // transient, must not be detached if (state == null && (!ClassMetaData.SYNTHETIC.equals(_meta.getDetachedState()) || !Serializable.class.isAssignableFrom(_meta.getDescribedType()) || !_repos.getConfiguration().getDetachStateInstance().isDetachedStateTransient())) { // return Boolean.FALSE instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "FALSE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); return true; } // no detached state: if instance uses detached state (and must be // synthetic and transient in serializable instance at this point), // not detached if state not set to DESERIALIZED if (state == null) { // if (pcGetDetachedState () == null) // instead of DESERIALIZED // return Boolean.FALSE; instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + "GetDetachedState", Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT))); LabelNode lblIfNn = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFNONNULL, lblIfNn)); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(Boolean.class), "FALSE", Type.getDescriptor(Boolean.class))); instructions.add(new InsnNode(Opcodes.ARETURN)); instructions.add(lblIfNn); } // give up; we just don't know instructions.add(AsmHelper.getLoadConstantInsn(null)); instructions.add(new InsnNode(Opcodes.ARETURN)); return true; } /** * Compare the given field to its Java default, returning the * comparison instruction. The field value will already be on the stack. * * @return the LabelNode for the else block. */ private static LabelNode ifDefaultValue(InsnList instructions, FieldMetaData fmd) { LabelNode lbl = new LabelNode(); switch (fmd.getDeclaredTypeCode()) { case JavaTypes.BOOLEAN: case JavaTypes.BYTE: case JavaTypes.CHAR: case JavaTypes.INT: case JavaTypes.SHORT: instructions.add(new JumpInsnNode(Opcodes.IFEQ, lbl)); break; case JavaTypes.DOUBLE: instructions.add(AsmHelper.getLoadConstantInsn(0D)); instructions.add(new InsnNode(Opcodes.DCMPL)); instructions.add(new JumpInsnNode(Opcodes.IFEQ, lbl)); break; case JavaTypes.FLOAT: instructions.add(AsmHelper.getLoadConstantInsn(0F)); instructions.add(new InsnNode(Opcodes.FCMPL)); instructions.add(new JumpInsnNode(Opcodes.IFEQ, lbl)); break; case JavaTypes.LONG: instructions.add(AsmHelper.getLoadConstantInsn(0L)); instructions.add(new InsnNode(Opcodes.LCMP)); instructions.add(new JumpInsnNode(Opcodes.IFEQ, lbl)); break; default: instructions.add(new JumpInsnNode(Opcodes.IFNULL, lbl)); } return lbl; } /** * Helper method to get the code for the class initializer method, * creating the method if it does not already exist. */ private MethodNode getOrCreateClassInitMethod(ClassNode classNode) { final Optional clinitMethodNode = classNode.methods.stream() .filter(m -> m.name.equals("")) .findFirst(); if (clinitMethodNode.isPresent()) { return clinitMethodNode.get(); } else { // add static initializer method if non exists MethodNode clinit = new MethodNode(Opcodes.ACC_STATIC, "", "()V", null, null); clinit.instructions.add(new InsnNode(Opcodes.RETURN)); classNode.methods.add(clinit); return clinit; } } /** * Adds bytecode modifying the cloning behavior of the class being * enhanced to correctly replace the pcStateManager * instance fields of any clone created with their default values. * Also, if this class is the base PC type and does not declared * a clone method, one will be added. Also, if _pc is a synthetic * subclass, create the clone() method that clears the state manager * that may have been initialized in a super's clone() method. */ private void addCloningCode() { if (_meta.getPCSuperclass() != null && !getCreateSubclass()) { return; } ClassNode classNode = pc.getClassNode(); MethodNode cloneMeth = AsmHelper.getMethodNode(classNode, "clone", Object.class) .orElse(null); String superName = managedType.getClassNode().superName; // add the clone method if necessary if (cloneMeth == null) { // add clone support for base classes // which also implement cloneable boolean isCloneable = Cloneable.class.isAssignableFrom(managedType.getType()); boolean extendsObject = superName.equals(Object.class.getName()); if (!isCloneable || (!extendsObject && !getCreateSubclass())) { return; } if (!getCreateSubclass()) { if (_log.isTraceEnabled()) { _log.trace(_loc.get("enhance-cloneable", managedType.getClassNode().name)); } } // add clone method // protected Object clone () throws CloneNotSupportedException cloneMeth = new MethodNode(0, "clone", Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT), null, new String[]{Type.getInternalName(CloneNotSupportedException.class)}); if (!setVisibilityToSuperMethod(cloneMeth)) { cloneMeth.access |= Opcodes.ACC_PROTECTED; } // return super.clone (); cloneMeth.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this cloneMeth.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, superName, "clone", Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT))); cloneMeth.instructions.add(new InsnNode(Opcodes.ARETURN)); } else { if (cloneMeth.instructions.size() <= 1) { // if the clone method is basically empty return; } } // find calls to the template instruction; on match // clone will be on stack AbstractInsnNode insn = cloneMeth.instructions.getFirst(); if ((insn = searchNextInstruction(insn, i -> i.getOpcode() == Opcodes.INVOKESPECIAL && i instanceof MethodInsnNode && ((MethodInsnNode) i).name.equals("clone")) ) != null) { // (() clone).pcStateManager = null; InsnList instructions = new InsnList(); instructions.add(new InsnNode(Opcodes.DUP)); instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, pc.getClassNode().name)); instructions.add(new InsnNode(Opcodes.ACONST_NULL)); instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); cloneMeth.instructions.insert(insn, instructions); } } /** * Gets the auxiliary enhancers registered as {@link Services services}. */ public AuxiliaryEnhancer[] getAuxiliaryEnhancers() { return _auxEnhancers; } /** * Allow any registered auxiliary code generators to run. */ private void runAuxiliaryEnhancers() { for (AuxiliaryEnhancer auxEnhancer : _auxEnhancers) { auxEnhancer.run(pc.getClassNode(), _meta); } } /** * Affirms if the given method be skipped. * * @param method method to be skipped or not * @return true if any of the auxiliary enhancers skips the given method, * or if the method is a constructor */ private boolean skipEnhance(MethodNode method) { if ("".equals(method.name) || "".equals(method.name)) { return true; } for (AuxiliaryEnhancer auxEnhancer : _auxEnhancers) { if (auxEnhancer.skipEnhance(method)) { return true; } } return false; } /** * Adds synthetic field access methods that will replace all direct * field accesses. */ private void addAccessors(ClassNodeTracker cnt) throws NoSuchMethodException { ClassNode classNode = cnt.getClassNode(); FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields() : _meta.getDeclaredFields(); for (int i = 0; i < fmds.length; i++) { if (getCreateSubclass()) { if (!getRedefine() && isPropertyAccess(fmds[i])) { addSubclassSetMethod(classNode, fmds[i]); addSubclassGetMethod(classNode, fmds[i]); } } else { addGetMethod(classNode, i, fmds[i]); addSetMethod(classNode, i, fmds[i]); } } } /** * Adds a non-static setter that delegates to the super methods, and * performs any necessary field tracking. */ private void addSubclassSetMethod(ClassNode classNode, FieldMetaData fmd) throws NoSuchMethodException { Class propType = fmd.getDeclaredType(); String setterName = getSetterName(fmd); MethodNode newMethod = new MethodNode(Opcodes.ACC_PUBLIC, setterName, Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(propType)), null, null); classNode.methods.add(newMethod); final InsnList instructions = newMethod.instructions; int nextFreeVarPos = 1 + Type.getType(propType).getSize(); setVisibilityToSuperMethod(newMethod); // not necessary if we're already tracking access via redefinition if (!getRedefine()) { // get the orig value onto stack instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this addGetManagedValueCode(classNode, instructions, fmd, true); int valVarPos = nextFreeVarPos++; instructions.add(new VarInsnNode(AsmHelper.getStoreInsn(fmd.getDeclaredType()), valVarPos)); addNotifyMutation(classNode, newMethod, newMethod.instructions.getLast(), fmd, valVarPos, 0); } // ##### test case: B extends A. Methods defined in A. What // ##### happens? // super.setXXX(...) instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(AsmHelper.getLoadInsn(propType), 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, managedType.getClassNode().name, setterName, Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(propType)))); instructions.add(new InsnNode(Opcodes.RETURN)); } private boolean setVisibilityToSuperMethod(MethodNode method) { ClassNode classNode = managedType.getClassNode(); final List methods = classNode.methods.stream() .filter(m -> m.name.equals(method.name) && Objects.equals(m.parameters, method.parameters)) .collect(Collectors.toList()); if (methods.isEmpty()) { throw new UserException(_loc.get("no-accessor", managedType.getClassNode().name, method.name)); } MethodNode superMeth = methods.get(0); method.access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED); if ((superMeth.access & Opcodes.ACC_PRIVATE) > 0) { method.access |= Opcodes.ACC_PRIVATE; return true; } if ((superMeth.access & Opcodes.ACC_PROTECTED) > 0) { method.access |= Opcodes.ACC_PROTECTED; return true; } if ((superMeth.access & Opcodes.ACC_PUBLIC) > 0) { method.access |= Opcodes.ACC_PUBLIC; return true; } return false; } /** * Adds a non-static getter that delegates to the super methods, and * performs any necessary field tracking. */ private void addSubclassGetMethod(ClassNode classNode, FieldMetaData fmd) { String getterName = "get" + StringUtil.capitalize(fmd.getName()); final String finalGetterName = getterName; final boolean hasGetter = managedType.getClassNode().methods.stream() .filter(m -> m.name.equals(finalGetterName) && (m.parameters == null || m.parameters.isEmpty())) .findAny() .isPresent(); if (!hasGetter) { getterName = "is" + StringUtil.capitalize(fmd.getName()); } final Class propType = fmd.getDeclaredType(); MethodNode getterMethod = new MethodNode(Opcodes.ACC_PUBLIC, getterName, Type.getMethodDescriptor(Type.getType(propType)), null, null); classNode.methods.add(getterMethod); final InsnList instructions = getterMethod.instructions; // if we're not already tracking field access via reflection, then we // must make the getter hook in lazy loading before accessing the super // method. if (!getRedefine()) { addNotifyAccess(getterMethod, getterMethod.instructions.getLast(), fmd); } instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, managedType.getClassNode().name, getterName, Type.getMethodDescriptor(Type.getType(propType)))); instructions.add(new InsnNode(AsmHelper.getReturnInsn(propType))); } /** * Adds a static getter method for the given field. * The generated method interacts with the instance state and the * StateManager to get the value of the field. * * @param index the relative number of the field * @param fmd metadata about the field to get */ private void addGetMethod(ClassNode classNode, int index, FieldMetaData fmd) throws NoSuchMethodException { MethodNode method = createGetMethod(classNode, fmd); classNode.methods.add(method); final InsnList instructions = method.instructions; int nextFreeVarPos = 1; // if reads are not checked, just return the value byte fieldFlag = getFieldFlag(fmd); if ((fieldFlag & PersistenceCapable.CHECK_READ) == 0 && (fieldFlag & PersistenceCapable.MEDIATE_READ) == 0) { instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this addGetManagedValueCode(classNode, instructions, fmd, true); instructions.add(new InsnNode(AsmHelper.getReturnInsn(fmd.getDeclaredType()))); return; } // if (inst.pcStateManager == null) return inst.; instructions.add(loadManagedInstance()); instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); LabelNode afterIfNonNull = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFNONNULL, afterIfNonNull)); instructions.add(loadManagedInstance()); addGetManagedValueCode(classNode, instructions, fmd, true); instructions.add(new InsnNode(AsmHelper.getReturnInsn(fmd.getDeclaredType()))); instructions.add(afterIfNonNull); // int field = pcInheritedFieldCount + ; int fieldLocalVarPos = nextFreeVarPos++; instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, INHERIT, Type.INT_TYPE.getDescriptor())); instructions.add(AsmHelper.getLoadConstantInsn(index)); instructions.add(new InsnNode(Opcodes.IADD)); instructions.add(new VarInsnNode(Opcodes.ISTORE, fieldLocalVarPos)); // inst.pcStateManager.accessingField (field); // return inst.; instructions.add(loadManagedInstance()); instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); instructions.add(new VarInsnNode(Opcodes.ILOAD, fieldLocalVarPos)); instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(SMTYPE), "accessingField", Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE))); instructions.add(loadManagedInstance()); addGetManagedValueCode(classNode, instructions, fmd, true); instructions.add(new InsnNode(AsmHelper.getReturnInsn(fmd.getDeclaredType()))); } /** * Adds a static setter method for the given field. * The generated method interacts with the instance state and the * StateManager to set the value of the field. * * @param index the relative number of the field * @param fmd metadata about the field to set */ private void addSetMethod(ClassNode classNode, int index, FieldMetaData fmd) throws NoSuchMethodException { MethodNode method = createSetMethod(classNode, fmd); classNode.methods.add(method); final InsnList instructions = method.instructions; // PCEnhancer uses static methods; PCSubclasser does not. // for a static method there is no 'this', so index starts with zero // but in that case we have the additional entity parameter on that position! int fieldParamPos = 1; // if (inst.pcStateManager == null) inst. = value; instructions.add(loadManagedInstance()); instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); LabelNode lblAfterIfNonNull = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFNONNULL, lblAfterIfNonNull)); instructions.add(loadManagedInstance()); instructions.add(new VarInsnNode(AsmHelper.getLoadInsn(fmd.getDeclaredType()), fieldParamPos)); addSetManagedValueCode(classNode, instructions, fmd); if (fmd.isVersion() && _addVersionInitFlag) { // if we are setting the version, flip the versionInit flag to true instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(AsmHelper.getLoadConstantInsn(1)); // pcVersionInit = true; instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, VERSION_INIT_STR, Type.BOOLEAN_TYPE.getDescriptor())); } instructions.add(new InsnNode(Opcodes.RETURN)); instructions.add(lblAfterIfNonNull); // inst.pcStateManager.settingField (inst, // pcInheritedFieldCount + , inst., value, 0); instructions.add(loadManagedInstance()); instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); instructions.add(loadManagedInstance()); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, INHERIT, Type.INT_TYPE.getDescriptor())); instructions.add(AsmHelper.getLoadConstantInsn(index)); instructions.add(new InsnNode(Opcodes.IADD)); instructions.add(loadManagedInstance()); addGetManagedValueCode(classNode, instructions, fmd, true); instructions.add(new VarInsnNode(AsmHelper.getLoadInsn(fmd.getDeclaredType()), fieldParamPos)); instructions.add(new InsnNode(Opcodes.ICONST_0)); final Method stateMgrMethod = getStateManagerMethod(fmd.getDeclaredType(), "setting", false, true); instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(stateMgrMethod.getDeclaringClass()), stateMgrMethod.getName(), Type.getMethodDescriptor(stateMgrMethod))); instructions.add(new InsnNode(Opcodes.RETURN)); } /** * Determines which attach / detach methods to use. */ private void addAttachDetachCode() throws NoSuchMethodException { // see if any superclasses are detachable boolean parentDetachable = false; for (ClassMetaData parent = _meta.getPCSuperclassMetaData(); parent != null; parent = parent.getPCSuperclassMetaData()) { if (parent.isDetachable()) { parentDetachable = true; break; } } ClassNode classNode = pc.getClassNode(); // if parent not detachable, we need to add the detach state fields and // accessor methods if (_meta.getPCSuperclass() == null || getCreateSubclass() || parentDetachable != _meta.isDetachable()) { addIsDetachedMethod(classNode); addDetachedStateMethods(_meta.usesDetachedState() != Boolean.FALSE); } // if we detach on serialize, we also need to implement the // externalizable interface to write just the state for the fields // being detached if (externalizeDetached()) { try { addDetachExternalize(parentDetachable, _meta.usesDetachedState() != Boolean.FALSE); } catch (NoSuchMethodException nsme) { throw new GeneralException(nsme); } } } /** * Add the fields to hold detached state and their accessor methods. * * @param impl whether to fully implement detach state functionality */ private void addDetachedStateMethods(boolean impl) { Field detachField = _meta.getDetachedStateField(); String name = null; Class declarer = null; final ClassNode classNode = pc.getClassNode(); if (impl && detachField == null) { name = PRE + "DetachedState"; FieldNode field = new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, name, AsmHelper.TYPE_OBJECT.getDescriptor(), null, null); classNode.fields.add(field); } else if (impl) { name = detachField.getName(); declarer = detachField.getDeclaringClass(); } // public Object pcGetDetachedState () MethodNode getDetachedStateMeth = new MethodNode(Opcodes.ACC_PUBLIC, PRE + "GetDetachedState", Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT), null, null); classNode.methods.add(getDetachedStateMeth); if (impl) { // return pcDetachedState; getDetachedStateMeth.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this getfield(classNode, getDetachedStateMeth.instructions, declarer, name, Object.class); } else { getDetachedStateMeth.instructions.add(new InsnNode(Opcodes.ACONST_NULL)); } getDetachedStateMeth.instructions.add(new InsnNode(Opcodes.ARETURN)); // public void pcSetDetachedState (Object state) MethodNode setDetachedStateMeth = new MethodNode(Opcodes.ACC_PUBLIC, PRE + "SetDetachedState", Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT), null, null); classNode.methods.add(setDetachedStateMeth); if (impl) { // pcDetachedState = state; setDetachedStateMeth.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this setDetachedStateMeth.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st parameter putfield(classNode, setDetachedStateMeth.instructions, declarer, name, Object.class); } setDetachedStateMeth.instructions.add(new InsnNode(Opcodes.RETURN)); } /** * Adds to code the instructions to get field * attrName declared in type declarer * onto the top of the stack. *

* The instance to access must already be on the top of the * stack when this is invoked. */ private void getfield(ClassNode classNode, InsnList instructions, Class declarer, String attrName, Class fieldType) { // first, see if we can convert the attribute name to a field name String fieldName = toBackingFieldName(attrName); FieldNode field = findField(classNode, declarer, fieldName); if (getCreateSubclass() && (field == null || !((field.access & Opcodes.ACC_PUBLIC) > 0))) { // we're creating the subclass, not redefining the user type. // Reflection.getXXX(this, Reflection.findField(...)); // Reflection.findField(declarer, fieldName, true); instructions.add(AsmHelper.getLoadConstantInsn(declarer)); instructions.add(AsmHelper.getLoadConstantInsn(fieldName)); instructions.add(new InsnNode(Opcodes.ICONST_1)); // true instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Reflection.class), "findField", Type.getMethodDescriptor(Type.getType(Field.class), Type.getType(Class.class), Type.getType(String.class), Type.BOOLEAN_TYPE))); // Reflection.getXXX(this, Field as stackparam); try { final Method getterMethod = getReflectionGetterMethod(fieldType, Field.class); instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Reflection.class), getterMethod.getName(), Type.getMethodDescriptor(getterMethod))); } catch (NoSuchMethodException e) { // should never happen throw new InternalException(e); } if (!fieldType.isPrimitive() && fieldType != Object.class) { instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(fieldType))); } } else { String owner = declarer != null ? Type.getInternalName(declarer) : classNode.name; instructions.add(new FieldInsnNode(Opcodes.GETFIELD, owner, fieldName, Type.getDescriptor(fieldType))); } } private FieldNode findField(ClassNode classNode, Class clazz, String fieldName) { if (classNode != null) { final Optional field = classNode.fields.stream() .filter(f -> f.name.equals(fieldName)) .findFirst(); if (field.isPresent()) { return field.get(); } } if (clazz == null) { return null; } try { final Field field = clazz.getDeclaredField(fieldName); return new FieldNode(Opcodes.ACC_PRIVATE, field.getName(), Type.getDescriptor(field.getType()), null, null); } catch (NoSuchFieldException e) { if (clazz.getSuperclass() != Object.class) { return findField(null, clazz.getSuperclass(), fieldName); } } return null; } /** * Adds to code the instructions to set field * attrName declared in type declarer * to the value of type fieldType on the top of the stack. *

* When this method is invoked, the value to load must * already be on the top of the stack, * and the instance to load into must be second. * * @param classNode * @param declarer internal class name (org/bla/..) which contains the field */ private void putfield(ClassNode classNode, InsnList instructions, Class declarer, String attrName, Class fieldType) { String fieldName = toBackingFieldName(attrName); if (getRedefine() || getCreateSubclass()) { // Reflection.set(this, Reflection.findField(...), value); // Reflection.findField(declarer, fieldName, true); instructions.add(AsmHelper.getLoadConstantInsn(declarer)); instructions.add(AsmHelper.getLoadConstantInsn(fieldName)); instructions.add(new InsnNode(Opcodes.ICONST_1)); // true instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Reflection.class), "findField", Type.getMethodDescriptor(Type.getType(Field.class), Type.getType(Class.class), Type.getType(String.class), Type.BOOLEAN_TYPE))); // Reflection.set(stackvalue, stackvalue, field); instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Reflection.class), "set", Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT, fieldType.isPrimitive() ? Type.getType(fieldType) : AsmHelper.TYPE_OBJECT, Type.getType(Field.class)))); } else { String owner = declarer != null ? Type.getInternalName(declarer) : classNode.name; instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, owner, fieldName, Type.getDescriptor(fieldType))); } } /** * If using property access, see if there is a different backing field * name for the persistent attribute name. */ private String toBackingFieldName(String name) { // meta is null when enhancing persistence-aware FieldMetaData fmd = _meta == null ? null : _meta.getField(name); if (_meta != null && isPropertyAccess(fmd) && _attrsToFields != null && _attrsToFields.containsKey(name)) { name = (String) _attrsToFields.get(name); } return name; } /** * If using property access, see if there is a different persistent * attribute name for the backing field name. */ private String fromBackingFieldName(String name) { // meta is null when enhancing persistence-aware FieldMetaData fmd = _meta == null ? null : _meta.getField(name); if (_meta != null && isPropertyAccess(fmd) && _fieldsToAttrs != null && _fieldsToAttrs.containsKey(name)) { return (String) _fieldsToAttrs.get(name); } else { return name; } } /** * Implement the externalizable interface to detach on serialize. */ private void addDetachExternalize(boolean parentDetachable, boolean detachedState) throws NoSuchMethodException { // ensure that the declared default constructor is public // for externalization final MethodNode ctNode = pc.getClassNode().methods.stream() .filter(m -> m.name.equals("") && m.desc.equals("()V")) .findAny() .get(); if ((ctNode.access & Opcodes.ACC_PUBLIC) == 0) { if (_log.isWarnEnabled()) { _log.warn(_loc.get("enhance-defcons-extern", _meta.getDescribedType())); } ctNode.access = ctNode.access & ~Opcodes.ACC_PRIVATE & ~Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC; } // declare externalizable interface if (!Externalizable.class.isAssignableFrom(_meta.getDescribedType())) { pc.declareInterface(Externalizable.class); } // make sure the user doesn't already have custom externalization or // serialization methods String readObjectDesc = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectInputStream.class)); boolean hasReadObject = managedType.getClassNode().methods.stream() .anyMatch(m -> m.name.equals("readObject") && m.desc.equals(readObjectDesc)); String writeObjectDesc = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectOutput.class)); boolean hasWriteObject = managedType.getClassNode().methods.stream() .anyMatch(m -> m.name.equals("writeObject") && m.desc.equals(writeObjectDesc)); if (hasReadObject || hasWriteObject) { throw new UserException(_loc.get("detach-custom-ser", _meta)); } String readExternalDesc = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectInput.class)); boolean hasReadExternal = managedType.getClassNode().methods.stream() .anyMatch(m -> m.name.equals("readExternal") && m.desc.equals(readExternalDesc)); String writeExternalDesc = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectInput.class)); boolean hasWriteExternal = managedType.getClassNode().methods.stream() .anyMatch(m -> m.name.equals("writeExternal") && m.desc.equals(writeExternalDesc)); if (hasReadExternal || hasWriteExternal) { throw new UserException(_loc.get("detach-custom-extern", _meta)); } // create list of all unmanaged serializable fields final List fields = managedType.getClassNode().fields; List unmgd = new ArrayList(fields.size()); for (FieldNode field : fields) { if ((field.access & (Opcodes.ACC_TRANSIENT | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL)) == 0 && !field.name.startsWith(PRE) && _meta.getDeclaredField(field.name) == null) { unmgd.add(field); } } addReadExternal(parentDetachable, detachedState); addReadUnmanaged(unmgd, parentDetachable); addWriteExternal(parentDetachable, detachedState); addWriteUnmanaged(unmgd, parentDetachable); } /** * Add custom readExternal method. */ private void addReadExternal(boolean parentDetachable, boolean detachedState) throws NoSuchMethodException { final String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectInput.class)); MethodNode readExternalMeth = new MethodNode(Opcodes.ACC_PUBLIC, "readExternal", methodDescriptor, null, new String[]{Type.getInternalName(IOException.class), Type.getInternalName(ClassNotFoundException.class)}); final ClassNode classNode = pc.getClassNode(); classNode.methods.add(readExternalMeth); InsnList instructions = readExternalMeth.instructions; // super.readExternal (in); // not sure if this works: this is depending on the order of the enhancement! // if the subclass gets enhanced first, then the superclass misses // the Externalizable at this point! Class sup = _meta.getDescribedType().getSuperclass(); if (!parentDetachable && Externalizable.class.isAssignableFrom(sup)) { instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(sup), "readExternal", methodDescriptor)); } // readUnmanaged (in); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(getType(_meta)), PRE + "ReadUnmanaged", methodDescriptor)); if (detachedState) { // pcSetDetachedState (in.readObject ()); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(ObjectInput.class), "readObject", Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT))); instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + "SetDetachedState", Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT))); // pcReplaceStateManager ((StateManager) in.readObject ()); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(ObjectInput.class), "readObject", Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT))); instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(StateManager.class))); instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + "ReplaceStateManager", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(StateManager.class)))); } addReadExternalFields(); // readExternalFields(in.readObject ()); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, "readExternalFields", methodDescriptor)); instructions.add(new InsnNode(Opcodes.RETURN)); } private void addReadExternalFields() throws NoSuchMethodException { final String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectInput.class)); MethodNode readExternalMeth = new MethodNode(Opcodes.ACC_PROTECTED, "readExternalFields", methodDescriptor, null, new String[]{Type.getInternalName(IOException.class), Type.getInternalName(ClassNotFoundException.class)}); final ClassNode classNode = pc.getClassNode(); classNode.methods.add(readExternalMeth); InsnList instructions = readExternalMeth.instructions; Class sup = _meta.getPCSuperclass(); if (sup != null) { //add a call to super.readExternalFields() instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(sup), "readExternalFields", methodDescriptor)); } // read managed fields FieldMetaData[] fmds = _meta.getDeclaredFields(); for (FieldMetaData fmd : fmds) { if (!fmd.isTransient()) { readExternal(classNode, instructions, fmd.getName(), Type.getType(fmd.getDeclaredType()), fmd); } } instructions.add(new InsnNode(Opcodes.RETURN)); } /** * Read unmanaged fields from the stream (pcReadUnmanaged). */ private void addReadUnmanaged(List unmgd, boolean parentDetachable) throws NoSuchMethodException { final String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectInput.class)); MethodNode readUnmanagedMeth = new MethodNode(Opcodes.ACC_PROTECTED, PRE + "ReadUnmanaged", methodDescriptor, null, new String[]{Type.getInternalName(IOException.class), Type.getInternalName(ClassNotFoundException.class)}); final ClassNode classNode = pc.getClassNode(); classNode.methods.add(readUnmanagedMeth); InsnList instructions = readUnmanagedMeth.instructions; // super.readUnmanaged (in); if (parentDetachable) { instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(getType(_meta.getPCSuperclassMetaData())), PRE + "ReadUnmanaged", methodDescriptor)); } // read declared unmanaged serializable fields for (FieldNode field : unmgd) { readExternal(classNode, instructions, field.name, Type.getType(field.desc), null); } instructions.add(new InsnNode(Opcodes.RETURN)); } /** * Helper method to read a field from an externalization input stream. */ private void readExternal(ClassNode classNode, InsnList instructions, String fieldName, Type fieldType, FieldMetaData fmd) throws NoSuchMethodException { if (fieldType == null) { fieldType = Type.getType(fmd.getDeclaredType()); } String typeName = fieldType.getClassName(); boolean isPrimitive = fieldType.getSort() != Type.OBJECT && fieldType.getSort() != Type.ARRAY; String methName; if (isPrimitive) { methName = typeName.substring(0, 1).toUpperCase(Locale.ENGLISH) + typeName.substring(1); methName = "read" + methName; } else { methName = "readObject"; } // = in.read (); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param Type retType = isPrimitive ? fieldType : AsmHelper.TYPE_OBJECT; instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(ObjectInput.class), methName, Type.getMethodDescriptor(retType))); if (!isPrimitive && !fieldType.getClassName().equals(Object.class.getName())) { instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, fieldType.getInternalName())); } if (fmd == null) { Class type = AsmHelper.getDescribedClass(pc.getClassLoader(), fieldType.getDescriptor()); if (type == null) { throw new RuntimeException("Cannot Load class " + fieldType.getDescriptor()); } putfield(classNode, instructions, null, fieldName, type); } else { addSetManagedValueCode(classNode, instructions, fmd); switch (fmd.getDeclaredTypeCode()) { case JavaTypes.DATE: case JavaTypes.ARRAY: case JavaTypes.COLLECTION: case JavaTypes.MAP: case JavaTypes.OBJECT: case JavaTypes.CALENDAR: // if (sm != null) // sm.proxyDetachedDeserialized (); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); LabelNode lblEndIf = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFNULL, lblEndIf)); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); instructions.add(AsmHelper.getLoadConstantInsn(fmd.getIndex())); instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(SMTYPE), "proxyDetachedDeserialized", Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE))); instructions.add(lblEndIf); } } } /** * Add custom writeExternal method. */ private void addWriteExternal(boolean parentDetachable, boolean detachedState) throws NoSuchMethodException { final String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectOutput.class)); MethodNode writeExternalMeth = new MethodNode(Opcodes.ACC_PUBLIC, "writeExternal", methodDescriptor, null, new String[]{Type.getInternalName(IOException.class)}); final ClassNode classNode = pc.getClassNode(); classNode.methods.add(writeExternalMeth); InsnList instructions = writeExternalMeth.instructions; // super.writeExternal (out); Class sup = getType(_meta).getSuperclass(); if (!parentDetachable && Externalizable.class.isAssignableFrom(sup)) { instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(sup), "writeExternal", methodDescriptor)); } // writeUnmanaged (out); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(getType(_meta)), PRE + "WriteUnmanaged", methodDescriptor)); LabelNode go2 = null; if (detachedState) { // if (sm != null) // if (sm.writeDetached (out)) // return; instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); LabelNode endIfNull = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFNULL, endIfNull)); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE))); instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(SMTYPE), "writeDetached", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(ObjectOutput.class)))); go2 = new LabelNode(); instructions.add(new JumpInsnNode(Opcodes.IFEQ, go2)); instructions.add(new InsnNode(Opcodes.RETURN)); // else // out.writeObject (pcGetDetachedState ()); instructions.add(endIfNull); instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + "GetDetachedState", Type.getMethodDescriptor(AsmHelper.TYPE_OBJECT))); instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(ObjectOutput.class), "writeObject", Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT))); instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(AsmHelper.getLoadConstantInsn(null)); instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(ObjectOutput.class), "writeObject", Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT))); } if (go2 != null) { instructions.add(go2); } addWriteExternalFields(); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, "writeExternalFields", methodDescriptor)); // return instructions.add(new InsnNode(Opcodes.RETURN)); } private void addWriteExternalFields() throws NoSuchMethodException { final String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectOutput.class)); MethodNode writeExternalFieldsMeth = new MethodNode(Opcodes.ACC_PROTECTED, "writeExternalFields", methodDescriptor, null, new String[]{Type.getInternalName(IOException.class)}); final ClassNode classNode = pc.getClassNode(); classNode.methods.add(writeExternalFieldsMeth); InsnList instructions = writeExternalFieldsMeth.instructions; Class sup = _meta.getPCSuperclass(); if (sup != null) { // add a call to super.writeExternalFields() instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(sup), "writeExternalFields", methodDescriptor)); } FieldMetaData[] fmds = _meta.getDeclaredFields(); for (FieldMetaData fmd : fmds) { if (!fmd.isTransient()) { writeExternal(classNode, instructions, fmd.getName(), Type.getType(fmd.getDeclaredType()), fmd); } } // return instructions.add(new InsnNode(Opcodes.RETURN)); } /** * Write unmanaged fields to the stream (pcWriteUnmanaged). */ private void addWriteUnmanaged(List unmgd, boolean parentDetachable) throws NoSuchMethodException { final String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectOutput.class)); MethodNode writeUnmanagedMeth = new MethodNode(Opcodes.ACC_PROTECTED, PRE + "WriteUnmanaged", methodDescriptor, null, new String[]{Type.getInternalName(IOException.class)}); final ClassNode classNode = pc.getClassNode(); classNode.methods.add(writeUnmanagedMeth); InsnList instructions = writeUnmanagedMeth.instructions; // super.writeUnmanaged (out); if (parentDetachable) { instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(getType(_meta.getPCSuperclassMetaData())), PRE + "WriteUnmanaged", methodDescriptor)); } // write declared unmanaged serializable fields for (FieldNode field : unmgd) { writeExternal(classNode, instructions, field.name, Type.getType(field.desc), null); } instructions.add(new InsnNode(Opcodes.RETURN)); } /** * Helper method to write a field to an externalization output stream. */ private void writeExternal(ClassNode classNode, InsnList instructions, String fieldName, Type fieldType, FieldMetaData fmd) throws NoSuchMethodException { String typeName = fieldType.getClassName(); boolean isPrimitive = fieldType.getSort() != Type.OBJECT && fieldType.getSort() != Type.ARRAY; String methName; if (isPrimitive) { methName = typeName.substring(0, 1).toUpperCase(Locale.ENGLISH) + typeName.substring(1); methName = "write" + methName; } else { methName = "writeObject"; } // out.write (); instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this if (fmd == null) { Class type = AsmHelper.getDescribedClass(pc.getClassLoader(), fieldType.getDescriptor()); getfield(classNode, instructions, null, fieldName, type); } else { addGetManagedValueCode(classNode, instructions, fmd, true); } String mdesc; if (fieldType.getSort() == Type.BYTE || fieldType.getSort() == Type.CHAR || fieldType.getSort() == Type.SHORT) { mdesc = Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE); } else if (!isPrimitive) { mdesc = Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.TYPE_OBJECT); } else { mdesc = Type.getMethodDescriptor(Type.VOID_TYPE, fieldType); } instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(ObjectOutput.class), methName, mdesc)); } /** * Load the field value specified by fmd onto the stack. * Before this method is called, the object that the data should be loaded * from will be on the top of the stack. * * @param fromSameClass if true, then fmd is * being loaded from an instance of the same class as the current execution * context. If false, then the instance on the top of the stack * might be a superclass of the current execution context's 'this' instance. */ private void addGetManagedValueCode(ClassNode classNode, InsnList instructions, FieldMetaData fmd, boolean fromSameClass) { // if redefining, then we must always reflect (or access the field // directly if accessible), since the redefined methods will always // trigger method calls to StateManager, even from internal direct- // access usage. We could work around this by not redefining, and // just do a subclass approach instead. But this is not a good option, // since it would sacrifice lazy loading and efficient dirty tracking. if (getRedefine() || isFieldAccess(fmd)) { getfield(classNode, instructions, getType(_meta), fmd.getName(), fmd.getDeclaredType()); } else if (getCreateSubclass()) { // property access, and we're not redefining. If we're operating // on an instance that is definitely the same type as 'this', then // call superclass method to bypass tracking. Otherwise, reflect // to both bypass tracking and avoid class verification errors. if (fromSameClass) { Method meth = (Method) fmd.getBackingMember(); instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(meth.getDeclaringClass()), meth.getName(), Type.getMethodDescriptor(meth))); } else { getfield(classNode, instructions, getType(_meta), fmd.getName(), fmd.getDeclaredType()); } } else { // regular enhancement + property access Method meth = (Method) fmd.getBackingMember(); instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + meth.getName(), Type.getMethodDescriptor(meth))); } } /** * Store the given value into the field value specified * by fmd. Before this method is called, the data to load will * be on the top of the stack and the object that the data should be loaded * into will be second in the stack. */ private InsnList getSetValueInsns(ClassNode classNode, FieldMetaData fmd, Object value) { InsnList instructions = new InsnList(); if (value == null) { instructions.add(new InsnNode(Opcodes.ACONST_NULL)); } else { instructions.add(AsmHelper.getLoadConstantInsn(value)); } // if redefining, then we must always reflect (or access the field // directly if accessible), since the redefined methods will always // trigger method calls to StateManager, even from internal direct- // access usage. We could work around this by not redefining, and // just do a subclass approach instead. But this is not a good option, // since it would sacrifice lazy loading and efficient dirty tracking. if (getRedefine() || isFieldAccess(fmd)) { putfield(classNode, instructions, fmd.getDeclaringType(), fmd.getName(), fmd.getDeclaredType()); } else if (getCreateSubclass()) { // property access, and we're not redefining. invoke the // superclass method to bypass tracking. instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, managedType.getClassNode().name, getSetterName(fmd), Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(fmd.getDeclaredType())))); } else { // regular enhancement + property access instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + getSetterName(fmd), Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(fmd.getDeclaredType())))); } return instructions; } /** * Store the value at the top of the stack into the field value specified * by fmd. Before this method is called, the data to load will * be on the top of the stack and the object that the data should be loaded * into will be second in the stack. */ private void addSetManagedValueCode(ClassNode classNode, InsnList instructions, FieldMetaData fmd) { // if redefining, then we must always reflect (or access the field // directly if accessible), since the redefined methods will always // trigger method calls to StateManager, even from internal direct- // access usage. We could work around this by not redefining, and // just do a subclass approach instead. But this is not a good option, // since it would sacrifice lazy loading and efficient dirty tracking. if (getRedefine() || isFieldAccess(fmd)) { putfield(classNode, instructions, getType(_meta), fmd.getName(), fmd.getDeclaredType()); } else if (getCreateSubclass()) { // property access, and we're not redefining. invoke the // superclass method to bypass tracking. instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, classNode.superName, getSetterName(fmd), Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(fmd.getDeclaredType())))); } else { // regular enhancement + property access instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, PRE + getSetterName(fmd), Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(fmd.getDeclaredType())))); } } /** * Add the {@link Instruction}s to load the instance to modify onto the * stack, and return it. If forStatic is set, then * code is in an accessor method or another static method; * otherwise, it is in one of the PC-specified methods. * * @return instruction to load the persistence context to the stack. */ private AbstractInsnNode loadManagedInstance() { // 1st method parameter is position 0 on the stack for a STATIC method // or use the this* for a non-STATIC method // In both cases we end up with ALOAD_0, while it essentially does different things. return new VarInsnNode(Opcodes.ALOAD, 0); } /** * Affirms if the given class is using field-based access. */ boolean isPropertyAccess(ClassMetaData meta) { return meta != null && (meta.isMixedAccess() || AccessCode.isProperty(meta.getAccessType())); } /** * Affirms if the given field is using field-based access. */ boolean isPropertyAccess(FieldMetaData fmd) { return fmd != null && AccessCode.isProperty(fmd.getAccessType()); } /** * Affirms if the given field is using method-based access. */ boolean isFieldAccess(FieldMetaData fmd) { return fmd != null && AccessCode.isField(fmd.getAccessType()); } /** * Create the generated getter {@link MethodNode} for fmd. The * calling environment will then populate this method's code block. */ private MethodNode createGetMethod(ClassNode classNode, FieldMetaData fmd) { if (isFieldAccess(fmd)) { // static pcGet (XXX inst) final FieldNode field = classNode.fields.stream() .filter(f -> f.name.equals(fmd.getName())) .findFirst() .get(); MethodNode getter = new MethodNode((field.access & ~Opcodes.ACC_TRANSIENT & ~Opcodes.ACC_VOLATILE) | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, PRE + "Get" + fmd.getName(), Type.getMethodDescriptor(Type.getType(fmd.getDeclaredType()), Type.getObjectType(classNode.name)), null, null); return getter; } // property access: // change the user's getter method to a new name and create a new method with the old name Method meth = (Method) fmd.getBackingMember(); MethodNode getter = AsmHelper.getMethodNode(classNode, meth).get(); // and a new method which replaces the old one MethodNode newGetter = new MethodNode(getter.access, meth.getName(), Type.getMethodDescriptor(meth), null, null); getter.name = PRE + meth.getName(); getter.access = (getter.access & ~Opcodes.ACC_PUBLIC & ~Opcodes.ACC_PRIVATE) | Opcodes.ACC_PROTECTED; moveAnnotations(getter, newGetter); // copy over all ParemeterizedType info if any. newGetter.signature = getter.signature; return newGetter; } /** * move the annotations over from the original method to the other method */ private void moveAnnotations(MethodNode from, MethodNode to) { if (from.visibleAnnotations != null) { if (to.visibleAnnotations == null) { to.visibleAnnotations = new ArrayList<>(); } to.visibleAnnotations.addAll(from.visibleAnnotations); from.visibleAnnotations.clear(); } } /** * Create the generated setter {@link MethodNode} for fmd. The * calling environment will then populate this method's code block. */ private MethodNode createSetMethod(ClassNode classNode, FieldMetaData fmd) { if (isFieldAccess(fmd)) { // static void pcSet (XXX inst, value) final FieldNode field = classNode.fields.stream() .filter(f -> f.name.equals(fmd.getName())) .findFirst() .get(); MethodNode setter = new MethodNode((field.access & ~Opcodes.ACC_TRANSIENT & ~Opcodes.ACC_VOLATILE) | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, PRE + "Set" + fmd.getName(), Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(getType(_meta)), Type.getType(fmd.getDeclaredType())), null, null); return setter; } // property access: // change the user's setter method to a new name and create a new method with the old name final MethodNode setter = AsmHelper.getMethodNode(classNode, getSetterName(fmd), void.class, fmd.getDeclaredType()).get(); final String setterName = setter.name; // and a new method which replaces the old one MethodNode newSetter = new MethodNode(setter.access, setterName, setter.desc, null, null); setter.name = PRE + setterName; setter.access = (setter.access & ~Opcodes.ACC_PRIVATE & ~Opcodes.ACC_PUBLIC) | Opcodes.ACC_PROTECTED; moveAnnotations(setter, newSetter); // copy over all ParemeterizedType info if any. newSetter.signature = setter.signature; return newSetter; } private void addGetEnhancementContractVersionMethod(ClassNodeTracker cnt) { MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, PRE + "GetEnhancementContractVersion", Type.getMethodDescriptor(Type.INT_TYPE), null, null); methodNode.instructions.add(AsmHelper.getLoadConstantInsn(ENHANCER_VERSION)); methodNode.instructions.add(new InsnNode(Opcodes.IRETURN)); cnt.getClassNode().methods.add(methodNode); } /** * Return the concrete type for the given class, i.e. impl for managed * interfaces */ public Class getType(ClassMetaData meta) { if (meta.getInterfaceImpl() != null) { return meta.getInterfaceImpl(); } return meta.getDescribedType(); } /** * Usage: java org.apache.openjpa.enhance.PCEnhancer [option]* * <class name | .java file | .class file | .jdo file>+ * Where the following options are recognized. *

    *
  • -properties/-p <properties file>: The path to a OpenJPA * properties file containing information as outlined in * {@link org.apache.openjpa.lib.conf.Configuration}; optional.
  • *
  • -<property name> <property value>: All bean * properties of the standard OpenJPA {@link OpenJPAConfiguration} can be * set by using their names and supplying a value; for example: *
  • -directory/-d <build directory>: The path to the base * directory where enhanced classes are stored. By default, the * enhancer overwrites the original .class file with the enhanced * version. Use this option to store the generated .class file in * another directory. The package structure will be created beneath * the given directory.
  • *
  • -addDefaultConstructor/-adc [true/t | false/f]: Whether to * add a default constructor to persistent classes missing one, as * opposed to throwing an exception. Defaults to true.
  • *
  • -tmpClassLoader/-tcl [true/t | false/f]: Whether to * load the pre-enhanced classes using a temporary class loader. * Defaults to true. Set this to false when attempting to debug * class loading errors.
  • *
  • -enforcePropertyRestrictions/-epr [true/t | false/f]: * Whether to throw an exception if a PROPERTY access entity appears * to be violating standard property restrictions. Defaults to false.
  • *
* Each additional argument can be either the full class name of the * type to enhance, the path to the .java file for the type, the path to * the .class file for the type, or the path to a .jdo file listing one * or more types to enhance. * If the type being enhanced has metadata, it will be enhanced as a * persistence capable class. If not, it will be considered a persistence * aware class, and all access to fields of persistence capable classes * will be replaced by the appropriate get/set method. If the type * explicitly declares the persistence-capable interface, it will * not be enhanced. Thus, it is safe to invoke the enhancer on classes * that are already enhanced. */ public static void main(String[] args) { Options opts = new Options(); args = opts.setFromCmdLine(args); if (!run(args, opts)) { // START - ALLOW PRINT STATEMENTS System.err.println(_loc.get("enhance-usage")); // STOP - ALLOW PRINT STATEMENTS } } /** * Run the tool. Returns false if invalid options given. Runs against all * the persistence units defined in the resource to parse. */ public static boolean run(final String[] args, Options opts) { return Configurations.runAgainstAllAnchors(opts, opts1 -> { OpenJPAConfiguration conf = new OpenJPAConfigurationImpl(); try { return run(conf, args, opts1); } finally { conf.close(); } }); } /** * Run the tool. Returns false if invalid options given. */ public static boolean run(OpenJPAConfiguration conf, String[] args, Options opts) throws IOException { Flags flags = new Flags(); flags.directory = Files.getFile(opts.removeProperty("directory", "d", null), null); flags.addDefaultConstructor = opts.removeBooleanProperty ("addDefaultConstructor", "adc", flags.addDefaultConstructor); flags.tmpClassLoader = opts.removeBooleanProperty ("tmpClassLoader", "tcl", flags.tmpClassLoader); flags.enforcePropertyRestrictions = opts.removeBooleanProperty ("enforcePropertyRestrictions", "epr", flags.enforcePropertyRestrictions); // for unit testing BytecodeWriter writer = (BytecodeWriter) opts.get( PCEnhancer.class.getName() + "#bytecodeWriter"); Configurations.populateConfiguration(conf, opts); return run(conf, args, flags, null, writer, null); } /** * Enhance the given classes. */ public static boolean run(OpenJPAConfiguration conf, String[] args, Flags flags, MetaDataRepository repos, BytecodeWriter writer, ClassLoader loader) throws IOException { if (loader == null) { loader = conf.getClassResolverInstance(). getClassLoader(PCEnhancer.class, null); } if (flags.tmpClassLoader) { loader = AccessController.doPrivileged(J2DoPrivHelper .newTemporaryClassLoaderAction(loader)); } if (repos == null) { repos = conf.newMetaDataRepositoryInstance(); repos.setSourceMode(MetaDataModes.MODE_META); } Log log = conf.getLog(OpenJPAConfiguration.LOG_TOOL); Collection classes; if (args == null || args.length == 0) { classes = repos.getPersistentTypeNames(true, loader); if (classes == null) { log.warn(_loc.get("no-class-to-enhance")); return false; } } else { ClassArgParser cap = conf.getMetaDataRepositoryInstance(). getMetaDataFactory().newClassArgParser(); cap.setClassLoader(loader); classes = new HashSet(); for (String arg : args) { classes.addAll(Arrays.asList(cap.parseTypes(arg))); } } EnhancementProject project = new EnhancementProject(); ClassNodeTracker cnt; PCEnhancer enhancer; Collection persAwareClasses = new HashSet(); int status; for (Object o : classes) { if (log.isInfoEnabled()) { log.info(_loc.get("enhance-running", o)); } if (o instanceof String) { cnt = project.loadClass((String) o, loader); } else { cnt = project.loadClass((Class) o); } enhancer = new PCEnhancer(conf, cnt, repos, loader); if (writer != null) { enhancer.setBytecodeWriter(writer); } enhancer.setDirectory(flags.directory); enhancer.setAddDefaultConstructor(flags.addDefaultConstructor); status = enhancer.run(); if (status == ENHANCE_NONE) { if (log.isTraceEnabled()) { log.trace(_loc.get("enhance-norun")); } } else if (status == ENHANCE_INTERFACE) { if (log.isTraceEnabled()) { log.trace(_loc.get("enhance-interface")); } } else if (status == ENHANCE_AWARE) { persAwareClasses.add(o); enhancer.record(); } else { enhancer.record(); } project.clear(); } if (log.isInfoEnabled() && !persAwareClasses.isEmpty()) { log.info(_loc.get("pers-aware-classes", persAwareClasses.size(), persAwareClasses)); } return true; } /** * Run flags. */ public static class Flags { public File directory = null; public boolean addDefaultConstructor = true; public boolean tmpClassLoader = true; public boolean enforcePropertyRestrictions = false; } /** * Plugin interface for additional enhancement. */ public interface AuxiliaryEnhancer { void run(ClassNode classNode, ClassMetaData meta); boolean skipEnhance(MethodNode m); } private void addGetIDOwningClass() { MethodNode idOCMeth = new MethodNode(Opcodes.ACC_PUBLIC, PRE + "GetIDOwningClass", Type.getMethodDescriptor(Type.getType(Class.class)), null, null); pc.getClassNode().methods.add(idOCMeth); idOCMeth.instructions.add(AsmHelper.getLoadConstantInsn(getType(_meta))); idOCMeth.instructions.add(new InsnNode(Opcodes.ARETURN)); } /** * This static public worker method detects and logs any Entities that may have been enhanced at build time by * a version of the enhancer that is older than the current version. * * @param cls - A non-null Class implementing org.apache.openjpa.enhance.PersistenceCapable. * @param log - A non-null org.apache.openjpa.lib.log.Log. * @return true if the provided Class is down level from the current PCEnhancer.ENHANCER_VERSION. False * otherwise. * @throws - IllegalStateException if cls doesn't implement org.apache.openjpa.enhance.PersistenceCapable. */ public static boolean checkEnhancementLevel(Class cls, Log log) { if (cls == null || log == null) { return false; } PersistenceCapable pc = PCRegistry.newInstance(cls, null, false); if (pc == null) { return false; } if (pc.pcGetEnhancementContractVersion() < PCEnhancer.ENHANCER_VERSION) { log.info(_loc.get("down-level-enhanced-entity", new Object[]{cls.getName(), pc.pcGetEnhancementContractVersion(), PCEnhancer.ENHANCER_VERSION})); return true; } return false; } /** * Read the optimizedIdCopy value from the config (if available) */ private void configureOptimizeIdCopy() { if (_repos != null && _repos.getConfiguration() != null) { _optimizeIdCopy = _repos.getConfiguration().getOptimizeIdCopy(); } } /* * Cycles through all primary keys verifying whether they can and should * be used for faster oid copy. The field must be private and must * not have a public setter. If this is the case, the list of pk fields is * returned. If not, returns null. */ private ArrayList optimizeIdCopy(Class oidType, FieldMetaData[] fmds) { // collect all object id fields and verify they // a) have a private field // b) do not have a public setter ArrayList pkFields = new ArrayList<>(); // build list of primary key fields for (int i = 0; i < fmds.length; i++) { if (!fmds[i].isPrimaryKey()) { continue; } // optimizing copy with PC type not (yet) supported if (fmds[i].getDeclaredTypeCode() == JavaTypes.PC) { return null; } String name = fmds[i].getName(); Field fld = Reflection.findField(oidType, name, false); if (fld == null || Modifier.isPublic(fld.getModifiers())) { return null; } Method setter = Reflection.findSetter(oidType, name, false); if (setter == null || !Modifier.isPublic(setter.getModifiers())) { pkFields.add(i); } else { return null; } } return pkFields.size() > 0 ? pkFields : null; } /* * Cycles through all constructors of an IdClass and examines the instructions to find * a matching constructor for the provided pk fields. If a match is found, it returns * the order (relative to the field metadata) of the constructor parameters. If a match * is not found, returns null. * * We use byte code analysis to find the fields the ct works on. */ private int[] getIdClassConstructorParmOrder(Class oidType, List pkfields, FieldMetaData[] fmds) { final ClassNode classNode = AsmHelper.readClassNode(oidType); final List cts = classNode.methods.stream() .filter(m -> "".equals(m.name)) .collect(Collectors.toList()); if (cts.isEmpty()) { return null; } int[] parmOrder = new int[pkfields.size()]; for (MethodNode ct : cts) { if ((ct.access & Opcodes.ACC_PUBLIC) == 0) { // ignore non public constructors continue; } Type[] argTypes = Type.getArgumentTypes(ct.desc); // make sure the constructors have the same # of parms as // the number of pk fields if (listSize(pkfields) != argTypes.length) { continue; } int parmOrderIndex = 0; AbstractInsnNode insn = ct.instructions.getFirst(); // skip to the next PUTFIELD instruction while ((insn = searchNextInstruction(insn, i -> i.getOpcode() == Opcodes.PUTFIELD)) != null) { FieldInsnNode putField = (FieldInsnNode) insn; for (int i = 0; i < pkfields.size(); i++) { int fieldNum = pkfields.get(i); // Compare the field being set with the current pk field String parmName = fmds[fieldNum].getName(); Class parmType = fmds[fieldNum].getType(); if (parmName.equals(putField.name)) { // backup and examine the load instruction parm if (AsmHelper.isLoadInsn(insn.getPrevious())) { // Get the local index from the instruction. This will be the index // of the constructor parameter. must be less than or equal to the // max parm index to prevent from picking up locals that could have // been produced within the constructor. Also make sure the parm type // matches the fmd type VarInsnNode loadInsn = (VarInsnNode) insn.getPrevious(); int parm = AsmHelper.getParamIndex(ct, loadInsn.var); if (parm < pkfields.size() && argTypes[parm].equals(Type.getType(parmType))) { parmOrder[parmOrderIndex] = fieldNum; parmOrderIndex++; } } else { // Some other instruction found. can't make a determination of which local/parm // is being used on the putfield. break; } } } insn = insn.getNext(); } if (parmOrderIndex == pkfields.size()) { return parmOrder; } } return null; } private int listSize(Collection coll) { return coll == null ? 0 : coll.size(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy