/*
* 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.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.cojen.classfile.ClassFile;
import org.cojen.classfile.CodeBuilder;
import org.cojen.classfile.Label;
import org.cojen.classfile.LocalVariable;
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.KeyFactory;
import com.amazon.carbonado.ConstraintException;
import com.amazon.carbonado.IsolationLevel;
import com.amazon.carbonado.OptimisticLockException;
import com.amazon.carbonado.PersistException;
import com.amazon.carbonado.Repository;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.SupportException;
import com.amazon.carbonado.Transaction;
import com.amazon.carbonado.info.StorableInfo;
import com.amazon.carbonado.info.StorableIntrospector;
import com.amazon.carbonado.info.StorableProperty;
import com.amazon.carbonado.sequence.SequenceValueProducer;
import com.amazon.carbonado.util.SoftValuedCache;
import static com.amazon.carbonado.gen.CommonMethodNames.*;
/**
* Generates and caches abstract implementations of {@link Storable} types
* suitable for use by master repositories. The generated classes extend those
* generated by {@link StorableGenerator}. Subclasses need not worry about
* transactions since this class takes care of that.
*
* @author Brian S O'Neill
* @author Olga Kuznetsova
* @since 1.2
*/
public final class MasterStorableGenerator<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_MASTER_METHOD_NAME = StorableGenerator.DO_TRY_LOAD_METHOD_NAME,
DO_TRY_INSERT_MASTER_METHOD_NAME = "doTryInsert$master",
DO_TRY_UPDATE_MASTER_METHOD_NAME = "doTryUpdate$master",
DO_TRY_DELETE_MASTER_METHOD_NAME = "doTryDelete$master";
private static final String APPEND_UNINIT_PROPERTY = "appendUninitializedPropertyName$";
private static final String INSERT_OP = "Insert";
private static final String UPDATE_OP = "Update";
private static final String DELETE_OP = "Delete";
// Cache of generated abstract classes.
private static SoftValuedCache<Object, Class<? extends Storable>> cCache =
SoftValuedCache.newCache(11);
/**
* 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 for the returned abstract
* class looks like this:
*
* <pre>
* public <init>(MasterSupport);
* </pre>
*
* 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_master() throws PersistException;
*
* // Update the object in the storage.
* protected abstract boolean doTryUpdate_master() throws PersistException;
*
* // Delete the object from the storage layer by the primary key.
* protected abstract boolean doTryDelete_master() throws PersistException;
* </pre>
*
* Subclasses can access the MasterSupport instance via the protected field
* named by {@link StorableGenerator#SUPPORT_FIELD_NAME SUPPORT_FIELD_NAME}.
*
* @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed
* @throws IllegalArgumentException if type is null
* @see MasterSupport
*/
public static <S extends Storable> Class<? extends S>
getAbstractClass(Class<S> type, EnumSet<MasterFeature> features)
throws SupportException, IllegalArgumentException
{
StorableInfo<S> info = StorableIntrospector.examine(type);
if (features == null) {
features = EnumSet.noneOf(MasterFeature.class);
} else {
features = features.clone();
}
// Remove any features which don't apply.
{
boolean anySequences = false;
boolean doNormalize = false;
if (features.contains(MasterFeature.INSERT_SEQUENCES) ||
features.contains(MasterFeature.NORMALIZE))
{
for (StorableProperty<S> property : info.getAllProperties().values()) {
if (property.isDerived() || property.isJoin()) {
continue;
}
if (!anySequences) {
if (property.getSequenceName() != null) {
anySequences = true;
}
}
if (!doNormalize) {
if (BigDecimal.class.isAssignableFrom(property.getType())) {
doNormalize = true;
}
}
if (anySequences && doNormalize) {
break;
}
}
if (!anySequences) {
features.remove(MasterFeature.INSERT_SEQUENCES);
}
if (!doNormalize) {
features.remove(MasterFeature.NORMALIZE);
}
}
if (info.getVersionProperty() == null) {
features.remove(MasterFeature.VERSIONING);
}
if (info.getPartitionKey() == null || info.getPartitionKey().getProperties().size() == 0) {
features.remove(MasterFeature.PARTITIONING);
}
}
// Add implied features.
if (features.contains(MasterFeature.VERSIONING)) {
// Implied feature.
features.add(MasterFeature.UPDATE_FULL);
}
if (alwaysHasTxn(INSERT_OP, features)) {
// Implied feature.
features.add(MasterFeature.INSERT_TXN);
}
if (alwaysHasTxn(UPDATE_OP, features)) {
// Implied feature.
features.add(MasterFeature.UPDATE_TXN);
}
if (alwaysHasTxn(DELETE_OP, features)) {
// Implied feature.
features.add(MasterFeature.DELETE_TXN);
}
if (requiresTxnForUpdate(INSERT_OP, features)) {
// Implied feature.
features.add(MasterFeature.INSERT_TXN_FOR_UPDATE);
}
if (requiresTxnForUpdate(UPDATE_OP, features)) {
// Implied feature.
features.add(MasterFeature.UPDATE_TXN_FOR_UPDATE);
}
if (requiresTxnForUpdate(DELETE_OP, features)) {
// Implied feature.
features.add(MasterFeature.DELETE_TXN_FOR_UPDATE);
}
Object key = KeyFactory.createKey(new Object[] {type, features});
synchronized (cCache) {
Class<? extends S> abstractClass = (Class<? extends S>) cCache.get(key);
if (abstractClass != null) {
return abstractClass;
}
abstractClass =
new MasterStorableGenerator<S>(type, features).generateAndInjectClass();
cCache.put(key, abstractClass);
return abstractClass;
}
}
private final EnumSet<MasterFeature> mFeatures;
private final StorableInfo<S> mInfo;
private final Map<String, ? extends StorableProperty<S>> mAllProperties;
private final ClassInjector mClassInjector;
private final ClassFile mClassFile;
private MasterStorableGenerator(Class<S> storableType, EnumSet<MasterFeature> features) {
mFeatures = features;
mInfo = StorableIntrospector.examine(storableType);
mAllProperties = mInfo.getAllProperties();
final Class<? extends S> abstractClass = StorableGenerator.getAbstractClass(storableType);
mClassInjector = ClassInjector.create
(storableType.getName(), abstractClass.getClassLoader());
mClassFile = new ClassFile(mClassInjector.getClassName(), abstractClass);
mClassFile.setModifiers(mClassFile.getModifiers().toAbstract(true));
mClassFile.markSynthetic();
mClassFile.setSourceFile(MasterStorableGenerator.class.getName());
mClassFile.setTarget("1.5");
}
private Class<? extends S> generateAndInjectClass() throws SupportException {
generateClass();
Class abstractClass = mClassInjector.defineClass(mClassFile);
return (Class<? extends S>) abstractClass;
}
private void generateClass() throws SupportException {
// Declare some types.
final TypeDesc storableType = TypeDesc.forClass(Storable.class);
final TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class);
final TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class);
final TypeDesc transactionType = TypeDesc.forClass(Transaction.class);
final TypeDesc optimisticLockType = TypeDesc.forClass(OptimisticLockException.class);
final TypeDesc persistExceptionType = TypeDesc.forClass(PersistException.class);
// Add constructor that accepts a MasterSupport.
{
TypeDesc[] params = {masterSupportType};
MethodInfo mi = mClassFile.addConstructor(Modifiers.PUBLIC, params);
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadLocal(b.getParameter(0));
b.invokeSuperConstructor(new TypeDesc[] {triggerSupportType});
b.returnVoid();
}
// Declare protected abstract methods.
{
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED.toAbstract(true),
DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(persistExceptionType);
mi = mClassFile.addMethod
(Modifiers.PROTECTED.toAbstract(true),
DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(persistExceptionType);
mi = mClassFile.addMethod
(Modifiers.PROTECTED.toAbstract(true),
DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(persistExceptionType);
}
// Add features pertaining to partitioning
{
if (mFeatures.contains(MasterFeature.PARTITIONING)) {
// Overwrite write function for each property if Partitioning is enabled to check
// that properties that are part of the Partition key are not overwritten when clean
for (StorableProperty<S> property : mAllProperties.values()) {
if (!property.isDerived() && property.isPartitionKeyMember()) {
Method writeMethod = property.getWriteMethod();
MethodInfo mi;
String writeName = property.getWriteMethodName();
final TypeDesc type = TypeDesc.forClass(property.getType());
if (writeMethod != null) {
mi = mClassFile.addMethod(writeMethod);
} else {
continue;
}
CodeBuilder b = new CodeBuilder(mi);
String stateFieldName =
StorableGenerator.PROPERTY_STATE_FIELD_NAME + (property.getNumber() >> 4);
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << ((property.getNumber() & 0xf) * 2));
b.math(Opcode.IAND);
b.loadConstant(StorableGenerator.PROPERTY_STATE_CLEAN << ((property.getNumber() & 0xf) * 2));
Label isMutable = b.createLabel();
b.ifComparisonBranch(isMutable, "!=");
CodeBuilderUtil.throwException
(b, IllegalStateException.class, "Cannot alter partition key");
isMutable.setLocation();
b.loadThis();
b.loadLocal(b.getParameter(0));
b.invokeSuper(mClassFile.getSuperClassName(),
writeName,
null, new TypeDesc[]{type});
b.returnVoid();
}
}
}
}
// Add required protected doTryInsert method.
{
if (mFeatures.contains(MasterFeature.PARTITIONING) ||
mFeatures.contains(MasterFeature.INSERT_SEQUENCES) ||
mFeatures.contains(MasterFeature.INSERT_NO_CHECK_PRIMARY_PK))
{
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED,
StorableGenerator.CHECK_PK_FOR_INSERT_METHOD_NAME,
null, null);
CodeBuilder b = new CodeBuilder(mi);
if (mFeatures.contains(MasterFeature.PARTITIONING)) {
// Check that partition key exists in insert operation
checkIfPartitionKeyPresent(b);
}
// If sequence support requested, implement special insert hook to
// call sequences for properties which are UNINITIALIZED. User may
// provide explicit values for properties with sequences.
if (mFeatures.contains(MasterFeature.INSERT_SEQUENCES)) {
int ordinal = 0;
for (StorableProperty<S> property : mAllProperties.values()) {
if (!property.isDerived() && property.getSequenceName() != null) {
// Check the state of this property, to see if it is
// uninitialized. Uninitialized state has value zero.
String stateFieldName =
StorableGenerator.PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
int shift = (ordinal & 0xf) * 2;
b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << shift);
b.math(Opcode.IAND);
Label isInitialized = b.createLabel();
b.ifZeroComparisonBranch(isInitialized, "!=");
// Load this in preparation for storing value to property.
b.loadThis();
// Call MasterSupport.getSequenceValueProducer(String).
TypeDesc seqValueProdType = TypeDesc.forClass(SequenceValueProducer.class);
b.loadThis();
b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
b.checkCast(masterSupportType);
b.loadConstant(property.getSequenceName());
b.invokeInterface
(masterSupportType, "getSequenceValueProducer",
seqValueProdType, new TypeDesc[] {TypeDesc.STRING});
// Find appropriate method to call for getting next sequence value.
TypeDesc propertyType = TypeDesc.forClass(property.getType());
TypeDesc propertyObjType = propertyType.toObjectType();
Method method;
try {
if (propertyObjType == TypeDesc.LONG.toObjectType()) {
method = SequenceValueProducer.class
.getMethod("nextLongValue", (Class[]) null);
} else if (propertyObjType == TypeDesc.INT.toObjectType()) {
method = SequenceValueProducer.class
.getMethod("nextIntValue", (Class[]) null);
} else if (propertyObjType == TypeDesc.STRING) {
method = SequenceValueProducer.class
.getMethod("nextDecimalValue", (Class[]) null);
} else {
throw new SupportException
("Unable to support sequence of type \"" +
TypeDesc.forClass(property.getType()).getFullName() +
"\" for property: " + property.getName());
}
} catch (NoSuchMethodException e) {
Error err = new NoSuchMethodError();
err.initCause(e);
throw err;
}
b.invoke(method);
b.convert(TypeDesc.forClass(method.getReturnType()), propertyType);
// Store property
b.storeField(property.getName(), propertyType);
// Set state to dirty.
b.loadThis();
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
b.loadConstant(StorableGenerator.PROPERTY_STATE_DIRTY << shift);
b.math(Opcode.IOR);
b.storeField(stateFieldName, TypeDesc.INT);
isInitialized.setLocation();
}
ordinal++;
}
// We've tried our best to fill in missing values, so now
// run the original check method, if required to.
}
if (!mFeatures.contains(MasterFeature.INSERT_NO_CHECK_PRIMARY_PK)) {
b.loadThis();
b.invokeSuper(mClassFile.getSuperClassName(),
StorableGenerator.CHECK_PK_FOR_INSERT_METHOD_NAME,
null, null);
}
b.returnVoid();
}
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED.toFinal(true),
StorableGenerator.DO_TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(persistExceptionType);
CodeBuilder b = new CodeBuilder(mi);
LocalVariable txnVar = b.createLocalVariable(null, transactionType);
Label tryStart = addEnterTransaction(b, INSERT_OP, txnVar);
LocalVariable wasVersionInitVar = null;
if (mFeatures.contains(MasterFeature.VERSIONING)) {
if (!mInfo.getVersionProperty().isDerived()) {
// Only set if uninitialized.
b.loadThis();
b.invokeVirtual(StorableGenerator.IS_VERSION_INITIALIZED_METHOD_NAME,
TypeDesc.BOOLEAN, null);
wasVersionInitVar = b.createLocalVariable(null, TypeDesc.BOOLEAN);
b.storeLocal(wasVersionInitVar);
b.loadLocal(wasVersionInitVar);
Label isInitialized = b.createLabel();
b.ifZeroComparisonBranch(isInitialized, "!=");
addAdjustVersionProperty(b, null, 1);
isInitialized.setLocation();
}
}
if (mFeatures.contains(MasterFeature.INSERT_CHECK_REQUIRED)) {
// Ensure that required properties have been set.
b.loadThis();
b.invokeVirtual(StorableGenerator.IS_REQUIRED_DATA_INITIALIZED_METHOD_NAME,
TypeDesc.BOOLEAN, null);
Label isInitialized = b.createLabel();
b.ifZeroComparisonBranch(isInitialized, "!=");
// Throw a ConstraintException.
TypeDesc exType = TypeDesc.forClass(ConstraintException.class);
b.newObject(exType);
b.dup();
// Append all the uninitialized property names to the exception message.
LocalVariable countVar = b.createLocalVariable(null, TypeDesc.INT);
b.loadConstant(0);
b.storeLocal(countVar);
TypeDesc sbType = TypeDesc.forClass(StringBuilder.class);
b.newObject(sbType);
b.dup();
b.loadConstant("Not all required properties have been set: ");
TypeDesc[] stringParam = {TypeDesc.STRING};
b.invokeConstructor(sbType, stringParam);
LocalVariable sbVar = b.createLocalVariable(null, sbType);
b.storeLocal(sbVar);
int ordinal = -1;
HashSet<Integer> stateAppendMethods = new HashSet<Integer>();
// Parameters are: StringBuilder, count, mask, property name
TypeDesc[] appendParams = {sbType, TypeDesc.INT, TypeDesc.INT, TypeDesc.STRING};
for (StorableProperty<S> property : mAllProperties.values()) {
ordinal++;
if (property.isDerived()
|| property.isIndependent()
|| property.isJoin()
|| property.isPrimaryKeyMember()
|| property.isNullable()
|| property.isAutomatic()
|| property.isVersion())
{
continue;
}
int stateField = ordinal >> 4;
String stateAppendMethodName = APPEND_UNINIT_PROPERTY + stateField;
if (!stateAppendMethods.contains(stateField)) {
stateAppendMethods.add(stateField);
MethodInfo mi2 = mClassFile.addMethod
(Modifiers.PRIVATE, stateAppendMethodName, TypeDesc.INT, appendParams);
CodeBuilder b2 = new CodeBuilder(mi2);
// Load the StringBuilder parameter.
b2.loadLocal(b2.getParameter(0));
String stateFieldName =
StorableGenerator.PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
b2.loadThis();
b2.loadField(stateFieldName, TypeDesc.INT);
// Load the mask parameter.
b2.loadLocal(b2.getParameter(2));
b2.math(Opcode.IAND);
Label propIsInitialized = b2.createLabel();
b2.ifZeroComparisonBranch(propIsInitialized, "!=");
// Load the count parameter.
b2.loadLocal(b2.getParameter(1));
Label noComma = b2.createLabel();
b2.ifZeroComparisonBranch(noComma, "==");
b2.loadConstant(", ");
b2.invokeVirtual(sbType, "append", sbType, stringParam);
noComma.setLocation();
// Load the property name parameter.
b2.loadLocal(b2.getParameter(3));
b2.invokeVirtual(sbType, "append", sbType, stringParam);
// Increment the count parameter.
b2.integerIncrement(b2.getParameter(1), 1);
propIsInitialized.setLocation();
// Return the possibly updated count.
b2.loadLocal(b2.getParameter(1));
b2.returnValue(TypeDesc.INT);
}
b.loadThis();
// Parameters are: StringBuilder, count, mask, property name
b.loadLocal(sbVar);
b.loadLocal(countVar);
b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2));
b.loadConstant(property.getName());
b.invokePrivate(stateAppendMethodName, TypeDesc.INT, appendParams);
b.storeLocal(countVar);
}
b.loadLocal(sbVar);
b.invokeVirtual(sbType, "toString", TypeDesc.STRING, null);
b.invokeConstructor(exType, new TypeDesc[] {TypeDesc.STRING});
b.throwObject();
isInitialized.setLocation();
}
// Copy of properties before normalization.
List<PropertyCopy> unnormalized = addNormalization(b, false);
Label doTryStart = b.createLabel().setLocation();
b.loadThis();
b.invokeVirtual(DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
if (wasVersionInitVar != null) {
// Decide if version property needs to rollback to uninitialized.
b.dup();
Label noRollback = b.createLabel();
// Don't rollback if insert succeeded.
b.ifZeroComparisonBranch(noRollback, "!=");
b.loadLocal(wasVersionInitVar);
// Rollback only if version was automatically set.
b.ifZeroComparisonBranch(noRollback, "!=");
unsetVersionProperty(b);
noRollback.setLocation();
}
addNormalizationRollback(b, doTryStart, unnormalized);
if (tryStart == null) {
b.returnValue(TypeDesc.BOOLEAN);
} else {
Label failed = b.createLabel();
b.ifZeroComparisonBranch(failed, "==");
addCommitAndExitTransaction(b, INSERT_OP, txnVar);
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
failed.setLocation();
addExitTransaction(b, INSERT_OP, txnVar);
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
addExitTransaction(b, INSERT_OP, txnVar, tryStart);
}
}
// Add required protected doTryUpdate method.
addDoTryUpdate: {
if (mFeatures.contains(MasterFeature.PARTITIONING)) {
// Check that partition key exists in update operation
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED,
StorableGenerator.CHECK_PK_FOR_UPDATE_METHOD_NAME,
null, null);
CodeBuilder b = new CodeBuilder(mi);
checkIfPartitionKeyPresent(b);
b.loadThis();
b.invokeSuper(mClassFile.getSuperClassName(),
StorableGenerator.CHECK_PK_FOR_UPDATE_METHOD_NAME,
null, null);
b.returnVoid();
}
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED.toFinal(true),
StorableGenerator.DO_TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(persistExceptionType);
CodeBuilder b = new CodeBuilder(mi);
if ((!mFeatures.contains(MasterFeature.VERSIONING)) &&
(!mFeatures.contains(MasterFeature.NORMALIZE)) &&
(!mFeatures.contains(MasterFeature.UPDATE_FULL)) &&
(!mFeatures.contains(MasterFeature.UPDATE_TXN)))
{
// Nothing special needs to be done, so just delegate and return.
b.loadThis();
b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
b.returnValue(TypeDesc.BOOLEAN);
break addDoTryUpdate;
}
LocalVariable txnVar = b.createLocalVariable(null, transactionType);
LocalVariable savedVar = null;
Label tryStart = addEnterTransaction(b, UPDATE_OP, txnVar);
Label failed = b.createLabel();
Label tryLoadStart = null, tryLoadEnd = null;
if (!mFeatures.contains(MasterFeature.UPDATE_FULL)) {
// Copy of properties before normalization.
List<PropertyCopy> unnormalized = addNormalization(b, true);
Label doTryStart = b.createLabel().setLocation();
// if (!this.doTryUpdateMaster()) {
// goto failed;
// }
b.loadThis();
b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
addNormalizationRollback(b, doTryStart, unnormalized);
b.ifZeroComparisonBranch(failed, "==");
} else {
// Storable saved = copy();
b.loadThis();
b.invokeVirtual(COPY_METHOD_NAME, storableType, null);
b.checkCast(mClassFile.getType());
savedVar = b.createLocalVariable(null, mClassFile.getType());
b.storeLocal(savedVar);
// support.locallyDisableLoadTrigger();
// try {
// if (!saved.tryLoad()) {
// goto failed;
// }
// } finally {
// support.locallyEnableLoadTrigger();
// }
b.loadThis();
b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
b.invokeInterface(triggerSupportType, "locallyDisableLoadTrigger", null, null);
tryLoadStart = b.createLabel().setLocation();
b.loadLocal(savedVar);
b.invokeInterface(storableType, TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null);
tryLoadEnd = b.createLabel().setLocation();
b.loadThis();
b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
b.invokeInterface(triggerSupportType, "locallyEnableLoadTrigger", null, null);
b.ifZeroComparisonBranch(failed, "==");
// Exception handler generated at the end of method.
// if (version support enabled) {
// if (!derived version) {
// if (this.getVersionNumber() != saved.getVersionNumber()) {
// throw new OptimisticLockException
// (this.getVersionNumber(), saved.getVersionNumber(), this);
// }
// } else {
// if (this.getVersionNumber() <= saved.getVersionNumber()) {
// throw new OptimisticLockException
// (saved.getVersionNumber(), this, this.getVersionNumber());
// }
// }
// }
if (mFeatures.contains(MasterFeature.VERSIONING)) {
StorableProperty<S> versionProperty = mInfo.getVersionProperty();
TypeDesc versionType = TypeDesc.forClass(versionProperty.getType());
Label allowedVersion = b.createLabel();
if (!versionProperty.isDerived()) {
b.loadThis();
b.invoke(versionProperty.getReadMethod());
b.loadLocal(savedVar);
b.invoke(versionProperty.getReadMethod());
CodeBuilderUtil.addValuesEqualCall
(b, versionType, true, allowedVersion, true);
b.newObject(optimisticLockType);
b.dup();
b.loadThis();
b.invoke(versionProperty.getReadMethod());
b.convert(versionType, TypeDesc.OBJECT);
b.loadLocal(savedVar);
b.invoke(versionProperty.getReadMethod());
b.convert(versionType, TypeDesc.OBJECT);
b.loadThis();
b.invokeConstructor
(optimisticLockType,
new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT, storableType});
b.throwObject();
} else {
b.loadThis();
b.invoke(versionProperty.getReadMethod());
LocalVariable newVersion = b.createLocalVariable(null, versionType);
b.storeLocal(newVersion);
b.loadLocal(savedVar);
b.invoke(versionProperty.getReadMethod());
LocalVariable savedVersion = b.createLocalVariable(null, versionType);
b.storeLocal(savedVersion);
// Skip check if new or saved version is null.
branchIfNull(b, newVersion, allowedVersion);
branchIfNull(b, savedVersion, allowedVersion);
TypeDesc primVersionType = versionType.toPrimitiveType();
if (primVersionType != null) {
if (versionType != primVersionType) {
b.loadLocal(newVersion);
b.convert(versionType, primVersionType);
newVersion = b.createLocalVariable(null, primVersionType);
b.storeLocal(newVersion);
b.loadLocal(savedVersion);
b.convert(versionType, primVersionType);
savedVersion = b.createLocalVariable(null, primVersionType);
b.storeLocal(savedVersion);
}
// Skip check if new or saved version is NaN.
branchIfNaN(b, newVersion, allowedVersion);
branchIfNaN(b, savedVersion, allowedVersion);
b.loadLocal(newVersion);
b.loadLocal(savedVersion);
b.ifComparisonBranch(allowedVersion, ">", primVersionType);
} else if (Comparable.class.isAssignableFrom(versionProperty.getType())) {
b.loadLocal(newVersion);
b.loadLocal(savedVersion);
b.invokeInterface(TypeDesc.forClass(Comparable.class), "compareTo",
TypeDesc.INT, new TypeDesc[] {TypeDesc.OBJECT});
b.ifZeroComparisonBranch(allowedVersion, ">");
} else {
throw new SupportException
("Derived version property must be Comparable: " +
versionProperty);
}
b.newObject(optimisticLockType);
b.dup();
b.loadLocal(savedVar);
b.invoke(versionProperty.getReadMethod());
b.convert(versionType, TypeDesc.OBJECT);
b.loadThis();
b.loadThis();
b.invoke(versionProperty.getReadMethod());
b.convert(versionType, TypeDesc.OBJECT);
b.invokeConstructor
(optimisticLockType,
new TypeDesc[] {TypeDesc.OBJECT, storableType, TypeDesc.OBJECT});
b.throwObject();
}
allowedVersion.setLocation();
}
// Copy of properties before normalization.
List<PropertyCopy> unnormalized = addNormalization(b, true);
Label doTryStart = b.createLabel().setLocation();
// this.copyDirtyProperties(saved);
// if (version support enabled) {
// saved.setVersionNumber(saved.getVersionNumber() + 1);
// }
b.loadThis();
b.loadLocal(savedVar);
b.invokeVirtual(COPY_DIRTY_PROPERTIES, null, new TypeDesc[] {storableType});
if (mFeatures.contains(MasterFeature.VERSIONING)) {
if (!mInfo.getVersionProperty().isDerived()) {
addAdjustVersionProperty(b, savedVar, -1);
}
}
// if (!saved.doTryUpdateMaster()) {
// goto failed;
// }
b.loadLocal(savedVar);
b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
addNormalizationRollback(b, doTryStart, unnormalized);
b.ifZeroComparisonBranch(failed, "==");
// saved.copyUnequalProperties(this);
b.loadLocal(savedVar);
b.loadThis();
b.invokeInterface
(storableType, COPY_UNEQUAL_PROPERTIES, null, new TypeDesc[] {storableType});
}
// txn.commit();
// txn.exit();
// return true;
addCommitAndExitTransaction(b, UPDATE_OP, txnVar);
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
// failed:
// txn.exit();
failed.setLocation();
addExitTransaction(b, UPDATE_OP, txnVar);
// return false;
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
if (tryLoadStart != null) {
b.exceptionHandler(tryLoadStart, tryLoadEnd, null);
b.loadThis();
b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
b.invokeInterface(triggerSupportType, "locallyEnableLoadTrigger", null, null);
b.throwObject();
}
addExitTransaction(b, UPDATE_OP, txnVar, tryStart);
}
// Add required protected doTryDelete method.
{
if (mFeatures.contains(MasterFeature.PARTITIONING)) {
// Check that partition key exists in delete operation
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED,
StorableGenerator.CHECK_PK_FOR_DELETE_METHOD_NAME,
null, null);
CodeBuilder b = new CodeBuilder(mi);
checkIfPartitionKeyPresent(b);
b.loadThis();
b.invokeSuper(mClassFile.getSuperClassName(),
StorableGenerator.CHECK_PK_FOR_DELETE_METHOD_NAME,
null, null);
b.returnVoid();
}
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED.toFinal(true),
StorableGenerator.DO_TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(persistExceptionType);
CodeBuilder b = new CodeBuilder(mi);
LocalVariable txnVar = b.createLocalVariable(null, transactionType);
Label tryStart = addEnterTransaction(b, DELETE_OP, txnVar);
b.loadThis();
b.invokeVirtual(DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
if (tryStart == null) {
b.returnValue(TypeDesc.BOOLEAN);
} else {
Label failed = b.createLabel();
b.ifZeroComparisonBranch(failed, "==");
addCommitAndExitTransaction(b, DELETE_OP, txnVar);
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
failed.setLocation();
addExitTransaction(b, DELETE_OP, txnVar);
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
addExitTransaction(b, DELETE_OP, txnVar, tryStart);
}
}
// Check that partition key exists for Load
{
if (mFeatures.contains(MasterFeature.PARTITIONING)) {
MethodInfo mi = mClassFile.addMethod
(Modifiers.PROTECTED,
StorableGenerator.CHECK_PK_FOR_LOAD_METHOD_NAME,
null, null);
CodeBuilder b = new CodeBuilder(mi);
checkIfPartitionKeyPresent(b);
b.loadThis();
b.invokeSuper(mClassFile.getSuperClassName(),
StorableGenerator.CHECK_PK_FOR_LOAD_METHOD_NAME,
null, null);
b.returnVoid();
}
}
}
private void branchIfNull(CodeBuilder b, LocalVariable value, Label isNull) {
if (!value.getType().isPrimitive()) {
b.loadLocal(value);
b.ifNullBranch(isNull, true);
}
}
private void branchIfNaN(CodeBuilder b, LocalVariable value, Label isNaN) {
TypeDesc type = value.getType();
if (type == TypeDesc.FLOAT || type == TypeDesc.DOUBLE) {
b.loadLocal(value);
if (type == TypeDesc.FLOAT) {
b.invokeStatic(TypeDesc.FLOAT.toObjectType(),
"isNaN", TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.FLOAT});
b.ifZeroComparisonBranch(isNaN, "!=");
} else {
b.invokeStatic(TypeDesc.DOUBLE.toObjectType(),
"isNaN", TypeDesc.BOOLEAN,
new TypeDesc[] {TypeDesc.DOUBLE});
b.ifZeroComparisonBranch(isNaN, "!=");
}
}
}
/**
* Generates code to enter a transaction, if required and if none in progress.
*
* @param opType type of operation, Insert, Update, or Delete
* @param txnVar required variable of type Transaction for storing transaction
* @return optional try start label for transaction
*/
private Label addEnterTransaction(CodeBuilder b, String opType, LocalVariable txnVar) {
if (!alwaysHasTxn(opType)) {
return null;
}
// Repository repo = masterSupport.getRootRepository();
TypeDesc repositoryType = TypeDesc.forClass(Repository.class);
TypeDesc transactionType = TypeDesc.forClass(Transaction.class);
TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class);
TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class);
TypeDesc isolationLevelType = TypeDesc.forClass(IsolationLevel.class);
b.loadThis();
b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
b.invokeInterface(masterSupportType, "getRootRepository",
repositoryType, null);
if (requiresTxnForUpdate(opType)) {
// Always create nested transaction.
// txn = repo.enterTransaction();
// txn.setForUpdate(true);
b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME,
transactionType, null);
b.storeLocal(txnVar);
b.loadLocal(txnVar);
b.loadConstant(true);
b.invokeInterface(transactionType, SET_FOR_UPDATE_METHOD_NAME, null,
new TypeDesc[] {TypeDesc.BOOLEAN});
} else {
LocalVariable repoVar = b.createLocalVariable(null, repositoryType);
b.storeLocal(repoVar);
// if (repo.getTransactionIsolationLevel() != null) {
// txn = null;
// } else {
// txn = repo.enterTransaction();
// }
b.loadLocal(repoVar);
b.invokeInterface(repositoryType, GET_TRANSACTION_ISOLATION_LEVEL_METHOD_NAME,
isolationLevelType, null);
Label notInTxn = b.createLabel();
b.ifNullBranch(notInTxn, true);
b.loadNull();
Label storeTxn = b.createLabel();
b.branch(storeTxn);
notInTxn.setLocation();
b.loadLocal(repoVar);
b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME,
transactionType, null);
storeTxn.setLocation();
b.storeLocal(txnVar);
}
return b.createLabel().setLocation();
}
private boolean alwaysHasTxn(String opType) {
return alwaysHasTxn(opType, mFeatures);
}
private static boolean alwaysHasTxn(String opType, EnumSet<MasterFeature> features) {
if (opType == UPDATE_OP) {
return
features.contains(MasterFeature.UPDATE_TXN) ||
features.contains(MasterFeature.UPDATE_TXN_FOR_UPDATE) ||
features.contains(MasterFeature.VERSIONING) ||
features.contains(MasterFeature.UPDATE_FULL);
} else if (opType == INSERT_OP) {
return
features.contains(MasterFeature.INSERT_TXN) ||
features.contains(MasterFeature.INSERT_TXN_FOR_UPDATE);
} else if (opType == DELETE_OP) {
return
features.contains(MasterFeature.DELETE_TXN) ||
features.contains(MasterFeature.DELETE_TXN_FOR_UPDATE);
}
return false;
}
private boolean requiresTxnForUpdate(String opType) {
return requiresTxnForUpdate(opType, mFeatures);
}
private static boolean requiresTxnForUpdate(String opType, EnumSet<MasterFeature> features) {
if (opType == UPDATE_OP) {
return
features.contains(MasterFeature.UPDATE_TXN_FOR_UPDATE) ||
features.contains(MasterFeature.VERSIONING) ||
features.contains(MasterFeature.UPDATE_FULL);
} else if (opType == INSERT_OP) {
return features.contains(MasterFeature.INSERT_TXN_FOR_UPDATE);
} else if (opType == DELETE_OP) {
return features.contains(MasterFeature.DELETE_TXN_FOR_UPDATE);
}
return false;
}
private void addCommitAndExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar) {
if (!alwaysHasTxn(opType)) {
return;
}
TypeDesc transactionType = TypeDesc.forClass(Transaction.class);
Label noTxn = b.createLabel();
if (!requiresTxnForUpdate(opType)) {
// Might be null, if transaction was already in progress. If
// requires transaction for update, then a new transaction is
// always created.
b.loadLocal(txnVar);
b.ifNullBranch(noTxn, true);
}
// txn.commit();
// txn.exit();
b.loadLocal(txnVar);
b.invokeInterface(transactionType, COMMIT_METHOD_NAME, null, null);
b.loadLocal(txnVar);
b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null);
noTxn.setLocation();
}
/**
*
* @param opType type of operation, Insert, Update, or Delete
*/
private void addExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar) {
if (!alwaysHasTxn(opType)) {
return;
}
TypeDesc transactionType = TypeDesc.forClass(Transaction.class);
Label noTxn = b.createLabel();
if (!requiresTxnForUpdate(opType)) {
// Might be null, if transaction was already in progress. If
// requires transaction for update, then a new transaction is
// always created.
b.loadLocal(txnVar);
b.ifNullBranch(noTxn, true);
}
// txn.exit();
b.loadLocal(txnVar);
b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null);
noTxn.setLocation();
}
/**
*
* @param opType type of operation, Insert, Update, or Delete
*/
private void addExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar,
Label tryStart)
{
if (tryStart == null) {
addExitTransaction(b, opType, txnVar);
return;
}
// } catch (... e) {
// txn.exit();
// throw e;
// }
Label tryEnd = b.createLabel().setLocation();
b.exceptionHandler(tryStart, tryEnd, null);
addExitTransaction(b, opType, txnVar);
b.throwObject();
}
/*
* Generates code to adjust the version property. If value parameter is negative, then
* version is incremented as follows:
*
* storable.setVersionNumber(storable.getVersionNumber() + 1);
*
* Otherwise, the version is set:
*
* storable.setVersionNumber(value);
*
* @param storableVar references storable instance, or null if this
* @param value if negative, increment version, else, set version to this value
*/
private void addAdjustVersionProperty(CodeBuilder b,
LocalVariable storableVar,
int value)
throws SupportException
{
// Push storable to stack in preparation for calling set method below.
if (storableVar == null) {
b.loadThis();
} else {
b.loadLocal(storableVar);
}
StorableProperty<?> versionProperty = mInfo.getVersionProperty();
TypeDesc versionType = TypeDesc.forClass(versionProperty.getType());
if (value >= 0) {
CodeBuilderUtil.initialVersion(b, versionType, value);
} else {
// Load current property value.
b.dup();
b.invoke(versionProperty.getReadMethod());
CodeBuilderUtil.incrementVersion(b, versionType);
}
b.invoke(versionProperty.getWriteMethod());
}
/**
* Sets the version property to its initial uninitialized state.
*/
private void unsetVersionProperty(CodeBuilder b) throws SupportException {
StorableProperty<?> property = mInfo.getVersionProperty();
// Set the property state to uninitialized.
{
String stateFieldName =
StorableGenerator.PROPERTY_STATE_FIELD_NAME + (property.getNumber() >> 4);
b.loadThis();
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
int shift = (property.getNumber() & 0xf) * 2;
b.loadConstant(~(StorableGenerator.PROPERTY_STATE_MASK << shift));
b.math(Opcode.IAND);
b.storeField(stateFieldName, TypeDesc.INT);
}
// Zero the property value.
TypeDesc type = TypeDesc.forClass(property.getType());
b.loadThis();
CodeBuilderUtil.blankValue(b, type);
b.storeField(property.getName(), type);
}
private List<PropertyCopy> addNormalization(CodeBuilder b, boolean forUpdate) {
List<PropertyCopy> unnormalized = null;
if (!mFeatures.contains(MasterFeature.NORMALIZE)) {
return unnormalized;
}
for (StorableProperty<S> property : mAllProperties.values()) {
if (property.isDerived() || property.isJoin()) {
continue;
}
if (!BigDecimal.class.isAssignableFrom(property.getType())) {
continue;
}
if (unnormalized == null) {
unnormalized = new ArrayList<PropertyCopy>();
}
PropertyCopy copy = new PropertyCopy<S>(b, property);
unnormalized.add(copy);
copy.makeCopy(b);
// Skip property if null.
b.loadLocal(copy.copyVar);
Label skipNormalize = b.createLabel();
b.ifNullBranch(skipNormalize, true);
if (forUpdate) {
// Also skip property if not dirty.
String stateFieldName =
StorableGenerator.PROPERTY_STATE_FIELD_NAME + (property.getNumber() >> 4);
b.loadThis();
b.loadField(stateFieldName, TypeDesc.INT);
int shift = (property.getNumber() & 0xf) * 2;
b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << shift);
b.math(Opcode.IAND);
b.loadConstant(StorableGenerator.PROPERTY_STATE_DIRTY << shift);
b.ifComparisonBranch(skipNormalize, "!=");
}
// Normalize by stripping trailing zeros and also workaround
// BigDecimal.ZERO bug when calling stripTrailingZeros. #6480539
// Load this in preparation for storing to field.
b.loadThis();
TypeDesc propertyType = copy.copyVar.getType();
b.loadStaticField(propertyType, "ZERO", propertyType);
b.loadLocal(copy.copyVar);
b.invokeVirtual(propertyType, "compareTo", TypeDesc.INT,
new TypeDesc[] {propertyType});
Label notZero = b.createLabel();
b.ifZeroComparisonBranch(notZero, "!=");
b.loadStaticField(propertyType, "ZERO", propertyType);
Label storeField = b.createLabel();
b.branch(storeField);
notZero.setLocation();
b.loadLocal(copy.copyVar);
b.invokeVirtual(propertyType, "stripTrailingZeros", propertyType, null);
storeField.setLocation();
b.storeField(property.getName(), propertyType);
skipNormalize.setLocation();
}
return unnormalized;
}
/**
* Assumes a boolean is on the stack, as returned by doTryInsert or doTryUpdate.
*/
private void addNormalizationRollback(CodeBuilder b, Label doTryStart,
List<PropertyCopy> unnormalized)
{
if (unnormalized == null) {
return;
}
Label doTryEnd = b.createLabel().setLocation();
b.dup();
Label success = b.createLabel();
b.ifZeroComparisonBranch(success, "!=");
for (int i=0; i<2; i++) {
if (i == 0) {
} else {
b.exceptionHandler(doTryStart, doTryEnd, null);
}
// Rollback normalized properties.
for (PropertyCopy copy : unnormalized) {
copy.restore(b);
}
if (i == 0) {
b.branch(success);
} else {
b.throwObject();
}
}
success.setLocation();
}
private void checkIfPartitionKeyPresent(CodeBuilder b) {
b.loadThis();
b.invokeVirtual(StorableGenerator.IS_PARTITION_KEY_INITIALIZED_METHOD_NAME, TypeDesc.BOOLEAN, null);
Label ptnkInitialized = b.createLabel();
b.ifZeroComparisonBranch(ptnkInitialized, "!=");
TypeDesc exType = TypeDesc.forClass(IllegalStateException.class);
b.newObject(exType);
b.dup();
b.loadConstant("Partition key not fully specified");
b.invokeConstructor(exType, new TypeDesc[] {TypeDesc.STRING});
b.throwObject();
ptnkInitialized.setLocation();
}
private static class PropertyCopy<S extends Storable> {
final StorableProperty<S> property;
final LocalVariable copyVar;
PropertyCopy(CodeBuilder b, StorableProperty<S> property) {
this.property = property;
copyVar = b.createLocalVariable(null, TypeDesc.forClass(property.getType()));
}
void makeCopy(CodeBuilder b) {
b.loadThis();
b.loadField(property.getName(), copyVar.getType());
b.storeLocal(copyVar);
}
void restore(CodeBuilder b) {
b.loadThis();
b.loadLocal(copyVar);
b.storeField(property.getName(), copyVar.getType());
}
}
}