From 4063328f97c0180ceab565cc3f411e3dcc07bca8 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Fri, 26 Oct 2007 21:24:43 +0000 Subject: Added support for outer joins. --- .../com/amazon/carbonado/info/ChainedProperty.java | 234 ++++++++++++++++++--- 1 file changed, 210 insertions(+), 24 deletions(-) (limited to 'src/main/java/com/amazon/carbonado/info') diff --git a/src/main/java/com/amazon/carbonado/info/ChainedProperty.java b/src/main/java/com/amazon/carbonado/info/ChainedProperty.java index e067350..71f1ba5 100644 --- a/src/main/java/com/amazon/carbonado/info/ChainedProperty.java +++ b/src/main/java/com/amazon/carbonado/info/ChainedProperty.java @@ -29,7 +29,7 @@ import com.amazon.carbonado.Storable; import com.amazon.carbonado.util.Appender; /** - * Represents a property to query against or to order by. Properties may be + * Represents a property to filter on or to order by. Properties may be * specified in a simple form, like "firstName", or in a chained form, like * "address.state". In both forms, the first property is the "prime" * property. All properties that follow are chained. @@ -46,7 +46,7 @@ public class ChainedProperty implements Appender { */ @SuppressWarnings("unchecked") public static ChainedProperty get(StorableProperty prime) { - return (ChainedProperty) cCanonical.put(new ChainedProperty(prime, null)); + return (ChainedProperty) cCanonical.put(new ChainedProperty(prime, null, null)); } /** @@ -58,7 +58,22 @@ public class ChainedProperty implements Appender { @SuppressWarnings("unchecked") public static ChainedProperty get(StorableProperty prime, StorableProperty... chain) { - return (ChainedProperty) cCanonical.put(new ChainedProperty(prime, chain)); + return (ChainedProperty) cCanonical.put(new ChainedProperty(prime, chain, null)); + } + + /** + * Returns a canonical instance. + * + * @throws IllegalArgumentException if prime is null or if chained + * properties are not formed properly + * @since 1.2 + */ + @SuppressWarnings("unchecked") + public static ChainedProperty get(StorableProperty prime, + StorableProperty[] chain, + boolean[] outerJoin) { + return (ChainedProperty) cCanonical.put + (new ChainedProperty(prime, chain, outerJoin)); } /** @@ -82,12 +97,20 @@ public class ChainedProperty implements Appender { String name; if (dot < 0) { - name = str; + name = str.trim(); } else { - name = str.substring(pos, dot); + name = str.substring(pos, dot).trim(); pos = dot + 1; } + List outerJoinList = null; + + if (name.startsWith("(") && name.endsWith(")")) { + outerJoinList = new ArrayList(4); + outerJoinList.add(true); + name = name.substring(1, name.length() - 1).trim(); + } + StorableProperty prime = info.getAllProperties().get(name); if (prime == null) { @@ -97,7 +120,11 @@ public class ChainedProperty implements Appender { } if (pos <= 0) { - return get(prime); + if (outerJoinList == null || !outerJoinList.get(0)) { + return get(prime); + } else { + return get(prime, null, new boolean[] {true}); + } } List> chain = new ArrayList>(4); @@ -106,12 +133,28 @@ public class ChainedProperty implements Appender { while (pos > 0) { dot = str.indexOf('.', pos); if (dot < 0) { - name = str.substring(pos); + name = str.substring(pos).trim(); pos = -1; } else { - name = str.substring(pos, dot); + name = str.substring(pos, dot).trim(); pos = dot + 1; } + + if (name.startsWith("(") && name.endsWith(")")) { + if (outerJoinList == null) { + outerJoinList = new ArrayList(4); + // Fill in false values. + outerJoinList.add(false); // prime is inner join + for (int i=chain.size(); --i>=0; ) { + outerJoinList.add(false); + } + } + outerJoinList.add(true); + name = name.substring(1, name.length() - 1).trim(); + } else if (outerJoinList != null) { + outerJoinList.add(false); + } + if (Storable.class.isAssignableFrom(type)) { StorableInfo propInfo = StorableIntrospector.examine((Class) type); @@ -131,24 +174,51 @@ public class ChainedProperty implements Appender { } } + boolean[] outerJoin = null; + if (outerJoinList != null) { + outerJoin = new boolean[outerJoinList.size()]; + for (int i=outerJoinList.size(); --i>=0; ) { + outerJoin[i] = outerJoinList.get(i); + } + } + return get(prime, - (StorableProperty[]) chain.toArray(new StorableProperty[chain.size()])); + (StorableProperty[]) chain.toArray(new StorableProperty[chain.size()]), + outerJoin); } private final StorableProperty mPrime; private final StorableProperty[] mChain; + private final boolean[] mOuterJoin; /** * @param prime must not be null * @param chain can be null if none - * @throws IllegalArgumentException if prime is null + * @param outerJoin can be null for all inner joins + * @throws IllegalArgumentException if prime is null or if outer join chain is too long */ - private ChainedProperty(StorableProperty prime, StorableProperty[] chain) { + private ChainedProperty(StorableProperty prime, StorableProperty[] chain, + boolean[] outerJoin) + { if (prime == null) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("No prime property"); } + mPrime = prime; mChain = (chain == null || chain.length == 0) ? null : chain.clone(); + + if (outerJoin != null) { + int expectedLength = (chain == null ? 0 : chain.length) + 1; + if (outerJoin.length > expectedLength) { + throw new IllegalArgumentException + ("Outer join array too long: " + outerJoin.length + " > " + expectedLength); + } + boolean[] newOuterJoin = new boolean[expectedLength]; + System.arraycopy(outerJoin, 0, newOuterJoin, 0, outerJoin.length); + outerJoin = newOuterJoin; + } + + mOuterJoin = outerJoin; } public StorableProperty getPrimeProperty() { @@ -219,6 +289,9 @@ public class ChainedProperty implements Appender { return mChain == null ? 0 : mChain.length; } + /** + * @param index valid range is 0 to chainCount - 1 + */ public StorableProperty getChainedProperty(int index) throws IndexOutOfBoundsException { if (mChain == null) { throw new IndexOutOfBoundsException(); @@ -227,25 +300,74 @@ public class ChainedProperty implements Appender { } } + /** + * Returns true if the property at the given index should be treated as an + * outer join. Index zero is the prime property. + * + * @param index valid range is 0 to chainCount + * @since 1.2 + */ + public boolean isOuterJoin(int index) throws IndexOutOfBoundsException { + if (index < 0) { + throw new IndexOutOfBoundsException(); + } + if (mOuterJoin == null) { + if (index > getChainCount()) { + throw new IndexOutOfBoundsException(); + } + return false; + } + return mOuterJoin[index]; + } + /** * Returns a new ChainedProperty with another property appended. */ public ChainedProperty append(StorableProperty property) { + return append(property, false); + } + + /** + * Returns a new ChainedProperty with another property appended. + * + * @param outerJoin pass true for outer join + * @since 1.2 + */ + public ChainedProperty append(StorableProperty property, boolean outerJoin) { + if (property == null) { + throw new IllegalArgumentException(); + } + StorableProperty[] newChain = new StorableProperty[getChainCount() + 1]; if (newChain.length > 1) { System.arraycopy(mChain, 0, newChain, 0, mChain.length); } newChain[newChain.length - 1] = property; - return get(mPrime, newChain); + + boolean[] newOuterJoin = mOuterJoin; + + if (outerJoin) { + newOuterJoin = new boolean[newChain.length + 1]; + if (mOuterJoin != null) { + System.arraycopy(mOuterJoin, 0, newOuterJoin, 0, mOuterJoin.length); + } + newOuterJoin[newOuterJoin.length - 1] = true; + } + + return get(mPrime, newChain, newOuterJoin); } /** * Returns a new ChainedProperty with another property appended. */ public ChainedProperty append(ChainedProperty property) { + if (property == null) { + throw new IllegalArgumentException(); + } + final int propChainCount = property.getChainCount(); if (propChainCount == 0) { - return append(property.getPrimeProperty()); + return append(property.getPrimeProperty(), property.isOuterJoin(0)); } StorableProperty[] newChain = @@ -262,7 +384,19 @@ public class ChainedProperty implements Appender { newChain[pos++] = property.getChainedProperty(i); } - return get(mPrime, newChain); + boolean[] newOuterJoin = mOuterJoin; + + if (property.mOuterJoin != null) { + newOuterJoin = new boolean[newChain.length + 1]; + if (mOuterJoin != null) { + System.arraycopy(mOuterJoin, 0, newOuterJoin, 0, mOuterJoin.length); + } + System.arraycopy(property.mOuterJoin, 0, + newOuterJoin, mOuterJoin.length, + property.mOuterJoin.length); + } + + return get(mPrime, newChain, newOuterJoin); } /** @@ -275,11 +409,23 @@ public class ChainedProperty implements Appender { throw new IllegalStateException(); } if (getChainCount() == 1) { - return get(mPrime); + if (!isOuterJoin(0)) { + return get(mPrime); + } else { + return get(mPrime, null, new boolean[] {true}); + } } StorableProperty[] newChain = new StorableProperty[getChainCount() - 1]; System.arraycopy(mChain, 0, newChain, 0, newChain.length); - return get(mPrime, newChain); + + boolean[] newOuterJoin = mOuterJoin; + + if (newOuterJoin != null && newOuterJoin.length > (newChain.length + 1)) { + newOuterJoin = new boolean[newChain.length + 1]; + System.arraycopy(mOuterJoin, 0, newOuterJoin, 0, newChain.length + 1); + } + + return get(mPrime, newChain, newOuterJoin); } /** @@ -293,11 +439,23 @@ public class ChainedProperty implements Appender { throw new IllegalStateException(); } if (getChainCount() == 1) { - return get(mChain[0]); + if (!isOuterJoin(1)) { + return get(mChain[0]); + } else { + return get(mChain[0], null, new boolean[] {true}); + } } StorableProperty[] newChain = new StorableProperty[getChainCount() - 1]; System.arraycopy(mChain, 1, newChain, 0, newChain.length); - return get(mChain[0], newChain); + + boolean[] newOuterJoin = mOuterJoin; + + if (newOuterJoin != null) { + newOuterJoin = new boolean[newChain.length + 1]; + System.arraycopy(mOuterJoin, 1, newOuterJoin, 0, mOuterJoin.length - 1); + } + + return get(mChain[0], newChain, newOuterJoin); } @Override @@ -309,6 +467,14 @@ public class ChainedProperty implements Appender { hash = hash * 31 + chain[i].hashCode(); } } + boolean[] outerJoin = mOuterJoin; + if (outerJoin != null) { + for (int i=outerJoin.length; --i>=0; ) { + if (outerJoin[i]) { + hash += 1; + } + } + } return hash; } @@ -324,8 +490,18 @@ public class ChainedProperty implements Appender { // equals method. Otherwise, canonical ChainedProperty instances // may refer to StorableProperty instances which are no longer // available through the Introspector. - return getType() == other.getType() && mPrime == other.mPrime - && identityEquals(mChain, other.mChain); + if (getType() == other.getType() && mPrime == other.mPrime + && identityEquals(mChain, other.mChain)) + { + // Compare outer joins. + int count = getChainCount() + 1; + for (int i=0; i implements Appender { */ @Override public String toString() { - if (mChain == null) { + if (mChain == null && !isOuterJoin(0)) { return mPrime.getName(); } StringBuilder buf = new StringBuilder(); @@ -380,7 +556,7 @@ public class ChainedProperty implements Appender { * many-to-one joins. */ public void appendTo(Appendable app) throws IOException { - app.append(mPrime.getName()); + appendPropTo(app, mPrime.getName(), isOuterJoin(0)); StorableProperty[] chain = mChain; if (chain != null) { app.append('.'); @@ -388,8 +564,18 @@ public class ChainedProperty implements Appender { if (i > 0) { app.append('.'); } - app.append(chain[i].getName()); + appendPropTo(app, chain[i].getName(), isOuterJoin(i + 1)); } } } + + private void appendPropTo(Appendable app, String name, boolean outer) throws IOException { + if (outer) { + app.append('('); + } + app.append(name); + if (outer) { + app.append(')'); + } + } } -- cgit v1.2.3