/* * 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.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.Options; 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 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 (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 = (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(); } } 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. 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 <code>type</code> from <code>conf</code>'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 <code>meta</code> by converting back to a class * and then loading from <code>conf</code>'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. */ 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(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 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 <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(PCEnhancer.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(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 * <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; } /** * 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 <code>ENHANCE_*</code> constant */ public int run() { Class<?> type = _managedType.getType(); try { // 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 (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() ? 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); // <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, 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, "<init>", 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, "<init>", 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, "<init>", 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, "<init>", 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, "<init>", 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, "<init>", 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, "<init>", 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 = <default>; 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 <code>pcCopyKeyFieldsFromObjectId</code> 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); } // <oid type> cast = (<oid type>) oid; int id = code.getNextLocalsIndex(); Class oidType = _meta.getObjectIdType(); code.checkcast().setType(oidType); code.astore().setLocal(id); // fs.store<type>Field (<index>, id.<field>); or... // this.<field> = id.<field> // 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, "<init>", 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, "<init>", 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() == true) { code.aload().setThis(); code.invokevirtual().setMethod(PRE + "GetIDOwningClass", Class.class, null); } else { code.classconstant().setClass(getType(_meta)); } } // new <oid class> (); 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() == true) { 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 <type>Identity (XXX.class, <pk>); 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, "<init>", void.class, args); if (!_meta.isOpenJPAIdentity() && _meta.isObjectIdTypeShared()) code.invokespecial().setMethod(ObjectId.class, "<init>", 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, "<init>", 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("<init>", (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: * <ul> * <li><code>private static int pcInheritedFieldCount</code></li> * <li><code>private static Class pcPCSuperclass</code> * </li> * <li><code>private static String[] pcFieldNames</code></li> * <li><code>private static Class[] pcFieldTypes</code></li> * <li><code>private static byte[] pcFieldFlags</code></li> * <li><code>protected transient StateManager pcStateManager</code> * if no PersistenceCapable superclass present)</li> * </ul> */ 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 = <superClass>.pcGetManagedFieldCount() code.invokestatic().setMethod(getType(_meta. getPCSuperclassMetaData()).getName(), PRE + "GetManagedFieldCount", int.class.getName(), null); code.putstatic().setField(INHERIT, int.class); } // pcPCSuperclass = <superClass>; // this intentionally calls getDescribedType() directly // instead of PCEnhancer.getType() code.classconstant().setClass( _meta.getPCSuperclassMetaData().getDescribedType()); code.putstatic().setField(SUPER, Class.class); } // pcFieldNames = new String[] { "<name1>", "<name2>", ... }; 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[] { <type1>.class, <type2>.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[] { <flag1>, <flag2>, ... }; 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("<init>", 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 <code>serialVersionUID</code> constant if not already defined, * as well as creating a custom <code>writeObject</code> 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 <managed-type>() code.anew().setType(_managedType); // for return code.dup(); // for post-<init> work code.dup(); // for <init> code.invokespecial().setMethod(_managedType.getType(), "<init>", void.class, null); // copy all the fields. // ##### limiting to JPA @Transient limitations FieldMetaData[] fmds = _meta.getFields(); for (int i = 0; i < fmds.length; i++) { if (fmds[i].isTransient()) continue; // o.<field> = this.<field> (or reflective analog) code.dup(); // for putfield code.aload().setThis(); // for getfield getfield(code, _managedType, fmds[i].getName()); putfield(code, _managedType, fmds[i].getName(), fmds[i].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 <code>pcSerializing</code> 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 (<version> != <default>) // 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 (<pk> != <default> [&& !"".equals (<pk>)]) // return Boolean.TRUE; FieldMetaData[] pks = _meta.getPrimaryKeyFields(); for (int i = 0; i < pks.length; i++) { if (pks[i].getValueStrategy() == ValueStrategies.NONE) continue; target = loadManagedInstance(code, false); if (ifins != null) ifins.setTarget(target); if (ifins2 != null) ifins2.setTarget(target); ifins2 = null; addGetManagedValueCode(code, pks[i]); ifins = ifDefaultValue(code, pks[i]); if (pks[i].getDeclaredTypeCode() == JavaTypes.STRING) { code.constant().setValue(""); loadManagedInstance(code, false); addGetManagedValueCode(code, pks[i]); 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("<clinit>"); 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("<clinit>", 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 <code>pcStateManager</code> * 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)) { // ((<type>) 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 (int i = 0; i < _auxEnhancers.length; i++) _auxEnhancers[i].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 ("<init>".equals(method.getName())) return true; for (int i = 0; i < _auxEnhancers.length; i++) if (_auxEnhancers[i].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.<field>; 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 + <fieldindex>; 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.<field>; 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.<field> = 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()==true && _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.setting<fieldType>Field (inst, // pcInheritedFieldCount + <index>, inst.<field>, 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>code</code> the instructions to get field * <code>attrName</code> declared in type <code>declarer</code> * 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 (int i = 0; i < fields.length; i++) { field = fields[i]; // if we reach a field declared in this type, then this is the // most-masking field, and is the one that we want. if (fields[i].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>code</code> the instructions to set field * <code>attrName</code> declared in type <code>declarer</code> * to the value of type <code>fieldType</code> 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>code</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 <code>name</code>. */ 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 <code>name</code>. */ 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("<init>", (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 (int i = 0; i < fields.length; i++) { if (!fields[i].isTransient() && !fields[i].isStatic() && !fields[i].isFinal() && !fields[i].getName().startsWith(PRE) && _meta.getDeclaredField(fields[i].getName()) == null) unmgd.add(fields[i]); } 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 (int i = 0; i < fmds.length; i++) { if (!fmds[i].isTransient()) { readExternal(code, fmds[i].getName(), fmds[i].getDeclaredType(), fmds[i]); } } 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 (Iterator itr = unmgd.iterator(); itr.hasNext();) { field = (BCField) itr.next(); 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"; // <field> = in.read<type> (); 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 (<index>); 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 (int i = 0; i < fmds.length; i++) { if (!fmds[i].isTransient()) { writeExternal(code, fmds[i].getName(), fmds[i].getDeclaredType(), fmds[i]); } } // 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 (Iterator itr = unmgd.iterator(); itr.hasNext();) { field = (BCField) itr.next(); 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<type> (<field>); 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 <code>fmd</code> 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 <code>true</code>, then <code>fmd</code> is * being loaded from an instance of the same class as the current execution * context. If <code>false</code>, 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 <code>fmd</code>. 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 <code>forStatic</code> is set, then * <code>code</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>code</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>code</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 <code>fmd</code>. The * calling environment will then populate this method's code block. */ private BCMethod createGetMethod(FieldMetaData fmd) { BCMethod getter; if (isFieldAccess(fmd)) { // static <fieldtype> pcGet<field> (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 <code>fmd</code>. The * calling environment will then populate this method's code block. */ private BCMethod createSetMethod(FieldMetaData fmd) { BCMethod setter; if (isFieldAccess(fmd)) { // static void pcSet<field> (XXX inst, <fieldtype> 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. * <ul> * <li><i>-properties/-p <properties file></i>: The path to a OpenJPA * properties file containing information as outlined in * {@link Configuration}; optional.</li> * <li><i>-<property name> <property value></i>: All bean * properties of the standard OpenJPA {@link OpenJPAConfiguration} can be * set by using their names and supplying a value; for example: * <li><i>-directory/-d <build directory></i>: 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.</li> * <li><i>-addDefaultConstructor/-adc [true/t | false/f]</i>: Whether to * add a default constructor to persistent classes missing one, as * opposed to throwing an exception. Defaults to true.</li> * <li><i>-tmpClassLoader/-tcl [true/t | false/f]</i>: 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.</li> * <li><i>-enforcePropertyRestrictions/-epr [true/t | false/f]</i>: * Whether to throw an exception if a PROPERTY access entity appears * to be violating standard property restrictions. Defaults to false.</li> * </ul> * 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() { 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(MetaDataRepository.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 (int i = 0; i < args.length; i++) classes.addAll(Arrays.asList(cap.parseTypes(args[i]))); } Project project = new Project(); BCClass bc; PCEnhancer enhancer; Collection persAwareClasses = new HashSet(); int status; for (Iterator itr = classes.iterator(); itr.hasNext();) { Object o = itr.next(); 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 static interface AuxiliaryEnhancer { public void run (BCClass bc, ClassMetaData meta); public 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<Integer> 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<Integer> pkFields = new ArrayList<Integer>(); // 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<Integer> pkfields, FieldMetaData[] fmds) { Project project = new Project(); BCClass bc = project.loadClass(oidType); BCMethod[] methods = bc.getDeclaredMethods("<init>"); 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; } }