From fec4f593ccac17493f0014bd8fabdedc278135c8 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" <bronee@gmail.com> Date: Tue, 22 Jul 2008 09:19:51 +0000 Subject: Added advanced conversion capability when setting query filter properties. This is used to ensure that BigDecimal values are properly normalized. --- .../com/amazon/carbonado/filter/FilterValues.java | 29 +- .../amazon/carbonado/filter/PropertyFilter.java | 214 +++----- .../com/amazon/carbonado/gen/MasterFeature.java | 3 + .../carbonado/gen/MasterStorableGenerator.java | 137 ++++- .../amazon/carbonado/gen/StorableGenerator.java | 4 +- .../carbonado/info/ConversionComparator.java | 213 -------- .../carbonado/info/StorableIntrospector.java | 1 + .../carbonado/raw/GenericEncodingStrategy.java | 128 ++--- .../amazon/carbonado/raw/GenericStorableCodec.java | 55 +- .../amazon/carbonado/raw/RawStorableGenerator.java | 4 +- .../com/amazon/carbonado/repo/map/MapStorage.java | 4 +- .../carbonado/util/ConversionComparator.java | 213 ++++++++ .../java/com/amazon/carbonado/util/Converter.java | 602 +++++++++++++++++++++ 13 files changed, 1109 insertions(+), 498 deletions(-) delete mode 100644 src/main/java/com/amazon/carbonado/info/ConversionComparator.java create mode 100644 src/main/java/com/amazon/carbonado/util/ConversionComparator.java create mode 100644 src/main/java/com/amazon/carbonado/util/Converter.java (limited to 'src/main/java/com/amazon') 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/ConversionComparator.java b/src/main/java/com/amazon/carbonado/info/ConversionComparator.java deleted file mode 100644 index 0feac47..0000000 --- a/src/main/java/com/amazon/carbonado/info/ConversionComparator.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2006 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.info; - -import java.util.Comparator; - -import org.cojen.classfile.TypeDesc; - -/** - * Compares type conversions, finding the one that is nearest. - * - * @author Brian S O'Neill - * @since 1.2 - */ -class ConversionComparator implements Comparator<Class> { - private final TypeDesc mFrom; - - public ConversionComparator(Class fromType) { - mFrom = TypeDesc.forClass(fromType); - } - - /** - * Returns true if a coversion is possible to the given type. - */ - public boolean isConversionPossible(Class toType) { - return isConversionPossible(mFrom, TypeDesc.forClass(toType)); - } - - @SuppressWarnings("unchecked") - private static boolean isConversionPossible(TypeDesc from, TypeDesc to) { - if (from == to) { - return true; - } - - if (from.toPrimitiveType() != null && to.toPrimitiveType() != null) { - from = from.toPrimitiveType(); - to = to.toPrimitiveType(); - } else { - from = from.toObjectType(); - to = to.toObjectType(); - } - - switch (from.getTypeCode()) { - case TypeDesc.OBJECT_CODE: default: - return to.toClass().isAssignableFrom(from.toClass()); - case TypeDesc.BOOLEAN_CODE: - return to == TypeDesc.BOOLEAN; - case TypeDesc.BYTE_CODE: - return to == TypeDesc.BYTE || to == TypeDesc.SHORT - || to == TypeDesc.INT || to == TypeDesc.LONG - || to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; - case TypeDesc.SHORT_CODE: - return to == TypeDesc.SHORT - || to == TypeDesc.INT || to == TypeDesc.LONG - || to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; - case TypeDesc.CHAR_CODE: - return to == TypeDesc.CHAR; - case TypeDesc.INT_CODE: - return to == TypeDesc.INT || to == TypeDesc.LONG || to == TypeDesc.DOUBLE; - case TypeDesc.FLOAT_CODE: - return to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; - case TypeDesc.LONG_CODE: - return to == TypeDesc.LONG; - case TypeDesc.DOUBLE_CODE: - return to == TypeDesc.DOUBLE; - } - } - - /** - * Evaluates two types, to see which one is nearest to the from type. - * Return {@literal <0} if "a" is nearest, 0 if both are equally good, - * {@literal >0} if "b" is nearest. - */ - public int compare(Class toType_a, Class toType_b) { - TypeDesc from = mFrom; - TypeDesc a = TypeDesc.forClass(toType_a); - TypeDesc b = TypeDesc.forClass(toType_b); - - if (from == a) { - if (from == b) { - return 0; - } - return -1; - } else if (from == b) { - return 1; - } - - int result = compare(from, a, b); - if (result != 0) { - return result; - } - - if (from.isPrimitive()) { - // Try boxing. - if (from.toObjectType() != null) { - from = from.toObjectType(); - return compare(from, a, b); - } - } else { - // Try unboxing. - if (from.toPrimitiveType() != null) { - from = from.toPrimitiveType(); - result = compare(from, a, b); - if (result != 0) { - return result; - } - // Try boxing back up. Test by unboxing 'to' types. - if (!toType_a.isPrimitive() && a.toPrimitiveType() != null) { - a = a.toPrimitiveType(); - } - if (!toType_b.isPrimitive() && b.toPrimitiveType() != null) { - b = b.toPrimitiveType(); - } - return compare(from, a, b); - } - } - - return 0; - } - - private static int compare(TypeDesc from, TypeDesc a, TypeDesc b) { - if (isConversionPossible(from, a)) { - if (isConversionPossible(from, b)) { - if (from.isPrimitive()) { - if (a.isPrimitive()) { - if (b.isPrimitive()) { - // Choose the one with the least amount of widening. - return primitiveWidth(a) - primitiveWidth(b); - } else { - return -1; - } - } else if (b.isPrimitive()) { - return 1; - } - } else { - // Choose the one with the shortest distance up the class - // hierarchy. - Class fromClass = from.toClass(); - if (!fromClass.isInterface()) { - if (a.toClass().isInterface()) { - if (!b.toClass().isInterface()) { - return -1; - } - } else if (b.toClass().isInterface()) { - return 1; - } else { - return distance(fromClass, a.toClass()) - - distance(fromClass, b.toClass()); - } - } - } - } else { - return -1; - } - } else if (isConversionPossible(from, b)) { - return 1; - } - - return 0; - } - - // 1 = boolean, 2 = byte, 3 = short, 4 = char, 5 = int, 6 = float, 7 = long, 8 = double - private static int primitiveWidth(TypeDesc type) { - switch (type.getTypeCode()) { - default: - return 0; - case TypeDesc.BOOLEAN_CODE: - return 1; - case TypeDesc.BYTE_CODE: - return 2; - case TypeDesc.SHORT_CODE: - return 3; - case TypeDesc.CHAR_CODE: - return 4; - case TypeDesc.INT_CODE: - return 5; - case TypeDesc.FLOAT_CODE: - return 6; - case TypeDesc.LONG_CODE: - return 7; - case TypeDesc.DOUBLE_CODE: - return 8; - } - } - - private static int distance(Class from, Class to) { - int distance = 0; - while (from != to) { - from = from.getSuperclass(); - if (from == null) { - return Integer.MAX_VALUE; - } - distance++; - } - return distance; - } -} 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/util/ConversionComparator.java b/src/main/java/com/amazon/carbonado/util/ConversionComparator.java new file mode 100644 index 0000000..71f52a3 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/util/ConversionComparator.java @@ -0,0 +1,213 @@ +/* + * Copyright 2006 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.util.Comparator; + +import org.cojen.classfile.TypeDesc; + +/** + * Compares type conversions, finding the one that is nearest. + * + * @author Brian S O'Neill + * @since 1.2 + */ +public class ConversionComparator implements Comparator<Class> { + private final TypeDesc mFrom; + + public ConversionComparator(Class fromType) { + mFrom = TypeDesc.forClass(fromType); + } + + /** + * Returns true if a coversion is possible to the given type. + */ + public boolean isConversionPossible(Class toType) { + return isConversionPossible(mFrom, TypeDesc.forClass(toType)); + } + + @SuppressWarnings("unchecked") + private static boolean isConversionPossible(TypeDesc from, TypeDesc to) { + if (from == to) { + return true; + } + + if (from.toPrimitiveType() != null && to.toPrimitiveType() != null) { + from = from.toPrimitiveType(); + to = to.toPrimitiveType(); + } else { + from = from.toObjectType(); + to = to.toObjectType(); + } + + switch (from.getTypeCode()) { + case TypeDesc.OBJECT_CODE: default: + return to.toClass().isAssignableFrom(from.toClass()); + case TypeDesc.BOOLEAN_CODE: + return to == TypeDesc.BOOLEAN; + case TypeDesc.BYTE_CODE: + return to == TypeDesc.BYTE || to == TypeDesc.SHORT + || to == TypeDesc.INT || to == TypeDesc.LONG + || to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; + case TypeDesc.SHORT_CODE: + return to == TypeDesc.SHORT + || to == TypeDesc.INT || to == TypeDesc.LONG + || to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; + case TypeDesc.CHAR_CODE: + return to == TypeDesc.CHAR; + case TypeDesc.INT_CODE: + return to == TypeDesc.INT || to == TypeDesc.LONG || to == TypeDesc.DOUBLE; + case TypeDesc.FLOAT_CODE: + return to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; + case TypeDesc.LONG_CODE: + return to == TypeDesc.LONG; + case TypeDesc.DOUBLE_CODE: + return to == TypeDesc.DOUBLE; + } + } + + /** + * Evaluates two types, to see which one is nearest to the from type. + * Return {@literal <0} if "a" is nearest, 0 if both are equally good, + * {@literal >0} if "b" is nearest. + */ + public int compare(Class toType_a, Class toType_b) { + TypeDesc from = mFrom; + TypeDesc a = TypeDesc.forClass(toType_a); + TypeDesc b = TypeDesc.forClass(toType_b); + + if (from == a) { + if (from == b) { + return 0; + } + return -1; + } else if (from == b) { + return 1; + } + + int result = compare(from, a, b); + if (result != 0) { + return result; + } + + if (from.isPrimitive()) { + // Try boxing. + if (from.toObjectType() != null) { + from = from.toObjectType(); + return compare(from, a, b); + } + } else { + // Try unboxing. + if (from.toPrimitiveType() != null) { + from = from.toPrimitiveType(); + result = compare(from, a, b); + if (result != 0) { + return result; + } + // Try boxing back up. Test by unboxing 'to' types. + if (!toType_a.isPrimitive() && a.toPrimitiveType() != null) { + a = a.toPrimitiveType(); + } + if (!toType_b.isPrimitive() && b.toPrimitiveType() != null) { + b = b.toPrimitiveType(); + } + return compare(from, a, b); + } + } + + return 0; + } + + private static int compare(TypeDesc from, TypeDesc a, TypeDesc b) { + if (isConversionPossible(from, a)) { + if (isConversionPossible(from, b)) { + if (from.isPrimitive()) { + if (a.isPrimitive()) { + if (b.isPrimitive()) { + // Choose the one with the least amount of widening. + return primitiveWidth(a) - primitiveWidth(b); + } else { + return -1; + } + } else if (b.isPrimitive()) { + return 1; + } + } else { + // Choose the one with the shortest distance up the class + // hierarchy. + Class fromClass = from.toClass(); + if (!fromClass.isInterface()) { + if (a.toClass().isInterface()) { + if (!b.toClass().isInterface()) { + return -1; + } + } else if (b.toClass().isInterface()) { + return 1; + } else { + return distance(fromClass, a.toClass()) + - distance(fromClass, b.toClass()); + } + } + } + } else { + return -1; + } + } else if (isConversionPossible(from, b)) { + return 1; + } + + return 0; + } + + // 1 = boolean, 2 = byte, 3 = short, 4 = char, 5 = int, 6 = float, 7 = long, 8 = double + private static int primitiveWidth(TypeDesc type) { + switch (type.getTypeCode()) { + default: + return 0; + case TypeDesc.BOOLEAN_CODE: + return 1; + case TypeDesc.BYTE_CODE: + return 2; + case TypeDesc.SHORT_CODE: + return 3; + case TypeDesc.CHAR_CODE: + return 4; + case TypeDesc.INT_CODE: + return 5; + case TypeDesc.FLOAT_CODE: + return 6; + case TypeDesc.LONG_CODE: + return 7; + case TypeDesc.DOUBLE_CODE: + return 8; + } + } + + private static int distance(Class from, Class to) { + int distance = 0; + while (from != to) { + from = from.getSuperclass(); + if (from == null) { + return Integer.MAX_VALUE; + } + distance++; + } + return distance; + } +} 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; + } + } +} -- cgit v1.2.3