/* * 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.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.cojen.util.WeakCanonicalSet; import com.amazon.carbonado.Storable; import com.amazon.carbonado.util.Appender; /** * 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. * * @author Brian S O'Neill */ public class ChainedProperty implements Serializable, Appender { private static final long serialVersionUID = 1L; static WeakCanonicalSet cCanonical = new WeakCanonicalSet(); /** * Returns a canonical instance which has no chain. * * @throws IllegalArgumentException if prime is null */ @SuppressWarnings("unchecked") public static ChainedProperty get(StorableProperty prime) { return (ChainedProperty) cCanonical.put(new ChainedProperty(prime, null, null)); } /** * Returns a canonical instance. * * @throws IllegalArgumentException if prime is null or if chained * properties are not formed properly */ @SuppressWarnings("unchecked") public static ChainedProperty get(StorableProperty prime, StorableProperty... 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)); } /** * Parses a chained property. * * @param info Info for Storable type containing property * @param str string to parse * @throws IllegalArgumentException if any parameter is null or string * format is incorrect */ @SuppressWarnings("unchecked") public static ChainedProperty parse(StorableInfo info, String str) throws IllegalArgumentException { if (info == null || str == null) { throw new IllegalArgumentException(); } int pos = 0; int dot = str.indexOf('.', pos); String name; if (dot < 0) { name = str.trim(); } else { 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) { throw new IllegalArgumentException ("Property \"" + name + "\" not found for type: \"" + info.getStorableType().getName() + '"'); } if (pos <= 0) { if (outerJoinList == null || !outerJoinList.get(0)) { return get(prime); } else { return get(prime, null, new boolean[] {true}); } } List> chain = new ArrayList>(4); Class type = prime.getType(); while (pos > 0) { dot = str.indexOf('.', pos); if (dot < 0) { name = str.substring(pos).trim(); pos = -1; } else { 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); Map> props = propInfo.getAllProperties(); StorableProperty prop = props.get(name); if (prop == null) { throw new IllegalArgumentException ("Property \"" + name + "\" not found for type: \"" + type.getName() + '"'); } chain.add(prop); type = prop.isJoin() ? prop.getJoinedType() : prop.getType(); } else { throw new IllegalArgumentException ("Property \"" + name + "\" not found for type \"" + type.getName() + "\" because it has no properties"); } } 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()]), 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 * @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, boolean[] outerJoin) { if (prime == null) { 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() { return mPrime; } /** * Returns the type of the last property in the chain, or of the prime * property if the chain is empty. */ public Class getType() { return getLastProperty().getType(); } /** * Returns true if any property in the chain can be null. * * @see com.amazon.carbonado.Nullable * @since 1.2 */ public boolean isNullable() { if (mPrime.isNullable()) { return true; } if (mChain != null) { for (StorableProperty prop : mChain) { if (prop.isNullable()) { return true; } } } return false; } /** * Returns true if any property in the chain is derived. * * @see com.amazon.carbonado.Derived * @since 1.2 */ public boolean isDerived() { if (mPrime.isDerived()) { return true; } if (mChain != null) { for (StorableProperty prop : mChain) { if (prop.isDerived()) { return true; } } } return false; } /** * Returns the last property in the chain, or the prime property if chain * is empty. */ public StorableProperty getLastProperty() { return mChain == null ? mPrime : mChain[mChain.length - 1]; } /** * Returns amount of properties chained from prime property, which may be * zero. */ public int getChainCount() { 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(); } else { return mChain[index]; } } /** * 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; 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(), property.isOuterJoin(0)); } StorableProperty[] newChain = new StorableProperty[getChainCount() + 1 + propChainCount]; int pos = 0; if (getChainCount() > 0) { System.arraycopy(mChain, 0, newChain, 0, mChain.length); pos = mChain.length; } newChain[pos++] = property.getPrimeProperty(); for (int i=0; i trim() { if (getChainCount() == 0) { throw new IllegalStateException(); } if (getChainCount() == 1) { 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); 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); } /** * Returns a new ChainedProperty which contains everything that follows * this ChainedProperty's prime property. * * @throws IllegalStateException if chain count is zero */ public ChainedProperty tail() { if (getChainCount() == 0) { throw new IllegalStateException(); } if (getChainCount() == 1) { 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); 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 public int hashCode() { int hash = mPrime.hashCode(); StorableProperty[] chain = mChain; if (chain != null) { for (int i=chain.length; --i>=0; ) { 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; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof ChainedProperty) { ChainedProperty other = (ChainedProperty) obj; // Note: Since StorableProperty instances are not canonicalized, // they must be compared with the '==' operator instead of the // equals method. Otherwise, canonical ChainedProperty instances // may refer to StorableProperty instances which are no longer // available through the Introspector. if (getType() == other.getType() && mPrime == other.mPrime && identityEquals(mChain, other.mChain)) { // Compare outer joins. int count = getChainCount() + 1; for (int i=0; i[] chain = mChain; if (chain != null) { for (int i=0; i