/*
* 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.raw;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.EnumSet;
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.WeakIdentityMap;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.PersistException;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.SupportException;
import com.amazon.carbonado.info.StorableIntrospector;
import com.amazon.carbonado.info.StorableProperty;
import com.amazon.carbonado.gen.MasterFeature;
import com.amazon.carbonado.gen.MasterStorableGenerator;
import com.amazon.carbonado.gen.MasterSupport;
import com.amazon.carbonado.gen.StorableGenerator;
import com.amazon.carbonado.gen.TriggerSupport;
/**
* Generates and caches abstract implementations of {@link Storable} types
* which are encoded and decoded in a raw format. The generated abstract
* classes extend those created by {@link MasterStorableGenerator}.
*
* @author Brian S O'Neill
* @see GenericStorableCodec
* @see RawSupport
*/
public class RawStorableGenerator {
// 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
ENCODE_KEY_METHOD_NAME = "encodeKey$",
DECODE_KEY_METHOD_NAME = "decodeKey$",
ENCODE_DATA_METHOD_NAME = "encodeData$",
DECODE_DATA_METHOD_NAME = "decodeData$";
@SuppressWarnings("unchecked")
private static Map<Class, Flavors<? extends Storable>> cCache = new WeakIdentityMap();
/**
* Collection of different abstract class flavors.
*/
static class Flavors<S extends Storable> {
private Reference<Class<? extends S>> mMasterFlavor;
private Reference<Class<? extends S>> mNonMasterFlavor;
/**
* May return null.
*/
Class<? extends S> getClass(boolean isMaster) {
Reference<Class<? extends S>> ref;
if (isMaster) {
ref = mMasterFlavor;
} else {
ref = mNonMasterFlavor;
}
return (ref != null) ? ref.get() : null;
}
@SuppressWarnings("unchecked")
void setClass(Class<? extends S> clazz, boolean isMaster) {
Reference<Class<? extends S>> ref = new SoftReference(clazz);
if (isMaster) {
mMasterFlavor = ref;
} else {
mNonMasterFlavor = ref;
}
}
}
// Can't be instantiated or extended
private RawStorableGenerator() {
}
/**
* 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. Three constructors are defined for the
* abstract implementation:
*
* <pre>
* public <init>(RawSupport);
*
* public <init>(RawSupport, byte[] key);
*
* public <init>(RawSupport, byte[] key, byte[] value);
* </pre>
*
* <p>Subclasses must implement the following abstract protected methods,
* whose exact names are defined by constants in this class:
*
* <pre>
* // Encode the primary key of this storable.
* protected abstract byte[] encodeKey();
*
* // Encode all properties of this storable excluding the primary key.
* protected abstract byte[] encodeData();
*
* // Decode the primary key into properties of this storable.
* // Note: this method is also invoked by the three argument constructor.
* protected abstract void decodeKey(byte[]);
*
* // Decode the data into properties of this storable.
* // Note: this method is also invoked by the three argument constructor.
* protected abstract void decodeData(byte[]);
* </pre>
*
* @param isMaster when true, version properties, sequences, and triggers are managed
* @throws IllegalArgumentException if type is null
*/
@SuppressWarnings("unchecked")
public static <S extends Storable> Class<? extends S>
getAbstractClass(Class<S> type, boolean isMaster)
throws SupportException, IllegalArgumentException
{
synchronized (cCache) {
Class<? extends S> abstractClass;
Flavors<S> flavors = (Flavors<S>) cCache.get(type);
if (flavors == null) {
flavors = new Flavors<S>();
cCache.put(type, flavors);
} else if ((abstractClass = flavors.getClass(isMaster)) != null) {
return abstractClass;
}
abstractClass = generateAbstractClass(type, isMaster);
flavors.setClass(abstractClass, isMaster);
return abstractClass;
}
}
@SuppressWarnings("unchecked")
private static <S extends Storable> Class<? extends S>
generateAbstractClass(Class<S> storableClass, boolean isMaster)
throws SupportException
{
EnumSet<MasterFeature> features;
if (isMaster) {
features = EnumSet.of(MasterFeature.VERSIONING,
MasterFeature.NORMALIZE,
MasterFeature.UPDATE_FULL,
MasterFeature.INSERT_SEQUENCES,
MasterFeature.INSERT_CHECK_REQUIRED);
} else {
features = EnumSet.of(MasterFeature.NORMALIZE,
MasterFeature.UPDATE_FULL);
}
final Class<? extends S> abstractClass =
MasterStorableGenerator.getAbstractClass(storableClass, features);
ClassInjector ci = ClassInjector.create
(storableClass.getName(), abstractClass.getClassLoader());
ClassFile cf = new ClassFile(ci.getClassName(), abstractClass);
cf.setModifiers(cf.getModifiers().toAbstract(true));
cf.markSynthetic();
cf.setSourceFile(RawStorableGenerator.class.getName());
cf.setTarget("1.5");
// 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 rawSupportType = TypeDesc.forClass(RawSupport.class);
final TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
// Add constructors.
// 1: Accepts a RawSupport.
// 2: Accepts a RawSupport and an encoded key.
// 3: Accepts a RawSupport, an encoded key and an encoded data.
for (int i=1; i<=3; i++) {
TypeDesc[] params = new TypeDesc[i];
params[0] = rawSupportType;
if (i >= 2) {
params[1] = byteArrayType;
if (i == 3) {
params[2] = byteArrayType;
}
}
MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.loadLocal(b.getParameter(0));
b.invokeSuperConstructor(new TypeDesc[] {masterSupportType});
if (i >= 2) {
params = new TypeDesc[] {byteArrayType};
b.loadThis();
b.loadLocal(b.getParameter(1));
b.invokeVirtual(DECODE_KEY_METHOD_NAME, null, params);
if (i == 3) {
b.loadThis();
b.loadLocal(b.getParameter(2));
b.invokeVirtual(DECODE_DATA_METHOD_NAME, null, params);
// Indicate load completed in order to mark properties as valid and
// invoke load triggers.
b.loadThis();
b.invokeVirtual(StorableGenerator.LOAD_COMPLETED_METHOD_NAME, null, null);
} else {
// Only the primary key is clean. Calling
// markPropertiesClean might have no effect since subclass
// may set fields directly. Instead, set state bits directly.
Collection<? extends StorableProperty<S>> properties = StorableIntrospector
.examine(storableClass).getPrimaryKeyProperties().values();
final int count = properties.size();
int ordinal = 0;
int andMask = ~0;
int orMask = 0;
for (StorableProperty property : properties) {
orMask |= StorableGenerator.PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
andMask &=
~(StorableGenerator.PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2));
ordinal++;
if ((ordinal & 0xf) == 0 || ordinal >= count) {
String stateFieldName =
StorableGenerator.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);
b.loadConstant(orMask);
b.math(Opcode.IOR);
}
b.storeField(stateFieldName, TypeDesc.INT);
andMask = ~0;
orMask = 0;
}
}
}
}
b.returnVoid();
}
// Declare protected abstract methods.
{
cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
ENCODE_KEY_METHOD_NAME, byteArrayType, null);
cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
DECODE_KEY_METHOD_NAME, null, new TypeDesc[]{byteArrayType});
cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
ENCODE_DATA_METHOD_NAME, byteArrayType, null);
cf.addMethod(Modifiers.PROTECTED.toAbstract(true),
DECODE_DATA_METHOD_NAME, null, new TypeDesc[]{byteArrayType});
}
// Add required protected doTryLoad_master method, which delegates to RawSupport.
{
MethodInfo mi = cf.addMethod
(Modifiers.PROTECTED.toFinal(true),
MasterStorableGenerator.DO_TRY_LOAD_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(TypeDesc.forClass(FetchException.class));
CodeBuilder b = new CodeBuilder(mi);
// return rawSupport.tryLoad(this, this.encodeKey$());
b.loadThis();
b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
b.checkCast(rawSupportType);
b.loadThis(); // pass this to load method
b.loadThis();
b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
TypeDesc[] params = {storableType, byteArrayType};
b.invokeInterface(rawSupportType, "tryLoad", byteArrayType, params);
LocalVariable encodedDataVar = b.createLocalVariable(null, byteArrayType);
b.storeLocal(encodedDataVar);
b.loadLocal(encodedDataVar);
Label notNull = b.createLabel();
b.ifNullBranch(notNull, false);
b.loadConstant(false);
b.returnValue(TypeDesc.BOOLEAN);
notNull.setLocation();
b.loadThis();
b.loadLocal(encodedDataVar);
params = new TypeDesc[] {byteArrayType};
b.invokeVirtual(DECODE_DATA_METHOD_NAME, null, params);
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
}
// Add required protected doTryInsert_master method, which delegates to RawSupport.
{
MethodInfo mi = cf.addMethod
(Modifiers.PROTECTED.toFinal(true),
MasterStorableGenerator.DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
// return rawSupport.tryInsert(this, this.encodeKey$(), this.encodeData$());
b.loadThis();
b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
b.checkCast(rawSupportType);
b.loadThis(); // pass this to tryInsert method
b.loadThis();
b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
b.loadThis();
b.invokeVirtual(ENCODE_DATA_METHOD_NAME, byteArrayType, null);
TypeDesc[] params = {storableType, byteArrayType, byteArrayType};
b.invokeInterface(rawSupportType, "tryInsert", TypeDesc.BOOLEAN, params);
b.returnValue(TypeDesc.BOOLEAN);
}
// Add required protected doTryUpdate_master method, which delegates to RawSupport.
{
MethodInfo mi = cf.addMethod
(Modifiers.PROTECTED.toFinal(true),
MasterStorableGenerator.DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
// rawSupport.store(this, this.encodeKey$(), this.encodeData$());
// return true;
b.loadThis();
b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
b.checkCast(rawSupportType);
b.loadThis(); // pass this to store method
b.loadThis();
b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
b.loadThis();
b.invokeVirtual(ENCODE_DATA_METHOD_NAME, byteArrayType, null);
TypeDesc[] params = {storableType, byteArrayType, byteArrayType};
b.invokeInterface(rawSupportType, "store", null, params);
b.loadConstant(true);
b.returnValue(TypeDesc.BOOLEAN);
}
// Add required protected doTryDelete_master method, which delegates to RawSupport.
{
MethodInfo mi = cf.addMethod
(Modifiers.PROTECTED.toFinal(true),
MasterStorableGenerator.DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
mi.addException(TypeDesc.forClass(PersistException.class));
CodeBuilder b = new CodeBuilder(mi);
// return rawSupport.tryDelete(this, this.encodeKey$());
b.loadThis();
b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType);
b.checkCast(rawSupportType);
b.loadThis(); // pass this to delete method
b.loadThis();
b.invokeVirtual(ENCODE_KEY_METHOD_NAME, byteArrayType, null);
TypeDesc[] params = {storableType, byteArrayType};
b.invokeInterface(rawSupportType, "tryDelete", TypeDesc.BOOLEAN, params);
b.returnValue(TypeDesc.BOOLEAN);
}
return ci.defineClass(cf);
}
}