From 90bd381e91c873af5956da9e5f0cfa2b29675633 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" <bronee@gmail.com> Date: Thu, 11 Oct 2007 21:00:12 +0000 Subject: Support sorting of arrays. --- .../com/amazon/carbonado/cursor/Comparators.java | 315 +++++++++++++++++++++ .../com/amazon/carbonado/cursor/SortedCursor.java | 75 ++++- .../carbonado/repo/indexed/ManagedIndex.java | 2 +- .../repo/replicated/ReplicatedRepository.java | 12 +- .../SyntheticStorableReferenceBuilder.java | 19 +- 5 files changed, 392 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/cursor/Comparators.java (limited to 'src/main/java/com') diff --git a/src/main/java/com/amazon/carbonado/cursor/Comparators.java b/src/main/java/com/amazon/carbonado/cursor/Comparators.java new file mode 100644 index 0000000..bddd883 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/cursor/Comparators.java @@ -0,0 +1,315 @@ +/* + * Copyright 2007 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.cursor; + +import java.util.Comparator; + +import org.cojen.classfile.TypeDesc; + +/** + * Collection of utility comparators. + * + * @author Brian S O'Neill + */ +class Comparators { + /** + * Returns a comparator which can sort single or multi-dimensional arrays + * of primitves or Comparables. + * + * @param unsigned applicable only to arrays of bytes, shorts, ints, or longs + * @return null if unsupported + */ + static <T> Comparator<T> arrayComparator(Class<T> arrayType, boolean unsigned) { + if (!arrayType.isArray()) { + throw new IllegalArgumentException(); + } + + Comparator c; + + TypeDesc componentType = TypeDesc.forClass(arrayType.getComponentType()); + switch (componentType.getTypeCode()) { + case TypeDesc.BYTE_CODE: + c = unsigned ? UnsignedByteArray : SignedByteArray; + break; + case TypeDesc.SHORT_CODE: + c = unsigned ? UnsignedShortArray : SignedShortArray; + break; + case TypeDesc.INT_CODE: + c = unsigned ? UnsignedIntArray : SignedIntArray; + break; + case TypeDesc.LONG_CODE: + c = unsigned ? UnsignedLongArray : SignedLongArray; + break; + case TypeDesc.BOOLEAN_CODE: + c = BooleanArray; + break; + case TypeDesc.CHAR_CODE: + c = CharArray; + break; + case TypeDesc.FLOAT_CODE: + c = FloatArray; + break; + case TypeDesc.DOUBLE_CODE: + c = DoubleArray; + break; + case TypeDesc.OBJECT_CODE: default: + if (componentType.isArray()) { + c = new ComparatorArray(arrayComparator(componentType.toClass(), unsigned)); + } else if (Comparable.class.isAssignableFrom(componentType.toClass())) { + c = ComparableArray; + } else { + c = null; + } + break; + } + + return (Comparator<T>) c; + } + + private static final Comparator<byte[]> SignedByteArray = new Comparator<byte[]>() { + public int compare(byte[] a, byte[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + byte ai = a[i]; + byte bi = b[i]; + if (ai != bi) { + return ai - bi; + } + } + return a.length - b.length; + } + }; + + private static final Comparator<byte[]> UnsignedByteArray = new Comparator<byte[]>() { + public int compare(byte[] a, byte[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + byte ai = a[i]; + byte bi = b[i]; + if (ai != bi) { + return (ai & 0xff) - (bi & 0xff); + } + } + return a.length - b.length; + } + }; + + private static final Comparator<short[]> SignedShortArray = new Comparator<short[]>() { + public int compare(short[] a, short[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + short ai = a[i]; + short bi = b[i]; + if (ai != bi) { + return ai - bi; + } + } + return a.length - b.length; + } + }; + + private static final Comparator<short[]> UnsignedShortArray = new Comparator<short[]>() { + public int compare(short[] a, short[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + short ai = a[i]; + short bi = b[i]; + if (ai != bi) { + return (ai & 0xffff) - (bi & 0xffff); + } + } + return a.length - b.length; + } + }; + + private static final Comparator<int[]> SignedIntArray = new Comparator<int[]>() { + public int compare(int[] a, int[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + int ai = a[i]; + int bi = b[i]; + if (ai != bi) { + return ai < bi ? -1 : 1; + } + } + return a.length - b.length; + } + }; + + private static final Comparator<int[]> UnsignedIntArray = new Comparator<int[]>() { + public int compare(int[] a, int[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + int ai = a[i]; + int bi = b[i]; + if (ai != bi) { + return (ai & 0xffffffffL) < (bi & 0xffffffffL) ? -1 : 1; + } + } + return a.length - b.length; + } + }; + + private static final Comparator<long[]> SignedLongArray = new Comparator<long[]>() { + public int compare(long[] a, long[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + long ai = a[i]; + long bi = b[i]; + if (ai != bi) { + return ai < bi ? -1 : 1; + } + } + return a.length - b.length; + } + }; + + private static final Comparator<long[]> UnsignedLongArray = new Comparator<long[]>() { + public int compare(long[] a, long[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + long ai = a[i]; + long bi = b[i]; + if (ai != bi) { + long sai = ai >>> 1; + long sbi = bi >>> 1; + if (sai < sbi) { + return -1; + } + if (sai > sbi) { + return 1; + } + return (ai & 1) == 0 ? -1 : 1; + } + } + return a.length - b.length; + } + }; + + private static final Comparator<boolean[]> BooleanArray = new Comparator<boolean[]>() { + public int compare(boolean[] a, boolean[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + boolean ai = a[i]; + boolean bi = b[i]; + if (ai != bi) { + return ai ? 1 : -1; + } + } + return a.length - b.length; + } + }; + + private static final Comparator<char[]> CharArray = new Comparator<char[]>() { + public int compare(char[] a, char[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + char ai = a[i]; + char bi = b[i]; + if (ai != bi) { + return ai - bi; + } + } + return a.length - b.length; + } + }; + + private static final Comparator<float[]> FloatArray = new Comparator<float[]>() { + public int compare(float[] a, float[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + float ai = a[i]; + float bi = b[i]; + if (ai != bi) { + return Float.compare(ai, bi); + } + } + return a.length - b.length; + } + }; + + private static final Comparator<double[]> DoubleArray = new Comparator<double[]>() { + public int compare(double[] a, double[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + double ai = a[i]; + double bi = b[i]; + if (ai != bi) { + return Double.compare(ai, bi); + } + } + return a.length - b.length; + } + }; + + private static final Comparator<Comparable[]> ComparableArray = new Comparator<Comparable[]>() + { + public int compare(Comparable[] a, Comparable[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + Comparable ai = a[i]; + Comparable bi = b[i]; + if (ai == bi) { + continue; + } + if (ai == null) { + return 1; + } + if (bi == null) { + return -1; + } + int compare = ai.compareTo(bi); + if (compare != 0) { + return compare; + } + } + return a.length - b.length; + } + }; + + private static class ComparatorArray implements Comparator<Object[]> { + private final Comparator<Object> mComparator; + + ComparatorArray(Comparator<Object> comparator) { + mComparator = comparator; + } + + public int compare(Object[] a, Object[] b) { + int len = Math.min(a.length, b.length); + for (int i=0; i<len; i++) { + Object ai = a[i]; + Object bi = b[i]; + if (ai == bi) { + continue; + } + if (ai == null) { + return 1; + } + if (bi == null) { + return -1; + } + int compare = mComparator.compare(ai, bi); + if (compare != 0) { + return compare; + } + } + return a.length - b.length; + } + } +} diff --git a/src/main/java/com/amazon/carbonado/cursor/SortedCursor.java b/src/main/java/com/amazon/carbonado/cursor/SortedCursor.java index 15a72d9..3c26caa 100644 --- a/src/main/java/com/amazon/carbonado/cursor/SortedCursor.java +++ b/src/main/java/com/amazon/carbonado/cursor/SortedCursor.java @@ -22,9 +22,14 @@ import java.lang.reflect.UndeclaredThrowableException; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import org.cojen.util.BeanComparator; +import org.cojen.util.BeanIntrospector; +import org.cojen.util.BeanProperty; + +import org.cojen.classfile.TypeDesc; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.FetchInterruptedException; @@ -58,12 +63,38 @@ public class SortedCursor<S> extends AbstractCursor<S> { public static <S> Comparator<S> createComparator(Class<S> type, String... orderProperties) { BeanComparator bc = BeanComparator.forClass(type); for (String property : orderProperties) { - bc = bc.orderBy(property); - bc = bc.caseSensitive(); + Class propertyType; + { + String name = property; + if (name.startsWith("+") || name.startsWith("-")) { + name = name.substring(1); + } + propertyType = propertyType(type, name); + } + bc = orderBy(bc, property, propertyType, Direction.ASCENDING); } return bc; } + /** + * @return null if unknown + */ + private static Class propertyType(Class enclosingType, String propertyName) { + Map<String, BeanProperty> properties = BeanIntrospector.getAllProperties(enclosingType); + int dotIndex = propertyName.indexOf('.'); + if (dotIndex < 0) { + BeanProperty bp = properties.get(propertyName); + return bp == null ? null : bp.getType(); + } else { + String parentName = propertyName.substring(0, dotIndex); + BeanProperty bp = properties.get(parentName); + if (bp == null) { + return null; + } + return propertyType(bp.getType(), propertyName.substring(dotIndex + 1)); + } + } + /** * Convenience method to create a comparator which orders storables by the * given properties. @@ -86,16 +117,14 @@ public class SortedCursor<S> extends AbstractCursor<S> { if (property == null) { throw new IllegalArgumentException(); } - bc = bc.orderBy(property.getChainedProperty().toString()); - bc = bc.caseSensitive(); - if (property.getDirection() == Direction.DESCENDING) { - bc = bc.reverse(); - } + bc = orderBy(bc, property.getChainedProperty().toString(), + property.getChainedProperty().getType(), property.getDirection()); } return bc; } + /** * Convenience method to create a comparator which orders storables by the * given properties. @@ -119,11 +148,35 @@ public class SortedCursor<S> extends AbstractCursor<S> { if (property == null) { throw new IllegalArgumentException(); } - bc = bc.orderBy(property.getChainedProperty().toString()); - bc = bc.caseSensitive(); - if (property.getDirection() == Direction.DESCENDING) { - bc = bc.reverse(); + bc = orderBy(bc, property.getChainedProperty().toString(), + property.getChainedProperty().getType(), property.getDirection()); + } + + return bc; + } + + private static BeanComparator orderBy(BeanComparator bc, String property, + Class type, Direction direction) + { + bc = bc.orderBy(property); + + if (type != null && type.isArray()) { + TypeDesc td = TypeDesc.forClass(type); + if (td.getRootComponentType() == TypeDesc.BYTE) { + bc = bc.using(Comparators.arrayComparator(type, true)); + } else { + bc = bc.using(Comparators.arrayComparator(type, false)); + if (bc == null) { + throw new IllegalArgumentException("Cannot sort by property type of " + + type.getName() + " for " + property); + } } + } else { + bc = bc.caseSensitive(); + } + + if (direction == Direction.DESCENDING) { + bc = bc.reverse(); } return bc; diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java b/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java index 73469db..2d7f983 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java @@ -287,7 +287,7 @@ class ManagedIndex<S extends Storable> implements IndexEntryAccessor<S> { buffer.close(); throw new UniqueConstraintException ("Cannot build unique index because duplicates exist: " - + this); + + this + ", " + last + " == " + obj); } } last = obj; diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java index 2c73200..b28fab5 100644 --- a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java @@ -24,8 +24,6 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cojen.util.BeanComparator; - import com.amazon.carbonado.CorruptEncodingException; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; @@ -49,6 +47,8 @@ import com.amazon.carbonado.capability.ResyncCapability; import com.amazon.carbonado.capability.ShutdownCapability; import com.amazon.carbonado.capability.StorableInfoCapability; +import com.amazon.carbonado.cursor.SortedCursor; + import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector; @@ -389,11 +389,7 @@ class ReplicatedRepository } } - BeanComparator bc = BeanComparator.forClass(type); - for (String order : orderBy) { - bc = bc.orderBy(order); - bc = bc.caseSensitive(); - } + Comparator comparator = SortedCursor.createComparator(type, orderBy); replicaQuery = replicaQuery.orderBy(orderBy); masterQuery = masterQuery.orderBy(orderBy); @@ -417,7 +413,7 @@ class ReplicatedRepository replicaStorage, replicaQuery, masterStorage, masterQuery, throttle, desiredSpeed, - bc, replicaTxn); + comparator, replicaTxn); replicaTxn.commit(); } finally { diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java index f9db620..4fddcd5 100644 --- a/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java @@ -29,6 +29,7 @@ import java.util.Set; import com.amazon.carbonado.Storable; import com.amazon.carbonado.SupportException; +import com.amazon.carbonado.cursor.SortedCursor; import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector; @@ -219,18 +220,14 @@ public class SyntheticStorableReferenceBuilder<S extends Storable> mSyntheticClass = mBuilder.getStorableClass(); // We need a comparator which follows the same order as the generated - // storable. We can't construct it until we get here - { - BeanComparator bc = BeanComparator.forClass(mSyntheticClass); - Iterator<String> props = mPrimaryKey.getProperties(); - while (props.hasNext()) { - String prop = props.next(); - // BeanComparator knows how to handle the '+' or '-' prefix. - bc = bc.orderBy(prop); - bc = bc.caseSensitive(); - } - mComparator = bc; + // storable. We can't construct it until we get here. + String[] orderBy = new String[mPrimaryKey.getPropertyCount()]; + int i=0; + Iterator<String> it = mPrimaryKey.getProperties(); + while (it.hasNext()) { + orderBy[i++] = it.next(); } + mComparator = SortedCursor.createComparator(mSyntheticClass, orderBy); } return mSyntheticClass; } -- cgit v1.2.3