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

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

There is a newer version: 4.0.1
Show 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.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.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.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.Properties;
import java.util.Set;

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.lib.util.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 serp.bytecode.BCClass;
import serp.bytecode.BCField;
import serp.bytecode.BCMethod;
import serp.bytecode.ClassInstruction;
import serp.bytecode.Code;
import serp.bytecode.Constants;
import serp.bytecode.Exceptions;
import serp.bytecode.FieldInstruction;
import serp.bytecode.GetFieldInstruction;
import serp.bytecode.IfInstruction;
import serp.bytecode.Instruction;
import serp.bytecode.JumpInstruction;
import serp.bytecode.LoadInstruction;
import serp.bytecode.LookupSwitchInstruction;
import serp.bytecode.MethodInstruction;
import serp.bytecode.Project;
import serp.bytecode.PutFieldInstruction;
import serp.bytecode.TableSwitchInstruction;

/**
 * Bytecode enhancer used to enhance persistent classes from metadata. The
 * enhancer must be invoked on all persistence-capable and persistence aware
 * classes.
 *
 * @author Abe White
 */
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;

    boolean _addVersionInitFlag = true;

    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 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 String REDEFINED_ATTRIBUTE
        = PCEnhancer.class.getName() + "#redefined-type";

    private static final AuxiliaryEnhancer[] _auxEnhancers;
    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[auxEnhancers.size()]);

        int rev = 0;
        Properties revisionProps = new Properties();
        try {
            InputStream in = PCEnhancer.class.getResourceAsStream("/META-INF/org.apache.openjpa.revision.properties");
            if (in != null) {
                try {
                    revisionProps.load(in);
                } finally {
                    in.close();
                }
            }
            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 BCClass _pc;
    private final BCClass _managedType;
    private final MetaDataRepository _repos;
    private final ClassMetaData _meta;
    private final Log _log;
    private Collection _oids = null;
    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, AccessController.doPrivileged(J2DoPrivHelper
            .loadProjectClassAction(new Project(), 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, AccessController.doPrivileged(J2DoPrivHelper
            .loadProjectClassAction(new Project(), 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, BCClass,
        MetaDataRepository, ClassLoader)} instead.
     */
    @Deprecated
    public PCEnhancer(OpenJPAConfiguration conf, BCClass 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, BCClass type,
        MetaDataRepository repos, ClassLoader loader) {
        _managedType = type;
        _pc = type;

        _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, BCClass type,
        ClassMetaData meta) {
        _managedType = type;
        _pc = type;

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

        _repos = repos;
        _meta = meta;
    }

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

    /**
     * Whether or not 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;
    }

    /**
     * Constructor. Supply configuration, type, and metadata.
     */
    public PCEnhancer(OpenJPAConfiguration conf, BCClass type,
        ClassMetaData meta) {
        this(conf, type, meta.getRepository());
    }

    /**
     * Return the bytecode representation of the persistence-capable class
     * being manipulated.
     */
    public BCClass 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 BCClass 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() {
        Class type = _managedType.getType();
        try {
            // if enum, skip, no need of any meta
            if (_pc.isEnum())
                return ENHANCE_NONE;

            // if managed interface, skip
            if (_pc.isInterface())
                return ENHANCE_INTERFACE;

            // check if already enhanced
            ClassLoader loader = AccessController.doPrivileged(J2DoPrivHelper.getClassLoaderAction(type));
            for (String iface : _managedType.getDeclaredInterfaceNames()) {
                if (iface.equals(PCTYPE.getName())) {
                    if (_log.isTraceEnabled()) {
                        _log.trace(_loc.get("pc-type", type, loader));
                    }
                    return ENHANCE_NONE;
                }
            }
            if (_log.isTraceEnabled()) {
                _log.trace(_loc.get("enhance-start", type, loader));
            }


            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();
                addFields();
                addStaticInitializer();
                addPCMethods();
                addAccessors();
                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",
                type.getName(), e.getMessage()), e);
        }
    }

    private void configureBCs() {
        if (!_bcsConfigured) {
            if (getRedefine()) {
                if (_managedType.getAttribute(REDEFINED_ATTRIBUTE) == null)
                    _managedType.addAttribute(REDEFINED_ATTRIBUTE);
                else
                    _isAlreadyRedefined = true;
            }

            if (getCreateSubclass()) {
                PCSubclassValidator val = new PCSubclassValidator(
                    _meta, _managedType, _log, _fail);
                val.assertCanSubclass();

                _pc = _managedType.getProject().loadClass(
                    toPCSubclassName(_managedType.getType()));
                if (_pc.getSuperclassBC() != _managedType) {
                    _pc.setSuperclass(_managedType);
                    _pc.setAbstract(_managedType.isAbstract());
                    _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);
        if (_oids != null)
            for (Object oid : _oids) {
                record((BCClass) oid);
            }
    }

    /**
     * Write the given class.
     */
    private void record(BCClass bc)
        throws IOException {
        if (_writer != null)
            _writer.write(bc);
        else if (_dir == null)
            AsmAdaptor.write(bc);
        else {
            File dir = Files.getPackageFile(_dir, bc.getPackageName(), true);
            AsmAdaptor.write(bc, new File(dir, bc.getClassName() + ".class"));
        }
    }

    /**
     * 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() {
        FieldMetaData[] fmds;
        if (getCreateSubclass())
            fmds = _meta.getFields();
        else
            fmds = _meta.getDeclaredFields();
        Method meth;
        BCMethod getter, setter;
        BCField 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;
            }

            meth = (Method) fmd.getBackingMember();
            // ##### this will fail if we override and don't call super.
            BCClass declaringType = _managedType.getProject()
                    .loadClass(fmd.getDeclaringType());
            getter = declaringType.getDeclaredMethod(meth.getName(),
                    meth.getParameterTypes());
            if (getter == null) {
                addViolation("property-no-getter", new Object[]{fmd},
                        true);
                continue;
            }
            returned = getReturnedField(getter);
            if (returned != null)
                registerBackingFieldInfo(fmd, getter, returned);

            setter = declaringType.getDeclaredMethod(getSetterName(fmd),
                    new Class[]{fmd.getDeclaredType()});
            if (setter == null) {
                if (returned == null) {
                    addViolation("property-no-setter",
                            new Object[]{fmd}, true);
                    continue;
                }
                else if (!getRedefine()) {
                    // create synthetic setter
                    setter = _managedType.declareMethod(getSetterName(fmd),
                            void.class, new Class[]{fmd.getDeclaredType()});
                    setter.makePrivate();
                    Code code = setter.getCode(true);
                    code.aload().setThis();
                    code.xload().setParam(0);
                    code.putfield().setField(returned);
                    code.vreturn();
                    code.calculateMaxStack();
                    code.calculateMaxLocals();
                }
            }

            if (setter != null)
                assigned = getAssignedField(setter);

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

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

    private void registerBackingFieldInfo(FieldMetaData fmd, BCMethod method,
        BCField 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;
        }

        _pc.declareInterface(AttributeTranslator.class);
        BCMethod method = _pc.declareMethod(PRE + "AttributeIndexToFieldName",
            String.class, new Class[] { int.class });
        method.makePublic();
        Code code = method.getCode(true);

        // switch (val)
        code.iload().setParam(0);
        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
            TableSwitchInstruction tabins = code.tableswitch();

            tabins.setLow(0);
            tabins.setHigh(fmds.length - 1);

            // case i:
            //     return <_attrsToFields.get(fmds[i].getName())>
            for (FieldMetaData fmd : fmds) {
                tabins.addTarget(code.constant().setValue(
                        _attrsToFields.get(fmd.getName())));
                code.areturn();
            }
            // default: throw new IllegalArgumentException ()
            tabins.setDefaultTarget(throwException
                (code, IllegalArgumentException.class));
        }
        else {
            // In mixed access mode, property indexes are not +1 incremental
            // a lookup switch must be used to do indexed lookup.
            LookupSwitchInstruction lookupins = code.lookupswitch();

            for (Integer i : propFmds) {
                lookupins.addCase(i,
                    code.constant().setValue(
                    _attrsToFields.get(fmds[i].getName())));
                code.areturn();
            }
            // default: throw new IllegalArgumentException ()
            lookupins.setDefaultTarget(throwException
                (code, IllegalArgumentException.class));
        }

        code.calculateMaxLocals();
        code.calculateMaxStack();
    }

    /**
     * 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 BCField getReturnedField(BCMethod meth) {
        return findField(meth, (AccessController.doPrivileged(
            J2DoPrivHelper.newCodeAction())).xreturn()
            .setType(meth.getReturnType()), false);
    }

    /**
     * Return the field assigned in the given method, or null if none.
     * Package-protected and static for testing.
     */
    static BCField getAssignedField(BCMethod meth) {
        return findField(meth, (AccessController.doPrivileged(
            J2DoPrivHelper.newCodeAction())).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 BCField findField(BCMethod meth, Instruction template,
        boolean findAccessed) {
        // ignore any static methods. OpenJPA only currently supports
        // non-static setters and getters
        if (meth.isStatic())
            return null;

        Code code = meth.getCode(false);
        if (code == null)
            return null;
        code.beforeFirst();

        BCField field = null, cur;
        Instruction templateIns, prevIns, earlierIns;
        while (code.searchForward(template)) {
            int backupCount = 3;
            templateIns = code.previous();
            if (!code.hasPrevious())
                return null;
            prevIns = code.previous();

            if (prevIns instanceof ClassInstruction
                && code.hasPrevious()) {
                prevIns = code.previous();
                backupCount++;
            }

            if (!code.hasPrevious())
                return null;
            earlierIns = code.previous();

            // 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 (!(earlierIns instanceof LoadInstruction)
                || !((LoadInstruction) earlierIns).isThis())
                return null;

            // if the middle instruction was a getfield, then it's the
            // field that's being accessed
            if (!findAccessed && prevIns instanceof GetFieldInstruction) {
                final FieldInstruction fPrevIns = (FieldInstruction) prevIns;
                cur = AccessController.doPrivileged(
                    J2DoPrivHelper.getFieldInstructionFieldAction(fPrevIns));
                // if the middle instruction was an xload_1, then the
                // matched instruction is the field that's being set.
            } else if (findAccessed && prevIns instanceof LoadInstruction
                && ((LoadInstruction) prevIns).getParam() == 0) {
                final FieldInstruction fTemplateIns =
                    (FieldInstruction) templateIns;
                cur = AccessController.doPrivileged(J2DoPrivHelper
                    .getFieldInstructionFieldAction(fTemplateIns));
            } else
                return null;

            if (field != null && cur != field)
                return null;
            field = cur;

            // ready for next search iteration
            while (backupCount > 0) {
                code.next();
                backupCount--;
            }
        }
        return field;
    }

    /**
     * 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 {
        // create template putfield/getfield instructions to search for
        Code template = AccessController.doPrivileged(
            J2DoPrivHelper.newCodeAction());
        Instruction put = template.putfield();
        Instruction get = template.getfield();
        Instruction stat = template.invokestatic();

        // look through all methods; this is done before any methods are added
        // so we don't need to worry about excluding synthetic methods.
        BCMethod[] methods = _managedType.getDeclaredMethods();
        Code code;
        for (BCMethod method : methods) {
            code = method.getCode(false);

            // don't modify the methods specified by the auxiliary enhancers
            if (code != null && !skipEnhance(method)) {
                replaceAndValidateFieldAccess(code, get, true, stat);
                replaceAndValidateFieldAccess(code, put, false, stat);
            }
        }
    }

    /**
     * Replaces all instructions matching the given template in the given
     * code block with calls to the appropriate generated getter/setter.
     *
     * @param code the code block to modify; the code iterator will
     * be placed before the first instruction on method start,
     * and will be after the last instruction on method completion
     * @param ins the template instruction to search for; either a
     * getfield or putfield instruction
     * @param get boolean indicating if this is a get instruction
     * @param stat template invokestatic instruction to replace with
     */
    private void replaceAndValidateFieldAccess(Code code, Instruction ins,
        boolean get, Instruction stat) throws NoSuchMethodException {
        code.beforeFirst();

        FieldInstruction fi;
        MethodInstruction mi;
        ClassMetaData owner;
        String name, typeName, methodName;
        while (code.searchForward(ins)) {
            // back up to the matched instruction
            fi = (FieldInstruction) code.previous();
            name = fi.getFieldName();
            typeName = fi.getFieldTypeName();
            owner = getPersistenceCapableOwner(name, fi.getFieldDeclarerType());
            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,
                            code.getMethod().getName() }));

                // if we're directly accessing a property-backing field outside
                // the property in our own class, notify user
                if (isBackingFieldOfAnotherProperty(name, code))
                    addViolation("property-field-access", new Object[]{ _meta,
                        owner, name, code.getMethod().getName() }, false);
            }

            if (owner == null ||
                owner.getDeclaredField(fromBackingFieldName(name)) == null) {
                // not persistent field?
                code.next();
                continue;
            } else if (!getRedefine() && !getCreateSubclass()
                && isFieldAccess(fmd)) {
                // replace the instruction with a call to the generated access
                // method
                mi = (MethodInstruction) code.set(stat);

                // invoke the proper access method, whether getter or setter
                String prefix = (get) ? PRE + "Get" : PRE + "Set";
                methodName = prefix + name;
                if (get) {
                    mi.setMethod(getType(owner).getName(),
                        methodName, typeName, new String[]
                        { getType(owner).getName() });
                } else {
                    mi.setMethod(getType(owner).getName(),
                        methodName, "void", new String[]
                        { getType(owner).getName(), typeName });
                }
                code.next();
            } else if (getRedefine()) {
                name = fromBackingFieldName(name);
                if (get) {
                    addNotifyAccess(code, owner.getField(name));
                    code.next();
                } else {
                    // insert the set operations after the field mutation, but
                    // first load the old value for use in the
                    // StateManager.settingXXX method.
                    loadManagedInstance(code, false);
                    final FieldInstruction fFi = fi;
                    code.getfield().setField(
                        AccessController.doPrivileged(J2DoPrivHelper
                            .getFieldInstructionFieldAction(fFi)));
                    int val = code.getNextLocalsIndex();
                    code.xstore().setLocal(val).setType(fi.getFieldType());

                    // move past the putfield
                    code.next();
                    addNotifyMutation(code, owner.getField(name), val, -1);
                }
            } else {
                code.next();
            }
            code.calculateMaxLocals();
            code.calculateMaxStack();
        }
    }

    private void addNotifyAccess(Code code, FieldMetaData fmd) {
        // PCHelper.accessingField(this, );
        code.aload().setThis();
        code.constant().setValue(fmd.getIndex());
        code.invokestatic().setMethod(RedefinitionHelper.class,
            "accessingField", void.class,
            new Class[] { Object.class, int.class });
    }

    /**
     * This must be called after setting the value in the object.
     *
     * @param val 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.
     * @throws NoSuchMethodException
     */
    private void addNotifyMutation(Code code, FieldMetaData fmd, int val,
        int param)
        throws NoSuchMethodException {
        // PCHelper.settingField(this, , old, new);
        code.aload().setThis();
        code.constant().setValue(fmd.getIndex());
        Class type = fmd.getDeclaredType();
        // we only have special signatures for primitives and Strings
        if (!type.isPrimitive() && type != String.class)
            type = Object.class;
        code.xload().setLocal(val).setType(type);
        if (param == -1) {
            loadManagedInstance(code, false);
            addGetManagedValueCode(code, fmd);
        } else {
            code.xload().setParam(param).setType(type);
        }
        code.invokestatic().setMethod(RedefinitionHelper.class, "settingField",
            void.class, new Class[] {
                Object.class, int.class, type, type
        });
    }

    /**
     * Return true if the given instruction accesses a field that is a backing
     * field of another property in this property-access class.
     */
    private boolean isBackingFieldOfAnotherProperty(String name, Code code) {
        String methName = code.getMethod().getName();
        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();
        addNewInstanceMethod(true);
        addNewInstanceMethod(false);
        addManagedFieldCountMethod();
        addReplaceFieldsMethods();
        addProvideFieldsMethods();
        addCopyFieldsMethod();

        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()
        throws NoSuchMethodException {
        // protected void pcClearFields ()
        BCMethod method = _pc.declareMethod(PRE + "ClearFields", void.class,
            null);
        method.makeProtected();
        Code code = method.getCode(true);

        // super.pcClearFields ()
        if (_meta.getPCSuperclass() != null && !getCreateSubclass()) {
            code.aload().setThis();
            code.invokespecial().setMethod(getType(_meta.
                getPCSuperclassMetaData()), PRE + "ClearFields", void.class,
                null);
        }

        FieldMetaData[] fmds = _meta.getDeclaredFields();
        for (FieldMetaData fmd : fmds) {
            if (fmd.getManagement() != FieldMetaData.MANAGE_PERSISTENT)
                continue;

            loadManagedInstance(code, false);
            switch (fmd.getDeclaredTypeCode()) {
                case JavaTypes.BOOLEAN:
                case JavaTypes.BYTE:
                case JavaTypes.CHAR:
                case JavaTypes.INT:
                case JavaTypes.SHORT:
                    code.constant().setValue(0);
                    break;
                case JavaTypes.DOUBLE:
                    code.constant().setValue(0D);
                    break;
                case JavaTypes.FLOAT:
                    code.constant().setValue(0F);
                    break;
                case JavaTypes.LONG:
                    code.constant().setValue(0L);
                    break;
                default:
                    code.constant().setNull();
                    break;
            }

            addSetManagedValueCode(code, fmd);
        }

        code.vreturn();
        code.calculateMaxStack();
        code.calculateMaxLocals();
    }

    /**
     * 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(boolean oid) {
        // public PersistenceCapable pcNewInstance (...)
        Class[] args =
            (oid) ? new Class[]{ SMTYPE, Object.class, boolean.class }
                : new Class[]{ SMTYPE, boolean.class };
        BCMethod method = _pc.declareMethod(PRE + "NewInstance", PCTYPE, args);
        Code code = method.getCode(true);

        // if the type is abstract, throw a UserException
        if (_pc.isAbstract()) {
            throwException(code, USEREXCEP);

            code.calculateMaxStack();
            code.calculateMaxLocals();
            return;
        }

        // XXX pc = new XXX ();
        code.anew().setType(_pc);
        code.dup();
        code.invokespecial().setMethod("", void.class, null);
        int inst = code.getNextLocalsIndex();
        code.astore().setLocal(inst);

        // if (clear)
        //   pc.pcClearFields ();
        code.iload().setParam((oid) ? 2 : 1);
        JumpInstruction noclear = code.ifeq();
        code.aload().setLocal(inst);
        code.invokevirtual().setMethod(PRE + "ClearFields", void.class, null);

        // pc.pcStateManager = sm;
        noclear.setTarget(code.aload().setLocal(inst));
        code.aload().setParam(0);
        code.putfield().setField(SM, SMTYPE);

        // copy key fields from oid
        if (oid) {
            code.aload().setLocal(inst);
            code.aload().setParam(1);
            code.invokevirtual().setMethod(PRE + "CopyKeyFieldsFromObjectId",
                void.class, new Class[]{ Object.class });
        }

        // return pc;
        code.aload().setLocal(inst);
        code.areturn();

        code.calculateMaxStack();
        code.calculateMaxLocals();
    }

    /**
     * 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() {
        // protected static int pcGetManagedFieldCount ()
        BCMethod method = _pc.declareMethod(PRE + "GetManagedFieldCount",
            int.class, null);
        method.setStatic(true);
        method.makeProtected();
        Code code = method.getCode(true);

        // 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 ()
        code.constant().setValue(_meta.getDeclaredFields().length);
        if (_meta.getPCSuperclass() != null) {
            Class superClass = getType(_meta.getPCSuperclassMetaData());
            String superName = getCreateSubclass() ?
                PCEnhancer.toPCSubclassName(superClass) :
                superClass.getName();
            code.invokestatic().setMethod(superName,
                PRE + "GetManagedFieldCount", int.class.getName(), null);
            code.iadd();
        }
        code.ireturn();
        code.calculateMaxStack();
    }

    /**
     * Adds the {@link PersistenceCapable#pcProvideField} and
     * {@link PersistenceCapable#pcProvideFields} methods to the bytecode.
     */
    private void addProvideFieldsMethods()
        throws NoSuchMethodException {
        // public void pcProvideField (int fieldNumber)
        BCMethod method = _pc.declareMethod(PRE + "ProvideField", void.class,
            new Class[]{ int.class });
        Code code = method.getCode(true);

        // adds everything through the switch ()
        int relLocal = beginSwitchMethod(PRE + "ProvideField", code);

        // if no fields in this inst, just throw exception
        FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
            : _meta.getDeclaredFields();
        if (fmds.length == 0)
            throwException(code, IllegalArgumentException.class);
        else {
            // switch (val)
            code.iload().setLocal(relLocal);
            TableSwitchInstruction tabins = code.tableswitch();
            tabins.setLow(0);
            tabins.setHigh(fmds.length - 1);

            //  = pcStateManager.providedField
            //     (this, fieldNumber);
            for (FieldMetaData fmd : fmds) {
                tabins.addTarget(loadManagedInstance(code, false));
                code.getfield().setField(SM, SMTYPE);
                loadManagedInstance(code, false);
                code.iload().setParam(0);
                loadManagedInstance(code, false);
                addGetManagedValueCode(code, fmd);
                code.invokeinterface().setMethod(getStateManagerMethod
                        (fmd.getDeclaredType(), "provided", false, false));
                code.vreturn();
            }

            // default: throw new IllegalArgumentException ()
            tabins.setDefaultTarget(throwException
                (code, IllegalArgumentException.class));
        }

        code.calculateMaxStack();
        code.calculateMaxLocals();

        addMultipleFieldsMethodVersion(method);
    }

    /**
     * Adds the {@link PersistenceCapable#pcReplaceField} and
     * {@link PersistenceCapable#pcReplaceFields} methods to the bytecode.
     */
    private void addReplaceFieldsMethods()
        throws NoSuchMethodException {
        // public void pcReplaceField (int fieldNumber)
        BCMethod method = _pc.declareMethod(PRE + "ReplaceField", void.class,
            new Class[]{ int.class });
        Code code = method.getCode(true);

        // adds everything through the switch ()
        int relLocal = beginSwitchMethod(PRE + "ReplaceField", code);

        // if no fields in this inst, just throw exception
        FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
            : _meta.getDeclaredFields();
        if (fmds.length == 0)
            throwException(code, IllegalArgumentException.class);
        else {
            // switch (val)
            code.iload().setLocal(relLocal);
            TableSwitchInstruction tabins = code.tableswitch();
            tabins.setLow(0);
            tabins.setHigh(fmds.length - 1);

            //  = pcStateManager.replaceField
            //  (this, fieldNumber);
            for (FieldMetaData fmd : fmds) {
                // for the addSetManagedValueCode call below.
                tabins.addTarget(loadManagedInstance(code, false, fmd));

                loadManagedInstance(code, false, fmd);
                code.getfield().setField(SM, SMTYPE);
                loadManagedInstance(code, false, fmd);
                code.iload().setParam(0);
                code.invokeinterface().setMethod(getStateManagerMethod
                        (fmd.getDeclaredType(), "replace", true, false));
                if (!fmd.getDeclaredType().isPrimitive())
                    code.checkcast().setType(fmd.getDeclaredType());

                addSetManagedValueCode(code, fmd);
                if (_addVersionInitFlag) {
                    if (fmd.isVersion()) {
                        // If this case is setting the version field
                        // pcVersionInit = true;
                        loadManagedInstance(code, false);
                        code.constant().setValue(1);
                        putfield(code, null, VERSION_INIT_STR, boolean.class);
                    }
                }
                code.vreturn();
            }

            // default: throw new IllegalArgumentException ()
            tabins.setDefaultTarget(throwException
                (code, IllegalArgumentException.class));
        }

        code.calculateMaxStack();
        code.calculateMaxLocals();

        addMultipleFieldsMethodVersion(method);
    }

    /**
     * Adds the {@link PersistenceCapable#pcCopyFields} method to the bytecode.
     */
    private void addCopyFieldsMethod()
        throws NoSuchMethodException {
        // public void pcCopyField (Object pc, int field)
        BCMethod method = _pc.declareMethod(PRE + "CopyField",
            void.class.getName(),
            new String[]{ _managedType.getName(), int.class.getName() });
        method.makeProtected();
        Code code = method.getCode(true);

        // adds everything through the switch ()
        int relLocal = beginSwitchMethod(PRE + "CopyField", code);

        // if no fields in this inst, just throw exception
        FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
            : _meta.getDeclaredFields();
        if (fmds.length == 0)
            throwException(code, IllegalArgumentException.class);
        else {
            // switch (val)
            code.iload().setLocal(relLocal);
            TableSwitchInstruction tabins = code.tableswitch();
            tabins.setLow(0);
            tabins.setHigh(fmds.length - 1);

            for (FieldMetaData fmd : fmds) {
                //  = other.;
                // or set (other.get);
                tabins.addTarget(loadManagedInstance(code, false, fmd));
                code.aload().setParam(0);
                addGetManagedValueCode(code, fmd, false);
                addSetManagedValueCode(code, fmd);

                // break;
                code.vreturn();
            }

            // default: throw new IllegalArgumentException ()
            tabins.setDefaultTarget(throwException
                (code, IllegalArgumentException.class));
        }

        code.calculateMaxStack();
        code.calculateMaxLocals();

        addMultipleFieldsMethodVersion(method);
    }

    /**
     * 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(String name, Code code) {
        boolean copy = (PRE + "CopyField").equals(name);
        int fieldNumber = (copy) ? 1 : 0;

        int relLocal = code.getNextLocalsIndex();
        if (getCreateSubclass()) {
            code.iload().setParam(fieldNumber);
            code.istore().setLocal(relLocal);
            return relLocal;
        }

        // int rel = fieldNumber - pcInheritedFieldCount
        code.iload().setParam(fieldNumber);
        code.getstatic().setField(INHERIT, int.class);
        code.isub();
        code.istore().setLocal(relLocal);
        code.iload().setLocal(relLocal);

        // super: if (rel < 0) super.pcReplaceField (fieldNumber); return;
        // no super: if (rel < 0) throw new IllegalArgumentException ();
        JumpInstruction ifins = code.ifge();
        if (_meta.getPCSuperclass() != null) {
            loadManagedInstance(code, false);
            String[] args;
            if (copy) {
                args = new String[]{ getType(_meta.getPCSuperclassMetaData()).
                    getName(), int.class.getName() };
                code.aload().setParam(0);
            } else
                args = new String[]{ int.class.getName() };
            code.iload().setParam(fieldNumber);
            code.invokespecial().setMethod(getType(_meta.
                getPCSuperclassMetaData()).getName(), name,
                void.class.getName(), args);
            code.vreturn();
        } else
            throwException(code, IllegalArgumentException.class);

        ifins.setTarget(code.nop());
        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
     * 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(BCMethod single) {
        boolean copy = (PRE + "CopyField").equals(single.getName());

        // public void s (int[] fields)
        Class[] args = (copy) ? new Class[]{ Object.class, int[].class }
            : new Class[]{ int[].class };
        BCMethod method = _pc.declareMethod(single.getName() + "s",
            void.class, args);
        Code code = method.getCode(true);

        int fieldNumbers = 0;
        int inst = 0;
        if (copy) {
            fieldNumbers = 1;

            if (getCreateSubclass()) {
                // get the managed instance into the local variable table
                code.aload().setParam(0);
                code.invokestatic().setMethod(ImplHelper.class,
                    "getManagedInstance", Object.class,
                    new Class[] { Object.class });
                code.checkcast().setType(_managedType);
                inst = code.getNextLocalsIndex();
                code.astore().setLocal(inst);

                // there might be a difference between the classes of 'this'
                // vs 'other' in this context; use the PC methods to get the SM
                code.aload().setParam(0);
                code.aload().setThis();
                code.getfield().setField(SM, SMTYPE);
                code.invokestatic().setMethod(ImplHelper.class,
                    "toPersistenceCapable", PersistenceCapable.class,
                    new Class[] { Object.class, Object.class });
                code.invokeinterface().setMethod(PersistenceCapable.class,
                    "pcGetStateManager", StateManager.class, null);
            } else {
                // XXX other = (XXX) pc;
                code.aload().setParam(0);
                code.checkcast().setType(_pc);
                inst = code.getNextLocalsIndex();
                code.astore().setLocal(inst);

                // access the other's sm field directly
                code.aload().setLocal(inst);
                code.getfield().setField(SM, SMTYPE);
            }

            // if (other.pcStateManager != pcStateManager)
            //	throw new IllegalArgumentException

            loadManagedInstance(code, false);
            code.getfield().setField(SM, SMTYPE);
            JumpInstruction ifins = code.ifacmpeq();
            throwException(code, IllegalArgumentException.class);
            ifins.setTarget(code.nop());

            // if (pcStateManager == null)
            //  throw new IllegalStateException
            loadManagedInstance(code, false);
            code.getfield().setField(SM, SMTYPE);
            ifins = code.ifnonnull();
            throwException(code, IllegalStateException.class);
            ifins.setTarget(code.nop());
        }

        // for (int i = 0;
        code.constant().setValue(0);
        int idx = code.getNextLocalsIndex();
        code.istore().setLocal(idx);
        JumpInstruction testins = code.go2();

        //  (fields[i]);
        Instruction bodyins = loadManagedInstance(code, false);
        if (copy)
            code.aload().setLocal(inst);
        code.aload().setParam(fieldNumbers);
        code.iload().setLocal(idx);
        code.iaload();
        code.invokevirtual().setMethod(single);

        // i++;
        code.iinc().setIncrement(1).setLocal(idx);

        // i < fields.length
        testins.setTarget(code.iload().setLocal(idx));
        code.aload().setParam(fieldNumbers);
        code.arraylength();
        code.ificmplt().setTarget(bodyins);
        code.vreturn();

        code.calculateMaxStack();
        code.calculateMaxLocals();
    }

    /**
     * Adds the 'stock' methods to the bytecode; these include methods
     * like {@link PersistenceCapable#pcFetchObjectId}
     * and {@link PersistenceCapable#pcIsTransactional}.
     */
    private void addStockMethods()
        throws NoSuchMethodException {
        try {
            // pcGetGenericContext
            translateFromStateManagerMethod(
                AccessController.doPrivileged(
                    J2DoPrivHelper.getDeclaredMethodAction(
                        SMTYPE, "get" + CONTEXTNAME, (Class[]) null)), false);

            // pcFetchObjectId
            translateFromStateManagerMethod(
                AccessController.doPrivileged(
                    J2DoPrivHelper.getDeclaredMethodAction(
                        SMTYPE, "fetchObjectId", (Class[]) null)), false);

            // pcIsDeleted
            translateFromStateManagerMethod(
                AccessController.doPrivileged(
                    J2DoPrivHelper.getDeclaredMethodAction(
                        SMTYPE, "isDeleted", (Class[]) null)), false);

            // pcIsDirty
            translateFromStateManagerMethod(
                AccessController.doPrivileged(
                    J2DoPrivHelper.getDeclaredMethodAction(
                        SMTYPE, "isDirty", (Class[]) null)), true);

            // pcIsNew
            translateFromStateManagerMethod(
                AccessController.doPrivileged(
                    J2DoPrivHelper.getDeclaredMethodAction(
                        SMTYPE, "isNew", (Class[]) null)), false);

            // pcIsPersistent
            translateFromStateManagerMethod(
                AccessController.doPrivileged(
                    J2DoPrivHelper.getDeclaredMethodAction(
                        SMTYPE, "isPersistent", (Class[]) null)), false);

            // pcIsTransactional
            translateFromStateManagerMethod(
                AccessController.doPrivileged(
                    J2DoPrivHelper.getDeclaredMethodAction(
                        SMTYPE, "isTransactional", (Class[]) null)), false);

            // pcSerializing
            translateFromStateManagerMethod(
                AccessController.doPrivileged(
                    J2DoPrivHelper.getDeclaredMethodAction(
                        SMTYPE, "serializing", (Class[]) null)), false);

            // pcDirty
            translateFromStateManagerMethod(
                AccessController.doPrivileged(
                    J2DoPrivHelper.getDeclaredMethodAction(
                        SMTYPE, "dirty", new Class[]{ String.class })), false);

            // pcGetStateManager
            BCMethod meth = _pc.declareMethod(PRE + "GetStateManager",
                StateManager.class, null);
            Code code = meth.getCode(true);
            loadManagedInstance(code, false);
            code.getfield().setField(SM, StateManager.class);
            code.areturn();
            code.calculateMaxStack();
            code.calculateMaxLocals();
        } catch (PrivilegedActionException pae) {
             throw (NoSuchMethodException) pae.getException();
        }
    }

    /**
     * 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();
        Class returnType = m.getReturnType();

        // add the method to the pc
        BCMethod method = _pc.declareMethod(name, returnType, params);
        Code code = method.getCode(true);

        // if (pcStateManager == null) return ;
        loadManagedInstance(code, false);
        code.getfield().setField(SM, SMTYPE);
        JumpInstruction ifins = code.ifnonnull();
        if (returnType.equals(boolean.class))
            code.constant().setValue(false);
        else if (!returnType.equals(void.class))
            code.constant().setNull();
        code.xreturn().setType(returnType);

        // 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);
            ifins.setTarget(loadManagedInstance(code, false));
            code.getfield().setField(SM, SMTYPE);
            code.dup(); // for the return statement below
            code.invokestatic().setMethod(RedefinitionHelper.class,
                "dirtyCheck", void.class, new Class[] { SMTYPE });
        } else {
            ifins.setTarget(loadManagedInstance(code, false));
            code.getfield().setField(SM, SMTYPE);
        }

        // return pcStateManager. ();
        // managed instance loaded above in if-else block
        for (int i = 0; i < params.length; i++)
            code.xload().setParam(i);
        code.invokeinterface().setMethod(m);
        code.xreturn().setType(returnType);

        code.calculateMaxStack();
        code.calculateMaxLocals();
    }

    /**
     * Adds the {@link PersistenceCapable#pcGetVersion} method to the bytecode.
     */
    private void addGetVersionMethod()
        throws NoSuchMethodException {
        BCMethod method = _pc.declareMethod(PRE + "GetVersion", Object.class,
            null);
        Code code = method.getCode(true);

        // if (pcStateManager == null)
        loadManagedInstance(code, false);
        code.getfield().setField(SM, SMTYPE);
        JumpInstruction ifins = code.ifnonnull();
        FieldMetaData versionField = _meta.getVersionField();

        if (versionField == null)
            code.constant().setNull(); // return null;
        else {
            // return ;
            Class wrapper = toPrimitiveWrapper(versionField);
            if (wrapper != versionField.getDeclaredType()) {
                code.anew().setType(wrapper);
                code.dup();
            }
            loadManagedInstance(code, false);
            addGetManagedValueCode(code, versionField);
            if (wrapper != versionField.getDeclaredType())
                code.invokespecial().setMethod(wrapper, "", void.class,
                    new Class[]{ versionField.getDeclaredType() });
        }
        code.areturn();

        // return pcStateManager.getVersion ();
        ifins.setTarget(loadManagedInstance(code, false));
        code.getfield().setField(SM, SMTYPE);
        code.invokeinterface().setMethod(SMTYPE, "getVersion", Object.class,
            null);
        code.areturn();

        code.calculateMaxStack();
        code.calculateMaxLocals();
    }

    /**
     * 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)
        BCMethod method = _pc.declareMethod(PRE + "ReplaceStateManager",
            void.class, new Class[]{ SMTYPE });
        method.getExceptions(true).addException(SecurityException.class);
        Code code = method.getCode(true);

        // if (pcStateManager != null)
        //	pcStateManager = pcStateManager.replaceStateManager(sm);
        loadManagedInstance(code, false);
        code.getfield().setField(SM, SMTYPE);
        JumpInstruction ifins = code.ifnull();
        loadManagedInstance(code, false);
        loadManagedInstance(code, false);
        code.getfield().setField(SM, SMTYPE);
        code.aload().setParam(0);
        code.invokeinterface().setMethod(SMTYPE, "replaceStateManager",
            SMTYPE, new Class[]{ SMTYPE });
        code.putfield().setField(SM, SMTYPE);
        code.vreturn();

        // SecurityManager sec = System.getSecurityManager ();
        // if (sec != null)
        //		sec.checkPermission (Permission.SET_STATE_MANAGER);
        ifins.setTarget(code.invokestatic().setMethod(System.class,
            "getSecurityManager", SecurityManager.class, null));

        // pcStateManager = sm;
        ifins.setTarget(loadManagedInstance(code, false));
        code.aload().setParam(0);
        code.putfield().setField(SM, SMTYPE);
        code.vreturn();

        code.calculateMaxStack();
        code.calculateMaxLocals();
    }

    /**
     * Creates the PersistenceCapable methods dealing with application
     * identity and gives them no-op implementations.
     */
    private void addNoOpApplicationIdentityMethods() {
        // public void pcCopyKeyFieldsToObjectId (ObjectIdFieldSupplier fs,
        // 	Object oid)
        BCMethod method = _pc.declareMethod(PRE + "CopyKeyFieldsToObjectId",
            void.class, new Class[]{ OIDFSTYPE, Object.class });
        Code code = method.getCode(true);
        code.vreturn();
        code.calculateMaxLocals();

        // public void pcCopyKeyFieldsToObjectId (Object oid)
        method = _pc.declareMethod(PRE + "CopyKeyFieldsToObjectId",
            void.class, new Class[]{ Object.class });
        code = method.getCode(true);
        code.vreturn();
        code.calculateMaxLocals();

        // public void pcCopyKeyFieldsFromObjectId (ObjectIdFieldConsumer fc,
        //	Object oid)
        method = _pc.declareMethod(PRE + "CopyKeyFieldsFromObjectId",
            void.class, new Class[]{ OIDFCTYPE, Object.class });
        code = method.getCode(true);
        code.vreturn();
        code.calculateMaxLocals();

        // public void pcCopyKeyFieldsFromObjectId (Object oid)
        method = _pc.declareMethod(PRE + "CopyKeyFieldsFromObjectId",
            void.class, new Class[]{ Object.class });
        code = method.getCode(true);
        code.vreturn();
        code.calculateMaxLocals();

        // public Object pcNewObjectIdInstance ()
        method = _pc.declareMethod(PRE + "NewObjectIdInstance",
            Object.class, null);
        code = method.getCode(true);
        code.constant().setNull();
        code.areturn();
        code.calculateMaxStack();
        code.calculateMaxLocals();

        // public Object pcNewObjectIdInstance (Object obj)
        method = _pc.declareMethod(PRE + "NewObjectIdInstance",
            Object.class, new Class[]{ Object.class });
        code = method.getCode(true);
        code.constant().setNull();
        code.areturn();
        code.calculateMaxStack();
        code.calculateMaxLocals();
    }

    /**
     * Adds the pcCopyKeyFieldsToObjectId methods
     * to classes using application identity.
     */
    private void addCopyKeyFieldsToObjectIdMethod(boolean fieldManager)
        throws NoSuchMethodException {
        // public void pcCopyKeyFieldsToObjectId (ObjectIdFieldSupplier fs,
        //	Object oid)
        String[] args = (fieldManager) ?
            new String[]{ OIDFSTYPE.getName(), Object.class.getName() }
            : new String[]{ Object.class.getName() };
        BCMethod method = _pc.declareMethod(PRE + "CopyKeyFieldsToObjectId",
            void.class.getName(), args);
        Code code = method.getCode(true);

        // single field identity always throws exception
        if (_meta.isOpenJPAIdentity()) {
            throwException(code, INTERNEXCEP);

            code.calculateMaxStack();
            code.calculateMaxLocals();
            return;
        }

        // call superclass method
        if (_meta.getPCSuperclass() != null && !getCreateSubclass()) {
            loadManagedInstance(code, false);
            for (int i = 0; i < args.length; i++)
                code.aload().setParam(i);
            code.invokespecial().setMethod(getType(_meta.
                getPCSuperclassMetaData()).getName(),
                PRE + "CopyKeyFieldsToObjectId", void.class.getName(), args);
        }

        // Object id = oid;
        if (fieldManager)
            code.aload().setParam(1);
        else
            code.aload().setParam(0);

        if (_meta.isObjectIdTypeShared()) {
            // oid = ((ObjectId) id).getId ();
            code.checkcast().setType(ObjectId.class);
            code.invokevirtual().setMethod(ObjectId.class, "getId",
                Object.class, null);
        }

        //  id = () oid;
        int id = code.getNextLocalsIndex();
        Class oidType = _meta.getObjectIdType();
        code.checkcast().setType(oidType);
        code.astore().setLocal(id);

        // int inherited = pcInheritedFieldCount;
        int inherited = 0;
        if (fieldManager) {
            code.getstatic().setField(INHERIT, int.class);
            inherited = code.getNextLocalsIndex();
            code.istore().setLocal(inherited);
        }

        // id. = fs.fetchField (); or...
        // id. = pc.;
        FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
            : _meta.getDeclaredFields();
        Class type;
        String name;
        Field field;
        Method setter;
        boolean reflect;
        // 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;
                            code.aload().setParam(0);
                            code.constant().setValue(k);
                            code.iload().setLocal(inherited);
                            code.iadd();
                            code.invokeinterface().setMethod(getFieldSupplierMethod(fmds[k].getObjectIdFieldType()));
                            localIndexes[k] = code.getNextLocalsIndex();
                            storeLocalValue(code, localIndexes[k], fmds[k].getObjectIdFieldTypeCode());
                        }
                    }

                    // found a matching constructor.  parm array is constructor parm order
                    code.anew().setType(oidType);
                    code.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) {
                            loadManagedInstance(code, false);
                            addGetManagedValueCode(code, fmds[parmIndex]);
                        } else {
                            // Load constructor parameters in appropriate order
                            loadLocalValue(code, localIndexes[parmIndex], fmds[parmIndex].getObjectIdFieldTypeCode());
                            if (fmds[parmIndex].getObjectIdFieldTypeCode() == JavaTypes.OBJECT &&
                                !fmds[parmIndex].getDeclaredType().isEnum()) {
                                code.checkcast().setType(ObjectId.class);
                                code.invokevirtual().setMethod(ObjectId.class, "getId",
                                    Object.class, null);
                            }
                            // 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()))
                                code.checkcast().setType(clsArgs[i]);
                        }
                    }
                    // invoke the public constructor to create a new local id
                    code.invokespecial().setMethod(oidType, "", void.class, clsArgs);
                    int ret = code.getNextLocalsIndex();
                    code.astore().setLocal(ret);

                    // swap out the app id with the new one
                    code.aload().setLocal( fieldManager ? 2 : 1);
                    code.checkcast().setType(ObjectId.class);
                    code.aload().setLocal(ret);
                    code.invokestatic().setMethod(ApplicationIds.class,
                            "setAppId", void.class, new Class[] { ObjectId.class,
                            Object.class });
                    code.vreturn();

                    code.calculateMaxStack();
                    code.calculateMaxLocals();
                    return;
                }
            }
        }

        for (int i = 0; i < fmds.length; i++) {
            if (!fmds[i].isPrimaryKey())
                continue;
            code.aload().setLocal(id);

            name = fmds[i].getName();
            type = fmds[i].getObjectIdFieldType();
            if (isFieldAccess(fmds[i])) {
                setter = null;
                field = Reflection.findField(oidType, name, true);
                reflect = !Modifier.isPublic(field.getModifiers());
                if (reflect) {
                    code.classconstant().setClass(oidType);
                    code.constant().setValue(name);
                    code.constant().setValue(true);
                    code.invokestatic().setMethod(Reflection.class,
                        "findField", Field.class, new Class[] { Class.class,
                        String.class, boolean.class });
                }
            } else {
                field = null;
                setter = Reflection.findSetter(oidType, name, type, true);
                reflect = !Modifier.isPublic(setter.getModifiers());
                if (reflect) {
                    code.classconstant().setClass(oidType);
                    code.constant().setValue(name);
                    code.classconstant().setClass(type);
                    code.constant().setValue(true);
                    code.invokestatic().setMethod(Reflection.class,
                        "findSetter", Method.class, new Class[] { Class.class,
                        String.class, Class.class, boolean.class });
                }
            }

            if (fieldManager) {
                code.aload().setParam(0);
                code.constant().setValue(i);
                code.iload().setLocal(inherited);
                code.iadd();
                code.invokeinterface().setMethod
                    (getFieldSupplierMethod(type));
                if (fmds[i].getObjectIdFieldTypeCode() == JavaTypes.OBJECT &&
                    !fmds[i].getDeclaredType().isEnum()) {
                	code.checkcast().setType(ObjectId.class);
                	code.invokevirtual().setMethod(ObjectId.class, "getId",
            			Object.class, null);
                }

                // 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()))
                    code.checkcast().setType(type);
            } else {
                loadManagedInstance(code, false);
                addGetManagedValueCode(code, fmds[i]);

                // get id/pk from pc instance
                if (fmds[i].getDeclaredTypeCode() == JavaTypes.PC)
                    addExtractObjectIdFieldValueCode(code, fmds[i]);
            }

            if (reflect && field != null) {
                code.invokestatic().setMethod(Reflection.class, "set",
                    void.class, new Class[] { Object.class, Field.class,
                    (type.isPrimitive()) ? type : Object.class });
            } else if (reflect) {
                code.invokestatic().setMethod(Reflection.class, "set",
                    void.class, new Class[] { Object.class, Method.class,
                    (type.isPrimitive()) ? type : Object.class });
            } else if (field != null)
                code.putfield().setField(field);
            else
                code.invokevirtual().setMethod(setter);
        }
        code.vreturn();

        code.calculateMaxStack();
        code.calculateMaxLocals();
    }

    /**
     * Adds the appropriate load method for the given type and local
     * index.
     */
    private void loadLocalValue(Code code, int locidx, int typeCode) {
        switch (typeCode) {
            case JavaTypes.CHAR:
            case JavaTypes.BYTE:
            case JavaTypes.SHORT:
            case JavaTypes.INT:
                code.iload().setLocal(locidx);
                break;
            case JavaTypes.DOUBLE:
                code.dload().setLocal(locidx);
                break;
            case JavaTypes.FLOAT:
                code.fload().setLocal(locidx);
                break;
            case JavaTypes.LONG:
                code.lload().setLocal(locidx);
                break;
            default:
                code.aload().setLocal(locidx);
                break;
        }
    }

    /**
     * Adds the appropriate store method for the given type and local
     * index.
     */
    private void storeLocalValue(Code code, int locidx, int typeCode) {
        switch (typeCode) {
            case JavaTypes.CHAR:
            case JavaTypes.BYTE:
            case JavaTypes.SHORT:
            case JavaTypes.INT:
                code.istore().setLocal(locidx);
                break;
            case JavaTypes.DOUBLE:
                code.dstore().setLocal(locidx);
                break;
            case JavaTypes.FLOAT:
                code.fstore().setLocal(locidx);
                break;
            case JavaTypes.LONG:
                code.lstore().setLocal(locidx);
                break;
            default:
                code.astore().setLocal(locidx);
                break;
        }
    }

    /**
     * Add code to extract the id of the given primary key relation field for
     * setting into an objectid instance.
     */
    private void addExtractObjectIdFieldValueCode(Code code, FieldMetaData pk) {
        // if (val != null)
        //  val = ((PersistenceCapable) val).pcFetchObjectId();
        int pc = code.getNextLocalsIndex();
        code.astore().setLocal(pc);
        code.aload().setLocal(pc);
        JumpInstruction ifnull1 = code.ifnull();
        code.aload().setLocal(pc);
        code.checkcast().setType(PersistenceCapable.class);
        if (!pk.getTypeMetaData().isOpenJPAIdentity())
            code.invokeinterface().setMethod(PersistenceCapable.class,
                PRE + "FetchObjectId", Object.class, null);
        else
            code.invokeinterface().setMethod(PersistenceCapable.class,
                PRE + "NewObjectIdInstance", Object.class, null);

        int oid = code.getNextLocalsIndex();
        code.astore().setLocal(oid);
        code.aload().setLocal(oid);
        JumpInstruction ifnull2 = code.ifnull();

        // 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) {
            code.aload().setLocal(oid);
            code.checkcast().setType(Id.class);
            code.invokevirtual().setMethod(Id.class, "getId",
                long.class, null);
        } else if (pkmeta.getIdentityType() == ClassMetaData.ID_DATASTORE) {
            code.aload().setLocal(oid);
        } else if (pkmeta.isOpenJPAIdentity()) {
            switch (pkcode) {
                case JavaTypes.BYTE_OBJ:
                    code.anew().setType(Byte.class);
                    code.dup();
                    // no break
                case JavaTypes.BYTE:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(ByteId.class);
                    code.invokevirtual().setMethod(ByteId.class, "getId",
                        byte.class, null);
                    if (pkcode == JavaTypes.BYTE_OBJ)
                        code.invokespecial().setMethod(Byte.class, "",
                            void.class, new Class[] {byte.class});
                    break;
                case JavaTypes.CHAR_OBJ:
                    code.anew().setType(Character.class);
                    code.dup();
                    // no break
                case JavaTypes.CHAR:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(CharId.class);
                    code.invokevirtual().setMethod(CharId.class, "getId",
                        char.class, null);
                    if (pkcode == JavaTypes.CHAR_OBJ)
                        code.invokespecial().setMethod(Character.class,
                            "", void.class, new Class[] {char.class});
                    break;
                case JavaTypes.DOUBLE_OBJ:
                    code.anew().setType(Double.class);
                    code.dup();
                    // no break
                case JavaTypes.DOUBLE:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(DoubleId.class);
                    code.invokevirtual().setMethod(DoubleId.class, "getId",
                        double.class, null);
                    if (pkcode == JavaTypes.DOUBLE_OBJ)
                        code.invokespecial().setMethod(Double.class, "",
                            void.class, new Class[]{double.class});
                    break;
                case JavaTypes.FLOAT_OBJ:
                    code.anew().setType(Float.class);
                    code.dup();
                    // no break
                case JavaTypes.FLOAT:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(FloatId.class);
                    code.invokevirtual().setMethod(FloatId.class, "getId",
                        float.class, null);
                    if (pkcode == JavaTypes.FLOAT_OBJ)
                        code.invokespecial().setMethod(Float.class, "",
                            void.class, new Class[]{float.class});
                    break;
                case JavaTypes.INT_OBJ:
                    code.anew().setType(Integer.class);
                    code.dup();
                    // no break
                case JavaTypes.INT:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(IntId.class);
                    code.invokevirtual().setMethod(IntId.class, "getId",
                        int.class, null);
                    if (pkcode == JavaTypes.INT_OBJ)
                        code.invokespecial().setMethod(Integer.class, "",
                            void.class, new Class[] {int.class});
                    break;
                case JavaTypes.LONG_OBJ:
                    code.anew().setType(Long.class);
                    code.dup();
                    // no break
                case JavaTypes.LONG:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(LongId.class);
                    code.invokevirtual().setMethod(LongId.class, "getId",
                        long.class, null);
                    if (pkcode == JavaTypes.LONG_OBJ)
                        code.invokespecial().setMethod(Long.class, "",
                            void.class, new Class[] {long.class});
                    break;
                case JavaTypes.SHORT_OBJ:
                    code.anew().setType(Short.class);
                    code.dup();
                    // no break
                case JavaTypes.SHORT:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(ShortId.class);
                    code.invokevirtual().setMethod(ShortId.class, "getId",
                        short.class, null);
                    if (pkcode == JavaTypes.SHORT_OBJ)
                        code.invokespecial().setMethod(Short.class, "",
                            void.class, new Class[]{short.class});
                    break;
                case JavaTypes.DATE:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(DateId.class);
                    code.invokevirtual().setMethod(DateId.class, "getId",
                        Date.class, null);
                    if (pktype != Date.class) {
                        // java.sql.Date.class
                        code.checkcast().setType(pktype);
                    }
                    break;
                case JavaTypes.STRING:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(StringId.class);
                    code.invokevirtual().setMethod(StringId.class, "getId",
                        String.class, null);
                    break;
                case JavaTypes.BIGDECIMAL:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(BigDecimalId.class);
                    code.invokevirtual().setMethod(BigDecimalId.class, "getId",
                        BigDecimal.class, null);
                    break;
                case JavaTypes.BIGINTEGER:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(BigIntegerId.class);
                    code.invokevirtual().setMethod(BigIntegerId.class, "getId",
                        BigInteger.class, null);
                    break;
                default:
                    code.aload().setLocal(oid);
                    code.checkcast().setType(ObjectId.class);
                    code.invokevirtual().setMethod(ObjectId.class, "getId",
                        Object.class, null);
            }
        } else if (pkmeta.getObjectIdType() != null) {
            code.aload().setLocal(oid);
            if (pkcode == JavaTypes.OBJECT) {
                code.checkcast().setType(ObjectId.class);
                code.invokevirtual().setMethod(ObjectId.class, "getId",
            			Object.class, null);
            }
            code.checkcast().setType(pktype);
        } else
            code.aload().setLocal(oid);
        JumpInstruction go2 = code.go2();

        // if (val == null)
        //   val = ;
        Instruction def;
        switch (pkcode) {
            case JavaTypes.BOOLEAN:
                def = code.constant().setValue(false);
                break;
            case JavaTypes.BYTE:
                def = code.constant().setValue((byte) 0);
                break;
            case JavaTypes.CHAR:
                def = code.constant().setValue((char) 0);
                break;
            case JavaTypes.DOUBLE:
                def = code.constant().setValue(0D);
                break;
            case JavaTypes.FLOAT:
                def = code.constant().setValue(0F);
                break;
            case JavaTypes.INT:
                def = code.constant().setValue(0);
                break;
            case JavaTypes.LONG:
                def = code.constant().setValue(0L);
                break;
            case JavaTypes.SHORT:
                def = code.constant().setValue((short) 0);
                break;
            default:
                def = code.constant().setNull();
        }
        ifnull1.setTarget(def);
        ifnull2.setTarget(def);
        go2.setTarget(code.nop());
    }

    /**
     * Adds the pcCopyKeyFieldsFromObjectId methods
     * to classes using application identity.
     */
    private void addCopyKeyFieldsFromObjectIdMethod(boolean fieldManager)
        throws NoSuchMethodException {
        // public void pcCopyKeyFieldsFromObjectId (ObjectIdFieldConsumer fc,
        //	Object oid)
        String[] args = (fieldManager)
            ?  new String[]{ OIDFCTYPE.getName(), Object.class.getName() }
            : new String[]{ Object.class.getName() };
        BCMethod method = _pc.declareMethod(PRE + "CopyKeyFieldsFromObjectId",
            void.class.getName(), args);
        Code code = method.getCode(true);

        // call superclass method
        if (_meta.getPCSuperclass() != null && !getCreateSubclass()) {
            loadManagedInstance(code, false);
            for (int i = 0; i < args.length; i++)
                code.aload().setParam(i);
            code.invokespecial().setMethod(getType(_meta.
                getPCSuperclassMetaData()).getName(),
                PRE + "CopyKeyFieldsFromObjectId", void.class.getName(), args);
        }

        if (fieldManager)
            code.aload().setParam(1);
        else
            code.aload().setParam(0);

        if (!_meta.isOpenJPAIdentity() && _meta.isObjectIdTypeShared()) {
            // oid = ((ObjectId) id).getId ();
            code.checkcast().setType(ObjectId.class);
            code.invokevirtual().setMethod(ObjectId.class, "getId",
                Object.class, null);
        }

        //  cast = () oid;
        int id = code.getNextLocalsIndex();
        Class oidType = _meta.getObjectIdType();
        code.checkcast().setType(oidType);
        code.astore().setLocal(id);

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

            name = fmds[i].getName();
            type = fmds[i].getObjectIdFieldType();
            if (!fieldManager
                && fmds[i].getDeclaredTypeCode() == JavaTypes.PC) {
                // if (sm == null) return;
                loadManagedInstance(code, false);
                code.getfield().setField(SM, SMTYPE);
                JumpInstruction ifins = code.ifnonnull();
                code.vreturn();
                // sm.getPCPrimaryKey(oid, i + pcInheritedFieldCount);
                ifins.setTarget(loadManagedInstance(code, false));
                code.dup(); // leave orig on stack to set value into
                code.getfield().setField(SM, SMTYPE);
                code.aload().setLocal(id);
                code.constant().setValue(i);
                code.getstatic().setField(INHERIT, int.class);
                code.iadd();
                code.invokeinterface().setMethod(StateManager.class,
                    "getPCPrimaryKey", Object.class,
                    new Class[] { Object.class, int.class });
                code.checkcast().setType(fmds[i].getDeclaredType());
            } else {
                unwrapped = (fmds[i].getDeclaredTypeCode() == JavaTypes.PC)
                    ? type : unwrapSingleFieldIdentity(fmds[i]);
                if (fieldManager) {
                    code.aload().setParam(0);
                    code.constant().setValue(i);
                    code.getstatic().setField(INHERIT, int.class);
                    code.iadd();
                } else
                    loadManagedInstance(code, false);

                if (unwrapped != type) {
                    code.anew().setType(type);
                    code.dup();
                }
                code.aload().setLocal(id);
                if (_meta.isOpenJPAIdentity()) {
                    if (oidType == ObjectId.class) {
                        code.invokevirtual().setMethod(oidType, "getId",
                            Object.class, null);
                        if (!fieldManager && type != Object.class)
                            code.checkcast().setType(fmds[i].getDeclaredType());
                    } else if (oidType == DateId.class) {
                        code.invokevirtual().setMethod(oidType, "getId",
                            Date.class, null);
                        if (!fieldManager && type != Date.class)
                            code.checkcast().setType(fmds[i].getDeclaredType());
                    } else {
                        code.invokevirtual().setMethod(oidType, "getId",
                            unwrapped, null);
                        if (unwrapped != type)
                            code.invokespecial().setMethod(type, "",
                                void.class, new Class[]{ unwrapped });
                    }
                } else if (isFieldAccess(fmds[i])) {
                    field = Reflection.findField(oidType, name, true);
                    if (Modifier.isPublic(field.getModifiers()))
                        code.getfield().setField(field);
                    else {
                        boolean usedFastOid = false;
                        if (_optimizeIdCopy) {
                            // If fastOids, ignore access type and try to use a public getter
                            getter = Reflection.findGetter(oidType, name, false);
                            if (getter != null && Modifier.isPublic(getter.getModifiers())) {
                                usedFastOid = true;
                                code.invokevirtual().setMethod(getter);
                            }
                        }
                        if (!usedFastOid) {
                            // Reflection.getXXX(oid, Reflection.findField(...));
                            code.classconstant().setClass(oidType);
                            code.constant().setValue(name);
                            code.constant().setValue(true);
                            code.invokestatic().setMethod(Reflection.class,
                                "findField", Field.class, new Class[] {
                                Class.class, String.class, boolean.class });
                            code.invokestatic().setMethod
                                (getReflectionGetterMethod(type, Field.class));
                            if (!type.isPrimitive() && type != Object.class)
                                code.checkcast().setType(type);
                        }
                    }
                } else {
                    getter = Reflection.findGetter(oidType, name, true);
                    if (Modifier.isPublic(getter.getModifiers()))
                        code.invokevirtual().setMethod(getter);
                    else {
                        // Reflection.getXXX(oid, Reflection.findGetter(...));
                        code.classconstant().setClass(oidType);
                        code.constant().setValue(name);
                        code.constant().setValue(true);
                        code.invokestatic().setMethod(Reflection.class,
                            "findGetter", Method.class, new Class[] {
                            Class.class, String.class, boolean.class });
                        code.invokestatic().setMethod
                            (getReflectionGetterMethod(type, Method.class));
                        if (!type.isPrimitive() && type != Object.class)
                            code.checkcast().setType(type);
                    }
                }
            }

            if (fieldManager)
                code.invokeinterface().setMethod(getFieldConsumerMethod(type));
            else
                addSetManagedValueCode(code, fmds[i]);
        }
        code.vreturn();

        code.calculateMaxStack();
        code.calculateMaxLocals();
    }

    /**
     * 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 ()
        Class[] args = (obj) ? new Class[]{ Object.class } : null;
        BCMethod method = _pc.declareMethod(PRE + "NewObjectIdInstance",
            Object.class, args);
        Code code = method.getCode(true);

        Boolean usesClsString = usesClassStringIdConstructor();
        Class oidType = _meta.getObjectIdType();
        if (obj && usesClsString == null) {
            // throw new IllegalArgumentException (...);
            String msg = _loc.get("str-cons", oidType,
                _meta.getDescribedType()).getMessage();
            code.anew().setType(IllegalArgumentException.class);
            code.dup();
            code.constant().setValue(msg);
            code.invokespecial().setMethod(IllegalArgumentException.class,
                "", void.class, new Class[]{ String.class });
            code.athrow();

            code.calculateMaxStack();
            code.calculateMaxLocals();
            return;
        }

        if (!_meta.isOpenJPAIdentity() && _meta.isObjectIdTypeShared()) {
            // new ObjectId (cls, oid)
            code.anew().setType(ObjectId.class);
            code.dup();
            if(_meta.isEmbeddedOnly() || _meta.hasAbstractPKField()) {
                code.aload().setThis();
                code.invokevirtual().setMethod(PRE + "GetIDOwningClass",
                    Class.class, null);
            } else {
                code.classconstant().setClass(getType(_meta));
            }
        }

        // new  ();
        code.anew().setType(oidType);
        code.dup();
        if (_meta.isOpenJPAIdentity() || (obj && usesClsString == Boolean.TRUE)) {
            if ((_meta.isEmbeddedOnly()
                && !(_meta.isEmbeddable() && _meta.getIdentityType() == ClassMetaData.ID_APPLICATION))
                || _meta.hasAbstractPKField()) {
                code.aload().setThis();
                code.invokevirtual().setMethod(PRE + "GetIDOwningClass", Class.class, null);
            } else {
                code.classconstant().setClass(getType(_meta));
            }
        }
        if (obj) {
            code.aload().setParam(0);
            code.checkcast().setType(String.class);
            if (usesClsString == Boolean.TRUE)
                args = new Class[]{ Class.class, String.class };
            else if (usesClsString == Boolean.FALSE)
                args = new Class[]{ String.class };
        } else if (_meta.isOpenJPAIdentity()) {
            // new Identity (XXX.class, );
            loadManagedInstance(code, false);
            FieldMetaData pk = _meta.getPrimaryKeyFields()[0];
            addGetManagedValueCode(code, pk);
            if (pk.getDeclaredTypeCode() == JavaTypes.PC)
                addExtractObjectIdFieldValueCode(code, pk);
            if (_meta.getObjectIdType() == ObjectId.class)
                args = new Class[]{ Class.class, Object.class };
            else if (_meta.getObjectIdType() == Date.class)
                args = new Class[]{ Class.class, Date.class };
            else
                args = new Class[]{ Class.class, pk.getObjectIdFieldType() };
        }

        code.invokespecial().setMethod(oidType, "", void.class, args);
        if (!_meta.isOpenJPAIdentity() && _meta.isObjectIdTypeShared())
            code.invokespecial().setMethod(ObjectId.class, "",
                void.class, new Class[]{ Class.class, Object.class });
        code.areturn();

        code.calculateMaxStack();
        code.calculateMaxLocals();
    }

    /**
     * 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[plist.size()]);

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

    /**
     * Helper method to add the code necessary to throw the given
     * exception type, sans message.
     */
    private Instruction throwException(Code code, Class type) {
        Instruction ins = code.anew().setType(type);
        code.dup();
        code.invokespecial().setMethod(type, "", void.class, null);
        code.athrow();
        return ins;
    }

    /**
     * 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() {
        // make the class implement PersistenceCapable
        _pc.declareInterface(PCTYPE);

        // add a version stamp
        addGetEnhancementContractVersionMethod();

        // find the default constructor
        BCMethod method = _pc.getDeclaredMethod("", (String[]) null);

        // a default constructor is required
        if (method == null) {
            String name = _pc.getName();
            if (!_defCons)
                throw new UserException(_loc.get("enhance-defaultconst", name));

            method = _pc.addDefaultConstructor();
            String access;
            if (_meta.isDetachable()) {
                // externalizable requires that the constructor
                // be public, so make the added constructor public
                method.makePublic();
                access = "public";
            } else if (_pc.isFinal()) {
                method.makePrivate();
                access = "private";
            } else {
                method.makeProtected();
                access = "protected";
            }
            if (!(_meta.getDescribedType().isInterface() || getCreateSubclass())
                && _log.isWarnEnabled())
                _log.warn(_loc.get("enhance-adddefaultconst", 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() { _pc.declareField(INHERIT, int.class).setStatic(true); _pc.declareField(PRE + "FieldNames", String[].class).setStatic(true); _pc.declareField(PRE + "FieldTypes", Class[].class).setStatic(true); _pc.declareField(PRE + "FieldFlags", byte[].class).setStatic(true); _pc.declareField(SUPER, Class.class).setStatic(true); if (_addVersionInitFlag && _meta.getVersionField() != null) { // protected transient boolean pcVersionInit; BCField field = _pc.declareField(VERSION_INIT_STR, boolean.class); field.makeProtected(); field.setTransient(true); } if (_meta.getPCSuperclass() == null || getCreateSubclass()) { BCField field = _pc.declareField(SM, SMTYPE); field.makeProtected(); field.setTransient(true); } } /** * 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() { Code code = getOrCreateClassInitCode(true); if (_meta.getPCSuperclass() != null) { if (getCreateSubclass()) { code.constant().setValue(0); code.putstatic().setField(INHERIT, int.class); } else { // pcInheritedFieldCount = .pcGetManagedFieldCount() code.invokestatic().setMethod(getType(_meta. getPCSuperclassMetaData()).getName(), PRE + "GetManagedFieldCount", int.class.getName(), null); code.putstatic().setField(INHERIT, int.class); } // pcPCSuperclass = ; // this intentionally calls getDescribedType() directly // instead of PCEnhancer.getType() code.classconstant().setClass( _meta.getPCSuperclassMetaData().getDescribedType()); code.putstatic().setField(SUPER, Class.class); } // pcFieldNames = new String[] { "", "", ... }; FieldMetaData[] fmds = _meta.getDeclaredFields(); code.constant().setValue(fmds.length); code.anewarray().setType(String.class); for (int i = 0; i < fmds.length; i++) { code.dup(); code.constant().setValue(i); code.constant().setValue(fmds[i].getName()); code.aastore(); } code.putstatic().setField(PRE + "FieldNames", String[].class); // pcFieldTypes = new Class[] { .class, .class, ... }; code.constant().setValue(fmds.length); code.anewarray().setType(Class.class); for (int i = 0; i < fmds.length; i++) { code.dup(); code.constant().setValue(i); code.classconstant().setClass(fmds[i].getDeclaredType()); code.aastore(); } code.putstatic().setField(PRE + "FieldTypes", Class[].class); // pcFieldFlags = new byte[] { , , ... }; code.constant().setValue(fmds.length); code.newarray().setType(byte.class); for (int i = 0; i < fmds.length; i++) { code.dup(); code.constant().setValue(i); code.constant().setValue(getFieldFlag(fmds[i])); code.bastore(); } code.putstatic().setField(PRE + "FieldFlags", byte[].class); // PCRegistry.register (cls, // pcFieldNames, pcFieldTypes, pcFieldFlags, // pcPCSuperclass, alias, new XXX ()); code.classconstant().setClass(_meta.getDescribedType()); code.getstatic().setField(PRE + "FieldNames", String[].class); code.getstatic().setField(PRE + "FieldTypes", Class[].class); code.getstatic().setField(PRE + "FieldFlags", byte[].class); code.getstatic().setField(SUPER, Class.class); if (_meta.isMapped() || _meta.isAbstract()) code.constant().setValue(_meta.getTypeAlias()); else code.constant().setNull(); if (_pc.isAbstract()) code.constant().setNull(); else { code.anew().setType(_pc); code.dup(); code.invokespecial().setMethod("", void.class, null); } code.invokestatic().setMethod(HELPERTYPE, "register", void.class, new Class[]{ Class.class, String[].class, Class[].class, byte[].class, Class.class, String.class, PCTYPE }); code.vreturn(); code.calculateMaxStack(); } /** * 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 BCField field = _pc.getDeclaredField("serialVersionUID"); if (field == null) { 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) { field = _pc.declareField("serialVersionUID", long.class); field.makePrivate(); field.setStatic(true); field.setFinal(true); Code code = getOrCreateClassInitCode(false); code.beforeFirst(); code.constant().setValue(uid.longValue()); code.putstatic().setField(field); code.calculateMaxStack(); } } // add write object method BCMethod write = _pc.getDeclaredMethod("writeObject", new Class[]{ ObjectOutputStream.class }); boolean full = write == null; if (full) { // private void writeObject (ObjectOutputStream out) write = _pc.declareMethod("writeObject", void.class, new Class[]{ ObjectOutputStream.class }); write.getExceptions(true).addException(IOException.class); write.makePrivate(); } modifyWriteObjectMethod(write, full); // and read object BCMethod read = _pc.getDeclaredMethod("readObject", new Class[]{ ObjectInputStream.class }); full = read == null; if (full) { // private void readObject (ObjectInputStream in) read = _pc.declareMethod("readObject", void.class, new Class[]{ ObjectInputStream.class }); read.getExceptions(true).addException(IOException.class); read.getExceptions(true).addException (ClassNotFoundException.class); read.makePrivate(); } modifyReadObjectMethod(read, 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 BCMethod method = _pc.declareMethod("writeReplace", Object.class, null); method.getExceptions(true).addException(ObjectStreamException.class); Code code = method.getCode(true); // Object o = new () code.anew().setType(_managedType); // for return code.dup(); // for post- work code.dup(); // for code.invokespecial().setMethod(_managedType.getType(), "", void.class, null); // 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) code.dup(); // for putfield code.aload().setThis(); // for getfield getfield(code, _managedType, fmd.getName()); putfield(code, _managedType, fmd.getName(), fmd.getDeclaredType()); } code.areturn().setType(Object.class); code.calculateMaxLocals(); code.calculateMaxStack(); } /** * 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(BCMethod method, boolean full) { Code code = method.getCode(true); code.beforeFirst(); // bool clear = pcSerializing (); loadManagedInstance(code, false); code.invokevirtual().setMethod(PRE + "Serializing", boolean.class, null); int clear = code.getNextLocalsIndex(); code.istore().setLocal(clear); if (full) { // out.defaultWriteObject (); code.aload().setParam(0); code.invokevirtual().setMethod(ObjectOutputStream.class, "defaultWriteObject", void.class, null); code.vreturn(); } Instruction tmplate = (AccessController.doPrivileged( J2DoPrivHelper.newCodeAction())).vreturn(); JumpInstruction toret; Instruction ret; code.beforeFirst(); while (code.searchForward(tmplate)) { ret = code.previous(); // if (clear) pcSetDetachedState (null); code.iload().setLocal(clear); toret = code.ifeq(); loadManagedInstance(code, false); code.constant().setNull(); code.invokevirtual().setMethod(PRE + "SetDetachedState", void.class, new Class[]{ Object.class }); toret.setTarget(ret); code.next(); // jump over return } code.calculateMaxStack(); code.calculateMaxLocals(); } /** * Adds a custom readObject method that delegates to the * {@link ObjectInputStream#readObject} method. */ private void modifyReadObjectMethod(BCMethod method, boolean full) { Code code = method.getCode(true); code.beforeFirst(); // if this instance uses synthetic detached state, note that it has // been deserialized if (ClassMetaData.SYNTHETIC.equals(_meta.getDetachedState())) { loadManagedInstance(code, false); code.getstatic().setField(PersistenceCapable.class, "DESERIALIZED", Object.class); code.invokevirtual().setMethod(PRE + "SetDetachedState", void.class, new Class[]{ Object.class }); } if (full) { // in.defaultReadObject (); code.aload().setParam(0); code.invokevirtual().setMethod(ObjectInputStream.class, "defaultReadObject", void.class, null); code.vreturn(); } code.calculateMaxStack(); code.calculateMaxLocals(); } /** * Creates the pcIsDetached() method to determine if an instance * is detached. */ private void addIsDetachedMethod() throws NoSuchMethodException { // public boolean pcIsDetached() BCMethod method = _pc.declareMethod(PRE + "IsDetached", Boolean.class, null); method.makePublic(); Code code = method.getCode(true); boolean needsDefinitiveMethod = writeIsDetachedMethod(code); code.calculateMaxStack(); code.calculateMaxLocals(); 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 method = _pc.declareMethod(ISDETACHEDSTATEDEFINITIVE, boolean.class, null); method.makePrivate(); code = method.getCode(true); code.constant().setValue(false); code.ireturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } /** * 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(Code code) throws NoSuchMethodException { // not detachable: return Boolean.FALSE if (!_meta.isDetachable()) { code.getstatic().setField(Boolean.class, "FALSE", Boolean.class); code.areturn(); return false; } // if (sm != null) // return (sm.isDetached ()) ? Boolean.TRUE : Boolean.FALSE; loadManagedInstance(code, false); code.getfield().setField(SM, SMTYPE); JumpInstruction ifins = code.ifnull(); loadManagedInstance(code, false); code.getfield().setField(SM, SMTYPE); code.invokeinterface().setMethod(SMTYPE, "isDetached", boolean.class, null); JumpInstruction iffalse = code.ifeq(); code.getstatic().setField(Boolean.class, "TRUE", Boolean.class); code.areturn(); iffalse.setTarget(code.getstatic().setField(Boolean.class, "FALSE", Boolean.class)); code.areturn(); // if we use detached state: // if (pcGetDetachedState () != null // && pcGetDetachedState != DESERIALIZED) // return Boolean.TRUE; Boolean state = _meta.usesDetachedState(); JumpInstruction notdeser = null; Instruction target; if (state != Boolean.FALSE) { ifins.setTarget(loadManagedInstance(code, false)); code.invokevirtual().setMethod(PRE + "GetDetachedState", Object.class, null); ifins = code.ifnull(); loadManagedInstance(code, false); code.invokevirtual().setMethod(PRE + "GetDetachedState", Object.class, null); code.getstatic().setField(PersistenceCapable.class, "DESERIALIZED", Object.class); notdeser = code.ifacmpeq(); code.getstatic().setField(Boolean.class, "TRUE", Boolean.class); code.areturn(); if (state == Boolean.TRUE) { // if we have to use detached state: // return Boolean.FALSE; target = code.getstatic().setField(Boolean.class, "FALSE", Boolean.class); ifins.setTarget(target); notdeser.setTarget(target); code.areturn(); return false; } } // create artificial target to simplify target = code.nop(); ifins.setTarget(target); if (notdeser != null) notdeser.setTarget(target); // 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 ( != ) // return true; loadManagedInstance(code, false); addGetManagedValueCode(code, version); ifins = ifDefaultValue(code, version); code.getstatic().setField(Boolean.class, "TRUE", Boolean.class); code.areturn(); if (!_addVersionInitFlag){ // else return false; ifins.setTarget(code.getstatic().setField(Boolean.class, "FALSE", Boolean.class)); }else{ // noop ifins.setTarget(code.nop()); // if (pcVersionInit != false) // return true // else return null; // (returning null because we don't know the correct answer) loadManagedInstance(code, false); getfield(code, null, VERSION_INIT_STR); ifins = code.ifeq(); code.getstatic().setField(Boolean.class, "TRUE", Boolean.class); code.areturn(); ifins.setTarget(code.nop()); code.constant().setNull(); } code.areturn(); return false; } // consider detached if auto-genned primary keys are non-default ifins = null; JumpInstruction ifins2 = null; boolean hasAutoAssignedPK = false; 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; target = loadManagedInstance(code, false); if (ifins != null) ifins.setTarget(target); if (ifins2 != null) ifins2.setTarget(target); ifins2 = null; addGetManagedValueCode(code, pk); ifins = ifDefaultValue(code, pk); if (pk.getDeclaredTypeCode() == JavaTypes.STRING) { code.constant().setValue(""); loadManagedInstance(code, false); addGetManagedValueCode(code, pk); code.invokevirtual().setMethod(String.class, "equals", boolean.class, new Class[]{Object.class}); ifins2 = code.ifne(); } code.getstatic().setField(Boolean.class, "TRUE", Boolean.class); code.areturn(); } } // create artificial target to simplify target = code.nop(); if (ifins != null) ifins.setTarget(target); if (ifins2 != null) ifins2.setTarget(target); // if has auto-assigned pk and we get to this point, must have default // value, so must be new instance if (hasAutoAssignedPK) { code.getstatic().setField(Boolean.class, "FALSE", Boolean.class); code.areturn(); return false; } // 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 code.aload().setThis(); code.invokespecial().setMethod(ISDETACHEDSTATEDEFINITIVE, boolean.class, null); ifins = code.ifne(); code.constant().setNull(); code.areturn(); ifins.setTarget(code.nop()); // 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 code.getstatic().setField(Boolean.class, "FALSE", Boolean.class); code.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; loadManagedInstance(code, false); code.invokevirtual().setMethod(PRE + "GetDetachedState", Object.class, null); ifins = code.ifnonnull(); code.getstatic().setField(Boolean.class, "FALSE", Boolean.class); code.areturn(); ifins.setTarget(code.nop()); } // give up; we just don't know code.constant().setNull(); code.areturn(); return true; } /** * Compare the given field to its Java default, returning the * comparison instruction. The field value will already be on the stack. */ private static JumpInstruction ifDefaultValue(Code code, FieldMetaData fmd) { switch (fmd.getDeclaredTypeCode()) { case JavaTypes.BOOLEAN: case JavaTypes.BYTE: case JavaTypes.CHAR: case JavaTypes.INT: case JavaTypes.SHORT: return code.ifeq(); case JavaTypes.DOUBLE: code.constant().setValue(0D); code.dcmpl(); return code.ifeq(); case JavaTypes.FLOAT: code.constant().setValue(0F); code.fcmpl(); return code.ifeq(); case JavaTypes.LONG: code.constant().setValue(0L); code.lcmp(); return code.ifeq(); default: return code.ifnull(); } } /** * Helper method to get the code for the class initializer method, * creating the method if it does not already exist. */ private Code getOrCreateClassInitCode(boolean replaceLast) { BCMethod clinit = _pc.getDeclaredMethod(""); Code code; if (clinit != null) { code = clinit.getCode(true); if (replaceLast) { Code template = AccessController.doPrivileged( J2DoPrivHelper.newCodeAction()); code.searchForward(template.vreturn()); code.previous(); code.set(template.nop()); code.next(); } return code; } // add static initializer method if non exists clinit = _pc.declareMethod("", void.class, null); clinit.makePackage(); clinit.setStatic(true); clinit.setFinal(true); code = clinit.getCode(true); if (!replaceLast) { code.vreturn(); code.previous(); } return code; } /** * 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; // add the clone method if necessary BCMethod clone = _pc.getDeclaredMethod("clone", (String[]) null); String superName = _managedType.getSuperclassName(); Code code = null; if (clone == 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.getName())); // add clone method // protected Object clone () throws CloneNotSupportedException clone = _pc.declareMethod("clone", Object.class, null); if (!setVisibilityToSuperMethod(clone)) clone.makeProtected(); clone.getExceptions(true).addException (CloneNotSupportedException.class); code = clone.getCode(true); // return super.clone (); loadManagedInstance(code, false); code.invokespecial().setMethod(superName, "clone", Object.class.getName(), null); code.areturn(); } else { // get the clone method code code = clone.getCode(false); if (code == null) return; } // create template super.clone () instruction to match against Instruction template = (AccessController.doPrivileged( J2DoPrivHelper.newCodeAction())).invokespecial() .setMethod(superName, "clone", Object.class.getName(), null); // find calls to the template instruction; on match // clone will be on stack code.beforeFirst(); if (code.searchForward(template)) { // (() clone).pcStateManager = null; code.dup(); code.checkcast().setType(_pc); code.constant().setNull(); code.putfield().setField(SM, SMTYPE); // if modified, increase stack code.calculateMaxStack(); code.calculateMaxLocals(); } } /** * 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, _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(BCMethod method) { if ("".equals(method.getName())) 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() throws NoSuchMethodException { FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields() : _meta.getDeclaredFields(); for (int i = 0; i < fmds.length; i++) { if (getCreateSubclass()) { if (!getRedefine() && isPropertyAccess(fmds[i])) { addSubclassSetMethod(fmds[i]); addSubclassGetMethod(fmds[i]); } } else { addGetMethod(i, fmds[i]); addSetMethod(i, fmds[i]); } } } /** * Adds a non-static setter that delegates to the super methods, and * performs any necessary field tracking. */ private void addSubclassSetMethod(FieldMetaData fmd) throws NoSuchMethodException { Class propType = fmd.getDeclaredType(); String setterName = getSetterName(fmd); BCMethod setter = _pc.declareMethod(setterName, void.class, new Class[] { propType }); setVisibilityToSuperMethod(setter); Code code = setter.getCode(true); // not necessary if we're already tracking access via redefinition if (!getRedefine()) { // get the orig value onto stack code.aload().setThis(); addGetManagedValueCode(code, fmd); int val = code.getNextLocalsIndex(); code.xstore().setLocal(val).setType(fmd.getDeclaredType()); addNotifyMutation(code, fmd, val, 0); } // ##### test case: B extends A. Methods defined in A. What // ##### happens? // super.setXXX(...) code.aload().setThis(); code.xload().setParam(0).setType(propType); code.invokespecial().setMethod(_managedType.getType(), setterName, void.class, new Class[] { propType }); code.vreturn(); code.calculateMaxLocals(); code.calculateMaxStack(); } private boolean setVisibilityToSuperMethod(BCMethod method) { BCMethod[] methods = _managedType.getMethods(method.getName(), method.getParamTypes()); if (methods.length == 0) throw new UserException(_loc.get("no-accessor", _managedType.getName(), method.getName())); BCMethod superMeth = methods[0]; if (superMeth.isPrivate()) { method.makePrivate(); return true; } else if (superMeth.isPackage()) { method.makePackage(); return true; } else if (superMeth.isProtected()) { method.makeProtected(); return true; } else if (superMeth.isPublic()) { method.makePublic(); return true; } return false; } /** * Adds a non-static getter that delegates to the super methods, and * performs any necessary field tracking. */ private void addSubclassGetMethod(FieldMetaData fmd) { String methName = "get" + StringUtil.capitalize(fmd.getName()); if (_managedType.getMethods(methName, new Class[0]).length == 0) methName = "is" + StringUtil.capitalize(fmd.getName()); BCMethod getter = _pc.declareMethod(methName, fmd.getDeclaredType(), null); setVisibilityToSuperMethod(getter); getter.makePublic(); Code code = getter.getCode(true); // 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(code, fmd); code.aload().setThis(); code.invokespecial().setMethod(_managedType.getType(), methName, fmd.getDeclaredType(), null); code.xreturn().setType(fmd.getDeclaredType()); code.calculateMaxLocals(); code.calculateMaxStack(); } /** * 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(int index, FieldMetaData fmd) throws NoSuchMethodException { BCMethod method = createGetMethod(fmd); Code code = method.getCode(true); // if reads are not checked, just return the value byte fieldFlag = getFieldFlag(fmd); if ((fieldFlag & PersistenceCapable.CHECK_READ) == 0 && (fieldFlag & PersistenceCapable.MEDIATE_READ) == 0) { loadManagedInstance(code, true, fmd); addGetManagedValueCode(code, fmd); code.xreturn().setType(fmd.getDeclaredType()); code.calculateMaxStack(); code.calculateMaxLocals(); return; } // if (inst.pcStateManager == null) return inst.; loadManagedInstance(code, true, fmd); code.getfield().setField(SM, SMTYPE); JumpInstruction ifins = code.ifnonnull(); loadManagedInstance(code, true, fmd); addGetManagedValueCode(code, fmd); code.xreturn().setType(fmd.getDeclaredType()); // int field = pcInheritedFieldCount + ; int fieldLocal = code.getNextLocalsIndex(); ifins.setTarget(code.getstatic().setField(INHERIT, int.class)); code.constant().setValue(index); code.iadd(); code.istore().setLocal(fieldLocal); // inst.pcStateManager.accessingField (field); // return inst.; loadManagedInstance(code, true, fmd); code.getfield().setField(SM, SMTYPE); code.iload().setLocal(fieldLocal); code.invokeinterface().setMethod(SMTYPE, "accessingField", void.class, new Class[]{ int.class }); loadManagedInstance(code, true, fmd); addGetManagedValueCode(code, fmd); code.xreturn().setType(fmd.getDeclaredType()); code.calculateMaxStack(); code.calculateMaxLocals(); } /** * 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(int index, FieldMetaData fmd) throws NoSuchMethodException { BCMethod method = createSetMethod(fmd); Code code = method.getCode(true); // PCEnhancer uses static methods; PCSubclasser does not. int firstParamOffset = getAccessorParameterOffset(fmd); // if (inst.pcStateManager == null) inst. = value; loadManagedInstance(code, true, fmd); code.getfield().setField(SM, SMTYPE); JumpInstruction ifins = code.ifnonnull(); loadManagedInstance(code, true, fmd); code.xload().setParam(firstParamOffset); addSetManagedValueCode(code, fmd); if(fmd.isVersion() && _addVersionInitFlag){ // if we are setting the version, flip the versionInit flag to true loadManagedInstance(code, true); code.constant().setValue(1); // pcVersionInit = true; putfield(code, null, VERSION_INIT_STR, boolean.class); } code.vreturn(); // inst.pcStateManager.settingField (inst, // pcInheritedFieldCount + , inst., value, 0); ifins.setTarget(loadManagedInstance(code, true, fmd)); code.getfield().setField(SM, SMTYPE); loadManagedInstance(code, true, fmd); code.getstatic().setField(INHERIT, int.class); code.constant().setValue(index); code.iadd(); loadManagedInstance(code, true, fmd); addGetManagedValueCode(code, fmd); code.xload().setParam(firstParamOffset); code.constant().setValue(0); code.invokeinterface().setMethod(getStateManagerMethod (fmd.getDeclaredType(), "setting", false, true)); code.vreturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } /** * 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; } } // if parent not detachable, we need to add the detach state fields and // accessor methods if (_meta.getPCSuperclass() == null || getCreateSubclass() || parentDetachable != _meta.isDetachable()) { addIsDetachedMethod(); 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; String declarer = null; if (impl && detachField == null) { name = PRE + "DetachedState"; declarer = _pc.getName(); BCField field = _pc.declareField(name, Object.class); field.makePrivate(); field.setTransient(true); } else if (impl) { name = detachField.getName(); declarer = detachField.getDeclaringClass().getName(); } // public Object pcGetDetachedState () BCMethod method = _pc.declareMethod(PRE + "GetDetachedState", Object.class, null); method.setStatic(false); method.makePublic(); int access = method.getAccessFlags(); Code code = method.getCode(true); if (impl) { // return pcDetachedState; loadManagedInstance(code, false); getfield(code, _managedType.getProject().loadClass(declarer), name); } else code.constant().setNull(); code.areturn(); code.calculateMaxLocals(); code.calculateMaxStack(); // public void pcSetDetachedState (Object state) method = _pc.declareMethod(PRE + "SetDetachedState", void.class, new Class []{ Object.class }); method.setAccessFlags(access); code = method.getCode(true); if (impl) { // pcDetachedState = state; loadManagedInstance(code, false); code.aload().setParam(0); putfield(code, _managedType.getProject().loadClass(declarer), name, Object.class); } code.vreturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } /** * 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(Code code, BCClass declarer, String attrName) { if (declarer == null) declarer = _managedType; // first, see if we can convert the attribute name to a field name String fieldName = toBackingFieldName(attrName); // next, find the field in the managed type hierarchy BCField field = null; outer: for (BCClass bc = _pc; bc != null; bc = bc.getSuperclassBC()) { BCField[] fields = AccessController .doPrivileged(J2DoPrivHelper.getBCClassFieldsAction(bc, fieldName)); for (BCField bcField : fields) { field = bcField; // if we reach a field declared in this type, then this is the // most-masking field, and is the one that we want. if (bcField.getDeclarer() == declarer) { break outer; } } } if (getCreateSubclass() && code.getMethod().getDeclarer() == _pc && (field == null || !field.isPublic())) { // we're creating the subclass, not redefining the user type. // Reflection.getXXX(this, Reflection.findField(...)); code.classconstant().setClass(declarer); code.constant().setValue(fieldName); code.constant().setValue(true); code.invokestatic().setMethod(Reflection.class, "findField", Field.class, new Class[] { Class.class, String.class, boolean.class }); Class type = _meta.getField(attrName).getDeclaredType(); try { code.invokestatic().setMethod( getReflectionGetterMethod(type, Field.class)); } catch (NoSuchMethodException e) { // should never happen throw new InternalException(e); } if (!type.isPrimitive() && type != Object.class) code.checkcast().setType(type); } else { code.getfield().setField(declarer.getName(), fieldName, field.getType().getName()); } } /** * 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 in code, * and the instance to load into must be second. */ private void putfield(Code code, BCClass declarer, String attrName, Class fieldType) { if (declarer == null) declarer = _managedType; String fieldName = toBackingFieldName(attrName); if (getRedefine() || getCreateSubclass()) { // Reflection.set(this, Reflection.findField(...), value); code.classconstant().setClass(declarer); code.constant().setValue(fieldName); code.constant().setValue(true); code.invokestatic().setMethod(Reflection.class, "findField", Field.class, new Class[] { Class.class, String.class, boolean.class }); code.invokestatic().setMethod(Reflection.class, "set", void.class, new Class[] { Object.class, fieldType.isPrimitive() ? fieldType : Object.class, Field.class }); } else { code.putfield() .setField(declarer.getName(), fieldName, fieldType.getName()); } } /** * 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 BCMethod meth = _pc.getDeclaredMethod("", (String[]) null); if (!meth.isPublic()) { if (_log.isWarnEnabled()) _log.warn(_loc.get("enhance-defcons-extern", _meta.getDescribedType())); meth.makePublic(); } // 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 Class[] input = new Class[]{ ObjectInputStream.class }; Class[] output = new Class[]{ ObjectOutputStream.class }; if (_managedType.getDeclaredMethod("readObject", input) != null || _managedType.getDeclaredMethod("writeObject", output) != null) throw new UserException(_loc.get("detach-custom-ser", _meta)); input[0] = ObjectInput.class; output[0] = ObjectOutput.class; if (_managedType.getDeclaredMethod("readExternal", input) != null || _managedType.getDeclaredMethod("writeExternal", output) != null) throw new UserException(_loc.get("detach-custom-extern", _meta)); // create list of all unmanaged serializable fields BCField[] fields = _managedType.getDeclaredFields(); Collection unmgd = new ArrayList(fields.length); for (BCField field : fields) { if (!field.isTransient() && !field.isStatic() && !field.isFinal() && !field.getName().startsWith(PRE) && _meta.getDeclaredField(field.getName()) == 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 { Class[] inargs = new Class[]{ ObjectInput.class }; BCMethod meth = _pc.declareMethod("readExternal", void.class, inargs); Exceptions exceps = meth.getExceptions(true); exceps.addException(IOException.class); exceps.addException(ClassNotFoundException.class); Code code = meth.getCode(true); // 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)) { loadManagedInstance(code, false); code.aload().setParam(0); code.invokespecial().setMethod(sup, "readExternal", void.class, inargs); } // readUnmanaged (in); loadManagedInstance(code, false); code.aload().setParam(0); code.invokevirtual().setMethod(getType(_meta), PRE + "ReadUnmanaged", void.class, inargs); if (detachedState) { // pcSetDetachedState (in.readObject ()); loadManagedInstance(code, false); code.aload().setParam(0); code.invokeinterface().setMethod(ObjectInput.class, "readObject", Object.class, null); code.invokevirtual().setMethod(PRE + "SetDetachedState", void.class, new Class[]{ Object.class }); // pcReplaceStateManager ((StateManager) in.readObject ()); loadManagedInstance(code, false); code.aload().setParam(0); code.invokeinterface().setMethod(ObjectInput.class, "readObject", Object.class, null); code.checkcast().setType(StateManager.class); code.invokevirtual().setMethod(PRE + "ReplaceStateManager", void.class, new Class[]{ StateManager.class }); } addReadExternalFields(); // readExternalFields(in.readObject ()); loadManagedInstance(code, false); code.aload().setParam(0); code.invokevirtual().setMethod("readExternalFields", void.class, inargs); code.vreturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } private void addReadExternalFields() throws NoSuchMethodException { Class[] inargs = new Class[]{ ObjectInput.class }; BCMethod meth = _pc.declareMethod("readExternalFields", void.class, inargs); meth.setAccessFlags(Constants.ACCESS_PROTECTED); Exceptions exceps = meth.getExceptions(true); exceps.addException(IOException.class); exceps.addException(ClassNotFoundException.class); Code code = meth.getCode(true); Class sup = _meta.getPCSuperclass(); if (sup != null) { //add a call to super.readExternalFields() loadManagedInstance(code, false); code.aload().setParam(0); code.invokespecial().setMethod(sup, "readExternalFields", void.class, inargs); } // read managed fields FieldMetaData[] fmds = _meta.getDeclaredFields(); for (FieldMetaData fmd : fmds) { if (!fmd.isTransient()) { readExternal(code, fmd.getName(), fmd.getDeclaredType(), fmd); } } code.vreturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } /** * Read unmanaged fields from the stream (pcReadUnmanaged). */ private void addReadUnmanaged(Collection unmgd, boolean parentDetachable) throws NoSuchMethodException { Class[] inargs = new Class[]{ ObjectInput.class }; BCMethod meth = _pc.declareMethod(PRE + "ReadUnmanaged", void.class, inargs); meth.makeProtected(); Exceptions exceps = meth.getExceptions(true); exceps.addException(IOException.class); exceps.addException(ClassNotFoundException.class); Code code = meth.getCode(true); // super.readUnmanaged (in); if (parentDetachable) { loadManagedInstance(code, false); code.aload().setParam(0); code.invokespecial().setMethod(getType(_meta. getPCSuperclassMetaData()), PRE + "ReadUnmanaged", void.class, inargs); } // read declared unmanaged serializable fields BCField field; for (Object o : unmgd) { field = (BCField) o; readExternal(code, field.getName(), field.getType(), null); } code.vreturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } /** * Helper method to read a field from an externalization input stream. */ private void readExternal(Code code, String fieldName, Class type, FieldMetaData fmd) throws NoSuchMethodException { String methName; if (type.isPrimitive()) { methName = type.getName(); methName = methName.substring(0, 1).toUpperCase(Locale.ENGLISH) + methName.substring(1); methName = "read" + methName; } else methName = "readObject"; // = in.read (); loadManagedInstance(code, false); code.aload().setParam(0); Class ret = (type.isPrimitive()) ? type : Object.class; code.invokeinterface().setMethod(ObjectInput.class, methName, ret, null); if (!type.isPrimitive() && type != Object.class) code.checkcast().setType(type); if (fmd == null) putfield(code, null, fieldName, type); else { addSetManagedValueCode(code, 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 (); loadManagedInstance(code, false); code.getfield().setField(SM, SMTYPE); IfInstruction ifins = code.ifnull(); loadManagedInstance(code, false); code.getfield().setField(SM, SMTYPE); code.constant().setValue(fmd.getIndex()); code.invokeinterface().setMethod(SMTYPE, "proxyDetachedDeserialized", void.class, new Class[]{ int.class }); ifins.setTarget(code.nop()); } } } /** * Add custom writeExternal method. */ private void addWriteExternal(boolean parentDetachable, boolean detachedState) throws NoSuchMethodException { Class[] outargs = new Class[]{ ObjectOutput.class }; BCMethod meth = _pc.declareMethod("writeExternal", void.class, outargs); Exceptions exceps = meth.getExceptions(true); exceps.addException(IOException.class); Code code = meth.getCode(true); // super.writeExternal (out); Class sup = getType(_meta).getSuperclass(); if (!parentDetachable && Externalizable.class.isAssignableFrom(sup)) { loadManagedInstance(code, false); code.aload().setParam(0); code.invokespecial().setMethod(sup, "writeExternal", void.class, outargs); } // writeUnmanaged (out); loadManagedInstance(code, false); code.aload().setParam(0); code.invokevirtual().setMethod(getType(_meta), PRE + "WriteUnmanaged", void.class, outargs); JumpInstruction go2 = null; if (detachedState) { // if (sm != null) // if (sm.writeDetached (out)) // return; loadManagedInstance(code, false); code.getfield().setField(SM, SMTYPE); IfInstruction ifnull = code.ifnull(); loadManagedInstance(code, false); code.getfield().setField(SM, SMTYPE); code.aload().setParam(0); code.invokeinterface().setMethod(SMTYPE, "writeDetached", boolean.class, outargs); go2 = code.ifeq(); code.vreturn(); // else // out.writeObject (pcGetDetachedState ()); Class[] objargs = new Class[]{ Object.class }; ifnull.setTarget(code.aload().setParam(0)); loadManagedInstance(code, false); code.invokevirtual().setMethod(PRE + "GetDetachedState", Object.class, null); code.invokeinterface().setMethod(ObjectOutput.class, "writeObject", void.class, objargs); // out.writeObject (null) // StateManager code.aload().setParam(0); code.constant().setValue((Object) null); code.invokeinterface().setMethod(ObjectOutput.class, "writeObject", void.class, objargs); } if (go2 != null) go2.setTarget(code.nop()); addWriteExternalFields(); loadManagedInstance(code, false); code.aload().setParam(0); code.invokevirtual().setMethod("writeExternalFields", void.class, outargs); // return code.vreturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } private void addWriteExternalFields() throws NoSuchMethodException { Class[] outargs = new Class[]{ ObjectOutput.class }; BCMethod meth = _pc.declareMethod("writeExternalFields", void.class, outargs); meth.setAccessFlags(Constants.ACCESS_PROTECTED); Exceptions exceps = meth.getExceptions(true); exceps.addException(IOException.class); Code code = meth.getCode(true); Class sup = _meta.getPCSuperclass(); if (sup != null) { // add a call to super.readExternalFields() loadManagedInstance(code, false); code.aload().setParam(0); code.invokespecial().setMethod(sup, "writeExternalFields", void.class, outargs); } FieldMetaData[] fmds = _meta.getDeclaredFields(); for (FieldMetaData fmd : fmds) { if (!fmd.isTransient()) { writeExternal(code, fmd.getName(), fmd.getDeclaredType(), fmd); } } // return code.vreturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } /** * Write unmanaged fields to the stream (pcWriteUnmanaged). */ private void addWriteUnmanaged(Collection unmgd, boolean parentDetachable) throws NoSuchMethodException { Class[] outargs = new Class[]{ ObjectOutput.class }; BCMethod meth = _pc.declareMethod(PRE + "WriteUnmanaged", void.class, outargs); meth.makeProtected(); Exceptions exceps = meth.getExceptions(true); exceps.addException(IOException.class); Code code = meth.getCode(true); // super.writeUnmanaged (out); if (parentDetachable) { loadManagedInstance(code, false); code.aload().setParam(0); code.invokespecial().setMethod(getType(_meta. getPCSuperclassMetaData()), PRE + "WriteUnmanaged", void.class, outargs); } // write declared unmanaged serializable fields BCField field; for (Object o : unmgd) { field = (BCField) o; writeExternal(code, field.getName(), field.getType(), null); } code.vreturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } /** * Helper method to write a field to an externalization output stream. */ private void writeExternal(Code code, String fieldName, Class type, FieldMetaData fmd) throws NoSuchMethodException { String methName; if (type.isPrimitive()) { methName = type.getName(); methName = methName.substring(0, 1).toUpperCase(Locale.ENGLISH) + methName.substring(1); methName = "write" + methName; } else methName = "writeObject"; // out.write (); code.aload().setParam(0); loadManagedInstance(code, false); if (fmd == null) getfield(code, null, fieldName); else addGetManagedValueCode(code, fmd); Class[] args = new Class[]{ type }; if (type == byte.class || type == char.class || type == short.class) args[0] = int.class; else if (!type.isPrimitive()) args[0] = Object.class; code.invokeinterface().setMethod(ObjectOutput.class, methName, void.class, args); } private void addGetManagedValueCode(Code code, FieldMetaData fmd) throws NoSuchMethodException { addGetManagedValueCode(code, fmd, true); } /** * 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(Code code, FieldMetaData fmd, boolean fromSameClass) throws NoSuchMethodException { // 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(code, null, fmd.getName()); } 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(); code.invokespecial().setMethod(meth); } else { getfield(code, null, fmd.getName()); } } else { // regular enhancement + property access Method meth = (Method) fmd.getBackingMember(); code.invokevirtual().setMethod(PRE + meth.getName(), meth.getReturnType(), meth.getParameterTypes()); } } /** * 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(Code code, FieldMetaData fmd) throws NoSuchMethodException { // 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(code, null, fmd.getName(), fmd.getDeclaredType()); } else if (getCreateSubclass()) { // property access, and we're not redefining. invoke the // superclass method to bypass tracking. code.invokespecial().setMethod(_managedType.getType(), getSetterName(fmd), void.class, new Class[] { fmd.getDeclaredType() }); } else { // regular enhancement + property access code.invokevirtual().setMethod(PRE + getSetterName(fmd), void.class, new Class[] { 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 the first instruction added to code. */ private Instruction loadManagedInstance(Code code, boolean forStatic, FieldMetaData fmd) { if (forStatic && isFieldAccess(fmd)) return code.aload().setParam(0); return code.aload().setThis(); } /** * Add the {@link Instruction}s to load the instance to modify onto the * stack, and return it. This method should not be used to load static * fields. * * @return the first instruction added to code. */ private Instruction loadManagedInstance(Code code, boolean forStatic) { return loadManagedInstance(code, forStatic, null); } private int getAccessorParameterOffset(FieldMetaData fmd) { return isFieldAccess(fmd) ? 1 : 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 BCMethod} for fmd. The * calling environment will then populate this method's code block. */ private BCMethod createGetMethod(FieldMetaData fmd) { BCMethod getter; if (isFieldAccess(fmd)) { // static pcGet (XXX inst) BCField field = _pc.getDeclaredField(fmd.getName()); getter = _pc.declareMethod(PRE + "Get" + fmd.getName(), fmd. getDeclaredType().getName(), new String[]{ _pc.getName() }); getter.setAccessFlags(field.getAccessFlags() & ~Constants.ACCESS_TRANSIENT & ~Constants.ACCESS_VOLATILE); getter.setStatic(true); getter.setFinal(true); return getter; } // property access: // copy the user's getter method to a new name; we can't just reset // the name, because that will also reset all calls to the method Method meth = (Method) fmd.getBackingMember(); getter = _pc.getDeclaredMethod(meth.getName(), meth.getParameterTypes()); BCMethod newgetter = _pc.declareMethod(PRE + meth.getName(), meth.getReturnType(), meth.getParameterTypes()); newgetter.setAccessFlags(getter.getAccessFlags()); newgetter.makeProtected(); transferCodeAttributes(getter, newgetter); return getter; } /** * Create the generated setter {@link BCMethod} for fmd. The * calling environment will then populate this method's code block. */ private BCMethod createSetMethod(FieldMetaData fmd) { BCMethod setter; if (isFieldAccess(fmd)) { // static void pcSet (XXX inst, value) BCField field = _pc.getDeclaredField(fmd.getName()); setter = _pc.declareMethod(PRE + "Set" + fmd.getName(), void.class, new Class[]{ getType(_meta), fmd.getDeclaredType() }); setter.setAccessFlags(field.getAccessFlags() & ~Constants.ACCESS_TRANSIENT & ~Constants.ACCESS_VOLATILE); setter.setStatic(true); setter.setFinal(true); return setter; } // property access: // copy the user's getter method to a new name; we can't just reset // the name, because that will also reset all calls to the method setter = _pc.getDeclaredMethod(getSetterName(fmd), new Class[]{ fmd.getDeclaredType() }); BCMethod newsetter = _pc.declareMethod(PRE + setter.getName(), setter.getReturnName(), setter.getParamNames()); newsetter.setAccessFlags(setter.getAccessFlags()); newsetter.makeProtected(); transferCodeAttributes(setter, newsetter); return setter; } private void addGetEnhancementContractVersionMethod() { // public int getEnhancementContractVersion() BCMethod method = _pc.declareMethod(PRE + "GetEnhancementContractVersion", int.class, null); method.makePublic(); Code code = method.getCode(true); code.constant().setValue(ENHANCER_VERSION); code.ireturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } /** * 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(); } /** * Move code-related attributes from one method to another. */ private static void transferCodeAttributes(BCMethod from, BCMethod to) { Code code = from.getCode(false); if (code != null) { to.addAttribute(code); from.removeCode(); } Exceptions exceps = from.getExceptions(false); if (exceps != null) to.addAttribute(exceps); } /** * 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 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, new Configurations.Runnable() { @Override public boolean run(Options opts) throws IOException { OpenJPAConfiguration conf = new OpenJPAConfigurationImpl(); try { return PCEnhancer.run(conf, args, opts); } 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))); } } Project project = new Project(); BCClass bc; 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) bc = project.loadClass((String) o, loader); else bc = project.loadClass((Class) o); enhancer = new PCEnhancer(conf, bc, 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 (BCClass bc, ClassMetaData meta); boolean skipEnhance(BCMethod m); } private void addGetIDOwningClass() throws NoSuchMethodException { BCMethod method = _pc.declareMethod(PRE + "GetIDOwningClass", Class.class, null); Code code = method.getCode(true); code.classconstant().setClass(getType(_meta)); code.areturn(); code.calculateMaxStack(); code.calculateMaxLocals(); } /** * 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. * * @throws - IllegalStateException if cls doesn't implement org.apache.openjpa.enhance.PersistenceCapable. * * @return true if the provided Class is down level from the current PCEnhancer.ENHANCER_VERSION. False * otherwise. */ 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. */ private int[] getIdClassConstructorParmOrder(Class oidType, ArrayList pkfields, FieldMetaData[] fmds) { Project project = new Project(); BCClass bc = project.loadClass(oidType); BCMethod[] methods = bc.getDeclaredMethods(""); if (methods == null || methods.length == 0) { return null; } int parmOrder[] = new int[pkfields.size()]; for (BCMethod method : methods) { // constructor must be public if (!method.isPublic()) { continue; } Class[] parmTypes = method.getParamTypes(); // make sure the constructors have the same # of parms as // the number of pk fields if (parmTypes.length != pkfields.size()) { continue; } int parmOrderIndex = 0; Code code = method.getCode(false); Instruction[] ins = code.getInstructions(); for (int i = 0; i < ins.length; i++) { if (ins[i] instanceof PutFieldInstruction) { PutFieldInstruction pfi = (PutFieldInstruction)ins[i]; for (int j = 0; j < pkfields.size(); j++) { int fieldNum = pkfields.get(j); // Compare the field being set with the current pk field String parmName = fmds[fieldNum].getName(); Class parmType = fmds[fieldNum].getType(); if (parmName.equals(pfi.getFieldName())) { // backup and examine the load instruction parm if (i > 0 && ins[i-1] instanceof LoadInstruction) { LoadInstruction li = (LoadInstruction)ins[i-1]; // 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 int parm = li.getLocal(); if (parm <= pkfields.size() && parmTypes[parm-1].equals(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; } } } } } if (parmOrderIndex == pkfields.size()) { return parmOrder; } } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy