Added: openjpa/branches/fb-3.0-asm/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancerSerp.java URL: http://svn.apache.org/viewvc/openjpa/branches/fb-3.0-asm/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancerSerp.java?rev=1760580&view=auto ============================================================================== --- openjpa/branches/fb-3.0-asm/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancerSerp.java (added) +++ openjpa/branches/fb-3.0-asm/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancerSerp.java Tue Sep 13 17:29:01 2016 @@ -0,0 +1,4950 @@ +/* + * 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.Serializable; +import java.io.ObjectStreamException; +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.lib.util.StringUtil; +import org.apache.openjpa.conf.OpenJPAConfiguration; +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.Services; +import org.apache.openjpa.lib.util.Localizer.Message; +import org.apache.openjpa.lib.util.svn.SVNUtils; +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.MetaDataRepository; +import org.apache.openjpa.meta.ValueStrategies; +import org.apache.openjpa.util.ApplicationIds; +import org.apache.openjpa.util.GeneralException; +import org.apache.openjpa.util.InternalException; +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.Id; +import org.apache.openjpa.util.IntId; +import org.apache.openjpa.util.FloatId; +import org.apache.openjpa.util.LongId; +import org.apache.openjpa.util.ObjectId; +import org.apache.openjpa.util.ShortId; +import org.apache.openjpa.util.StringId; +import org.apache.openjpa.util.OpenJPAException; +import org.apache.openjpa.util.UserException; +import org.apache.openjpa.util.ImplHelper; +import serp.bytecode.BCClass; +import serp.bytecode.BCField; +import serp.bytecode.BCMethod; +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; +import serp.bytecode.ClassInstruction; + +/** + * 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 PCEnhancerSerp { + // 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 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 AuxiliarySerpEnhancer[] _auxEnhancers; + static { + Class[] classes = Services.getImplementorClasses( + AuxiliarySerpEnhancer.class, + AccessController.doPrivileged( + J2DoPrivHelper.getClassLoaderAction(AuxiliarySerpEnhancer.class))); + List auxEnhancers = new ArrayList(classes.length); + for (int i = 0; i < classes.length; i++) { + try { + auxEnhancers.add(AccessController.doPrivileged( + J2DoPrivHelper.newInstanceAction(classes[i]))); + } catch (Throwable t) { + // aux enhancer may rely on non-existant spec classes, etc + } + } + _auxEnhancers = (AuxiliarySerpEnhancer[]) auxEnhancers.toArray + (new AuxiliarySerpEnhancer[auxEnhancers.size()]); + + int rev = 0; + Properties revisionProps = new Properties(); + try { + InputStream in = PCEnhancerSerp.class.getResourceAsStream("/META-INF/org.apache.openjpa.revision.properties"); + if (in != null) { + try { + revisionProps.load(in); + } finally { + in.close(); + } + } + String prop = revisionProps.getProperty("openjpa.enhancer.revision"); + rev = SVNUtils.svnInfoToInteger(prop); + } 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. + // As of OpenJPA-3.0.0 we switched to '3' as we are now using ASM to generate the bytecode + ENHANCER_VERSION = 3; + } + } + + 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 <code>type</code> from <code>conf</code>'s + * repository. + */ + public PCEnhancerSerp(OpenJPAConfiguration conf, Class type) { + this(conf, AccessController.doPrivileged(J2DoPrivHelper + .loadProjectClassAction(new Project(), type)), + (MetaDataRepository) 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 + * @deprecated use {@link #PCEnhancerSerp(OpenJPAConfiguration, BCClass, + MetaDataRepository, ClassLoader)} instead. + */ + public PCEnhancerSerp(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 PCEnhancerSerp(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(MetaDataRepository.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 PCEnhancerSerp(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(PCEnhancerSerp.class) + "." + + cls.getName().replace('.', '$') + "$pcsubclass"; + } + + /** + * Whether or not <code>className</code> 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(PCEnhancerSerp.class)) + && className.endsWith("$pcsubclass"); + } + + /** + * If <code>className</code> is a dynamically-created persistence-capable + * subclass name, returns the name of the class that it subclasses. + * Otherwise, returns <code>className</code>. + * + * @since 1.1.0 + */ + public static String toManagedTypeName(String className) { + if (isPCSubclassName(className)) { + className = className.substring( + ClassUtil.getPackageName(PCEnhancerSerp.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 PCEnhancerSerp(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 + * <code>setCreateSubclass(true)</code>. + * + * @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 + * <code>setCreateSubclass(true)</code>. + * + * @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; + } + + /** + * Set the {@link BytecodeWriter} to write the bytecode to or null if none. + */ + public void setBytecodeWriter(BytecodeWriter writer) { + _writer = writer; + } + + /** + * Perform bytecode enhancements. + * + * @return <code>ENHANCE_*</code> constant + */ + public int run() { + Class<?> type = _managedType.getType(); + try { + // if managed interface, skip + if (_pc.isInterface()) + return PCEnhancer.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 PCEnhancer.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 PCEnhancer.ENHANCE_PC; + } + return PCEnhancer.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 (Iterator itr = _oids.iterator(); itr.hasNext();) + record((BCClass) itr.next()); + } + + /** + * 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 (int i = 0; i < fmds.length; i++) { + + if (!(fmds[i].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[]{ fmds[i], fmds[i].getBackingMember() }, + true); + } + continue; + } + + meth = (Method) fmds[i].getBackingMember(); + // ##### this will fail if we override and don't call super. + BCClass declaringType = _managedType.getProject() + .loadClass(fmds[i].getDeclaringType()); + getter = declaringType.getDeclaredMethod(meth.getName(), + meth.getParameterTypes()); + if (getter == null) { + addViolation("property-no-getter", new Object[]{ fmds[i] }, + true); + continue; + } + returned = getReturnedField(getter); + if (returned != null) + registerBackingFieldInfo(fmds[i], getter, returned); + + setter = declaringType.getDeclaredMethod(getSetterName(fmds[i]), + new Class[]{ fmds[i].getDeclaredType() }); + if (setter == null) { + if (returned == null) { + addViolation("property-no-setter", + new Object[]{ fmds[i] }, true); + continue; + } else if (!getRedefine()) { + // create synthetic setter + setter = _managedType.declareMethod(getSetterName(fmds[i]), + void.class, new Class[]{ fmds[i].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(fmds[i], setter, assigned); + + if (assigned != returned) + addViolation("property-setter-getter-mismatch", new Object[] + { fmds[i], 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<Integer> propFmds = new ArrayList<Integer>(); + FieldMetaData[] fmds = _meta.getFields(); + + if (_meta.isMixedAccess()) { + // Stores indexes of property access fields to be used in + // + propFmds = new ArrayList<Integer>(); + + // 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 (int i = 0; i < fmds.length; i++) { + tabins.addTarget(code.constant().setValue( + _attrsToFields.get(fmds[i].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 <code>meth</code>. 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 (int i = 0; i < methods.length; i++) { + code = methods[i].getCode(false); + + // don't modify the methods specified by the auxiliary enhancers + if (code != null && !skipEnhance(methods[i])) { + 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, <absolute-index>); + 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, <absolute-index>, 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 !"<init>".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 <code>pcIsTransactional</code>, + * <code>pcFetchObjectId</code>, 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() == true) { + 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 (int i = 0; i < fmds.length; i++) { + if (fmds[i].getManagement() != FieldMetaData.MANAGE_PERSISTENT) + continue; + + loadManagedInstance(code, false); + switch (fmds[i].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, fmds[i]); + } + + code.vreturn(); + code.calculateMaxStack(); + code.calculateMaxLocals(); + } + + /** + * Adds the <code>pcNewInstance</code> 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("<init>", 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 <code>protected static int pcGetManagedFieldCount ()</code> + * 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 <fields> + 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 <clinit> is ever invoked, and so our + // pcInheritedFieldCount field isn't initialized! so instead, + // return <fields> + <superclass>.pcGetManagedFieldCount () + code.constant().setValue(_meta.getDeclaredFields().length); + if (_meta.getPCSuperclass() != null) { + Class superClass = getType(_meta.getPCSuperclassMetaData()); + String superName = getCreateSubclass() ? + PCEnhancerSerp.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); + + // <field> = pcStateManager.provided<type>Field + // (this, fieldNumber); + for (int i = 0; i < fmds.length; i++) { + tabins.addTarget(loadManagedInstance(code, false)); + code.getfield().setField(SM, SMTYPE); + loadManagedInstance(code, false); + code.iload().setParam(0); + loadManagedInstance(code, false); + addGetManagedValueCode(code, fmds[i]); + code.invokeinterface().setMethod(getStateManagerMethod + (fmds[i].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); + + // <field> = pcStateManager.replace<type>Field + // (this, fieldNumber); + for (int i = 0; i < fmds.length; i++) { + // for the addSetManagedValueCode call below. + tabins.addTarget(loadManagedInstance(code, false, fmds[i])); + + loadManagedInstance(code, false, fmds[i]); + code.getfield().setField(SM, SMTYPE); + loadManagedInstance(code, false, fmds[i]); + code.iload().setParam(0); + code.invokeinterface().setMethod(getStateManagerMethod + (fmds[i].getDeclaredType(), "replace", true, false)); + if (!fmds[i].getDeclaredType().isPrimitive()) + code.checkcast().setType(fmds[i].getDeclaredType()); + + addSetManagedValueCode(code, fmds[i]); + if(_addVersionInitFlag){ + if(fmds[i].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 (int i = 0; i < fmds.length; i++) { + // <field> = other.<field>; + // or set<field> (other.get<field>); + tabins.addTarget(loadManagedInstance(code, false, fmds[i])); + code.aload().setParam(0); + addGetManagedValueCode(code, fmds[i], false); + addSetManagedValueCode(code, fmds[i]); + + // 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 <method>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(); + + // <method> (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 <default>; + 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.<method> (<args>); + // 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 <versionField>; + 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, "<init>", 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 <code>pcCopyKeyFieldsToObjectId</code> 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); + } + + // <oid type> id = (<oid type>) 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.<field> = fs.fetch<type>Field (<index>); or... + // id.<field> = pc.<field>; + 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<Integer> 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, "<init>", 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,
[... 2727 lines stripped ...]
