summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2008-07-22 09:19:51 +0000
committerBrian S. O'Neill <bronee@gmail.com>2008-07-22 09:19:51 +0000
commitfec4f593ccac17493f0014bd8fabdedc278135c8 (patch)
treec435dcbe45e420f3bf2b91dc70e0e296fdc7687b /src/main
parent005101b7b1e7579b899c8a379f4516309e164c09 (diff)
Added advanced conversion capability when setting query filter properties. This is used to ensure that BigDecimal values are properly normalized.
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/com/amazon/carbonado/filter/FilterValues.java29
-rw-r--r--src/main/java/com/amazon/carbonado/filter/PropertyFilter.java214
-rw-r--r--src/main/java/com/amazon/carbonado/gen/MasterFeature.java3
-rw-r--r--src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java137
-rw-r--r--src/main/java/com/amazon/carbonado/gen/StorableGenerator.java4
-rw-r--r--src/main/java/com/amazon/carbonado/info/StorableIntrospector.java1
-rw-r--r--src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java128
-rw-r--r--src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java55
-rw-r--r--src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java4
-rw-r--r--src/main/java/com/amazon/carbonado/repo/map/MapStorage.java4
-rw-r--r--src/main/java/com/amazon/carbonado/util/ConversionComparator.java (renamed from src/main/java/com/amazon/carbonado/info/ConversionComparator.java)4
-rw-r--r--src/main/java/com/amazon/carbonado/util/Converter.java602
12 files changed, 898 insertions, 287 deletions
diff --git a/src/main/java/com/amazon/carbonado/filter/FilterValues.java b/src/main/java/com/amazon/carbonado/filter/FilterValues.java
index 6e7202c..a56fc22 100644
--- a/src/main/java/com/amazon/carbonado/filter/FilterValues.java
+++ b/src/main/java/com/amazon/carbonado/filter/FilterValues.java
@@ -99,7 +99,7 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
try {
obj = current.getPropertyFilter().adaptValue(value);
} catch (IllegalArgumentException e) {
- throw mismatch(int.class, value);
+ throw mismatch(e);
}
return with(current, obj);
}
@@ -117,7 +117,7 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
try {
obj = current.getPropertyFilter().adaptValue(value);
} catch (IllegalArgumentException e) {
- throw mismatch(long.class, value);
+ throw mismatch(e);
}
return with(current, obj);
}
@@ -135,7 +135,7 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
try {
obj = current.getPropertyFilter().adaptValue(value);
} catch (IllegalArgumentException e) {
- throw mismatch(float.class, value);
+ throw mismatch(e);
}
return with(current, obj);
}
@@ -153,7 +153,7 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
try {
obj = current.getPropertyFilter().adaptValue(value);
} catch (IllegalArgumentException e) {
- throw mismatch(double.class, value);
+ throw mismatch(e);
}
return with(current, obj);
}
@@ -171,7 +171,7 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
try {
obj = current.getPropertyFilter().adaptValue(value);
} catch (IllegalArgumentException e) {
- throw mismatch(boolean.class, value);
+ throw mismatch(e);
}
return with(current, obj);
}
@@ -189,7 +189,7 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
try {
obj = current.getPropertyFilter().adaptValue(value);
} catch (IllegalArgumentException e) {
- throw mismatch(char.class, value);
+ throw mismatch(e);
}
return with(current, obj);
}
@@ -207,7 +207,7 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
try {
obj = current.getPropertyFilter().adaptValue(value);
} catch (IllegalArgumentException e) {
- throw mismatch(byte.class, value);
+ throw mismatch(e);
}
return with(current, obj);
}
@@ -225,7 +225,7 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
try {
obj = current.getPropertyFilter().adaptValue(value);
} catch (IllegalArgumentException e) {
- throw mismatch(short.class, value);
+ throw mismatch(e);
}
return with(current, obj);
}
@@ -243,7 +243,7 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
try {
obj = current.getPropertyFilter().adaptValue(value);
} catch (IllegalArgumentException e) {
- throw mismatch(value == null ? null : value.getClass(), value);
+ throw mismatch(e);
}
return with(current, obj);
}
@@ -672,17 +672,12 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
return current;
}
- private IllegalArgumentException mismatch(Class<?> actualType, Object actualValue) {
+ private IllegalArgumentException mismatch(IllegalArgumentException e) {
PropertyFilterList<S> current = currentProperty();
PropertyFilter<S> propFilter = current.getPropertyFilter();
StringBuilder b = new StringBuilder();
-
- try {
- propFilter.appendMismatchMessage(b, actualType, actualValue);
- } catch (IOException e) {
- // Not gonna happen
- }
+ b.append(e.getMessage());
int subFilterCount = current.getPreviousRemaining() + current.getNextRemaining() + 1;
@@ -696,7 +691,7 @@ public class FilterValues<S extends Storable> implements Serializable, Appender
b.append(" sub filter in \"");
try {
appendTo(b);
- } catch (IOException e) {
+ } catch (IOException e2) {
// Not gonna happen
}
b.append('"');
diff --git a/src/main/java/com/amazon/carbonado/filter/PropertyFilter.java b/src/main/java/com/amazon/carbonado/filter/PropertyFilter.java
index 5014733..2cecca3 100644
--- a/src/main/java/com/amazon/carbonado/filter/PropertyFilter.java
+++ b/src/main/java/com/amazon/carbonado/filter/PropertyFilter.java
@@ -19,6 +19,10 @@
package com.amazon.carbonado.filter;
import java.io.IOException;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
import java.util.Collections;
import java.util.List;
@@ -28,6 +32,8 @@ import com.amazon.carbonado.Storable;
import com.amazon.carbonado.info.ChainedProperty;
import com.amazon.carbonado.info.StorableProperty;
+import com.amazon.carbonado.util.Converter;
+
/**
* Filter tree node that performs a relational test against a specific property
* value.
@@ -38,7 +44,13 @@ public class PropertyFilter<S extends Storable> extends Filter<S> {
private static final long serialVersionUID = 1L;
// Indicates property has been bound to a constant value.
- private static int BOUND_CONSTANT = -1;
+ private static final int BOUND_CONSTANT = -1;
+
+ private static final Converter cConverter;
+
+ static {
+ cConverter = Converter.build(Hidden.Adapter.class);
+ }
/**
* Returns a canonical instance, creating a new one if there isn't one
@@ -334,171 +346,63 @@ public class PropertyFilter<S extends Storable> extends Filter<S> {
* @throws IllegalArgumentException if type doesn't match
*/
Object adaptValue(int value) {
- Class<?> type = getBoxedType();
- if (type == Integer.class) {
- return Integer.valueOf(value);
- } else if (type == Long.class) {
- return Long.valueOf(value);
- } else if (type == Double.class) {
- return Double.valueOf(value);
- } else if (type == Number.class || type == Object.class) {
- return Integer.valueOf(value);
- }
- throw mismatch(int.class, value);
+ return cConverter.convert(value, getType());
}
/**
* @throws IllegalArgumentException if type doesn't match
*/
Object adaptValue(long value) {
- Class<?> type = getBoxedType();
- if (type == Long.class) {
- return Long.valueOf(value);
- } else if (type == Number.class || type == Object.class) {
- return Long.valueOf(value);
- }
- throw mismatch(long.class, value);
+ return cConverter.convert(value, getType());
}
/**
* @throws IllegalArgumentException if type doesn't match
*/
Object adaptValue(float value) {
- Class<?> type = getBoxedType();
- if (type == Float.class) {
- return Float.valueOf(value);
- } else if (type == Double.class) {
- return Double.valueOf(value);
- } else if (type == Number.class || type == Object.class) {
- return Float.valueOf(value);
- }
- throw mismatch(float.class, value);
+ return cConverter.convert(value, getType());
}
/**
* @throws IllegalArgumentException if type doesn't match
*/
Object adaptValue(double value) {
- Class<?> type = getBoxedType();
- if (type == Double.class) {
- return Double.valueOf(value);
- } else if (type == Number.class || type == Object.class) {
- return Double.valueOf(value);
- }
- throw mismatch(float.class, value);
+ return cConverter.convert(value, getType());
}
/**
* @throws IllegalArgumentException if type doesn't match
*/
Object adaptValue(boolean value) {
- Class<?> type = getBoxedType();
- if (type == Boolean.class || type == Object.class) {
- return Boolean.valueOf(value);
- }
- throw mismatch(boolean.class, value);
+ return cConverter.convert(value, getType());
}
/**
* @throws IllegalArgumentException if type doesn't match
*/
Object adaptValue(char value) {
- Class<?> type = getBoxedType();
- if (type == Character.class || type == Object.class) {
- return Character.valueOf(value);
- } else if (type == String.class || type == CharSequence.class) {
- return String.valueOf(value);
- }
- throw mismatch(char.class, value);
+ return cConverter.convert(value, getType());
}
/**
* @throws IllegalArgumentException if type doesn't match
*/
Object adaptValue(byte value) {
- Class<?> type = getBoxedType();
- if (type == Byte.class) {
- return Byte.valueOf(value);
- } else if (type == Short.class) {
- return Short.valueOf(value);
- } else if (type == Integer.class) {
- return Integer.valueOf(value);
- } else if (type == Long.class) {
- return Long.valueOf(value);
- } else if (type == Double.class) {
- return Double.valueOf(value);
- } else if (type == Float.class) {
- return Float.valueOf(value);
- } else if (type == Number.class || type == Object.class) {
- return Byte.valueOf(value);
- }
- throw mismatch(byte.class, value);
+ return cConverter.convert(value, getType());
}
/**
* @throws IllegalArgumentException if type doesn't match
*/
Object adaptValue(short value) {
- Class<?> type = getBoxedType();
- if (type == Short.class) {
- return Short.valueOf(value);
- } else if (type == Integer.class) {
- return Integer.valueOf(value);
- } else if (type == Long.class) {
- return Long.valueOf(value);
- } else if (type == Double.class) {
- return Double.valueOf(value);
- } else if (type == Float.class) {
- return Float.valueOf(value);
- } else if (type == Number.class || type == Object.class) {
- return Short.valueOf(value);
- }
- throw mismatch(short.class, value);
+ return cConverter.convert(value, Object.class);
}
/**
* @throws IllegalArgumentException if type doesn't match
*/
Object adaptValue(Object value) {
- if (getBoxedType().isInstance(value)) {
- return value;
- }
-
- Class<?> type = getType();
-
- if (value == null) {
- if (!type.isPrimitive()) {
- return value;
- }
- } else if (type.isPrimitive()) {
- TypeDesc actualPrim = TypeDesc.forClass(value.getClass()).toPrimitiveType();
- if (actualPrim != null) {
- if (type == actualPrim.toClass()) {
- return value;
- }
- // Unbox and rebox.
- switch (actualPrim.getTypeCode()) {
- case TypeDesc.BYTE_CODE:
- return adaptValue(((Number) value).byteValue());
- case TypeDesc.SHORT_CODE:
- return adaptValue(((Number) value).shortValue());
- case TypeDesc.INT_CODE:
- return adaptValue(((Number) value).intValue());
- case TypeDesc.LONG_CODE:
- return adaptValue(((Number) value).longValue());
- case TypeDesc.FLOAT_CODE:
- return adaptValue(((Number) value).floatValue());
- case TypeDesc.DOUBLE_CODE:
- return adaptValue(((Number) value).doubleValue());
- case TypeDesc.BOOLEAN_CODE:
- return adaptValue(((Boolean) value).booleanValue());
- case TypeDesc.CHAR_CODE:
- return adaptValue(((Character) value).charValue());
- }
- }
- }
-
- throw mismatch(value == null ? null : value.getClass(), value);
+ return cConverter.convert(value, getType());
}
@Override
@@ -546,29 +450,59 @@ public class PropertyFilter<S extends Storable> extends Filter<S> {
}
}
- void appendMismatchMessage(Appendable a, Class<?> actualType, Object actualValue)
- throws IOException
- {
- if (actualType == null || actualValue == null) {
- a.append("Actual value is null, which cannot be assigned to type \"");
- } else {
- a.append("Actual value \"");
- a.append(String.valueOf(actualValue));
- a.append("\", of type \"");
- a.append(TypeDesc.forClass(actualType).getFullName());
- a.append("\", is incompatible with expected type of \"");
- }
- a.append(TypeDesc.forClass(getType()).getFullName());
- a.append('"');
- }
+ private static class Hidden {
+ public static abstract class Adapter extends Converter {
+ public String convertToString(char value) {
+ return String.valueOf(value);
+ }
+
+ public CharSequence convertToCharSequence(char value) {
+ return String.valueOf(value);
+ }
+
+ public String convertToString(StringBuffer value) {
+ return value.toString();
+ }
+
+ public String convertToString(StringBuilder value) {
+ return value.toString();
+ }
+
+ public BigInteger convertToBigInteger(long value) {
+ return BigInteger.valueOf(value);
+ }
- private IllegalArgumentException mismatch(Class<?> actualType, Object actualValue) {
- StringBuilder b = new StringBuilder();
- try {
- appendMismatchMessage(b, actualType, actualValue);
- } catch (IOException e) {
- // Not gonna happen
+ public BigDecimal convertToBigDecimal(long value) {
+ if (value > -10 && value < 10) {
+ return BigDecimal.valueOf(value);
+ }
+ // Normalize value.
+ return BigDecimal.valueOf(value).stripTrailingZeros();
+ }
+
+ public BigDecimal convertToBigDecimal(double value) {
+ if (value == 0) {
+ return BigDecimal.ZERO;
+ }
+ // Normalize value.
+ return BigDecimal.valueOf(value).stripTrailingZeros();
+ }
+
+ public BigDecimal convertToBigDecimal(BigInteger value) {
+ if (BigInteger.ZERO.equals(value)) {
+ return BigDecimal.ZERO;
+ }
+ // Normalize value.
+ return new BigDecimal(value, 0).stripTrailingZeros();
+ }
+
+ public BigDecimal convertToBigDecimal(BigDecimal value) {
+ if (value.compareTo(BigDecimal.ZERO) == 0) {
+ return BigDecimal.ZERO;
+ }
+ // Normalize value.
+ return value.stripTrailingZeros();
+ }
}
- return new IllegalArgumentException(b.toString());
}
}
diff --git a/src/main/java/com/amazon/carbonado/gen/MasterFeature.java b/src/main/java/com/amazon/carbonado/gen/MasterFeature.java
index a1bb278..f433ce6 100644
--- a/src/main/java/com/amazon/carbonado/gen/MasterFeature.java
+++ b/src/main/java/com/amazon/carbonado/gen/MasterFeature.java
@@ -28,6 +28,9 @@ public enum MasterFeature {
/** Insert and update operations implement record versioning, if version property exists */
VERSIONING,
+ /** Insert and update operations normalize property types such as BigDecimal */
+ NORMALIZE,
+
/** Update operations load clean copy first, to prevent destructive update */
UPDATE_FULL,
diff --git a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java
index d8d2901..092eea8 100644
--- a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java
+++ b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java
@@ -19,8 +19,13 @@
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;
@@ -489,9 +494,19 @@ public final class MasterStorableGenerator<S extends Storable> {
isInitialized.setLocation();
}
+ // Copy of properties before normalization.
+ List<PropertyCopy> unnormalized = null;
+ if (mFeatures.contains(MasterFeature.NORMALIZE)) {
+ unnormalized = addNormalization(b, false);
+ }
+
+ Label doTryStart = b.createLabel().setLocation();
+
b.loadThis();
b.invokeVirtual(DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
+ addNormalizationRollback(b, doTryStart, unnormalized);
+
if (tryStart == null) {
b.returnValue(TypeDesc.BOOLEAN);
} else {
@@ -520,6 +535,7 @@ public final class MasterStorableGenerator<S extends Storable> {
CodeBuilder b = new CodeBuilder(mi);
if ((!mFeatures.contains(MasterFeature.VERSIONING)) &&
+ (!mFeatures.contains(MasterFeature.NORMALIZE)) &&
(!mFeatures.contains(MasterFeature.UPDATE_FULL)) &&
(!mFeatures.contains(MasterFeature.UPDATE_TXN)))
{
@@ -539,7 +555,22 @@ public final class MasterStorableGenerator<S extends Storable> {
Label tryLoadStart = null, tryLoadEnd = null;
- if (mFeatures.contains(MasterFeature.UPDATE_FULL)) {
+ // Copy of properties before normalization.
+ List<PropertyCopy> unnormalized = null;
+ if (mFeatures.contains(MasterFeature.NORMALIZE)) {
+ unnormalized = addNormalization(b, false);
+ }
+
+ Label doTryStart = b.createLabel().setLocation();
+
+ if (!mFeatures.contains(MasterFeature.UPDATE_FULL)) {
+ // if (!this.doTryUpdateMaster()) {
+ // goto failed;
+ // }
+ b.loadThis();
+ b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
+ b.ifZeroComparisonBranch(failed, "==");
+ } else {
// Storable saved = copy();
b.loadThis();
b.invokeVirtual(COPY_METHOD_NAME, storableType, null);
@@ -701,13 +732,6 @@ public final class MasterStorableGenerator<S extends Storable> {
b.loadThis();
b.invokeInterface
(storableType, COPY_UNEQUAL_PROPERTIES, null, new TypeDesc[] {storableType});
- } else {
- // if (!this.doTryUpdateMaster()) {
- // goto failed;
- // }
- b.loadThis();
- b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null);
- b.ifZeroComparisonBranch(failed, "==");
}
// txn.commit();
@@ -1023,4 +1047,101 @@ public final class MasterStorableGenerator<S extends Storable> {
b.invoke(versionProperty.getWriteMethod());
}
+
+ private List<PropertyCopy> addNormalization(CodeBuilder b, boolean forUpdate) {
+ List<PropertyCopy> unnormalized = null;
+
+ for (StorableProperty<S> property : mAllProperties.values()) {
+ if (property.isDerived()) {
+ 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);
+
+ b.loadLocal(copy.copyVar);
+ Label skipNormalize = b.createLabel();
+ b.ifNullBranch(skipNormalize, true);
+
+ if (forUpdate) {
+ // FIXME: for update, also check if dirty
+ }
+
+ // Normalize by stripping trailing zeros.
+ // FIXME: Workaround BigDecimal.ZERO bug.
+ b.loadThis();
+ b.loadLocal(copy.copyVar);
+ TypeDesc propertyType = copy.copyVar.getType();
+ b.invokeVirtual(propertyType, "stripTrailingZeros", propertyType, null);
+ 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) {
+ 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 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());
+ }
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java
index 0eee3de..e88ba39 100644
--- a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java
+++ b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java
@@ -2732,7 +2732,7 @@ public final class StorableGenerator<S extends Storable> {
LocalVariable encodedVar;
try {
- encodedVar = encoder.buildSerialEncoding(b, null, null);
+ encodedVar = encoder.buildSerialEncoding(b, null);
} catch (SupportException e) {
CodeBuilderUtil.throwException(b, SupportException.class, e.getMessage());
return;
@@ -2780,7 +2780,7 @@ public final class StorableGenerator<S extends Storable> {
GenericEncodingStrategy<S> encoder = new GenericEncodingStrategy<S>(mStorableType, null);
try {
- encoder.buildSerialDecoding(b, null, null, encodedVar);
+ encoder.buildSerialDecoding(b, null, encodedVar);
} catch (SupportException e) {
CodeBuilderUtil.throwException(b, SupportException.class, e.getMessage());
return;
diff --git a/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java b/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java
index 80d263a..bd1d52b 100644
--- a/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java
+++ b/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java
@@ -72,6 +72,7 @@ import com.amazon.carbonado.Version;
import com.amazon.carbonado.adapter.AdapterDefinition;
import com.amazon.carbonado.constraint.ConstraintDefinition;
import com.amazon.carbonado.lob.Lob;
+import com.amazon.carbonado.util.ConversionComparator;
/**
* Supports examination of {@link Storable} types, returning all metadata
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java
index e7a8ff9..c5417c4 100644
--- a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java
+++ b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java
@@ -25,7 +25,6 @@ import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.EnumSet;
import java.util.List;
import java.util.Map;
@@ -69,26 +68,6 @@ import com.amazon.carbonado.info.StorablePropertyAdapter;
* @author Brian S O'Neill
*/
public class GenericEncodingStrategy<S extends Storable> {
- /**
- * Defines extra encoding options.
- *
- * @since 1.2
- */
- public static enum Option {
- /**
- * Access properties by public methods instead of protected fields.
- * Option should be used if class being generated doesn't have access
- * to these fields.
- */
- USE_METHODS,
-
- /**
- * Property values such as BigDecimal are normalized before being
- * encoded.
- */
- NORMALIZE,
- }
-
private static enum Mode { KEY, DATA, SERIAL }
private final Class<S> mType;
@@ -165,7 +144,9 @@ public class GenericEncodingStrategy<S extends Storable> {
* of a Storable instance.
* @param adapterInstanceClass class containing static references to
* adapter instances - defaults to instanceVar
- * @param options optional encoding options
+ * @param useReadMethods when true, access properties by public read
+ * methods instead of protected fields - should be used if class being
+ * generated doesn't have access to these fields
* @param partialStartVar optional variable for supporting partial key
* generation. It must be an int, whose runtime value must be less than the
* properties array length. It marks the range start of the partial
@@ -186,7 +167,7 @@ public class GenericEncodingStrategy<S extends Storable> {
OrderedProperty<S>[] properties,
LocalVariable instanceVar,
Class<?> adapterInstanceClass,
- EnumSet<Option> options,
+ boolean useReadMethods,
LocalVariable partialStartVar,
LocalVariable partialEndVar)
throws SupportException
@@ -195,7 +176,7 @@ public class GenericEncodingStrategy<S extends Storable> {
return buildEncoding(Mode.KEY, assembler,
extractProperties(properties), extractDirections(properties),
instanceVar, adapterInstanceClass,
- options,
+ useReadMethods,
-1, // no generation support
partialStartVar, partialEndVar);
}
@@ -213,7 +194,9 @@ public class GenericEncodingStrategy<S extends Storable> {
* of a Storable instance.
* @param adapterInstanceClass class containing static references to
* adapter instances - defaults to instanceVar
- * @param options optional encoding options
+ * @param useWriteMethods when true, set properties by public write
+ * methods instead of protected fields - should be used if class being
+ * generated doesn't have access to these fields
* @param encodedVar required variable, which must be a byte array. At
* runtime, it references an encoded key.
*
@@ -225,14 +208,14 @@ public class GenericEncodingStrategy<S extends Storable> {
OrderedProperty<S>[] properties,
LocalVariable instanceVar,
Class<?> adapterInstanceClass,
- EnumSet<Option> options,
+ boolean useWriteMethods,
LocalVariable encodedVar)
throws SupportException
{
properties = ensureKeyProperties(properties);
buildDecoding(Mode.KEY, assembler,
extractProperties(properties), extractDirections(properties),
- instanceVar, adapterInstanceClass, options,
+ instanceVar, adapterInstanceClass, useWriteMethods,
-1, null, // no generation support
encodedVar);
}
@@ -252,7 +235,8 @@ public class GenericEncodingStrategy<S extends Storable> {
* of a Storable instance.
* @param adapterInstanceClass class containing static references to
* adapter instances - defaults to instanceVar
- * @param options optional encoding options
+ * @param useReadMethods when true, access properties by public read
+ * methods instead of protected fields
* @param generation when non-negative, write a storable layout generation
* value in one or four bytes. Generation 0..127 is encoded in one byte, and
* 128..max is encoded in four bytes, with the most significant bit set.
@@ -267,7 +251,7 @@ public class GenericEncodingStrategy<S extends Storable> {
StorableProperty<S>[] properties,
LocalVariable instanceVar,
Class<?> adapterInstanceClass,
- EnumSet<Option> options,
+ boolean useReadMethods,
int generation)
throws SupportException
{
@@ -275,7 +259,7 @@ public class GenericEncodingStrategy<S extends Storable> {
return buildEncoding(Mode.DATA, assembler,
properties, null,
instanceVar, adapterInstanceClass,
- options, generation, null, null);
+ useReadMethods, generation, null, null);
}
/**
@@ -291,7 +275,9 @@ public class GenericEncodingStrategy<S extends Storable> {
* of a Storable instance.
* @param adapterInstanceClass class containing static references to
* adapter instances - defaults to instanceVar
- * @param options optional encoding options
+ * @param useWriteMethods when true, set properties by public write
+ * methods instead of protected fields - should be used if class being
+ * generated doesn't have access to these fields
* @param generation when non-negative, decoder expects a storable layout
* generation value to match this value. Otherwise, it throws a
* CorruptEncodingException.
@@ -311,7 +297,7 @@ public class GenericEncodingStrategy<S extends Storable> {
StorableProperty<S>[] properties,
LocalVariable instanceVar,
Class<?> adapterInstanceClass,
- EnumSet<Option> options,
+ boolean useWriteMethods,
int generation,
Label altGenerationHandler,
LocalVariable encodedVar)
@@ -319,7 +305,7 @@ public class GenericEncodingStrategy<S extends Storable> {
{
properties = ensureDataProperties(properties);
buildDecoding(Mode.DATA, assembler, properties, null,
- instanceVar, adapterInstanceClass, options,
+ instanceVar, adapterInstanceClass, useWriteMethods,
generation, altGenerationHandler, encodedVar);
}
@@ -330,19 +316,17 @@ public class GenericEncodingStrategy<S extends Storable> {
* @param assembler code assembler to receive bytecode instructions
* @param properties specific properties to decode, defaults to all
* properties if null
- * @param options optional encoding options
* @return local variable referencing a byte array with encoded data
* @throws SupportException if any property type is not supported
* @since 1.2
*/
public LocalVariable buildSerialEncoding(CodeAssembler assembler,
- StorableProperty<S>[] properties,
- EnumSet<Option> options)
+ StorableProperty<S>[] properties)
throws SupportException
{
properties = ensureAllProperties(properties);
return buildEncoding
- (Mode.SERIAL, assembler, properties, null, null, null, options, -1, null, null);
+ (Mode.SERIAL, assembler, properties, null, null, null, false, -1, null, null);
}
/**
@@ -352,7 +336,6 @@ public class GenericEncodingStrategy<S extends Storable> {
* @param assembler code assembler to receive bytecode instructions
* @param properties specific properties to decode, defaults to all
* properties if null
- * @param options optional encoding options
* @param encodedVar required variable, which must be a byte array. At
* runtime, it references encoded data.
* @throws SupportException if any property type is not supported
@@ -361,13 +344,12 @@ public class GenericEncodingStrategy<S extends Storable> {
*/
public void buildSerialDecoding(CodeAssembler assembler,
StorableProperty<S>[] properties,
- EnumSet<Option> options,
LocalVariable encodedVar)
throws SupportException
{
properties = ensureAllProperties(properties);
buildDecoding
- (Mode.SERIAL, assembler, properties, null, null, null, options, -1, null, encodedVar);
+ (Mode.SERIAL, assembler, properties, null, null, null, false, -1, null, encodedVar);
}
/**
@@ -636,7 +618,7 @@ public class GenericEncodingStrategy<S extends Storable> {
Direction[] directions,
LocalVariable instanceVar,
Class<?> adapterInstanceClass,
- EnumSet<Option> options,
+ boolean useReadMethods,
int generation,
LocalVariable partialStartVar,
LocalVariable partialEndVar)
@@ -715,7 +697,7 @@ public class GenericEncodingStrategy<S extends Storable> {
// property is optional, then a byte prefix is needed to
// identify a null reference.
- loadPropertyValue(a, info, 0, options,
+ loadPropertyValue(a, info, 0, useReadMethods,
instanceVar, adapterInstanceClass, partialStartVar);
boolean descending = mode == Mode.KEY
@@ -803,10 +785,7 @@ public class GenericEncodingStrategy<S extends Storable> {
hasVariableLength = true;
}
- if (info.getPropertyType() == info.getStorageType() &&
- // BigDecimal is adapted in this method, to strip trailing zeros.
- info.getPropertyType() != TypeDesc.forClass(BigDecimal.class))
- {
+ if (info.getPropertyType() == info.getStorageType()) {
// Property won't be adapted, so loading it twice is no big deal.
continue;
}
@@ -961,7 +940,7 @@ public class GenericEncodingStrategy<S extends Storable> {
} else {
// Load property to test for null.
loadPropertyValue(stashedProperties, stashedFromInstances,
- a, info, i, options,
+ a, info, i, useReadMethods,
instanceVar, adapterInstanceClass, partialStartVar);
Label isNull = a.createLabel();
@@ -993,7 +972,7 @@ public class GenericEncodingStrategy<S extends Storable> {
propType.toClass() == BigDecimal.class)
{
loadPropertyValue(stashedProperties, stashedFromInstances,
- a, info, i, options,
+ a, info, i, useReadMethods,
instanceVar, adapterInstanceClass, partialStartVar);
String methodName;
@@ -1179,7 +1158,7 @@ public class GenericEncodingStrategy<S extends Storable> {
boolean fromInstance = loadPropertyValue
(stashedProperties, stashedFromInstances,
- a, info, i, options,
+ a, info, i, useReadMethods,
instanceVar, adapterInstanceClass, partialStartVar);
TypeDesc propType = info.getStorageType();
@@ -1279,7 +1258,8 @@ public class GenericEncodingStrategy<S extends Storable> {
* @param info info for property to load
* @param ordinal zero-based property ordinal, used only if instanceVar
* refers to an object array.
- * @param options optional encoding options
+ * @param useReadMethod when true, access property by public read method
+ * instead of protected field
* @param instanceVar local variable referencing Storable instance,
* defaults to "this" if null. If variable type is an Object array, then
* property values are read from the runtime value of this array instead
@@ -1297,7 +1277,7 @@ public class GenericEncodingStrategy<S extends Storable> {
Boolean[] stashedFromInstances,
CodeAssembler a,
StorablePropertyInfo info, int ordinal,
- EnumSet<Option> options,
+ boolean useReadMethod,
LocalVariable instanceVar,
Class<?> adapterInstanceClass,
LocalVariable partialStartVar)
@@ -1308,7 +1288,7 @@ public class GenericEncodingStrategy<S extends Storable> {
}
boolean fromInstance = loadPropertyValue
- (a, info, ordinal, options, instanceVar, adapterInstanceClass, partialStartVar);
+ (a, info, ordinal, useReadMethod, instanceVar, adapterInstanceClass, partialStartVar);
if (stashedProperties != null) {
LocalVariable propVar = stashedProperties[ordinal];
@@ -1329,7 +1309,8 @@ public class GenericEncodingStrategy<S extends Storable> {
* @param info info for property to load
* @param ordinal zero-based property ordinal, used only if instanceVar
* refers to an object array.
- * @param options optional encoding options
+ * @param useReadMethod when true, access property by public read method
+ * instead of protected field
* @param instanceVar local variable referencing Storable instance,
* defaults to "this" if null. If variable type is an Object array, then
* property values are read from the runtime value of this array instead
@@ -1345,7 +1326,7 @@ public class GenericEncodingStrategy<S extends Storable> {
*/
protected boolean loadPropertyValue(CodeAssembler a,
StorablePropertyInfo info, int ordinal,
- EnumSet<Option> options,
+ boolean useReadMethod,
LocalVariable instanceVar,
Class<?> adapterInstanceClass,
LocalVariable partialStartVar)
@@ -1356,11 +1337,9 @@ public class GenericEncodingStrategy<S extends Storable> {
final boolean isObjectArrayInstanceVar = instanceVar != null
&& instanceVar.getType() == TypeDesc.forClass(Object[].class);
- final boolean useMethod = options != null && options.contains(Option.USE_METHODS);
-
final boolean useAdapterInstance = adapterInstanceClass != null
&& info.getToStorageAdapter() != null
- && (useMethod || isObjectArrayInstanceVar);
+ && (useReadMethod || isObjectArrayInstanceVar);
if (useAdapterInstance) {
// Push adapter instance to stack to be used later.
@@ -1373,7 +1352,7 @@ public class GenericEncodingStrategy<S extends Storable> {
if (instanceVar == null) {
a.loadThis();
- if (useMethod) {
+ if (useReadMethod) {
info.addInvokeReadMethod(a);
} else {
// Access property value directly from protected field of "this".
@@ -1386,7 +1365,7 @@ public class GenericEncodingStrategy<S extends Storable> {
}
} else if (!isObjectArrayInstanceVar) {
a.loadLocal(instanceVar);
- if (useMethod) {
+ if (useReadMethod) {
info.addInvokeReadMethod(a, instanceVar.getType());
} else {
// Access property value directly from protected field of
@@ -1420,18 +1399,6 @@ public class GenericEncodingStrategy<S extends Storable> {
if (useAdapterInstance) {
// Invoke adapter method on instance pushed earlier.
a.invoke(info.getToStorageAdapter());
- } else {
- if (options != null && options.contains(Option.NORMALIZE)) {
- TypeDesc bdType = TypeDesc.forClass(BigDecimal.class);
- if (type == bdType) {
- // Normalize by stripping trailing zeros.
- a.dup();
- Label isNull = a.createLabel();
- a.ifNullBranch(isNull, true);
- a.invokeVirtual(bdType, "stripTrailingZeros", bdType, null);
- isNull.setLocation();
- }
- }
}
return !isObjectArrayInstanceVar;
@@ -1831,7 +1798,7 @@ public class GenericEncodingStrategy<S extends Storable> {
Direction[] directions,
LocalVariable instanceVar,
Class<?> adapterInstanceClass,
- EnumSet<Option> options,
+ boolean useWriteMethods,
int generation,
Label altGenerationHandler,
LocalVariable encodedVar)
@@ -1936,7 +1903,7 @@ public class GenericEncodingStrategy<S extends Storable> {
// Just store raw property value.
}
- storePropertyValue(a, info, options, instanceVar, adapterInstanceClass);
+ storePropertyValue(a, info, useWriteMethods, instanceVar, adapterInstanceClass);
return;
}
}
@@ -2150,7 +2117,7 @@ public class GenericEncodingStrategy<S extends Storable> {
storePropertyLocation.setLocation();
- storePropertyValue(a, info, options, instanceVar, adapterInstanceClass);
+ storePropertyValue(a, info, useWriteMethods, instanceVar, adapterInstanceClass);
nextPropertyLocation.setLocation();
}
@@ -2370,7 +2337,8 @@ public class GenericEncodingStrategy<S extends Storable> {
* array must also be on the operand stack.
*
* @param info info for property to store to
- * @param options optional encoding options
+ * @param useWriteMethod when true, set property by public write method
+ * instead of protected field
* @param instanceVar local variable referencing Storable instance,
* defaults to "this" if null. If variable type is an Object array, then
* property values are written to the runtime value of this array instead
@@ -2380,7 +2348,7 @@ public class GenericEncodingStrategy<S extends Storable> {
* @see #pushDecodingInstanceVar pushDecodingInstanceVar
*/
protected void storePropertyValue(CodeAssembler a, StorablePropertyInfo info,
- EnumSet<Option> options,
+ boolean useWriteMethod,
LocalVariable instanceVar,
Class<?> adapterInstanceClass) {
TypeDesc type = info.getPropertyType();
@@ -2389,11 +2357,9 @@ public class GenericEncodingStrategy<S extends Storable> {
boolean isObjectArrayInstanceVar = instanceVar != null
&& instanceVar.getType() == TypeDesc.forClass(Object[].class);
- final boolean useMethod = options != null && options.contains(Option.USE_METHODS);
-
boolean useAdapterInstance = adapterInstanceClass != null
&& info.getToStorageAdapter() != null
- && (useMethod || isObjectArrayInstanceVar);
+ && (useWriteMethod || isObjectArrayInstanceVar);
if (useAdapterInstance) {
// Push adapter instance to adapt property value. It must be on the
@@ -2416,7 +2382,7 @@ public class GenericEncodingStrategy<S extends Storable> {
}
if (instanceVar == null) {
- if (useMethod) {
+ if (useWriteMethod) {
info.addInvokeWriteMethod(a);
} else {
// Set property value directly to protected field of instance.
@@ -2499,7 +2465,7 @@ public class GenericEncodingStrategy<S extends Storable> {
return;
}
- if (useMethod) {
+ if (useWriteMethod) {
info.addInvokeWriteMethod(a, instanceVarType);
} else {
// Set property value directly to protected field of referenced
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java
index 3a642e4..dd43515 100644
--- a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java
+++ b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java
@@ -21,7 +21,6 @@ package com.amazon.carbonado.raw;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
-import java.util.EnumSet;
import java.util.Map;
import org.cojen.classfile.ClassFile;
@@ -56,8 +55,6 @@ import com.amazon.carbonado.gen.CodeBuilderUtil;
import com.amazon.carbonado.util.ThrowUnchecked;
import com.amazon.carbonado.util.QuickConstructorGenerator;
-import static com.amazon.carbonado.raw.GenericEncodingStrategy.Option;
-
/**
* Generic codec that supports any kind of storable by auto-generating and
* caching storable implementations.
@@ -200,17 +197,15 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
// TODO: Consider caching generated key. Rebuild if null or if pk is dirty.
- EnumSet<Option> options = EnumSet.of(Option.NORMALIZE);
-
// assembler = b
// properties = null (defaults to all key properties)
// instanceVar = null (null means "this")
// adapterInstanceClass = null (null means use instanceVar, in this case is "this")
- // options = options
+ // useReadMethods = false (will read fields directly)
// partialStartVar = null (only support encoding all properties)
// partialEndVar = null (only support encoding all properties)
LocalVariable encodedVar =
- encodingStrategy.buildKeyEncoding(b, null, null, null, options, null, null);
+ encodingStrategy.buildKeyEncoding(b, null, null, null, false, null, null);
b.loadLocal(encodedVar);
b.returnValue(byteArrayType);
@@ -224,16 +219,14 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
byteArrayType, null);
CodeBuilder b = new CodeBuilder(mi);
- EnumSet<Option> options = EnumSet.of(Option.NORMALIZE);
-
// assembler = b
// properties = null (defaults to all non-key properties)
// instanceVar = null (null means "this")
// adapterInstanceClass = null (null means use instanceVar, in this case is "this")
- // options = options
+ // useReadMethods = false (will read fields directly)
// generation = generation
LocalVariable encodedVar =
- encodingStrategy.buildDataEncoding(b, null, null, null, options, generation);
+ encodingStrategy.buildDataEncoding(b, null, null, null, false, generation);
b.loadLocal(encodedVar);
b.returnValue(byteArrayType);
@@ -250,9 +243,9 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
// properties = null (defaults to all key properties)
// instanceVar = null (null means "this")
// adapterInstanceClass = null (null means use instanceVar, in this case is "this")
- // options = null (will set fields directly)
+ // useWriteMethods = false (will set fields directly)
// encodedVar = references byte array with encoded key
- encodingStrategy.buildKeyDecoding(b, null, null, null, null, b.getParameter(0));
+ encodingStrategy.buildKeyDecoding(b, null, null, null, false, b.getParameter(0));
b.returnVoid();
}
@@ -269,12 +262,12 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
// properties = null (defaults to all non-key properties)
// instanceVar = null (null means "this")
// adapterInstanceClass = null (null means use instanceVar, in this case is "this")
- // options = null (will set fields directly)
+ // useWriteMethods = false (will set fields directly)
// generation = generation
// altGenerationHandler = altGenerationHandler
// encodedVar = references byte array with encoded data
encodingStrategy.buildDataDecoding
- (b, null, null, null, null, generation, altGenerationHandler, b.getParameter(0));
+ (b, null, null, null, false, generation, altGenerationHandler, b.getParameter(0));
b.returnVoid();
@@ -591,17 +584,15 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
LocalVariable instanceVar = b.createLocalVariable(null, instanceType);
b.storeLocal(instanceVar);
- EnumSet<Option> options = EnumSet.of(Option.NORMALIZE);
-
// assembler = b
// properties = properties to encode
// instanceVar = instanceVar which references storable instance
// adapterInstanceClass = null (null means use instanceVar)
- // options = options
+ // useReadMethods = false (will read fields directly)
// partialStartVar = null (only support encoding all properties)
// partialEndVar = null (only support encoding all properties)
LocalVariable encodedVar = mEncodingStrategy.buildKeyEncoding
- (b, properties, instanceVar, null, options, null, null);
+ (b, properties, instanceVar, null, false, null, null);
b.loadLocal(encodedVar);
b.returnValue(byteArrayType);
@@ -622,17 +613,15 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
LocalVariable instanceVar = b.createLocalVariable(null, instanceType);
b.storeLocal(instanceVar);
- EnumSet<Option> options = EnumSet.of(Option.NORMALIZE);
-
// assembler = b
// properties = properties to encode
// instanceVar = instanceVar which references storable instance
// adapterInstanceClass = null (null means use instanceVar)
- // options = options
+ // useReadMethods = false (will read fields directly)
// partialStartVar = int parameter 1, references start property index
// partialEndVar = int parameter 2, references end property index
LocalVariable encodedVar = mEncodingStrategy.buildKeyEncoding
- (b, properties, instanceVar, null, options, b.getParameter(1), b.getParameter(2));
+ (b, properties, instanceVar, null, false, b.getParameter(1), b.getParameter(2));
b.loadLocal(encodedVar);
b.returnValue(byteArrayType);
@@ -656,17 +645,15 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
new TypeDesc[] {objectArrayType});
CodeBuilder b = new CodeBuilder(mi);
- EnumSet<Option> options = EnumSet.of(Option.NORMALIZE);
-
// assembler = b
// properties = properties to encode
// instanceVar = parameter 0, an object array
// adapterInstanceClass = adapterInstanceClass - see comment above
- // options = options
+ // useReadMethods = false (will read fields directly)
// partialStartVar = null (only support encoding all properties)
// partialEndVar = null (only support encoding all properties)
LocalVariable encodedVar = mEncodingStrategy.buildKeyEncoding
- (b, properties, b.getParameter(0), adapterInstanceClass, options, null, null);
+ (b, properties, b.getParameter(0), adapterInstanceClass, false, null, null);
b.loadLocal(encodedVar);
b.returnValue(byteArrayType);
@@ -683,18 +670,16 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
new TypeDesc[] {objectArrayType, TypeDesc.INT, TypeDesc.INT});
CodeBuilder b = new CodeBuilder(mi);
- EnumSet<Option> options = EnumSet.of(Option.NORMALIZE);
-
// assembler = b
// properties = properties to encode
// instanceVar = parameter 0, an object array
// adapterInstanceClass = adapterInstanceClass - see comment above
- // options = options
+ // useReadMethods = false (will read fields directly)
// partialStartVar = int parameter 1, references start property index
// partialEndVar = int parameter 2, references end property index
LocalVariable encodedVar = mEncodingStrategy.buildKeyEncoding
(b, properties, b.getParameter(0), adapterInstanceClass,
- options, b.getParameter(1), b.getParameter(2));
+ false, b.getParameter(1), b.getParameter(2));
b.loadLocal(encodedVar);
b.returnValue(byteArrayType);
@@ -730,11 +715,11 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
// properties = no parameters - we just want the key prefix
// instanceVar = null (no parameters means we don't need this)
// adapterInstanceClass = null (no parameters means we don't need this)
- // options = null (no parameters means we don't need this)
+ // useReadMethods = false (no parameters means we don't need this)
// partialStartVar = null (no parameters means we don't need this)
// partialEndVar = null (no parameters means we don't need this)
LocalVariable encodedVar = mEncodingStrategy.buildKeyEncoding
- (b, new OrderedProperty[0], null, null, null, null, null);
+ (b, new OrderedProperty[0], null, null, false, null, null);
b.loadLocal(encodedVar);
b.storeStaticField(BLANK_KEY_FIELD_NAME, byteArrayType);
@@ -798,13 +783,13 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
// properties = null (defaults to all non-key properties)
// instanceVar = "dest" storable
// adapterInstanceClass = null (null means use instanceVar, in this case is "dest")
- // options = null (will set fields directly)
+ // useWriteMethods = false (will set fields directly)
// generation = generation
// altGenerationHandler = null (generation should match)
// encodedVar = "data" byte array
try {
altStrategy.buildDataDecoding
- (b, null, destVar, null, null, generation, null, dataVar);
+ (b, null, destVar, null, false, generation, null, dataVar);
} catch (SupportException e) {
throw new CorruptEncodingException(e);
}
diff --git a/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java b/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java
index 57467cc..07f38a7 100644
--- a/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java
+++ b/src/main/java/com/amazon/carbonado/raw/RawStorableGenerator.java
@@ -180,11 +180,13 @@ public class RawStorableGenerator {
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.UPDATE_FULL);
+ features = EnumSet.of(MasterFeature.NORMALIZE,
+ MasterFeature.UPDATE_FULL);
}
final Class<? extends S> abstractClass =
diff --git a/src/main/java/com/amazon/carbonado/repo/map/MapStorage.java b/src/main/java/com/amazon/carbonado/repo/map/MapStorage.java
index 7c2fa0a..27934ef 100644
--- a/src/main/java/com/amazon/carbonado/repo/map/MapStorage.java
+++ b/src/main/java/com/amazon/carbonado/repo/map/MapStorage.java
@@ -132,10 +132,12 @@ class MapStorage<S extends Storable>
EnumSet<MasterFeature> features;
if (repo.isMaster()) {
features = EnumSet.of(MasterFeature.INSERT_CHECK_REQUIRED,
+ MasterFeature.NORMALIZE,
MasterFeature.VERSIONING,
MasterFeature.INSERT_SEQUENCES);
} else {
- features = EnumSet.of(MasterFeature.INSERT_CHECK_REQUIRED);
+ features = EnumSet.of(MasterFeature.INSERT_CHECK_REQUIRED,
+ MasterFeature.NORMALIZE);
}
Class<? extends S> delegateStorableClass =
diff --git a/src/main/java/com/amazon/carbonado/info/ConversionComparator.java b/src/main/java/com/amazon/carbonado/util/ConversionComparator.java
index 0feac47..71f52a3 100644
--- a/src/main/java/com/amazon/carbonado/info/ConversionComparator.java
+++ b/src/main/java/com/amazon/carbonado/util/ConversionComparator.java
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-package com.amazon.carbonado.info;
+package com.amazon.carbonado.util;
import java.util.Comparator;
@@ -28,7 +28,7 @@ import org.cojen.classfile.TypeDesc;
* @author Brian S O'Neill
* @since 1.2
*/
-class ConversionComparator implements Comparator<Class> {
+public class ConversionComparator implements Comparator<Class> {
private final TypeDesc mFrom;
public ConversionComparator(Class fromType) {
diff --git a/src/main/java/com/amazon/carbonado/util/Converter.java b/src/main/java/com/amazon/carbonado/util/Converter.java
new file mode 100644
index 0000000..6f1b479
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/Converter.java
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2008 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.util;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import java.math.BigInteger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+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.SoftValuedHashMap;
+
+/**
+ * General purpose type converter. Custom conversions are possible by supplying
+ * an abstract subclass which has public conversion methods whose names begin
+ * with "convert". Each conversion method takes a single argument and returns a
+ * value.
+ *
+ * @author Brian S O'Neill
+ * @since 1.2
+ */
+public abstract class Converter {
+ private static final Map<Class, Converter> cCache = new SoftValuedHashMap<Class, Converter>();
+
+ public static synchronized <C extends Converter> C build(Class<C> converterType) {
+ C converter = (C) cCache.get(converterType);
+ if (converter == null) {
+ converter = new Builder<C>(converterType).build();
+ cCache.put(converterType, converter);
+ }
+ return converter;
+ }
+
+ /**
+ * @throws IllegalArgumentException if conversion is not supported
+ */
+ public abstract <T> T convert(Object from, Class<T> toType);
+
+ /**
+ * @throws IllegalArgumentException if conversion is not supported
+ */
+ public abstract <T> T convert(byte from, Class<T> toType);
+
+ /**
+ * @throws IllegalArgumentException if conversion is not supported
+ */
+ public abstract <T> T convert(short from, Class<T> toType);
+
+ /**
+ * @throws IllegalArgumentException if conversion is not supported
+ */
+ public abstract <T> T convert(int from, Class<T> toType);
+
+ /**
+ * @throws IllegalArgumentException if conversion is not supported
+ */
+ public abstract <T> T convert(long from, Class<T> toType);
+
+ /**
+ * @throws IllegalArgumentException if conversion is not supported
+ */
+ public abstract <T> T convert(float from, Class<T> toType);
+
+ /**
+ * @throws IllegalArgumentException if conversion is not supported
+ */
+ public abstract <T> T convert(double from, Class<T> toType);
+
+ /**
+ * @throws IllegalArgumentException if conversion is not supported
+ */
+ public abstract <T> T convert(boolean from, Class<T> toType);
+
+ /**
+ * @throws IllegalArgumentException if conversion is not supported
+ */
+ public abstract <T> T convert(char from, Class<T> toType);
+
+ protected IllegalArgumentException conversionNotSupported
+ (Object fromValue, Class fromType, Class toType)
+ {
+ StringBuilder b = new StringBuilder();
+
+ if (fromType == null && fromValue != null) {
+ fromType = fromValue.getClass();
+ }
+
+ if (fromValue == null) {
+ b.append("Actual value null cannot be converted to type ");
+ } else {
+ b.append("Actual value \"");
+ b.append(String.valueOf(fromValue));
+ b.append("\", of type \"");
+ b.append(TypeDesc.forClass(fromType).getFullName());
+ b.append("\", cannot be converted to expected type of ");
+ }
+
+ if (toType == null) {
+ b.append("null");
+ } else {
+ b.append('"');
+ b.append(TypeDesc.forClass(toType).getFullName());
+ b.append('"');
+ }
+
+ return new IllegalArgumentException(b.toString());
+ }
+
+ private static class Builder<C extends Converter> {
+ private final Class<C> mConverterType;
+
+ // Map "from class" to "to class" to optional conversion method.
+ private final Map<Class, Map<Class, Method>> mConvertMap;
+
+ private final Class[][] mBoxMatrix = {
+ {byte.class, Byte.class, Number.class, Object.class},
+ {short.class, Short.class, Number.class, Object.class},
+ {int.class, Integer.class, Number.class, Object.class},
+ {long.class, Long.class, Number.class, Object.class},
+ {float.class, Float.class, Number.class, Object.class},
+ {double.class, Double.class, Number.class, Object.class},
+ {boolean.class, Boolean.class, Object.class},
+ {char.class, Character.class, Object.class},
+ };
+
+ private ClassFile mClassFile;
+
+ private int mInnerConvertCounter;
+
+ Builder(Class<C> converterType) {
+ if (!Converter.class.isAssignableFrom(converterType)) {
+ throw new IllegalArgumentException("Not a TypeConverter: " + converterType);
+ }
+
+ try {
+ converterType.getConstructor();
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException
+ ("TypeConverter must have a public no-arg constructor: " + converterType);
+ }
+
+ mConverterType = converterType;
+ mConvertMap = new HashMap<Class, Map<Class, Method>>();
+
+ // Add built-in primitive boxing/unboxing conversions.
+ for (Class[] tuple : mBoxMatrix) {
+ Map<Class, Method> to = new HashMap<Class, Method>();
+ for (Class toType : tuple) {
+ to.put(toType, null);
+ }
+ mConvertMap.put(tuple[0], to);
+ mConvertMap.put(tuple[1], to);
+ }
+
+ for (Method m : converterType.getMethods()) {
+ if (!m.getName().startsWith("convert")) {
+ continue;
+ }
+ Class toType = m.getReturnType();
+ if (toType == null || toType == void.class) {
+ continue;
+ }
+ Class[] params = m.getParameterTypes();
+ if (params == null || params.length != 1) {
+ continue;
+ }
+
+ Map<Class, Method> to = mConvertMap.get(params[0]);
+ if (to == null) {
+ to = new HashMap<Class, Method>();
+ mConvertMap.put(params[0], to);
+ }
+
+ to.put(toType, m);
+ }
+
+ // Add automatic widening conversions.
+
+ // Copy to prevent concurrent modification.
+ Map<Class, Map<Class, Method>> convertMap =
+ new HashMap<Class, Map<Class, Method>>(mConvertMap);
+
+ for (Map.Entry<Class, Map<Class, Method>> entry : convertMap.entrySet()) {
+ Class fromType = entry.getKey();
+
+ // Copy to prevent concurrent modification.
+ Map<Class, Method> toMap = new HashMap<Class, Method>(entry.getValue());
+
+ for (Map.Entry<Class, Method> to : toMap.entrySet()) {
+ Class toType = to.getKey();
+ Method conversionMethod = to.getValue();
+ addAutomaticConversion(fromType, toType, conversionMethod);
+ }
+ }
+
+ /*
+ for (Map.Entry<Class, Map<Class, Method>> entry : mConvertMap.entrySet()) {
+ Class fromType = entry.getKey();
+ for (Map.Entry<Class, Method> to : entry.getValue().entrySet()) {
+ Class toType = to.getKey();
+ Method conversionMethod = to.getValue();
+ System.out.println("from: " + fromType.getName() + ", to: " +
+ toType.getName() + ", via: " + conversionMethod);
+ }
+ }
+ */
+ }
+
+ C build() {
+ ClassInjector ci = ClassInjector
+ .create(mConverterType.getName(), mConverterType.getClassLoader());
+
+ mClassFile = new ClassFile(ci.getClassName(), mConverterType);
+ mClassFile.markSynthetic();
+ mClassFile.setSourceFile(Converter.class.getName());
+ mClassFile.setTarget("1.5");
+
+ mClassFile.addDefaultConstructor();
+
+ addPrimitiveConvertMethod(byte.class);
+ addPrimitiveConvertMethod(short.class);
+ addPrimitiveConvertMethod(int.class);
+ addPrimitiveConvertMethod(long.class);
+ addPrimitiveConvertMethod(float.class);
+ addPrimitiveConvertMethod(double.class);
+ addPrimitiveConvertMethod(boolean.class);
+ addPrimitiveConvertMethod(char.class);
+
+ Method m = getAbstractConvertMethod(Object.class);
+ if (m != null) {
+ CodeBuilder b = new CodeBuilder(mClassFile.addMethod(m));
+
+ b.loadLocal(b.getParameter(0));
+ Label notNull = b.createLabel();
+ b.ifNullBranch(notNull, false);
+ b.loadNull();
+ b.returnValue(TypeDesc.OBJECT);
+
+ notNull.setLocation();
+ addConversionSwitch(b, null);
+ }
+
+ Class clazz = ci.defineClass(mClassFile);
+
+ try {
+ return (C) clazz.newInstance();
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void addPrimitiveConvertMethod(Class fromType) {
+ Method m = getAbstractConvertMethod(fromType);
+ if (m == null) {
+ return;
+ }
+
+ CodeBuilder b = new CodeBuilder(mClassFile.addMethod(m));
+
+ addConversionSwitch(b, fromType);
+ }
+
+ /*
+ * Generate big switch statements that operate on Classes.
+ *
+ * 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.
+ */
+
+ private void addConversionSwitch(CodeBuilder b, Class fromType) {
+ Map<Class, Method> toMap;
+ Map<Class, ?> caseMap;
+
+ if (fromType == null) {
+ Map<Class, Map<Class, Method>> convertMap =
+ new HashMap<Class, Map<Class, Method>>(mConvertMap);
+ // Remove primitive type cases, since they will never match.
+ Iterator<Class> it = convertMap.keySet().iterator();
+ while (it.hasNext()) {
+ if (it.next().isPrimitive()) {
+ it.remove();
+ }
+ }
+
+ toMap = null;
+ caseMap = convertMap;
+ } else {
+ toMap = mConvertMap.get(fromType);
+ caseMap = toMap;
+ }
+
+ int caseCount = caseCount(caseMap.size());
+
+ 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<Class>[] caseMatches = caseMatches(caseMap, 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();
+ }
+ }
+
+ final TypeDesc classType = TypeDesc.forClass(Class.class);
+
+ LocalVariable caseVar;
+ if (toMap == null) {
+ b.loadLocal(b.getParameter(0));
+ b.invokeVirtual(TypeDesc.OBJECT, "getClass", classType, null);
+ caseVar = b.createLocalVariable(null, classType);
+ b.storeLocal(caseVar);
+ } else {
+ caseVar = b.getParameter(1);
+ }
+
+ if (caseMap.size() > 1) {
+ b.loadLocal(caseVar);
+
+ b.invokeVirtual(Class.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);
+ }
+
+ TypeDesc fromTypeDesc = TypeDesc.forClass(fromType);
+
+ for (int i=0; i<caseCount; i++) {
+ List<Class> matches = caseMatches[i];
+ if (matches == null || matches.size() == 0) {
+ continue;
+ }
+
+ switchLabels[i].setLocation();
+
+ int matchCount = matches.size();
+ for (int j=0; j<matchCount; j++) {
+ Class toType = matches.get(j);
+ TypeDesc toTypeDesc = TypeDesc.forClass(toType);
+
+ // Test against class instance to find exact match.
+
+ b.loadConstant(toTypeDesc);
+ b.loadLocal(caseVar);
+ Label notEqual;
+ if (j == matchCount - 1) {
+ notEqual = null;
+ b.ifEqualBranch(noMatch, false);
+ } else {
+ notEqual = b.createLabel();
+ b.ifEqualBranch(notEqual, false);
+ }
+
+ if (toMap == null) {
+ // Switch in a switch, but do so in a separate method
+ // to keep this one small.
+
+ String name = "convert$" + (++mInnerConvertCounter);
+ TypeDesc[] params = {toTypeDesc, classType};
+ {
+ MethodInfo mi = mClassFile.addMethod
+ (Modifiers.PRIVATE, name, TypeDesc.OBJECT, params);
+ CodeBuilder b2 = new CodeBuilder(mi);
+ addConversionSwitch(b2, toType);
+ }
+
+ b.loadThis();
+ b.loadLocal(b.getParameter(0));
+ b.checkCast(toTypeDesc);
+ b.loadLocal(b.getParameter(1));
+ b.invokePrivate(name, TypeDesc.OBJECT, params);
+ b.returnValue(TypeDesc.OBJECT);
+ } else {
+ Method convertMethod = toMap.get(toType);
+
+ if (convertMethod == null) {
+ b.loadLocal(b.getParameter(0));
+ TypeDesc fromPrimDesc = fromTypeDesc.toPrimitiveType();
+ if (fromPrimDesc != null) {
+ b.convert(fromTypeDesc, fromPrimDesc);
+ b.convert(fromPrimDesc, toTypeDesc.toObjectType());
+ } else {
+ b.convert(fromTypeDesc, toTypeDesc.toObjectType());
+ }
+ } else {
+ b.loadThis();
+ b.loadLocal(b.getParameter(0));
+ Class paramType = convertMethod.getParameterTypes()[0];
+ b.convert(fromTypeDesc, TypeDesc.forClass(paramType));
+ b.invoke(convertMethod);
+ TypeDesc retType = TypeDesc.forClass(convertMethod.getReturnType());
+ b.convert(retType, retType.toObjectType());
+ }
+
+ b.returnValue(TypeDesc.OBJECT);
+ }
+
+ if (notEqual != null) {
+ notEqual.setLocation();
+ }
+ }
+ }
+
+ noMatch.setLocation();
+
+ final TypeDesc valueType = b.getParameter(0).getType();
+
+ if (fromType == null) {
+ // Check if object is already the desired type.
+
+ b.loadLocal(b.getParameter(1));
+ b.loadLocal(b.getParameter(0));
+ b.invokeVirtual(classType, "isInstance", TypeDesc.BOOLEAN,
+ new TypeDesc[] {TypeDesc.OBJECT});
+ Label notSupported = b.createLabel();
+ b.ifZeroComparisonBranch(notSupported, "==");
+ b.loadLocal(b.getParameter(0));
+ b.convert(valueType, valueType.toObjectType());
+ b.returnValue(TypeDesc.OBJECT);
+
+ notSupported.setLocation();
+ }
+
+ b.loadThis();
+ b.loadLocal(b.getParameter(0));
+ b.convert(valueType, valueType.toObjectType());
+ if (valueType.isPrimitive()) {
+ b.loadConstant(valueType);
+ } else {
+ b.loadNull();
+ }
+ b.loadLocal(b.getParameter(1));
+ b.invokeVirtual("conversionNotSupported",
+ TypeDesc.forClass(IllegalArgumentException.class),
+ new TypeDesc[] {TypeDesc.OBJECT, classType, classType});
+ b.throwObject();
+ }
+
+ private int caseCount(int size) {
+ BigInteger capacity = BigInteger.valueOf(size * 2 + 1);
+ while (!capacity.isProbablePrime(100)) {
+ capacity = capacity.add(BigInteger.valueOf(2));
+ }
+ return capacity.intValue();
+ }
+
+ /**
+ * Returns the types 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<Class>[] caseMatches(Map<Class, ?> caseMap, int caseCount) {
+ List<Class>[] cases = new List[caseCount];
+
+ for (Class to : caseMap.keySet()) {
+ int hashCode = to.hashCode();
+ int caseValue = (hashCode & 0x7fffffff) % caseCount;
+ List matches = cases[caseValue];
+ if (matches == null) {
+ matches = cases[caseValue] = new ArrayList<Class>();
+ }
+ matches.add(to);
+ }
+
+ return cases;
+ }
+
+ /**
+ * @return null if should not be defined
+ */
+ private Method getAbstractConvertMethod(Class fromType) {
+ Method m;
+ try {
+ m = mConverterType.getMethod("convert", fromType, Class.class);
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ if (!Modifier.isAbstract(m.getModifiers())) {
+ return null;
+ }
+ return m;
+ }
+
+ private void addAutomaticConversion(Class fromType, Class toType, Method method) {
+ addConversionIfNotExists(fromType, toType, method);
+
+ // Add no-op conversions.
+ addConversionIfNotExists(fromType, fromType, null);
+ addConversionIfNotExists(toType, toType, null);
+
+ for (Class[] pair : mBoxMatrix) {
+ if (fromType == pair[0]) {
+ addConversionIfNotExists(pair[1], toType, method);
+ if (toType == pair[1]) {
+ addConversionIfNotExists(pair[1], pair[0], method);
+ }
+ } else if (fromType == pair[1]) {
+ addConversionIfNotExists(pair[0], toType, method);
+ if (toType == pair[1]) {
+ addConversionIfNotExists(pair[0], pair[1], method);
+ }
+ }
+ if (toType == pair[0]) {
+ addConversionIfNotExists(fromType, pair[1], method);
+ }
+ }
+
+ if (fromType == short.class || fromType == Short.class) {
+ addAutomaticConversion(byte.class, toType, method);
+ } else if (fromType == int.class || fromType == Integer.class) {
+ addAutomaticConversion(short.class, toType, method);
+ } else if (fromType == long.class || fromType == Long.class) {
+ addAutomaticConversion(int.class, toType, method);
+ } else if (fromType == float.class || fromType == Float.class) {
+ addAutomaticConversion(short.class, toType, method);
+ } else if (fromType == double.class || fromType == Double.class) {
+ addAutomaticConversion(int.class, toType, method);
+ addAutomaticConversion(float.class, toType, method);
+ }
+
+ if (toType == byte.class || toType == Byte.class) {
+ addAutomaticConversion(fromType, Short.class, method);
+ } else if (toType == short.class || toType == Short.class) {
+ addAutomaticConversion(fromType, Integer.class, method);
+ addAutomaticConversion(fromType, Float.class, method);
+ } else if (toType == int.class || toType == Integer.class) {
+ addAutomaticConversion(fromType, Long.class, method);
+ addAutomaticConversion(fromType, Double.class, method);
+ } else if (toType == float.class || toType == Float.class) {
+ addAutomaticConversion(fromType, Double.class, method);
+ }
+ }
+
+ private boolean addConversionIfNotExists(Class fromType, Class toType, Method method) {
+ Map<Class, Method> to = mConvertMap.get(fromType);
+ if (to == null) {
+ to = new HashMap<Class, Method>();
+ mConvertMap.put(fromType, to);
+ }
+ Method existing = to.get(toType);
+ if (existing != null) {
+ if (method == null) {
+ return false;
+ }
+ ConversionComparator cc = new ConversionComparator(fromType);
+ Class existingFromType = existing.getParameterTypes()[0];
+ Class candidateFromType = method.getParameterTypes()[0];
+ if (cc.compare(existingFromType, candidateFromType) <= 0) {
+ return false;
+ }
+ }
+ to.put(toType, method);
+ return true;
+ }
+ }
+}