/*
* Copyright 2006-2012 Amazon Technologies, Inc. or its affiliates.
* Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
* of Amazon Technologies, Inc. or its affiliates. All rights reserved.
*
* Licensed 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 com.amazon.carbonado.gen;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.math.BigInteger;
import org.cojen.classfile.ClassFile;
import org.cojen.classfile.CodeBuilder;
import org.cojen.classfile.Label;
import org.cojen.classfile.LocalVariable;
import org.cojen.classfile.MethodDesc;
import org.cojen.classfile.MethodInfo;
import org.cojen.classfile.Modifiers;
import org.cojen.classfile.Opcode;
import org.cojen.classfile.TypeDesc;
import org.cojen.util.ClassInjector;
import org.cojen.util.WeakIdentityMap;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.FetchNoneException;
import com.amazon.carbonado.PersistException;
import com.amazon.carbonado.PersistNoneException;
import com.amazon.carbonado.Query;
import com.amazon.carbonado.Repository;
import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Storage;
import com.amazon.carbonado.SupportException;
import com.amazon.carbonado.Transaction;
import com.amazon.carbonado.Trigger;
import com.amazon.carbonado.UniqueConstraintException;
import com.amazon.carbonado.lob.Lob;
import com.amazon.carbonado.info.ChainedProperty;
import com.amazon.carbonado.info.OrderedProperty;
import com.amazon.carbonado.info.StorableInfo;
import com.amazon.carbonado.info.StorableIntrospector;
import com.amazon.carbonado.info.StorableKey;
import com.amazon.carbonado.info.StorableProperty;
import com.amazon.carbonado.info.StorablePropertyAdapter;
import com.amazon.carbonado.info.StorablePropertyAnnotation;
import com.amazon.carbonado.info.StorablePropertyConstraint;
import com.amazon.carbonado.raw.DataDecoder;
import com.amazon.carbonado.raw.DataEncoder;
import com.amazon.carbonado.raw.GenericEncodingStrategy;
import static com.amazon.carbonado.gen.CommonMethodNames.*;
/**
* Generates and caches abstract implementations of {@link Storable}
* types. This greatly simplifies the process of defining new kinds of {@link
* Repository Repositories}, since most of the mundane code generation is taken
* care of.
*
* @author Brian S O'Neill
* @author Don Schneider
* @author Tobias Holgers
* @see MasterStorableGenerator
* @see DelegateStorableGenerator
* @since 1.2
*/
public final class StorableGenerator<S extends Storable> {
// Note: All generated fields/methods have a "$" character in them to
// prevent name collisions with any inherited fields/methods. User storable
// properties are defined as fields which exactly match the property
// name. We don't want collisions with those either. Legal bean properties
// cannot have "$" in them, so there's nothing to worry about.
/** Name of protected abstract method in generated storable */
public static final String
DO_TRY_LOAD_METHOD_NAME = "doTryLoad$",
DO_TRY_INSERT_METHOD_NAME = "doTryInsert$",
DO_TRY_UPDATE_METHOD_NAME = "doTryUpdate$",
DO_TRY_DELETE_METHOD_NAME = "doTryDelete$";
/**
* Name of protected method in generated storable which checks that
* primary keys are initialized, throwing an exception otherwise.
*/
public static final String
CHECK_PK_FOR_INSERT_METHOD_NAME = "checkPkForInsert$",
CHECK_PK_FOR_UPDATE_METHOD_NAME = "checkPkForUpdate$",
CHECK_PK_FOR_DELETE_METHOD_NAME = "checkPkForDelete$",
CHECK_PK_FOR_LOAD_METHOD_NAME = "checkPkForLoad$";
/**
* Name of protected method in generated storable that returns false if any
* primary keys are uninitialized.
*/
public static final String IS_PK_INITIALIZED_METHOD_NAME = "isPkInitialized$";
/**
* Name of protected method in generated storable that returns false if any
* partition keys are uninitialized.
*/
public static final String IS_PARTITION_KEY_INITIALIZED_METHOD_NAME = "isPartitionKeyInitialized$";
/**
* Name prefix of protected method in generated storable that returns false
* if a specific alternate key is uninitialized. The complete name is
* formed by the prefix appended with the zero-based alternate key number.
*/
public static final String IS_ALT_KEY_INITIALIZED_PREFIX = "isAltKeyInitialized$";
/**
* Name of protected method in generated storable that returns false if any
* non-nullable, non-pk properties are uninitialized.
*/
public static final String IS_REQUIRED_DATA_INITIALIZED_METHOD_NAME =
"isRequiredDataInitialized$";
/**
* Name of protected method in generated storable that returns false if
* version property is uninitialized. If no version property exists, then
* this method is not defined.
*/
public static final String IS_VERSION_INITIALIZED_METHOD_NAME = "isVersionInitialized$";
/**
* Name of protected method which must be called after load to identify all
* properties as valid and to fire any load triggers.
*
* @since 1.2
*/
public static final String LOAD_COMPLETED_METHOD_NAME = "loadCompleted$";
/**
* Prefix of protected field in generated storable that holds property
* states. Each property consumes two bits to hold its state, and so each
* 32-bit field holds states for up to 16 properties.
*/
public static final String PROPERTY_STATE_FIELD_NAME = "propertyState$";
/** Adapter field names are propertyName + "$adapter$" + number */
public static final String ADAPTER_FIELD_ELEMENT = "$adapter$";
/** Constraint field names are propertyName + "$constraint$" + number */
public static final String CONSTRAINT_FIELD_ELEMENT = "$constraint$";
/** Reference to TriggerSupport instance */
public static final String SUPPORT_FIELD_NAME = "support$";
/** Property state indicating that property has never been set, loaded, or saved */
public static final int PROPERTY_STATE_UNINITIALIZED = 0;
/** Property state indicating that property has been set, but not saved */
public static final int PROPERTY_STATE_DIRTY = 3;
/** Property state indicating that property value reflects a clean value */
public static final int PROPERTY_STATE_CLEAN = 1;
/** Property state mask is 3, to cover the two bits used by a property state */
public static final int PROPERTY_STATE_MASK = 3;
// Private method which returns a property's state.
private static final String PROPERTY_STATE_EXTRACT_METHOD_NAME = "extractState$";
private static final String PRIVATE_INSERT_METHOD_NAME = "insert$";
private static final String PRIVATE_UPDATE_METHOD_NAME = "update$";
private static final String PRIVATE_DELETE_METHOD_NAME = "delete$";
// Cache of generated abstract classes.
private static Map<Class, Reference<Class<? extends Storable>>> cAbstractCache;
static {
cAbstractCache = new WeakIdentityMap();
}
// When true, calls to get uninitialized properties throw an
// IllegalStateException. By default it is false, but the default will
// change to true when query projection is supported.
private static final boolean cStrictAccess;
static {
String value = System.getProperty(StorableGenerator.class.getName() + ".strictAccess");
cStrictAccess = value != null && value.equals("true");
}
// There are three flavors of equals methods, used by addEqualsMethod.
private static final int EQUAL_KEYS = 0;
private static final int EQUAL_PROPERTIES = 1;
private static final int EQUAL_FULL = 2;
private static final String UNCAUGHT_METHOD_NAME = "uncaught$";
private static final String INSERT_OP = "Insert";
private static final String UPDATE_OP = "Update";
private static final String DELETE_OP = "Delete";
// Different uses for generated property switch statements.
private static final int SWITCH_FOR_STATE = 1, SWITCH_FOR_GET = 2, SWITCH_FOR_SET = 3;
/**
* Returns an abstract implementation of the given Storable type, which is
* fully thread-safe. The Storable type itself may be an interface or a
* class. If it is a class, then it must not be final, and it must have a
* public, no-arg constructor. The constructor signature for the returned
* abstract class is defined as follows:
*
* <pre>
* /**
* * @param support Access to triggers
* */
* public <init>(TriggerSupport support);
* </pre>
*
* <p>Subclasses must implement the following abstract protected methods,
* whose exact names are defined by constants in this class:
*
* <pre>
* // Load the object by examining the primary key.
* protected abstract boolean doTryLoad() throws FetchException;
*
* // Insert the object into the storage layer.
* protected abstract boolean doTryInsert() throws PersistException;
*
* // Update the object in the storage.
* protected abstract boolean doTryUpdate() throws PersistException;
*
* // Delete the object from the storage layer by the primary key.
* protected abstract boolean doTryDelete() throws PersistException;
* </pre>
*
* A set of protected hook methods are provided which ensure that all
* primary keys are initialized before performing a repository
* operation. Subclasses may override them, if they are capable of filling
* in unspecified primary keys. One such example is applying a sequence on
* insert.
*
* <pre>
* // Throws exception if any primary keys are uninitialized.
* // Actual method name defined by CHECK_PK_FOR_INSERT_METHOD_NAME.
* protected void checkPkForInsert() throws IllegalStateException;
*
* // Throws exception if any primary keys are uninitialized.
* // Actual method name defined by CHECK_PK_FOR_UPDATE_METHOD_NAME.
* protected void checkPkForUpdate() throws IllegalStateException;
*
* // Throws exception if any primary keys are uninitialized.
* // Actual method name defined by CHECK_PK_FOR_DELETE_METHOD_NAME.
* protected void checkPkForDelete() throws IllegalStateException;
* </pre>
*
* Each property value is defined as a protected field whose name and type
* matches the property. Subclasses should access these fields directly
* during loading and storing. For loading, it bypasses constraint
* checks. For both, it provides better performance.
*
* <p>Subclasses also have access to a set of property state bits stored
* in protected int fields. Subclasses are not responsible for updating
* these values. The intention is that these states may be used by
* subclasses to support partial updates. They may otherwise be ignored.
*
* <p>As a convenience, protected methods are provided to test and alter
* the property state bits. Subclass constructors that fill all properties
* with loaded values must call loadCompleted to ensure all properties are
* identified as being valid and to fire any load triggers.
*
* <pre>
* // Returns true if all primary key properties have been set.
* protected boolean isPkInitialized();
*
* // Returns true if all partition key properties have been set.
* protected boolean isPartitionKeyInitialized();
*
* // Returns true if all required data properties are set.
* // A required data property is a non-nullable, non-primary key.
* protected boolean isRequiredDataInitialized();
*
* // Returns true if a version property has been set.
* // Note: This method is not generated if there is no version property.
* protected boolean isVersionInitialized();
*
* // Must be called after load to identify all properties as valid
* // and to fire any load triggers.
* // Actual method name defined by LOAD_COMPLETED_METHOD_NAME.
* protected void loadCompleted() throws FetchException;
* </pre>
*
* Property state field names are defined by the concatenation of
* {@code PROPERTY_STATE_FIELD_NAME} and a zero-based decimal
* number. To determine which field holds a particular property's state,
* the field number is computed as the property number divided by 16. The
* specific two-bit state position is the remainder of this division times 2.
*
* @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed
* @throws IllegalArgumentException if type is null
*/
@SuppressWarnings("unchecked")
public static <S extends Storable> Class<? extends S> getAbstractClass(Class<S> type)
throws IllegalArgumentException
{
synchronized (cAbstractCache) {
Class<? extends S> abstractClass;
Reference<Class<? extends Storable>> ref = cAbstractCache.get(type);
if (ref != null) {
abstractClass = (Class<? extends S>) ref.get();
if (abstractClass != null) {
return abstractClass;
}
}
abstractClass = new StorableGenerator<S>(type).generateAndInjectClass();
cAbstractCache.put(type, new SoftReference<Class<? extends Storable>>(abstractClass));
return abstractClass;
}
}
private final Class<S> mStorableType;
private final TypeDesc mSupportType;
private final StorableInfo<S> mInfo;
private final Map<String, ? extends StorableProperty<S>> mAllProperties;
private final ClassInjector mClassInjector;
private final ClassFile mClassFile;
private StorableGenerator(Class<S> storableType) {
mStorableType = storableType;
mSupportType = TypeDesc.forClass(TriggerSupport.class);
mInfo = StorableIntrospector.examine(storableType);
mAllProperties = mInfo.getAllProperties();
mClassInjector = ClassInjector.create
(storableType.getName(), storableType.getClassLoader());
mClassFile = CodeBuilderUtil.createStorableClassFile
(mClassInjector, storableType, true,
StorableGenerator.class.getName());
}
private Class<? extends S> generateAndInjectClass() {
generateClass();
Class abstractClass = mClassInjector.defineClass(mClassFile);
return abstractClass;
}
private void generateClass() {
// Use this static method for passing uncaught exceptions.
defineUncaughtExceptionHandler();
// private final TriggerSupport support;
mClassFile.addField(Modifiers.PROTECTED.toFinal(true),
SUPPORT_FIELD_NAME,
mSupportType);
{
// Add protected constructor.
TypeDesc[] params = {mSupportType};
final int supportParam = 0;
MethodInfo mi = mClassFile.addConstructor(Modifiers.PROTECTED, params);
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.invokeSuperConstructor(null);
//// this.support = support
b.loadThis();
b.loadLocal(b.getParameter(supportParam));
b.storeField(SUPPORT_FIELD_NAME, mSupportType);
b.returnVoid();
}
// Add static fields for adapters and constraints, and create static
// initializer to populate fields.
{
// CodeBuilder for static initializer, defined only if there's
// something to put in it.
CodeBuilder clinit = null;
// Adapter and constraint fields are protected static.
final Modifiers fieldModifiers = Modifiers.PROTECTED.toStatic(true).toFinal(true);
// Add adapter field.
for (StorableProperty property : mAllProperties.values()) {
StorablePropertyAdapter spa = property.getAdapter();
if (spa == null) {
continue;
}
String fieldName = property.getName() + ADAPTER_FIELD_ELEMENT + 0;
TypeDesc adapterType = TypeDesc.forClass
(spa.getAdapterConstructor().getDeclaringClass());
mClassFile.addField(fieldModifiers, fieldName, adapterType);
if (clinit == null) {
clinit = new CodeBuilder(mClassFile.addInitializer());
}
// Assign value to new field.
// admin$adapter$0 = new YesNoAdapter.Adapter
// (UserInfo.class, "admin", annotation);
clinit.newObject(adapterType);
clinit.dup();
clinit.loadConstant(TypeDesc.forClass(mStorableType));
clinit.loadConstant(property.getName());
// Generate code to load property annotation third parameter.
loadPropertyAnnotation(clinit, property, spa.getAnnotation());
clinit.invoke(spa.getAdapterConstructor());
clinit.storeStaticField(fieldName, adapterType);
}
// Add contraint fields.
for (StorableProperty property : mAllProperties.values()) {
int count = property.getConstraintCount();
for (int i=0; i<count; i++) {
StorablePropertyConstraint spc = property.getConstraint(i);
String fieldName = property.getName() + CONSTRAINT_FIELD_ELEMENT + i;
TypeDesc constraintType = TypeDesc.forClass
(spc.getConstraintConstructor().getDeclaringClass());
mClassFile.addField(fieldModifiers, fieldName, constraintType);
if (clinit == null) {
clinit = new CodeBuilder(mClassFile.addInitializer());
}
// Assign value to new field.
// admin$constraint$0 = new LengthConstraint.Constraint
// (UserInfo.class, "firstName", annotation);
clinit.newObject(constraintType);
clinit.dup();
clinit.loadConstant(TypeDesc.forClass(mStorableType));
clinit.loadConstant(property.getName());
// Generate code to load property annotation third parameter.
loadPropertyAnnotation(clinit, property, spc.getAnnotation());
clinit.invoke(spc.getConstraintConstructor());
clinit.storeStaticField(fieldName, constraintType);
}
}
if (clinit != null) {
// Must return else verifier complains.
clinit.returnVoid();
}
}
// Add property fields and methods.
// Also remember ordinal of optional version property for use later.
int versionOrdinal = -1;
{
int maxOrdinal = mAllProperties.size() - 1;
boolean requireStateField = false;
for (StorableProperty<S> property : mAllProperties.values()) {
int ordinal = property.getNumber();
if (!property.isDerived() && property.isVersion()) {
versionOrdinal = ordinal;
}
final String name = property.getName();
final TypeDesc type = TypeDesc.forClass(property.getType());
if (!property.isDerived()) {
if (property.isJoin()) {
// Mark as transient since join properties can be
// reconstructed from the other fields.
mClassFile.addField(Modifiers.PRIVATE.toTransient(true), name, type);
requireStateField = true;
} else {
// Double words are volatile to prevent word tearing
// without explicit synchronization. When strict access
// is enabled, all access methods are synchronized anyhow.
boolean isVolatile = (!cStrictAccess) && type.isDoubleWord();
mClassFile.addField(Modifiers.PROTECTED.toVolatile(isVolatile),
name, type);
requireStateField = true;
}
}
final String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
if (ordinal == maxOrdinal || ((ordinal & 0xf) == 0xf)) {
if (requireStateField) {
mClassFile.addField
(Modifiers.PROTECTED, stateFieldName, TypeDesc.INT);
}
requireStateField = false;
}
// Add read method.
buildReadMethod: if (!property.isDerived()) {
Method readMethod = property.getReadMethod();
MethodInfo mi;
if (readMethod != null) {
mi = mClassFile.addMethod(readMethod);
} else {
// Add a synthetic protected read method.
String readName = property.getReadMethodName();
mi = mClassFile.addMethod(Modifiers.PROTECTED, readName, type, null);
mi.markSynthetic();
if (property.isJoin()) {
mi.addException(TypeDesc.forClass(FetchException.class));
}
}
if (cStrictAccess || property.isJoin()) {
// Synchronization is required for join property
// accessors, as they may alter bit masks. Synchronization is
// also required when strict access is enabled.
mi.setModifiers(mi.getModifiers().toSynchronized(true));
}
// Now add code that actually gets the property value.
CodeBuilder b = new CodeBuilder(mi);
if (!property.isJoin()) {
if (cStrictAccess) {
// Make sure property state allows access.
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2));
b.math(Opcode.IAND);
Label isValid = b.createLabel();
b.ifZeroComparisonBranch(isValid, "!=");
CodeBuilderUtil.throwConcatException
(b, IllegalStateException.class,
"Cannot access uninitialized property: ", name);
isValid.setLocation();
}
} else {
// Join properties support on-demand loading.
// Check if property has been loaded.
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2));
b.math(Opcode.IAND);
Label isLoaded = b.createLabel();
b.ifZeroComparisonBranch(isLoaded, "!=");
// Store loaded join result here.
LocalVariable join = b.createLocalVariable(name, type);
// Check if any internal properties are nullable, but
// the matching external property is not. If so, load
// each of these special internal values and check if
// null. If null, short-circuit the load and use null
// as the join result.
Label shortCircuit = b.createLabel();
buildShortCircuit: {
int count = property.getJoinElementCount();
nullPossible: {
for (int i=0; i<count; i++) {
StorableProperty internal = property.getInternalJoinElement(i);
StorableProperty external = property.getExternalJoinElement(i);
if (internal.isNullable() && !external.isNullable()) {
break nullPossible;
}
}
break buildShortCircuit;
}
for (int i=0; i<count; i++) {
StorableProperty internal = property.getInternalJoinElement(i);
StorableProperty external = property.getExternalJoinElement(i);
if (internal.isNullable() && !external.isNullable()) {
loadThisProperty(b, internal);
Label notNull = b.createLabel();
b.ifNullBranch(notNull, false);
b.loadNull();
b.storeLocal(join);
b.branch(shortCircuit);
notNull.setLocation();
}
}
}
// Get the storage for the join type.
loadStorageForFetch(b, TypeDesc.forClass(property.getJoinedType()));
TypeDesc storageType = TypeDesc.forClass(Storage.class);
// There are two ways that property can be loaded. The
// general form is to use a Query. Calling load on the
// property itself is preferred, but it is only
// possible if the join is against a key and all
// external properties have a write method.
boolean canUseDirectForm = !property.isQuery();
if (canUseDirectForm) {
int joinCount = property.getJoinElementCount();
for (int i=0; i<joinCount; i++) {
StorableProperty external = property.getExternalJoinElement(i);
if (external.getWriteMethod() == null) {
canUseDirectForm = false;
}
}
}
final TypeDesc storableDesc = TypeDesc.forClass(Storable.class);
if (canUseDirectForm) {
// Generate direct load form.
// Storage instance is already on the stack... replace it
// with an instance of the joined type.
b.invokeInterface
(storageType, PREPARE_METHOD_NAME, storableDesc, null);
b.checkCast(type);
b.storeLocal(join);
// Set the keys on the joined type.
int count = property.getJoinElementCount();
for (int i=0; i<count; i++) {
b.loadLocal(join);
StorableProperty internal = property.getInternalJoinElement(i);
StorableProperty external = property.getExternalJoinElement(i);
loadThisProperty(b, internal);
CodeBuilderUtil.convertValue
(b, internal.getType(), external.getType());
b.invoke(external.getWriteMethod());
}
// Now load the object.
b.loadLocal(join);
// Always call "try load", even for non-nullable joins.
b.invokeInterface
(storableDesc, TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null);
Label wasLoaded = b.createLabel();
b.ifZeroComparisonBranch(wasLoaded, "!=");
// Not loaded, so replace joined object with null.
b.loadNull();
b.storeLocal(join);
wasLoaded.setLocation();
} else {
// Generate query load form.
// Storage instance is already on the stack... replace it
// with a Query. First, we need to define the query string.
StringBuilder queryBuilder = new StringBuilder();
// Set the keys on the joined type.
int count = property.getJoinElementCount();
for (int i=0; i<count; i++) {
if (i > 0) {
queryBuilder.append(" & ");
}
queryBuilder.append(property.getExternalJoinElement(i).getName());
queryBuilder.append(" = ?");
}
b.loadConstant(queryBuilder.toString());
TypeDesc queryType = TypeDesc.forClass(Query.class);
b.invokeInterface(storageType, QUERY_METHOD_NAME, queryType,
new TypeDesc[]{TypeDesc.STRING});
// Now fill in the parameters of the query.
for (int i=0; i<count; i++) {
StorableProperty<S> internal = property.getInternalJoinElement(i);
loadThisProperty(b, internal);
TypeDesc bindType =
CodeBuilderUtil.bindQueryParam(internal.getType());
CodeBuilderUtil.convertValue
(b, internal.getType(), bindType.toClass());
b.invokeInterface(queryType, WITH_METHOD_NAME, queryType,
new TypeDesc[]{bindType});
}
// Now run the query.
if (property.isQuery()) {
// Just save and return the query.
b.storeLocal(join);
} else {
// Always call "try load", even for non-nullable joins.
b.invokeInterface
(queryType, TRY_LOAD_ONE_METHOD_NAME, storableDesc, null);
b.checkCast(type);
b.storeLocal(join);
}
}
// Store loaded property.
shortCircuit.setLocation();
b.loadThis();
b.loadLocal(join);
b.storeField(property.getName(), type);
// Add code to identify this property as being loaded,
// except if value is null and join is not nullable.
if (!property.isNullable()) {
b.loadLocal(join);
b.ifNullBranch(isLoaded, true);
}
b.loadThis();
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2));
b.math(Opcode.IOR);
b.storeField(stateFieldName, TypeDesc.INT);
isLoaded.setLocation();
}
// Load property value and return it.
loadThisProperty(b, property);
b.returnValue(type);
}
// Add write method.
buildWriteMethod: if (!property.isDerived() && !property.isQuery()) {
Method writeMethod = property.getWriteMethod();
MethodInfo mi;
if (writeMethod != null) {
mi = mClassFile.addMethod(writeMethod);
} else {
// Add a synthetic protected write method.
String writeName = property.getWriteMethodName();
mi = mClassFile.addMethod(Modifiers.PROTECTED, writeName, null,
new TypeDesc[]{type});
mi.markSynthetic();
}
mi.setModifiers(mi.getModifiers().toSynchronized(true));
CodeBuilder b = new CodeBuilder(mi);
// Primary keys cannot be altered if state is "clean".
if (property.isPrimaryKeyMember()) {
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2));
b.math(Opcode.IAND);
b.loadConstant(PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2));
Label isMutable = b.createLabel();
b.ifComparisonBranch(isMutable, "!=");
CodeBuilderUtil.throwException
(b, IllegalStateException.class, "Cannot alter primary key");
isMutable.setLocation();
}
int spcCount = property.getConstraintCount();
boolean nullNotAllowed =
!property.getType().isPrimitive() &&
!property.isJoin() && !property.isNullable();
if (nullNotAllowed || spcCount > 0) {
// Add constraint checks.
Label skipConstraints = b.createLabel();
if (nullNotAllowed) {
// Don't allow null value to be set.
b.loadLocal(b.getParameter(0));
Label notNull = b.createLabel();
b.ifNullBranch(notNull, false);
CodeBuilderUtil.throwConcatException
(b, IllegalArgumentException.class,
"Cannot set property \"", property.getName(), "\" to null");
notNull.setLocation();
} else {
// Don't invoke constraints if value is null.
if (!property.getType().isPrimitive()) {
b.loadLocal(b.getParameter(0));
b.ifNullBranch(skipConstraints, true);
}
}
// Add code to invoke constraints.
for (int spcIndex = 0; spcIndex < spcCount; spcIndex++) {
StorablePropertyConstraint spc = property.getConstraint(spcIndex);
String fieldName =
property.getName() + CONSTRAINT_FIELD_ELEMENT + spcIndex;
TypeDesc constraintType = TypeDesc.forClass
(spc.getConstraintConstructor().getDeclaringClass());
b.loadStaticField(fieldName, constraintType);
b.loadLocal(b.getParameter(0));
b.convert
(b.getParameter(0).getType(), TypeDesc.forClass
(spc.getConstrainMethod().getParameterTypes()[0]));
b.invoke(spc.getConstrainMethod());
}
skipConstraints.setLocation();
}
Label setValue = b.createLabel();
if (!property.isJoin() || Lob.class.isAssignableFrom(property.getType())) {
Label markDirty = b.createLabel();
if (Lob.class.isAssignableFrom(property.getType())) {
// Contrary to how standard properties are managed,
// only mark dirty if value changed. Exception is made
// for null -- always mark dirty. This allows LOB property
// to be updated to null without having to load it.
b.loadLocal(b.getParameter(0));
b.ifNullBranch(markDirty, true);
loadThisProperty(b, property);
LocalVariable tempProp = b.createLocalVariable(null, type);
b.storeLocal(tempProp);
b.loadLocal(tempProp);
b.ifNullBranch(markDirty, true);
b.loadLocal(tempProp);
b.loadLocal(b.getParameter(0));
CodeBuilderUtil.addValuesEqualCall(b, type, false, setValue, true);
}
markDirty.setLocation();
markOrdinaryPropertyDirty(b, property);
} else {
b.loadLocal(b.getParameter(0));
if (property.isNullable()) {
// Don't attempt to extract internal properties from null.
b.ifNullBranch(setValue, true);
} else {
Label notNull = b.createLabel();
b.ifNullBranch(notNull, false);
CodeBuilderUtil.throwConcatException
(b, IllegalArgumentException.class,
"Non-nullable join property \"", property.getName(),
"\" cannot be set to null");
notNull.setLocation();
}
// Copy internal properties from joined object.
int count = property.getJoinElementCount();
for (int i=0; i<count; i++) {
StorableProperty internal = property.getInternalJoinElement(i);
if (internal.getWriteMethod() == null) {
continue;
}
StorableProperty external = property.getExternalJoinElement(i);
b.loadLocal(b.getParameter(0));
b.invoke(external.getReadMethod());
CodeBuilderUtil.convertValue
(b, external.getType(), internal.getType());
LocalVariable newInternalPropVar =
b.createLocalVariable(null, TypeDesc.forClass(internal.getType()));
b.storeLocal(newInternalPropVar);
// Since join properties may be pre-loaded, they
// are set via the public write method. If internal
// property is clean and equal to new value, then
// don't set internal property. Doing so would mark
// it as dirty, which is not the right behavior
// when pre-loading join properties. The internal
// properties should remain clean.
Label setInternalProp = b.createLabel();
// Access state of internal property directly.
int ord = internal.getNumber();
b.loadThis();
b.loadField(PROPERTY_STATE_FIELD_NAME + (ord >> 4), TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_MASK << ((ord & 0xf) * 2));
b.math(Opcode.IAND);
b.loadConstant(PROPERTY_STATE_CLEAN << ((ord & 0xf) * 2));
// If not clean, skip equal check.
b.ifComparisonBranch(setInternalProp, "!=");
// If new internal property value is equal to
// existing value, skip setting it.
b.loadThis();
b.invoke(internal.getReadMethod());
b.loadLocal(newInternalPropVar);
Label skipSetInternalProp = b.createLabel();
CodeBuilderUtil.addValuesEqualCall
(b, TypeDesc.forClass(internal.getType()),
true, skipSetInternalProp, true);
setInternalProp.setLocation();
// Call set method to ensure that state bits are
// properly adjusted.
b.loadThis();
b.loadLocal(newInternalPropVar);
b.invoke(internal.getWriteMethod());
skipSetInternalProp.setLocation();
}
// Add code to identify this property as being loaded.
b.loadThis();
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2));
b.math(Opcode.IOR);
b.storeField(stateFieldName, TypeDesc.INT);
}
// Now add code that actually sets the property value.
setValue.setLocation();
b.loadThis();
b.loadLocal(b.getParameter(0));
b.storeField(property.getName(), type);
b.returnVoid();
}
// Add optional protected adapted read methods.
if (property.getAdapter() != null) {
// End name with '$' to prevent any possible collisions.
String readName = property.getReadMethodName() + '$';
StorablePropertyAdapter adapter = property.getAdapter();
for (Method adaptMethod : adapter.findAdaptMethodsFrom(type.toClass())) {
TypeDesc toType = TypeDesc.forClass(adaptMethod.getReturnType());
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED, readName, toType, null);
mi.markSynthetic();
// Now add code that actually gets the property value and
// then invokes adapt method.
CodeBuilder b = new CodeBuilder(mi);
// Push adapter class to stack.
String fieldName = property.getName() + ADAPTER_FIELD_ELEMENT + 0;
TypeDesc adapterType = TypeDesc.forClass
(adapter.getAdapterConstructor().getDeclaringClass());
b.loadStaticField(fieldName, adapterType);
// Load property value.
loadThisProperty(b, property);
b.invoke(adaptMethod);
b.returnValue(toType);
}
}
// Add optional protected adapted write methods.
// Note: Calling these methods does not affect any state bits.
// They are only intended to be used by subclasses during loading.
if (property.getAdapter() != null && !property.isDerived()) {
// End name with '$' to prevent any possible collisions.
String writeName = property.getWriteMethodName() + '$';
StorablePropertyAdapter adapter = property.getAdapter();
for (Method adaptMethod : adapter.findAdaptMethodsTo(type.toClass())) {
TypeDesc fromType = TypeDesc.forClass(adaptMethod.getParameterTypes()[0]);
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED, writeName, null, new TypeDesc[] {fromType});
mi.markSynthetic();
mi.setModifiers(mi.getModifiers().toSynchronized(true));
// Now add code that actually adapts parameter and then
// stores the property value.
CodeBuilder b = new CodeBuilder(mi);
// Push this in preparation for storing a field.
b.loadThis();
// Push adapter class to stack.
String fieldName = property.getName() + ADAPTER_FIELD_ELEMENT + 0;
TypeDesc adapterType = TypeDesc.forClass
(adapter.getAdapterConstructor().getDeclaringClass());
b.loadStaticField(fieldName, adapterType);
b.loadLocal(b.getParameter(0));
b.invoke(adaptMethod);
// Always store to field directly, to prevent state
// bits from changing.
b.storeField(property.getName(), type);
b.returnVoid();
}
}
addPropertyBridges(property);
}
}
// Add tryLoad method which delegates to abstract doTryLoad method.
addTryLoad: {
// Define the tryLoad method.
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(true),
TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null);
if (mi == null) {
break addTryLoad;
}
mi.addException(TypeDesc.forClass(FetchException.class));
CodeBuilder b = new CodeBuilder(mi);
// Add empty method that will be overrriden if there is a partition
// key to check that it is initialized
b.loadThis();
b.invokeVirtual(CHECK_PK_FOR_LOAD_METHOD_NAME, null, null);
CodeBuilder b1 = new CodeBuilder
(mClassFile.addMethod
(Modifiers.PROTECTED, CHECK_PK_FOR_LOAD_METHOD_NAME, null, null));
b1.loadThis();
b1.returnVoid();
// Check that primary key is initialized.
b.loadThis();
b.invokeVirtual(IS_PK_INITIALIZED_METHOD_NAME, TypeDesc.BOOLEAN, null);
Label pkInitialized = b.createLabel();
b.ifZeroComparisonBranch(pkInitialized, "!=");
Label loaded = b.createLabel();
Label notLoaded = b.createLabel();
if (mInfo.getAlternateKeyCount() == 0) {
CodeBuilderUtil.throwException(b, IllegalStateException.class,
"Primary key not fully specified");
} else {
// If any alternate keys, check them too.
// Load our Storage, in preparation for query against it.
loadStorageForFetch(b, TypeDesc.forClass(mStorableType));
Label runQuery = b.createLabel();
TypeDesc queryType = TypeDesc.forClass(Query.class);
for (int i=0; i<mInfo.getAlternateKeyCount(); i++) {
b.loadThis();
b.invokeVirtual(IS_ALT_KEY_INITIALIZED_PREFIX + i, TypeDesc.BOOLEAN, null);
Label noAltKey = b.createLabel();
b.ifZeroComparisonBranch(noAltKey, "==");
StorableKey<S> altKey = mInfo.getAlternateKey(i);
// Form query filter.
StringBuilder queryBuilder = new StringBuilder();
for (OrderedProperty<S> op : altKey.getProperties()) {
if (queryBuilder.length() > 0) {
queryBuilder.append(" & ");
}
queryBuilder.append(op.getChainedProperty().toString());
queryBuilder.append(" = ?");
}
// Get query instance from Storage already loaded on stack.
b.loadConstant(queryBuilder.toString());
b.invokeInterface(TypeDesc.forClass(Storage.class),
QUERY_METHOD_NAME, queryType,
new TypeDesc[]{TypeDesc.STRING});
// Now fill in the parameters of the query.
for (OrderedProperty<S> op : altKey.getProperties()) {
StorableProperty<S> prop = op.getChainedProperty().getPrimeProperty();
loadThisProperty(b, prop);
TypeDesc bindType = CodeBuilderUtil.bindQueryParam(prop.getType());
CodeBuilderUtil.convertValue(b, prop.getType(), bindType.toClass());
b.invokeInterface(queryType, WITH_METHOD_NAME, queryType,
new TypeDesc[]{bindType});
}
StorableKey<S> parKey = mInfo.getPartitionKey();
if (parKey != null) {
// Transfer set partition key properties to query.
for (OrderedProperty<S> op : parKey.getProperties()) {
if (altKey.getProperties().contains(op)) {
// Was already supplied to query.
continue;
}
StorableProperty<S> prop = op.getChainedProperty().getPrimeProperty();
Label skip = b.createLabel();
if (!prop.isDerived()) {
int num = prop.getNumber();
b.loadThis();
b.loadField(PROPERTY_STATE_FIELD_NAME + (num >> 4), TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_MASK << ((num & 0xf) * 2));
b.math(Opcode.IAND);
b.ifZeroComparisonBranch(skip, "==");
}
b.loadConstant(prop.getName() + " = ?");
b.invokeInterface(queryType, AND_METHOD_NAME, queryType,
new TypeDesc[]{TypeDesc.STRING});
loadThisProperty(b, prop);
TypeDesc bindType = CodeBuilderUtil.bindQueryParam(prop.getType());
CodeBuilderUtil.convertValue(b, prop.getType(), bindType.toClass());
b.invokeInterface(queryType, WITH_METHOD_NAME, queryType,
new TypeDesc[]{bindType});
skip.setLocation();
}
}
b.branch(runQuery);
noAltKey.setLocation();
}
CodeBuilderUtil.throwException(b, IllegalStateException.class,
"Primary or alternate key not fully specified");
// Run query sitting on the stack.
runQuery.setLocation();
// Locally disable load triggers, to hide the fact that we're
// using a query to load by alternate key.
b.loadThis();
b.loadField(SUPPORT_FIELD_NAME, mSupportType);
b.invoke(lookupMethod(mSupportType.toClass(), "locallyDisableLoadTrigger"));
// try-finally start label
Label disableTriggerStart = b.createLabel().setLocation();
b.invokeInterface(queryType, TRY_LOAD_ONE_METHOD_NAME,
TypeDesc.forClass(Storable.class), null);
LocalVariable fetchedVar = b.createLocalVariable(null, TypeDesc.OBJECT);
b.storeLocal(fetchedVar);
// try-finally end label
Label disableTriggerEnd = b.createLabel().setLocation();
b.loadThis();
b.loadField(SUPPORT_FIELD_NAME, mSupportType);
b.invoke(lookupMethod(mSupportType.toClass(), "locallyEnableLoadTrigger"));
// If query fetch is null, then object not found. Return false.
b.loadLocal(fetchedVar);
b.ifNullBranch(notLoaded, true);
// Copy all properties from fetched object into this one.
// Allow copy to destroy everything, including primary key.
b.loadThis();
b.invokeVirtual(MARK_ALL_PROPERTIES_DIRTY, null, null);
b.loadLocal(fetchedVar);
b.checkCast(TypeDesc.forClass(mStorableType));
b.loadThis();
b.invokeInterface(TypeDesc.forClass(Storable.class),
COPY_ALL_PROPERTIES, null,
new TypeDesc[] {TypeDesc.forClass(Storable.class)});
b.branch(loaded);
// Handler for exception when load trigger is disabled.
b.exceptionHandler(disableTriggerStart, disableTriggerEnd, null);
LocalVariable exceptionVar = b.createLocalVariable(null, TypeDesc.OBJECT);
b.storeLocal(exceptionVar);
b.loadThis();
b.loadField(SUPPORT_FIELD_NAME, mSupportType);
b.invoke(lookupMethod(mSupportType.toClass(), "locallyEnableLoadTrigger"));
b.loadLocal(exceptionVar);
b.throwObject();
}
pkInitialized.setLocation();
// Call doTryLoad and mark all properties as clean if load succeeded.
b.loadThis();
b.invokeVirtual(DO_TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null);
b.ifZeroComparisonBranch(notLoaded, "==");
loaded.setLocation();
// Only indicate load completed if doTryLoad returned true.
b.loadThis();
b.invokeVirtual(LOAD_COMPLETED_METHOD_NAME, null, null);
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
notLoaded.setLocation();
// Mark properties dirty, to be consistent with a delete side-effect.
b.loadThis();
b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null);
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
// Define the abstract method.
mi = mClassFile.addMethod
(Modifiers.PROTECTED.toAbstract(true),
DO_TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(TypeDesc.forClass(FetchException.class));
}
// Add load method which calls tryLoad.
addLoad: {
// Define the load method.
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(true),
LOAD_METHOD_NAME, null, null);
if (mi == null) {
break addLoad;
}
mi.addException(TypeDesc.forClass(FetchException.class));
CodeBuilder b = new CodeBuilder(mi);
// Call tryLoad and throw an exception if false returned.
b.loadThis();
b.invokeVirtual(TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null);
Label wasNotLoaded = b.createLabel();
b.ifZeroComparisonBranch(wasNotLoaded, "==");
b.returnVoid();
wasNotLoaded.setLocation();
TypeDesc noMatchesType = TypeDesc.forClass(FetchNoneException.class);
b.newObject(noMatchesType);
b.dup();
b.loadThis();
b.invokeVirtual(TO_STRING_KEY_ONLY_METHOD_NAME, TypeDesc.STRING, null);
b.invokeConstructor(noMatchesType, new TypeDesc[] {TypeDesc.STRING});
b.throwObject();
}
final TypeDesc triggerType = TypeDesc.forClass(Trigger.class);
final TypeDesc transactionType = TypeDesc.forClass(Transaction.class);
// Add insert(boolean forTry) method which delegates to abstract doTryInsert method.
{
MethodInfo mi = mClassFile.addMethod
(Modifiers.PRIVATE.toSynchronized(true),
PRIVATE_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.BOOLEAN});
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
LocalVariable forTryVar = b.getParameter(0);
LocalVariable triggerVar = b.createLocalVariable(null, triggerType);
LocalVariable txnVar = b.createLocalVariable(null, transactionType);
LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT);
Label tryStart = addGetTriggerAndEnterTxn
(b, INSERT_OP, forTryVar, false, triggerVar, txnVar, stateVar);
// Perform pk check after trigger has run, to allow it to define pk.
requirePkInitialized(b, CHECK_PK_FOR_INSERT_METHOD_NAME);
// Call doTryInsert.
b.loadThis();
b.invokeVirtual(DO_TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null);
Label notInserted = b.createLabel();
b.ifZeroComparisonBranch(notInserted, "==");
addTriggerAfterAndExitTxn
(b, INSERT_OP, forTryVar, false, triggerVar, txnVar, stateVar);
// Only mark properties clean if doTryInsert returned true.
b.loadThis();
b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null);
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
notInserted.setLocation();
addTriggerFailedAndExitTxn(b, INSERT_OP, triggerVar, txnVar, stateVar);
b.loadLocal(forTryVar);
Label isForTry = b.createLabel();
b.ifZeroComparisonBranch(isForTry, "!=");
TypeDesc constraintType = TypeDesc.forClass(UniqueConstraintException.class);
b.newObject(constraintType);
b.dup();
b.loadThis();
b.invokeVirtual(TO_STRING_METHOD_NAME, TypeDesc.STRING, null);
b.invokeConstructor(constraintType, new TypeDesc[] {TypeDesc.STRING});
b.throwObject();
isForTry.setLocation();
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
addTriggerFailedAndExitTxn
(b, INSERT_OP, forTryVar, false, triggerVar, txnVar, stateVar, tryStart);
// Define the abstract method.
mi = mClassFile.addMethod
(Modifiers.PROTECTED.toAbstract(true),
DO_TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(TypeDesc.forClass(PersistException.class));
}
// Add insert method which calls insert(forTry = false)
addInsert: {
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, INSERT_METHOD_NAME, null, null);
if (mi == null) {
break addInsert;
}
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadConstant(false);
b.invokePrivate(PRIVATE_INSERT_METHOD_NAME, TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.BOOLEAN});
b.pop();
b.returnVoid();
}
// Add tryInsert method which calls insert(forTry = true)
addTryInsert: {
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null);
if (mi == null) {
break addTryInsert;
}
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadConstant(true);
b.invokePrivate(PRIVATE_INSERT_METHOD_NAME, TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.BOOLEAN});
b.returnValue(TypeDesc.BOOLEAN);
}
// Add update(boolean forTry) method which delegates to abstract doTryUpdate method.
{
MethodInfo mi = mClassFile.addMethod
(Modifiers.PRIVATE.toSynchronized(true),
PRIVATE_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.BOOLEAN});
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
requirePkInitialized(b, CHECK_PK_FOR_UPDATE_METHOD_NAME);
// If version property is present, it too must be initialized. The
// versionOrdinal variable was set earlier, when properties were defined.
if (versionOrdinal >= 0) {
b.loadThis();
b.loadField(PROPERTY_STATE_FIELD_NAME + (versionOrdinal >> 4), TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_MASK << ((versionOrdinal & 0xf) * 2));
b.math(Opcode.IAND);
Label versionIsSet = b.createLabel();
b.ifZeroComparisonBranch(versionIsSet, "!=");
CodeBuilderUtil.throwException
(b, IllegalStateException.class, "Version not set");
versionIsSet.setLocation();
}
LocalVariable forTryVar = b.getParameter(0);
LocalVariable triggerVar = b.createLocalVariable(null, triggerType);
LocalVariable txnVar = b.createLocalVariable(null, transactionType);
LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT);
Label tryStart = addGetTriggerAndEnterTxn
(b, UPDATE_OP, forTryVar, false, triggerVar, txnVar, stateVar);
// Call doTryUpdate.
b.loadThis();
b.invokeVirtual(DO_TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null);
Label notUpdated = b.createLabel();
b.ifZeroComparisonBranch(notUpdated, "==");
addTriggerAfterAndExitTxn
(b, UPDATE_OP, forTryVar, false, triggerVar, txnVar, stateVar);
// Only mark properties clean if doUpdate returned true.
b.loadThis();
// Note: all properties marked clean because doUpdate should have
// loaded values for all properties.
b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null);
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
notUpdated.setLocation();
addTriggerFailedAndExitTxn(b, UPDATE_OP, triggerVar, txnVar, stateVar);
// Mark properties dirty, to be consistent with a delete side-effect.
b.loadThis();
b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null);
b.loadLocal(forTryVar);
Label isForTry = b.createLabel();
b.ifZeroComparisonBranch(isForTry, "!=");
TypeDesc persistNoneType = TypeDesc.forClass(PersistNoneException.class);
b.newObject(persistNoneType);
b.dup();
b.loadConstant("Cannot update missing object: ");
b.loadThis();
b.invokeVirtual(TO_STRING_METHOD_NAME, TypeDesc.STRING, null);
b.invokeVirtual(TypeDesc.STRING, "concat",
TypeDesc.STRING, new TypeDesc[] {TypeDesc.STRING});
b.invokeConstructor(persistNoneType, new TypeDesc[] {TypeDesc.STRING});
b.throwObject();
isForTry.setLocation();
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
addTriggerFailedAndExitTxn
(b, UPDATE_OP, forTryVar, false, triggerVar, txnVar, stateVar, tryStart);
// Define the abstract method.
mi = mClassFile.addMethod
(Modifiers.PROTECTED.toAbstract(true),
DO_TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(TypeDesc.forClass(PersistException.class));
}
// Add update method which calls update(forTry = false)
addUpdate: {
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, UPDATE_METHOD_NAME, null, null);
if (mi == null) {
break addUpdate;
}
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadConstant(false);
b.invokePrivate(PRIVATE_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.BOOLEAN});
b.pop();
b.returnVoid();
}
// Add tryUpdate method which calls update(forTry = true)
addTryUpdate: {
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null);
if (mi == null) {
break addTryUpdate;
}
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadConstant(true);
b.invokePrivate(PRIVATE_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.BOOLEAN});
b.returnValue(TypeDesc.BOOLEAN);
}
// Add delete(boolean forTry) method which delegates to abstract doTryDelete method.
{
MethodInfo mi = mClassFile.addMethod
(Modifiers.PRIVATE.toSynchronized(true),
PRIVATE_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.BOOLEAN});
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
requirePkInitialized(b, CHECK_PK_FOR_DELETE_METHOD_NAME);
LocalVariable forTryVar = b.getParameter(0);
LocalVariable triggerVar = b.createLocalVariable(null, triggerType);
LocalVariable txnVar = b.createLocalVariable(null, transactionType);
LocalVariable stateVar = b.createLocalVariable(null, TypeDesc.OBJECT);
Label tryStart = addGetTriggerAndEnterTxn
(b, DELETE_OP, forTryVar, false, triggerVar, txnVar, stateVar);
// Call doTryDelete.
b.loadThis();
b.invokeVirtual(DO_TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null);
b.loadThis();
b.invokeVirtual(MARK_PROPERTIES_DIRTY, null, null);
Label notDeleted = b.createLabel();
b.ifZeroComparisonBranch(notDeleted, "==");
addTriggerAfterAndExitTxn
(b, DELETE_OP, forTryVar, false, triggerVar, txnVar, stateVar);
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
notDeleted.setLocation();
addTriggerFailedAndExitTxn(b, DELETE_OP, triggerVar, txnVar, stateVar);
b.loadLocal(forTryVar);
Label isForTry = b.createLabel();
b.ifZeroComparisonBranch(isForTry, "!=");
TypeDesc persistNoneType = TypeDesc.forClass(PersistNoneException.class);
b.newObject(persistNoneType);
b.dup();
b.loadConstant("Cannot delete missing object: ");
b.loadThis();
b.invokeVirtual(TO_STRING_METHOD_NAME, TypeDesc.STRING, null);
b.invokeVirtual(TypeDesc.STRING, "concat",
TypeDesc.STRING, new TypeDesc[] {TypeDesc.STRING});
b.invokeConstructor(persistNoneType, new TypeDesc[] {TypeDesc.STRING});
b.throwObject();
isForTry.setLocation();
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
addTriggerFailedAndExitTxn
(b, DELETE_OP, forTryVar, false, triggerVar, txnVar, stateVar, tryStart);
// Define the abstract method.
mi = mClassFile.addMethod
(Modifiers.PROTECTED.toAbstract(true),
DO_TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(TypeDesc.forClass(PersistException.class));
}
// Add delete method which calls delete(forTry = false)
addDelete: {
// Define the delete method.
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, DELETE_METHOD_NAME, null, null);
if (mi == null) {
break addDelete;
}
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadConstant(false);
b.invokePrivate(PRIVATE_DELETE_METHOD_NAME, TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.BOOLEAN});
b.pop();
b.returnVoid();
}
// Add tryDelete method which calls delete(forTry = true)
addTryDelete: {
// Define the delete method.
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null);
if (mi == null) {
break addTryDelete;
}
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadConstant(true);
b.invokePrivate(PRIVATE_DELETE_METHOD_NAME, TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.BOOLEAN});
b.returnValue(TypeDesc.BOOLEAN);
}
// Add storableType method
addStorableType: {
final TypeDesc type = TypeDesc.forClass(mStorableType);
final TypeDesc storableClassType = TypeDesc.forClass(Class.class);
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, STORABLE_TYPE_METHOD_NAME, storableClassType, null);
if (mi == null) {
break addStorableType;
}
CodeBuilder b = new CodeBuilder(mi);
b.loadConstant(type);
b.returnValue(storableClassType);
}
// Add copy method.
addCopy: {
TypeDesc type = TypeDesc.forClass(mInfo.getStorableType());
// Add copy method.
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(true),
COPY_METHOD_NAME, mClassFile.getType(), null);
if (mi == null) {
break addCopy;
}
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.invokeVirtual(CLONE_METHOD_NAME, TypeDesc.OBJECT, null);
b.checkCast(mClassFile.getType());
b.returnValue(type);
}
// Part of properly defining copy method, except needs to be added even
// if copy method was not added because it is inherited and final.
CodeBuilderUtil.defineCopyBridges(mClassFile, mInfo.getStorableType());
// Create all the property copier methods.
// Boolean params: pkProperties, versionProperty, dataProperties, unequalOnly, dirtyOnly
addCopyPropertiesMethod(COPY_ALL_PROPERTIES,
true, true, true, false, false);
addCopyPropertiesMethod(COPY_PRIMARY_KEY_PROPERTIES,
true, false, false, false, false);
addCopyPropertiesMethod(COPY_VERSION_PROPERTY,
false, true, false, false, false);
addCopyPropertiesMethod(COPY_UNEQUAL_PROPERTIES,
false, true, true, true, false);
addCopyPropertiesMethod(COPY_DIRTY_PROPERTIES,
false, true, true, false, true);
// Define hasDirtyProperties method.
addHasDirtyProps: {
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, HAS_DIRTY_PROPERTIES, TypeDesc.BOOLEAN, null);
if (mi == null) {
break addHasDirtyProps;
}
CodeBuilder b = new CodeBuilder(mi);
Label isDirty = b.createLabel();
branchIfDirty(b, false, isDirty);
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
isDirty.setLocation();
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
}
// Define isPropertyUninitialized, isPropertyDirty, and isPropertyClean methods.
addPropertyStateExtractMethod();
addPropertyStateCheckMethod(IS_PROPERTY_UNINITIALIZED, PROPERTY_STATE_UNINITIALIZED);
addPropertyStateCheckMethod(IS_PROPERTY_DIRTY, PROPERTY_STATE_DIRTY);
addPropertyStateCheckMethod(IS_PROPERTY_CLEAN, PROPERTY_STATE_CLEAN);
// Define isPropertySupported method.
addIsPropertySupported: {
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, IS_PROPERTY_SUPPORTED,
TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING});
if (mi == null) {
break addIsPropertySupported;
}
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadField(SUPPORT_FIELD_NAME, mSupportType);
b.loadLocal(b.getParameter(0));
b.invokeInterface(mSupportType, "isPropertySupported", TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.STRING});
b.returnValue(TypeDesc.BOOLEAN);
}
// Define reflection-like methods for manipulating properties by name.
addGetPropertyValueMethod();
addSetPropertyValueMethod();
addPropertyMapMethod();
// Define serialization methods.
addWriteToMethod();
addReadFromMethod();
// Define standard object methods.
addHashCodeMethod();
addEqualsMethod(EQUAL_FULL);
addEqualsMethod(EQUAL_KEYS);
addEqualsMethod(EQUAL_PROPERTIES);
addToStringMethod(false);
addToStringMethod(true);
addMarkCleanMethod(MARK_PROPERTIES_CLEAN);
addMarkCleanMethod(MARK_ALL_PROPERTIES_CLEAN);
addMarkDirtyMethod(MARK_PROPERTIES_DIRTY);
addMarkDirtyMethod(MARK_ALL_PROPERTIES_DIRTY);
// Define loadCompleted method.
{
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED, LOAD_COMPLETED_METHOD_NAME, null, null);
mi.addException(TypeDesc.forClass(FetchException.class));
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null);
// Now invoke trigger.
b.loadThis();
b.loadField(SUPPORT_FIELD_NAME, mSupportType);
b.invoke(lookupMethod(mSupportType.toClass(), "getLoadTrigger"));
LocalVariable triggerVar =
b.createLocalVariable(null, TypeDesc.forClass(Trigger.class));
b.storeLocal(triggerVar);
b.loadLocal(triggerVar);
Label noTrigger = b.createLabel();
b.ifNullBranch(noTrigger, true);
b.loadLocal(triggerVar);
b.loadThis();
b.invoke(lookupMethod(triggerVar.getType().toClass(), "afterLoad", Object.class));
// In case trigger modified the properties, make sure they're still clean.
b.loadThis();
b.invokeVirtual(MARK_ALL_PROPERTIES_CLEAN, null, null);
noTrigger.setLocation();
b.returnVoid();
}
{
// Define protected isPkInitialized method.
addIsInitializedMethod
(IS_PK_INITIALIZED_METHOD_NAME, mInfo.getPrimaryKeyProperties());
{
// Define protected isPartitionKeyInitialized method
// It will return true if there are no Partition keys
final Map<String, StorableProperty<S>> partitionProperties = new LinkedHashMap<String, StorableProperty<S>>();
for (StorableProperty<S> property : mAllProperties.values()) {
if (!property.isDerived() && property.isPartitionKeyMember()) {
partitionProperties.put(property.getName(), property);
}
}
// Add methods to check that the partition key is defined
addIsInitializedMethod(IS_PARTITION_KEY_INITIALIZED_METHOD_NAME, partitionProperties);
}
// Define protected methods to check if alternate key is initialized.
addAltKeyMethods:
for (int i=0; i<mInfo.getAlternateKeyCount(); i++) {
Map<String, StorableProperty<S>> altProps =
new LinkedHashMap<String, StorableProperty<S>>();
StorableKey<S> altKey = mInfo.getAlternateKey(i);
for (OrderedProperty<S> op : altKey.getProperties()) {
ChainedProperty<S> cp = op.getChainedProperty();
if (cp.getChainCount() > 0) {
// This should not be possible.
continue addAltKeyMethods;
}
StorableProperty<S> property = cp.getPrimeProperty();
altProps.put(property.getName(), property);
}
addIsInitializedMethod(IS_ALT_KEY_INITIALIZED_PREFIX + i, altProps);
}
// Define protected isRequiredDataInitialized method.
defineIsRequiredDataInitialized: {
Map<String, StorableProperty<S>> requiredProperties =
new LinkedHashMap<String, StorableProperty<S>>();
for (StorableProperty property : mAllProperties.values()) {
if (!property.isDerived() &&
!property.isPrimaryKeyMember() &&
!property.isJoin() &&
!property.isNullable()) {
requiredProperties.put(property.getName(), property);
}
}
addIsInitializedMethod
(IS_REQUIRED_DATA_INITIALIZED_METHOD_NAME, requiredProperties);
}
// Define optional protected isVersionInitialized method. The
// versionOrdinal variable was set earlier, when properties were defined.
if (versionOrdinal >= 0) {
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED, IS_VERSION_INITIALIZED_METHOD_NAME,
TypeDesc.BOOLEAN, null);
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadField(PROPERTY_STATE_FIELD_NAME + (versionOrdinal >> 4), TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_MASK << ((versionOrdinal & 0xf) * 2));
b.math(Opcode.IAND);
// zero == false, not zero == true
b.convert(TypeDesc.INT, TypeDesc.BOOLEAN);
b.returnValue(TypeDesc.BOOLEAN);
}
}
}
/*
private static Method lookupMethod(Class type, MethodInfo mi) {
MethodDesc desc = mi.getMethodDescriptor();
TypeDesc[] params = desc.getParameterTypes();
Class[] args;
if (params == null || params.length == 0) {
args = null;
} else {
args = new Class[params.length];
for (int i=0; i<args.length; i++) {
args[i] = params[i].toClass();
}
}
return lookupMethod(type, mi.getName(), args);
}
*/
private static Method lookupMethod(Class type, String name, Class... args) {
try {
return type.getMethod(name, args);
} catch (NoSuchMethodException e) {
Error error = new NoSuchMethodError();
error.initCause(e);
throw error;
}
}
private void addPropertyBridges(StorableProperty<S> property) {
Class[] covariantTypes = property.getCovariantTypes();
if (covariantTypes == null || covariantTypes.length == 0) {
return;
}
// Define copy bridges to allow covariant property types.
for (Class type : covariantTypes) {
TypeDesc desc = TypeDesc.forClass(type);
if (property.getReadMethod() != null &&
property.getReadMethod().getReturnType() != type)
{
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC.toBridge(true), property.getReadMethodName(), desc, null);
if (mi != null) {
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.invoke(property.getReadMethod());
b.returnValue(desc);
}
}
if (property.getWriteMethod() != null &&
property.getWriteMethod().getParameterTypes()[0] != type)
{
// Not actually defined as a bridge method since parameter type differs.
MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, property.getWriteMethodName(), null, new TypeDesc[] {desc});
if (mi != null) {
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadLocal(b.getParameter(0));
b.checkCast(TypeDesc.forClass(property.getType()));
b.invoke(property.getWriteMethod());
b.returnVoid();
}
}
}
}
/**
* Generates a copy properties method with several options to control its
* behavior. Although eight combinations can be defined, only four are
* required by Storable interface. Uninitialized properties are never
* copied.
*
* @param pkProperties when true, copy primary key properties
* @param dataProperties when true, copy data properties
* @param unequalOnly when true, only copy unequal properties
* @param dirtyOnly when true, only copy dirty properties
*/
private void addCopyPropertiesMethod
(String methodName,
boolean pkProperties,
boolean versionProperty,
boolean dataProperties,
boolean unequalOnly,
boolean dirtyOnly)
{
TypeDesc[] param = { TypeDesc.forClass(Storable.class) };
TypeDesc storableTypeDesc = TypeDesc.forClass(mStorableType);
MethodInfo mi= addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(true), methodName, null, param);
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
LocalVariable target = CodeBuilderUtil.uneraseGenericParameter(b, storableTypeDesc, 0);
LocalVariable stateBits = null;
int mask = PROPERTY_STATE_MASK;
for (StorableProperty property : mAllProperties.values()) {
// Decide if property should be part of the copy.
boolean shouldCopy =
(!property.isDerived() || property.shouldCopyDerived()) &&
!property.isJoin() &&
(property.isPrimaryKeyMember() && pkProperties ||
property.isVersion() && versionProperty ||
!property.isPrimaryKeyMember() && dataProperties);
if (shouldCopy) {
int ordinal = property.getNumber();
if (stateBits == null && !property.isDerived()) {
// Load state bits into local for quick retrieval.
stateBits = b.createLocalVariable(null, TypeDesc.INT);
String stateFieldName =
StorableGenerator.PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.storeLocal(stateBits);
}
Label skipCopy = b.createLabel();
// Check if independent property is supported, and skip if not.
if (property.isIndependent()) {
addSkipIndependent(b, target, property, skipCopy);
}
if (stateBits != null && !property.isDerived()) {
// Skip property if uninitialized.
b.loadLocal(stateBits);
b.loadConstant(mask);
b.math(Opcode.IAND);
b.ifZeroComparisonBranch(skipCopy, "==");
if (dirtyOnly) {
// Add code to find out if property has been dirty.
b.loadLocal(stateBits);
b.loadConstant(mask);
b.math(Opcode.IAND);
b.loadConstant(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2));
b.ifComparisonBranch(skipCopy, "!=");
}
}
TypeDesc type = TypeDesc.forClass(property.getType());
if (unequalOnly) {
// Add code to find out if they're equal.
loadThisProperty(b, property, type); // [this.propValue
b.loadLocal(target); // [this.propValue, target
b.invoke(property.getReadMethod()); // [this.propValue, target.propValue
CodeBuilderUtil.addValuesEqualCall
(b, TypeDesc.forClass(property.getType()), true, skipCopy, true);
}
b.loadLocal(target); // [target
loadThisProperty(b, property, type); // [target, this.propValue
if (property.getWriteMethod() != null) {
// Favor the write method, if it exists.
b.invoke(property.getWriteMethod());
} else {
b.storeField(property.getName(), type);
}
skipCopy.setLocation();
}
if ((mask <<= 2) == 0) {
mask = PROPERTY_STATE_MASK;
stateBits = null;
}
}
b.returnVoid();
}
private void addSkipIndependent(CodeBuilder b,
LocalVariable target,
StorableProperty property,
Label skipCopy)
{
TypeDesc storableTypeDesc = TypeDesc.forClass(Storable.class);
if (target != null) {
b.loadLocal(target);
b.loadConstant(property.getName());
b.invokeInterface(storableTypeDesc,
"isPropertySupported",
TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.STRING});
b.ifZeroComparisonBranch(skipCopy, "==");
}
b.loadThis();
b.loadConstant(property.getName());
b.invokeInterface(storableTypeDesc,
"isPropertySupported",
TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.STRING});
b.ifZeroComparisonBranch(skipCopy, "==");
}
/**
* Loads the property value of the current storable onto the stack. If the
* property is derived the read method is used, otherwise it just loads the
* value from the appropriate field.
*
* entry stack: [
* exit stack: [value
*
* @param b - {@link CodeBuilder} to which to add the load code
* @param property - property to load
*/
private void loadThisProperty(CodeBuilder b, StorableProperty property) {
loadThisProperty(b, property, TypeDesc.forClass(property.getType()));
}
/**
* Loads the property value of the current storable onto the stack. If the
* property is derived the read method is used, otherwise it just loads the
* value from the appropriate field.
*
* entry stack: [
* exit stack: [value
*
* @param b - {@link CodeBuilder} to which to add the load code
* @param property - property to load
* @param type - type of the property
*/
private void loadThisProperty(CodeBuilder b, StorableProperty property, TypeDesc type) {
b.loadThis();
if (property.isDerived()) {
b.invoke(property.getReadMethod());
} else {
b.loadField(property.getName(), type);
}
}
/**
* Generates code that loads a property annotation to the stack.
*/
private void loadPropertyAnnotation(CodeBuilder b,
StorableProperty property,
StorablePropertyAnnotation annotation) {
/* Example
UserInfo.class.getMethod("setFirstName", new Class[] {String.class})
.getAnnotation(LengthConstraint.class)
*/
String methodName = annotation.getAnnotatedMethod().getName();
boolean isAccessor = !methodName.startsWith("set");
b.loadConstant(TypeDesc.forClass(property.getEnclosingType()));
b.loadConstant(methodName);
if (isAccessor) {
// Accessor method has no parameters.
b.loadNull();
} else {
// Mutator method has one parameter.
b.loadConstant(1);
b.newObject(TypeDesc.forClass(Class[].class));
b.dup();
b.loadConstant(0);
b.loadConstant(TypeDesc.forClass(property.getType()));
b.storeToArray(TypeDesc.forClass(Class[].class));
}
b.invokeVirtual(Class.class.getName(), "getMethod",
TypeDesc.forClass(Method.class), new TypeDesc[] {
TypeDesc.STRING, TypeDesc.forClass(Class[].class)
});
b.loadConstant(TypeDesc.forClass(annotation.getAnnotationType()));
b.invokeVirtual(Method.class.getName(), "getAnnotation",
TypeDesc.forClass(Annotation.class), new TypeDesc[] {
TypeDesc.forClass(Class.class)
});
b.checkCast(TypeDesc.forClass(annotation.getAnnotationType()));
}
/**
* Generates code that loads a Storage instance on the stack, throwing a
* FetchException if Storage request fails.
*
* @param type type of Storage to request
*/
private void loadStorageForFetch(CodeBuilder b, TypeDesc type) {
b.loadThis();
b.loadField(SUPPORT_FIELD_NAME, mSupportType);
TypeDesc storageType = TypeDesc.forClass(Storage.class);
TypeDesc repositoryType = TypeDesc.forClass(Repository.class);
b.invokeInterface
(mSupportType, "getRootRepository", repositoryType, null);
b.loadConstant(type);
// This may throw a RepositoryException.
Label tryStart = b.createLabel().setLocation();
b.invokeInterface(repositoryType, STORAGE_FOR_METHOD_NAME, storageType,
new TypeDesc[]{TypeDesc.forClass(Class.class)});
Label tryEnd = b.createLabel().setLocation();
Label noException = b.createLabel();
b.branch(noException);
b.exceptionHandler(tryStart, tryEnd,
RepositoryException.class.getName());
b.invokeVirtual
(RepositoryException.class.getName(), "toFetchException",
TypeDesc.forClass(FetchException.class), null);
b.throwObject();
noException.setLocation();
}
/**
* For the given join property, marks all of its dependent internal join
* element properties as dirty.
*/
/*
private void markInternalJoinElementsDirty(CodeBuilder b, StorableProperty joinProperty) {
int count = mAllProperties.size();
int ordinal = 0;
int mask = 0;
for (StorableProperty property : mAllProperties.values()) {
if (property != joinProperty && !property.isDerived() && !property.isJoin()) {
// Check to see if property is an internal member of joinProperty.
for (int i=joinProperty.getJoinElementCount(); --i>=0; ) {
if (property == joinProperty.getInternalJoinElement(i)) {
mask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
}
}
}
ordinal++;
if (((ordinal & 0xf) == 0 || ordinal >= count) && mask != 0) {
String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4);
b.loadThis();
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.loadConstant(mask);
b.math(Opcode.IOR);
b.storeField(stateFieldName, TypeDesc.INT);
mask = 0;
}
}
}
*/
/**
* Generates code to set all state properties to zero.
*/
/*
private void clearState(CodeBuilder b) {
int ordinal = -1;
int maxOrdinal = mAllProperties.size() - 1;
boolean requireStateField = false;
for (StorableProperty property : mAllProperties.values()) {
ordinal++;
if (!property.isDerived()) {
requireStateField = true;
if (ordinal == maxOrdinal || ((ordinal & 0xf) == 0xf)) {
if (requireStateField) {
String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
b.loadThis();
b.loadConstant(0);
b.storeField(stateFieldName, TypeDesc.INT);
}
requireStateField = false;
}
}
}
}
*/
private void addMarkCleanMethod(String name) {
MethodInfo mi =
addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), name, null, null);
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
final int count = mAllProperties.size();
int ordinal = 0;
int andMask = 0;
int orMask = 0;
boolean anyNonDerived = false;
for (StorableProperty property : mAllProperties.values()) {
if (!property.isDerived()) {
anyNonDerived = true;
if (property.isQuery()) {
// Don't erase cached query.
andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
} else if (!property.isJoin()) {
if (name == MARK_ALL_PROPERTIES_CLEAN) {
// Force clean state (1) always.
orMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
} else if (name == MARK_PROPERTIES_CLEAN) {
// Mask will convert dirty (3) to clean (1). State 2, which
// is illegal, is converted to 0.
andMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
}
}
}
ordinal++;
if ((ordinal & 0xf) == 0 || ordinal >= count) {
if (anyNonDerived) {
String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4);
b.loadThis();
if (andMask == 0) {
b.loadConstant(orMask);
} else {
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.loadConstant(andMask);
b.math(Opcode.IAND);
if (orMask != 0) {
b.loadConstant(orMask);
b.math(Opcode.IOR);
}
}
b.storeField(stateFieldName, TypeDesc.INT);
}
andMask = 0;
orMask = 0;
anyNonDerived = false;
}
}
b.returnVoid();
}
private void addMarkDirtyMethod(String name) {
MethodInfo mi =
addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), name, null, null);
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
final int count = mAllProperties.size();
int ordinal = 0;
int andMask = 0;
int orMask = 0;
boolean anyNonDerived = false;
for (StorableProperty property : mAllProperties.values()) {
if (!property.isDerived()) {
anyNonDerived = true;
if (property.isJoin()) {
// Erase cached join properties, but don't erase cached query.
if (!property.isQuery()) {
andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
}
} else if (name == MARK_ALL_PROPERTIES_DIRTY) {
// Force dirty state (3).
orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
}
}
ordinal++;
if ((ordinal & 0xf) == 0 || ordinal >= count) {
if (anyNonDerived) {
String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4);
if (name == MARK_ALL_PROPERTIES_DIRTY) {
if (orMask != 0 || andMask != 0) {
b.loadThis(); // [this
b.loadThis(); // [this, this
b.loadField(stateFieldName, TypeDesc.INT); // [this, this.stateField
if (andMask != 0) {
b.loadConstant(~andMask);
b.math(Opcode.IAND);
}
if (orMask != 0) {
b.loadConstant(orMask);
b.math(Opcode.IOR);
}
b.storeField(stateFieldName, TypeDesc.INT);
}
} else {
// This is a great trick to convert all states of value 1
// (clean) into value 3 (dirty). States 0, 2, and 3 stay the
// same. Since joins cannot have state 1, they aren't affected.
// stateField |= ((stateField & 0x55555555) << 1);
b.loadThis(); // [this
b.loadThis(); // [this, this
b.loadField(stateFieldName, TypeDesc.INT); // [this, this.stateField
if (andMask != 0) {
b.loadConstant(~andMask);
b.math(Opcode.IAND);
}
b.dup(); // [this, this.stateField, this.stateField
b.loadConstant(0x55555555);
b.math(Opcode.IAND); // [this, this.stateField, this.stateField &0x55555555
b.loadConstant(1);
b.math(Opcode.ISHL); // [this, this.stateField, orMaskValue
b.math(Opcode.IOR); // [this, newStateFieldValue
b.storeField(stateFieldName, TypeDesc.INT);
}
}
andMask = 0;
orMask = 0;
anyNonDerived = false;
}
}
b.returnVoid();
}
/**
* For the given ordinary key property, marks all of its dependent join
* element properties as uninitialized, and marks given property as dirty.
*/
private void markOrdinaryPropertyDirty
(CodeBuilder b, StorableProperty ordinaryProperty)
{
int count = mAllProperties.size();
int ordinal = 0;
int andMask = 0xffffffff;
int orMask = 0;
boolean anyNonDerived = false;
for (StorableProperty property : mAllProperties.values()) {
if (!property.isDerived()) {
anyNonDerived = true;
if (property == ordinaryProperty) {
orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
} else if (property.isJoin()) {
// Check to see if ordinary is an internal member of join property.
for (int i=property.getJoinElementCount(); --i>=0; ) {
if (ordinaryProperty == property.getInternalJoinElement(i)) {
andMask &= ~(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2));
}
}
}
}
ordinal++;
if ((ordinal & 0xf) == 0 || ordinal >= count) {
if (anyNonDerived && (andMask != 0xffffffff || orMask != 0)) {
String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4);
b.loadThis();
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
if (andMask != 0xffffffff) {
b.loadConstant(andMask);
b.math(Opcode.IAND);
}
if (orMask != 0) {
b.loadConstant(orMask);
b.math(Opcode.IOR);
}
b.storeField(stateFieldName, TypeDesc.INT);
}
andMask = 0xffffffff;
orMask = 0;
anyNonDerived = false;
}
}
}
// Generates code that branches to the given label if any properties are dirty.
private void branchIfDirty(CodeBuilder b, boolean includePk, Label label) {
int count = mAllProperties.size();
int ordinal = 0;
int andMask = 0;
boolean anyNonDerived = false;
for (StorableProperty property : mAllProperties.values()) {
if (!property.isDerived()) {
anyNonDerived = true;
if (!property.isJoin() && (!property.isPrimaryKeyMember() || includePk)) {
// Logical 'and' will convert state 1 (clean) to state 0, so
// that it will be ignored. State 3 (dirty) is what we're
// looking for, and it turns into 2. Essentially, we leave the
// high order bit on, since there is no state which has the
// high order bit on unless the low order bit is also on.
andMask |= 2 << ((ordinal & 0xf) * 2);
}
}
ordinal++;
if ((ordinal & 0xf) == 0 || ordinal >= count) {
if (anyNonDerived) {
String stateFieldName = PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4);
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.loadConstant(andMask);
b.math(Opcode.IAND);
// At least one property is dirty, so short circuit.
b.ifZeroComparisonBranch(label, "!=");
}
andMask = 0;
anyNonDerived = false;
}
}
}
private void addIsInitializedMethod
(String name, Map<String, ? extends StorableProperty<S>> properties)
{
// Don't check Automatic, Independent, or Version properties.
{
boolean cloned = false;
for (StorableProperty<S> prop : properties.values()) {
if (prop.isAutomatic() || prop.isIndependent() || prop.isVersion()) {
if (!cloned) {
properties = new LinkedHashMap<String, StorableProperty<S>>(properties);
cloned = true;
}
// This isn't concurrent modification since the loop is
// still operating on the original properties map.
properties.remove(prop.getName());
}
}
}
MethodInfo mi = mClassFile.addMethod(Modifiers.PROTECTED, name, TypeDesc.BOOLEAN, null);
CodeBuilder b = new CodeBuilder(mi);
if (properties.size() == 0) {
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
return;
}
if (properties.size() == 1) {
int ordinal = properties.values().iterator().next().getNumber();
b.loadThis();
b.loadField(PROPERTY_STATE_FIELD_NAME + (ordinal >> 4), TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2));
b.math(Opcode.IAND);
// zero == false, not zero == true
b.convert(TypeDesc.INT, TypeDesc.BOOLEAN);
b.returnValue(TypeDesc.BOOLEAN);
return;
}
// Multiple properties is a bit more tricky. The goal here is to
// minimize the amount of work that needs to be done at runtime.
int ordinal = 0;
int mask = 0;
boolean anyNonDerived = false;
for (StorableProperty property : mAllProperties.values()) {
if (!property.isDerived()) {
anyNonDerived = true;
if (properties.containsKey(property.getName())) {
mask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
}
}
ordinal++;
if ((ordinal & 0xf) == 0 || ordinal >= mAllProperties.size()) {
if (anyNonDerived && mask != 0) {
// This is a great trick to convert all states of value 1
// (clean) into value 3 (dirty). States 0, 2, and 3 stay the
// same. Since joins cannot have state 1, they aren't affected.
// stateField | ((stateField & 0x55555555) << 1);
b.loadThis();
b.loadField(PROPERTY_STATE_FIELD_NAME + ((ordinal - 1) >> 4), TypeDesc.INT);
b.dup(); // [this.stateField, this.stateField
b.loadConstant(0x55555555);
b.math(Opcode.IAND); // [this.stateField, this.stateField & 0x55555555
b.loadConstant(1);
b.math(Opcode.ISHL); // [this.stateField, orMaskValue
b.math(Opcode.IOR); // [newStateFieldValue
// Flip all bits for property states. If final result is
// non-zero, then there were uninitialized properties.
b.loadConstant(mask);
b.math(Opcode.IXOR);
if (mask != 0xffffffff) {
b.loadConstant(mask);
b.math(Opcode.IAND);
}
Label cont = b.createLabel();
b.ifZeroComparisonBranch(cont, "==");
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
cont.setLocation();
}
mask = 0;
anyNonDerived = false;
}
}
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
}
/**
* Generates code that verifies that all primary keys are initialized.
*
* @param b builder that will invoke generated method
* @param methodName name to give to generated method
*/
private void requirePkInitialized(CodeBuilder b, String methodName) {
// Add code to call method which we are about to define.
b.loadThis();
b.invokeVirtual(methodName, null, null);
// Now define new method, discarding original builder object.
b = new CodeBuilder(mClassFile.addMethod(Modifiers.PROTECTED, methodName, null, null));
b.loadThis();
b.invokeVirtual(IS_PK_INITIALIZED_METHOD_NAME, TypeDesc.BOOLEAN, null);
Label pkInitialized = b.createLabel();
b.ifZeroComparisonBranch(pkInitialized, "!=");
CodeBuilderUtil.throwException
(b, IllegalStateException.class, "Primary key not fully specified");
pkInitialized.setLocation();
b.returnVoid();
}
/**
* Generates a private method which accepts a property name and returns
* PROPERTY_STATE_UNINITIALIZED, PROPERTY_STATE_DIRTY, or
* PROPERTY_STATE_CLEAN.
*/
private void addPropertyStateExtractMethod() {
MethodInfo mi = mClassFile.addMethod(Modifiers.PRIVATE, PROPERTY_STATE_EXTRACT_METHOD_NAME,
TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING});
addPropertySwitch(new CodeBuilder(mi), SWITCH_FOR_STATE);
}
private void addPropertySwitch(CodeBuilder b, int switchFor) {
// Generate big switch statement that operates on Strings. See also
// org.cojen.util.BeanPropertyAccessor, which also generates this kind of
// switch.
// For switch case count, obtain a prime number, at least twice as
// large as needed. This should minimize hash collisions. Since all the
// hash keys are known up front, the capacity could be tweaked until
// there are no collisions, but this technique is easier and
// deterministic.
int caseCount;
{
BigInteger capacity = BigInteger.valueOf(mAllProperties.size() * 2 + 1);
while (!capacity.isProbablePrime(100)) {
capacity = capacity.add(BigInteger.valueOf(2));
}
caseCount = capacity.intValue();
}
int[] cases = new int[caseCount];
for (int i=0; i<caseCount; i++) {
cases[i] = i;
}
Label[] switchLabels = new Label[caseCount];
Label noMatch = b.createLabel();
List<StorableProperty<?>>[] caseMatches = caseMatches(caseCount);
for (int i=0; i<caseCount; i++) {
List<?> matches = caseMatches[i];
if (matches == null || matches.size() == 0) {
switchLabels[i] = noMatch;
} else {
switchLabels[i] = b.createLabel();
}
}
b.loadLocal(b.getParameter(0));
b.invokeVirtual(String.class.getName(), "hashCode", TypeDesc.INT, null);
b.loadConstant(0x7fffffff);
b.math(Opcode.IAND);
b.loadConstant(caseCount);
b.math(Opcode.IREM);
b.switchBranch(cases, switchLabels, noMatch);
// Params to invoke String.equals.
TypeDesc[] params = {TypeDesc.OBJECT};
Label derivedMatch = null;
Label joinMatch = null;
Label unreadable = null;
Label unwritable = null;
Label readException = null;
Label writeException = null;
for (int i=0; i<caseCount; i++) {
List<StorableProperty<?>> matches = caseMatches[i];
if (matches == null || matches.size() == 0) {
continue;
}
switchLabels[i].setLocation();
int matchCount = matches.size();
for (int j=0; j<matchCount; j++) {
StorableProperty<?> prop = matches.get(j);
// Test against name to find exact match.
b.loadConstant(prop.getName());
b.loadLocal(b.getParameter(0));
b.invokeVirtual(String.class.getName(), "equals", TypeDesc.BOOLEAN, params);
Label notEqual;
if (j == matchCount - 1) {
notEqual = null;
b.ifZeroComparisonBranch(noMatch, "==");
} else {
notEqual = b.createLabel();
b.ifZeroComparisonBranch(notEqual, "==");
}
if (switchFor == SWITCH_FOR_STATE) {
if (prop.isDerived()) {
if (derivedMatch == null) {
derivedMatch = b.createLabel();
}
b.branch(derivedMatch);
} else if (prop.isJoin()) {
if (joinMatch == null) {
joinMatch = b.createLabel();
}
b.branch(joinMatch);
} else {
int ordinal = prop.getNumber();
b.loadThis();
b.loadField(PROPERTY_STATE_FIELD_NAME + (ordinal >> 4), TypeDesc.INT);
int shift = (ordinal & 0xf) * 2;
if (shift != 0) {
b.loadConstant(shift);
b.math(Opcode.ISHR);
}
b.loadConstant(PROPERTY_STATE_MASK);
b.math(Opcode.IAND);
b.returnValue(TypeDesc.INT);
}
} else if (switchFor == SWITCH_FOR_GET) {
if (prop.getReadMethod() == null) {
if (unreadable == null) {
unreadable = b.createLabel();
}
b.branch(unreadable);
} else if (throwsCheckedException(prop.getReadMethod())) {
if (readException == null) {
readException = b.createLabel();
}
b.branch(readException);
} else {
b.loadThis();
b.invoke(prop.getReadMethod());
TypeDesc type = TypeDesc.forClass(prop.getType());
b.convert(type, type.toObjectType());
b.returnValue(TypeDesc.OBJECT);
}
} else if (switchFor == SWITCH_FOR_SET) {
if (prop.getWriteMethod() == null) {
if (unwritable == null) {
unwritable = b.createLabel();
}
b.branch(unwritable);
} else if (throwsCheckedException(prop.getWriteMethod())) {
if (writeException == null) {
writeException = b.createLabel();
}
b.branch(writeException);
} else {
b.loadThis();
b.loadLocal(b.getParameter(1));
TypeDesc type = TypeDesc.forClass(prop.getType());
b.checkCast(type.toObjectType());
b.convert(type.toObjectType(), type);
b.invoke(prop.getWriteMethod());
b.returnVoid();
}
}
if (notEqual != null) {
notEqual.setLocation();
}
}
}
noMatch.setLocation();
throwIllegalArgException(b, "Unknown property: ", b.getParameter(0));
if (derivedMatch != null) {
derivedMatch.setLocation();
throwIllegalArgException
(b, "Cannot get state for derived property: ", b.getParameter(0));
}
if (joinMatch != null) {
joinMatch.setLocation();
throwIllegalArgException(b, "Cannot get state for join property: ", b.getParameter(0));
}
if (unreadable != null) {
unreadable.setLocation();
throwIllegalArgException(b, "No accessor method for property: ", b.getParameter(0));
}
if (unwritable != null) {
unwritable.setLocation();
throwIllegalArgException(b, "No mutator method for property: ", b.getParameter(0));
}
if (readException != null) {
readException.setLocation();
throwIllegalArgException(b, "Accessor method declares throwing a checked exception: ",
b.getParameter(0));
}
if (writeException != null) {
writeException.setLocation();
throwIllegalArgException(b, "Mutator method declares throwing a checked exception: ",
b.getParameter(0));
}
}
private static boolean throwsCheckedException(Method method) {
Class<?>[] exceptionTypes = method.getExceptionTypes();
if (exceptionTypes == null) {
return false;
}
for (Class<?> exceptionType : exceptionTypes) {
if (RuntimeException.class.isAssignableFrom(exceptionType)) {
continue;
}
if (Error.class.isAssignableFrom(exceptionType)) {
continue;
}
return true;
}
return false;
}
private static void throwIllegalArgException(CodeBuilder b, String message,
LocalVariable concatStr)
{
TypeDesc exceptionType = TypeDesc.forClass(IllegalArgumentException.class);
TypeDesc[] params = {TypeDesc.STRING};
b.newObject(exceptionType);
b.dup();
b.loadConstant(message);
b.loadLocal(concatStr);
b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params);
b.invokeConstructor(exceptionType, params);
b.throwObject();
}
/**
* Returns the properties that match on a given case. The array length is
* the same as the case count. Each list represents the matches. The lists
* themselves may be null if no matches for that case.
*/
private List<StorableProperty<?>>[] caseMatches(int caseCount) {
List<StorableProperty<?>>[] cases = new List[caseCount];
for (StorableProperty<?> prop : mAllProperties.values()) {
int hashCode = prop.getName().hashCode();
int caseValue = (hashCode & 0x7fffffff) % caseCount;
List matches = cases[caseValue];
if (matches == null) {
matches = cases[caseValue] = new ArrayList<StorableProperty<?>>();
}
matches.add(prop);
}
return cases;
}
/**
* Generates public method which accepts a property name and returns a
* boolean true, if the given state matches the property's actual state.
*
* @param name name of method
* @param state property state to check
*/
private void addPropertyStateCheckMethod(String name, int state) {
MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, name,
TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING});
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
// Call private method to extract state and compare.
b.loadThis();
b.loadLocal(b.getParameter(0));
b.invokePrivate(PROPERTY_STATE_EXTRACT_METHOD_NAME,
TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING});
Label isFalse = b.createLabel();
if (state == 0) {
b.ifZeroComparisonBranch(isFalse, "!=");
} else {
b.loadConstant(state);
b.ifComparisonBranch(isFalse, "!=");
}
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
isFalse.setLocation();
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
}
private void addGetPropertyValueMethod() {
MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, GET_PROPERTY_VALUE,
TypeDesc.OBJECT, new TypeDesc[] {TypeDesc.STRING});
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
addPropertySwitch(b, SWITCH_FOR_GET);
}
private void addSetPropertyValueMethod() {
MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, SET_PROPERTY_VALUE, null,
new TypeDesc[] {TypeDesc.STRING, TypeDesc.OBJECT});
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
addPropertySwitch(b, SWITCH_FOR_SET);
}
private void addPropertyMapMethod() {
TypeDesc mapType = TypeDesc.forClass(Map.class);
MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, PROPERTY_MAP, mapType, null);
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
TypeDesc propertyMapType = TypeDesc.forClass(StorablePropertyMap.class);
b.loadConstant(TypeDesc.forClass(mStorableType));
b.loadThis();
b.invokeStatic(propertyMapType, "createMap", propertyMapType,
new TypeDesc[] {TypeDesc.forClass(Class.class),
TypeDesc.forClass(Storable.class)});
b.returnValue(mapType);
}
private void addWriteToMethod() {
TypeDesc streamType = TypeDesc.forClass(OutputStream.class);
MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), WRITE_TO, null,
new TypeDesc[] {streamType});
if (mi == null) {
return;
}
GenericEncodingStrategy<S> encoder = new GenericEncodingStrategy<S>(mStorableType, null);
CodeBuilder b = new CodeBuilder(mi);
LocalVariable encodedVar;
try {
encodedVar = encoder.buildSerialEncoding(b, null);
} catch (SupportException e) {
// Wipe out any code generated so far.
b = new CodeBuilder(mi);
CodeBuilderUtil.throwException(b, SupportException.class, e.getMessage());
return;
}
b.loadLocal(encodedVar);
b.arrayLength();
b.loadLocal(b.getParameter(0));
b.invokeStatic(TypeDesc.forClass(DataEncoder.class), "writeLength", TypeDesc.INT,
new TypeDesc[] {TypeDesc.INT, streamType});
b.pop();
b.loadLocal(b.getParameter(0));
b.loadLocal(encodedVar);
b.invokeVirtual(streamType, "write", null, new TypeDesc[] {encodedVar.getType()});
b.returnVoid();
}
private void addReadFromMethod() {
TypeDesc streamType = TypeDesc.forClass(InputStream.class);
MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), READ_FROM, null,
new TypeDesc[] {streamType});
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
TypeDesc dataDecoderType = TypeDesc.forClass(DataDecoder.class);
b.loadLocal(b.getParameter(0));
b.invokeStatic(dataDecoderType, "readLength", TypeDesc.INT, new TypeDesc[] {streamType});
LocalVariable encodedVar = b.createLocalVariable(null, TypeDesc.forClass(byte[].class));
b.newObject(encodedVar.getType());
b.storeLocal(encodedVar);
b.loadLocal(b.getParameter(0));
b.loadLocal(encodedVar);
b.invokeStatic(dataDecoderType, "readFully", null,
new TypeDesc[] {streamType, encodedVar.getType()});
GenericEncodingStrategy<S> encoder = new GenericEncodingStrategy<S>(mStorableType, null);
try {
encoder.buildSerialDecoding(b, null, encodedVar);
} catch (SupportException e) {
// Wipe out any code generated so far.
b = new CodeBuilder(mi);
CodeBuilderUtil.throwException(b, SupportException.class, e.getMessage());
return;
}
b.returnVoid();
}
/**
* Defines a hashCode method.
*/
private void addHashCodeMethod() {
Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(true);
MethodInfo mi = addMethodIfNotFinal(modifiers, "hashCode", TypeDesc.INT, null);
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
boolean mixIn = false;
for (StorableProperty property : mAllProperties.values()) {
if (property.isDerived() || property.isJoin()) {
continue;
}
TypeDesc fieldType = TypeDesc.forClass(property.getType());
b.loadThis();
b.loadField(property.getName(), fieldType);
CodeBuilderUtil.addValueHashCodeCall(b, fieldType, true, mixIn);
mixIn = true;
}
b.returnValue(TypeDesc.INT);
}
/**
* Defines an equals method.
*
* @param equalityType Type of equality to define - {@link EQUAL_KEYS} for "equalKeys",
* {@link EQUAL_PROPERTIES} for "equalProperties", and {@link EQUAL_FULL} for "equals"
*/
private void addEqualsMethod(int equalityType) {
TypeDesc[] objectParam = {TypeDesc.OBJECT};
String equalsMethodName;
switch (equalityType) {
default:
throw new IllegalArgumentException();
case EQUAL_KEYS:
equalsMethodName = EQUAL_PRIMARY_KEYS_METHOD_NAME;
break;
case EQUAL_PROPERTIES:
equalsMethodName = EQUAL_PROPERTIES_METHOD_NAME;
break;
case EQUAL_FULL:
equalsMethodName = EQUALS_METHOD_NAME;
}
Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(true);
MethodInfo mi = addMethodIfNotFinal
(modifiers, equalsMethodName, TypeDesc.BOOLEAN, objectParam);
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
// if (this == target) return true;
b.loadThis();
b.loadLocal(b.getParameter(0));
Label notEqual = b.createLabel();
b.ifEqualBranch(notEqual, false);
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
notEqual.setLocation();
// FIXME: Using instanceof means that equals is not symmetric.
// if (! target instanceof this) return false;
TypeDesc userStorableTypeDesc = TypeDesc.forClass(mStorableType);
b.loadLocal(b.getParameter(0));
b.instanceOf(userStorableTypeDesc);
Label fail = b.createLabel();
b.ifZeroComparisonBranch(fail, "==");
// this.class other = (this.class)target;
LocalVariable other = b.createLocalVariable(null, userStorableTypeDesc);
b.loadLocal(b.getParameter(0));
b.checkCast(userStorableTypeDesc);
b.storeLocal(other);
for (StorableProperty property : mAllProperties.values()) {
if (property.isDerived() || property.isJoin()) {
continue;
}
// If we're only comparing keys, and this isn't a key, skip it
if ((equalityType == EQUAL_KEYS) && !property.isPrimaryKeyMember()) {
continue;
}
// Check if independent property is supported, and skip if not.
Label skipCheck = b.createLabel();
if (equalityType != EQUAL_KEYS && property.isIndependent()) {
addSkipIndependent(b, other, property, skipCheck);
}
TypeDesc fieldType = TypeDesc.forClass(property.getType());
loadThisProperty(b, property);
b.loadLocal(other);
b.invoke(property.getReadMethod());
CodeBuilderUtil.addValuesEqualCall(b, fieldType, true, fail, false);
skipCheck.setLocation();
}
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
fail.setLocation();
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
}
/**
* Defines a toString method, which assumes that the ClassFile is targeting
* version 1.5 of Java.
*
* @param keyOnly when true, generate a toStringKeyOnly method instead
*/
private void addToStringMethod(boolean keyOnly) {
TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(true);
MethodInfo mi = addMethodIfNotFinal(modifiers,
keyOnly ?
TO_STRING_KEY_ONLY_METHOD_NAME :
TO_STRING_METHOD_NAME,
TypeDesc.STRING, null);
if (mi == null) {
return;
}
CodeBuilder b = new CodeBuilder(mi);
b.newObject(stringBuilder);
b.dup();
b.invokeConstructor(stringBuilder, null);
b.loadConstant(mStorableType.getName());
invokeAppend(b, TypeDesc.STRING);
String detail;
if (keyOnly) {
detail = " (key only) {";
} else {
detail = " {";
}
b.loadConstant(detail);
invokeAppend(b, TypeDesc.STRING);
// First pass, just print primary keys.
LocalVariable commaCountVar = b.createLocalVariable(null, TypeDesc.INT);
b.loadConstant(-1);
b.storeLocal(commaCountVar);
for (StorableProperty property : mInfo.getPrimaryKeyProperties().values()) {
addPropertyAppendCall(b, property, commaCountVar);
}
// Second pass, print non-primary keys.
if (!keyOnly) {
for (StorableProperty property : mAllProperties.values()) {
// Don't print any derived or join properties since they may throw an exception.
if (!property.isPrimaryKeyMember() &&
(!property.isDerived()) && (!property.isJoin()))
{
addPropertyAppendCall(b, property, commaCountVar);
}
}
}
b.loadConstant('}');
invokeAppend(b, TypeDesc.CHAR);
// For key string, also show all the alternate keys. This makes the
// FetchNoneException message more helpful.
if (keyOnly) {
int altKeyCount = mInfo.getAlternateKeyCount();
for (int i=0; i<altKeyCount; i++) {
b.loadConstant(-1);
b.storeLocal(commaCountVar);
b.loadConstant(", {");
invokeAppend(b, TypeDesc.STRING);
StorableKey<S> key = mInfo.getAlternateKey(i);
for (OrderedProperty<S> op : key.getProperties()) {
StorableProperty<S> property = op.getChainedProperty().getPrimeProperty();
addPropertyAppendCall(b, property, commaCountVar);
}
b.loadConstant('}');
invokeAppend(b, TypeDesc.CHAR);
}
}
b.invokeVirtual(stringBuilder, TO_STRING_METHOD_NAME, TypeDesc.STRING, null);
b.returnValue(TypeDesc.STRING);
}
private void invokeAppend(CodeBuilder b, TypeDesc type) {
TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {type});
}
private void addPropertyAppendCall(CodeBuilder b,
StorableProperty property,
LocalVariable commaCountVar)
{
Label skipPrint = b.createLabel();
// Check if independent property is supported, and skip if not.
if (property.isIndependent()) {
addSkipIndependent(b, null, property, skipPrint);
}
int ordinal = property.getNumber();
// Check if property is initialized, and skip if not.
b.loadThis();
b.loadField(PROPERTY_STATE_FIELD_NAME + (ordinal >> 4), TypeDesc.INT);
b.loadConstant(PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2));
b.math(Opcode.IAND);
b.ifZeroComparisonBranch(skipPrint, "==");
b.integerIncrement(commaCountVar, 1);
b.loadLocal(commaCountVar);
Label noComma = b.createLabel();
b.ifZeroComparisonBranch(noComma, "==");
b.loadConstant(", ");
invokeAppend(b, TypeDesc.STRING);
noComma.setLocation();
addPropertyAppendCall(b, property);
skipPrint.setLocation();
}
private void addPropertyAppendCall(CodeBuilder b, StorableProperty property) {
b.loadConstant(property.getName());
invokeAppend(b, TypeDesc.STRING);
b.loadConstant('=');
invokeAppend(b, TypeDesc.CHAR);
loadThisProperty(b, property);
TypeDesc type = TypeDesc.forClass(property.getType());
if (type.isPrimitive()) {
if (type == TypeDesc.BYTE || type == TypeDesc.SHORT) {
type = TypeDesc.INT;
}
} else {
if (type != TypeDesc.STRING) {
if (type.isArray()) {
if (!type.getComponentType().isPrimitive()) {
b.invokeStatic("java.util.Arrays", "deepToString",
TypeDesc.STRING,
new TypeDesc[] {TypeDesc.OBJECT.toArrayType()});
} else {
b.invokeStatic("java.util.Arrays", TO_STRING_METHOD_NAME,
TypeDesc.STRING, new TypeDesc[] {type});
}
}
type = TypeDesc.OBJECT;
}
}
invokeAppend(b, type);
}
/**
* Generates code to get a trigger, forcing a transaction if trigger is not
* null. Also, if there is a trigger, the "before" method is called.
*
* @param opType type of operation, Insert, Update, or Delete
* @param forTryVar optional boolean variable for selecting whether to call
* "before" or "beforeTry" method
* @param forTry used if forTryVar is null
* @param triggerVar required variable of type Trigger for storing trigger
* @param txnVar required variable of type Transaction for storing transaction
* @param stateVar variable of type Object for storing state
* @return try start label for transaction
*/
private Label addGetTriggerAndEnterTxn(CodeBuilder b,
String opType,
LocalVariable forTryVar,
boolean forTry,
LocalVariable triggerVar,
LocalVariable txnVar,
LocalVariable stateVar)
{
// trigger = support$.getXxxTrigger();
b.loadThis();
b.loadField(SUPPORT_FIELD_NAME, mSupportType);
Method m = lookupMethod(mSupportType.toClass(), "get" + opType + "Trigger");
b.invoke(m);
b.storeLocal(triggerVar);
// state = null;
b.loadNull();
b.storeLocal(stateVar);
// if (trigger == null) {
// txn = null;
// } else {
// txn = support.getRootRepository().enterTransaction();
// tryStart:
// if (forTry) {
// state = trigger.beforeTryXxx(txn, this);
// } else {
// state = trigger.beforeXxx(txn, this);
// }
// }
b.loadLocal(triggerVar);
Label hasTrigger = b.createLabel();
b.ifNullBranch(hasTrigger, false);
// txn = null
b.loadNull();
b.storeLocal(txnVar);
Label cont = b.createLabel();
b.branch(cont);
hasTrigger.setLocation();
// txn = support.getRootRepository().enterTransaction();
TypeDesc repositoryType = TypeDesc.forClass(Repository.class);
TypeDesc transactionType = TypeDesc.forClass(Transaction.class);
b.loadThis();
b.loadField(SUPPORT_FIELD_NAME, mSupportType);
b.invokeInterface(mSupportType, "getRootRepository", repositoryType, null);
b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME, transactionType, null);
b.storeLocal(txnVar);
Label tryStart = b.createLabel().setLocation();
// if (forTry) {
// state = trigger.beforeTryXxx(txn, this);
// } else {
// state = trigger.beforeXxx(txn, this);
// }
b.loadLocal(triggerVar);
b.loadLocal(txnVar);
b.loadThis();
if (forTryVar == null) {
if (forTry) {
b.invokeVirtual(triggerVar.getType(), "beforeTry" + opType,
TypeDesc.OBJECT,
new TypeDesc[] {transactionType, TypeDesc.OBJECT});
} else {
b.invokeVirtual(triggerVar.getType(), "before" + opType,
TypeDesc.OBJECT,
new TypeDesc[] {transactionType, TypeDesc.OBJECT});
}
b.storeLocal(stateVar);
} else {
b.loadLocal(forTryVar);
Label isForTry = b.createLabel();
b.ifZeroComparisonBranch(isForTry, "!=");
b.invokeVirtual(triggerVar.getType(), "before" + opType,
TypeDesc.OBJECT, new TypeDesc[] {transactionType, TypeDesc.OBJECT});
b.storeLocal(stateVar);
b.branch(cont);
isForTry.setLocation();
b.invokeVirtual(triggerVar.getType(), "beforeTry" + opType,
TypeDesc.OBJECT, new TypeDesc[] {transactionType, TypeDesc.OBJECT});
b.storeLocal(stateVar);
}
cont.setLocation();
return tryStart;
}
/**
* Generates code to call a trigger after the persistence operation has
* been invoked.
*
* @param opType type of operation, Insert, Update, or Delete
* @param forTryVar optional boolean variable for selecting whether to call
* "after" or "afterTry" method
* @param forTry used if forTryVar is null
* @param triggerVar required variable of type Trigger for retrieving trigger
* @param txnVar required variable of type Transaction for storing transaction
* @param stateVar required variable of type Object for retrieving state
*/
private void addTriggerAfterAndExitTxn(CodeBuilder b,
String opType,
LocalVariable forTryVar,
boolean forTry,
LocalVariable triggerVar,
LocalVariable txnVar,
LocalVariable stateVar)
{
// if (trigger != null) {
b.loadLocal(triggerVar);
Label cont = b.createLabel();
b.ifNullBranch(cont, true);
// if (forTry) {
// trigger.afterTryXxx(this, state);
// } else {
// trigger.afterXxx(this, state);
// }
b.loadLocal(triggerVar);
b.loadThis();
b.loadLocal(stateVar);
if (forTryVar == null) {
if (forTry) {
b.invokeVirtual(TypeDesc.forClass(Trigger.class), "afterTry" + opType, null,
new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT});
} else {
b.invokeVirtual(TypeDesc.forClass(Trigger.class), "after" + opType, null,
new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT});
}
} else {
b.loadLocal(forTryVar);
Label isForTry = b.createLabel();
b.ifZeroComparisonBranch(isForTry, "!=");
b.invokeVirtual(TypeDesc.forClass(Trigger.class), "after" + opType, null,
new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT});
Label commitAndExit = b.createLabel();
b.branch(commitAndExit);
isForTry.setLocation();
b.invokeVirtual(TypeDesc.forClass(Trigger.class), "afterTry" + opType, null,
new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT});
commitAndExit.setLocation();
}
// txn.commit();
// txn.exit();
TypeDesc transactionType = TypeDesc.forClass(Transaction.class);
b.loadLocal(txnVar);
b.invokeInterface(transactionType, COMMIT_METHOD_NAME, null, null);
b.loadLocal(txnVar);
b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null);
cont.setLocation();
}
/**
* Generates code to call a trigger after the persistence operation has
* failed.
*
* @param opType type of operation, Insert, Update, or Delete
* @param triggerVar required variable of type Trigger for retrieving trigger
* @param txnVar required variable of type Transaction for storing transaction
* @param stateVar required variable of type Object for retrieving state
*/
private void addTriggerFailedAndExitTxn(CodeBuilder b,
String opType,
LocalVariable triggerVar,
LocalVariable txnVar,
LocalVariable stateVar)
{
TypeDesc transactionType = TypeDesc.forClass(Transaction.class);
// if (trigger != null) {
b.loadLocal(triggerVar);
Label isNull = b.createLabel();
b.ifNullBranch(isNull, true);
// try {
// trigger.failedXxx(this, state);
// } catch (Throwable e) {
// uncaught(e);
// }
Label tryStart = b.createLabel().setLocation();
b.loadLocal(triggerVar);
b.loadThis();
b.loadLocal(stateVar);
b.invokeVirtual(TypeDesc.forClass(Trigger.class), "failed" + opType, null,
new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT});
Label tryEnd = b.createLabel().setLocation();
Label cont = b.createLabel();
b.branch(cont);
b.exceptionHandler(tryStart, tryEnd, Throwable.class.getName());
b.invokeStatic(UNCAUGHT_METHOD_NAME, null,
new TypeDesc[] {TypeDesc.forClass(Throwable.class)});
cont.setLocation();
// txn.exit();
b.loadLocal(txnVar);
b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null);
isNull.setLocation();
}
/**
* Generates exception handler code to call a trigger after the persistence
* operation has failed.
*
* @param opType type of operation, Insert, Update, or Delete
* @param forTryVar optional boolean variable for selecting whether to
* throw or catch Trigger.Abort.
* @param forTry used if forTryVar is null
* @param triggerVar required variable of type Trigger for retrieving trigger
* @param txnVar required variable of type Transaction for storing transaction
* @param stateVar required variable of type Object for retrieving state
* @param tryStart start of exception handler around transaction
*/
private void addTriggerFailedAndExitTxn(CodeBuilder b,
String opType,
LocalVariable forTryVar,
boolean forTry,
LocalVariable triggerVar,
LocalVariable txnVar,
LocalVariable stateVar,
Label tryStart)
{
if (tryStart == null) {
addTriggerFailedAndExitTxn(b, opType, triggerVar, txnVar, stateVar);
return;
}
// } catch (... e) {
// if (trigger != null) {
// try {
// trigger.failedXxx(this, state);
// } catch (Throwable e) {
// uncaught(e);
// }
// }
// txn.exit();
// if (e instanceof Trigger.Abort) {
// if (forTryVar) {
// return false;
// } else {
// // Try to add some trace for context
// throw ((Trigger.Abort) e).withStackTrace();
// }
// }
// if (e instanceof RepositoryException) {
// throw ((RepositoryException) e).toPersistException();
// }
// throw e;
// }
Label tryEnd = b.createLabel().setLocation();
b.exceptionHandler(tryStart, tryEnd, null);
LocalVariable exceptionVar = b.createLocalVariable(null, TypeDesc.OBJECT);
b.storeLocal(exceptionVar);
addTriggerFailedAndExitTxn(b, opType, triggerVar, txnVar, stateVar);
b.loadLocal(exceptionVar);
TypeDesc abortException = TypeDesc.forClass(Trigger.Abort.class);
b.instanceOf(abortException);
Label nextCheck = b.createLabel();
b.ifZeroComparisonBranch(nextCheck, "==");
if (forTryVar == null) {
if (forTry) {
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
} else {
b.loadLocal(exceptionVar);
b.checkCast(abortException);
b.invokeVirtual(abortException, "withStackTrace", abortException, null);
b.throwObject();
}
} else {
b.loadLocal(forTryVar);
Label isForTry = b.createLabel();
b.ifZeroComparisonBranch(isForTry, "!=");
b.loadLocal(exceptionVar);
b.checkCast(abortException);
b.invokeVirtual(abortException, "withStackTrace", abortException, null);
b.throwObject();
isForTry.setLocation();
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
}
nextCheck.setLocation();
b.loadLocal(exceptionVar);
TypeDesc repException = TypeDesc.forClass(RepositoryException.class);
b.instanceOf(repException);
Label throwAny = b.createLabel();
b.ifZeroComparisonBranch(throwAny, "==");
b.loadLocal(exceptionVar);
b.checkCast(repException);
b.invokeVirtual(repException, "toPersistException",
TypeDesc.forClass(PersistException.class), null);
b.throwObject();
throwAny.setLocation();
b.loadLocal(exceptionVar);
b.throwObject();
}
/**
* Generates method which passes exception to uncaught exception handler.
*/
private void defineUncaughtExceptionHandler() {
MethodInfo mi = mClassFile.addMethod
(Modifiers.PRIVATE.toStatic(true), UNCAUGHT_METHOD_NAME, null,
new TypeDesc[] {TypeDesc.forClass(Throwable.class)});
CodeBuilder b = new CodeBuilder(mi);
// Thread t = Thread.currentThread();
// t.getUncaughtExceptionHandler().uncaughtException(t, e);
TypeDesc threadType = TypeDesc.forClass(Thread.class);
b.invokeStatic(Thread.class.getName(), "currentThread", threadType, null);
LocalVariable threadVar = b.createLocalVariable(null, threadType);
b.storeLocal(threadVar);
b.loadLocal(threadVar);
TypeDesc handlerType = TypeDesc.forClass(Thread.UncaughtExceptionHandler.class);
b.invokeVirtual(threadType, "getUncaughtExceptionHandler", handlerType, null);
b.loadLocal(threadVar);
b.loadLocal(b.getParameter(0));
b.invokeInterface(handlerType, "uncaughtException", null,
new TypeDesc[] {threadType, TypeDesc.forClass(Throwable.class)});
b.returnVoid();
}
/**
* @return MethodInfo for completing definition or null if superclass
* already implements method as final.
*/
private MethodInfo addMethodIfNotFinal(Modifiers modifiers, String name,
TypeDesc retType, TypeDesc[] params)
{
if (CodeBuilderUtil.isPublicMethodFinal(mStorableType, name, retType, params)) {
return null;
}
return mClassFile.addMethod(modifiers, name, retType, params);
}
}