From 7977fcd381e17c4e3bdfebfbf7ca4c0d90cdba1f Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 10 Jun 2007 04:22:34 +0000 Subject: Added methods to access Storable properties by name. --- RELEASE-NOTES.txt | 1 + src/main/java/com/amazon/carbonado/Storable.java | 34 +++- .../amazon/carbonado/gen/CommonMethodNames.java | 2 + .../amazon/carbonado/gen/StorableGenerator.java | 174 +++++++++++++++------ .../repo/indexed/DependentStorableFetcher.java | 6 +- .../carbonado/repo/indexed/IndexEntryAccessor.java | 3 +- .../com/amazon/carbonado/spi/LobEngineTrigger.java | 20 +-- .../synthetic/SyntheticStorableBuilder.java | 9 -- 8 files changed, 171 insertions(+), 78 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 31630f1..cf2086d 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -23,6 +23,7 @@ Carbonado change history - BDBRepository detects if changes are made to primary key and throws exception. - Added support for derived properies. - Enhanced query engine to optimize for covering indexes. +- Added methods to access Storable properties by name. 1.1 to 1.1.1 ------------------------------- diff --git a/src/main/java/com/amazon/carbonado/Storable.java b/src/main/java/com/amazon/carbonado/Storable.java index 72ff1c7..b3372c3 100644 --- a/src/main/java/com/amazon/carbonado/Storable.java +++ b/src/main/java/com/amazon/carbonado/Storable.java @@ -351,7 +351,7 @@ public interface Storable> { * loaded or set. * * @param propertyName name of property to interrogate - * @throws IllegalArgumentException if property is unknown or is a join + * @throws IllegalArgumentException if property is unknown, is a join or is derived */ boolean isPropertyUninitialized(String propertyName); @@ -360,7 +360,7 @@ public interface Storable> { * load or store operation has been performed yet. * * @param propertyName name of property to interrogate - * @throws IllegalArgumentException if property is unknown or is a join + * @throws IllegalArgumentException if property is unknown, is a join or is derived */ boolean isPropertyDirty(String propertyName); @@ -369,7 +369,7 @@ public interface Storable> { * properties are clean after a successful load or store operation. * * @param propertyName name of property to interrogate - * @throws IllegalArgumentException if property is unknown or is a join + * @throws IllegalArgumentException if property is unknown, is a join or is derived */ boolean isPropertyClean(String propertyName); @@ -382,6 +382,34 @@ public interface Storable> { */ boolean isPropertySupported(String propertyName); + /** + * Returns a Storable property value by name. + * + * @param propertyName name of property to get value of + * @return property value, which is boxed if property type is primitive + * @throws IllegalArgumentException if property is unknown + * @throws UnsupportedOperationException if property is independent but unsupported + * @throws NullPointerException if property name is null + * @since 1.2 + */ + Object getPropertyValue(String propertyName); + + /** + * Sets a Storable property value by name. Call insert or update to persist + * the change. + * + * @param propertyName name of property to set value to + * @param value new value for property + * @throws IllegalArgumentException if property is unknown or if value is + * unsupported due to a constraint + * @throws UnsupportedOperationException if property is independent but unsupported + * @throws ClassCastException if value is of wrong type + * @throws NullPointerException if property name is null or if primitive + * value is required but value is null + * @since 1.2 + */ + void setPropertyValue(String propertyName, Object value); + /** * Returns an exact shallow copy of this object, including the state. */ diff --git a/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java b/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java index d9db47c..f0262ea 100644 --- a/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java +++ b/src/main/java/com/amazon/carbonado/gen/CommonMethodNames.java @@ -52,6 +52,8 @@ public class CommonMethodNames { IS_PROPERTY_DIRTY = "isPropertyDirty", IS_PROPERTY_CLEAN = "isPropertyClean", IS_PROPERTY_SUPPORTED = "isPropertySupported", + GET_PROPERTY_VALUE = "getPropertyValue", + SET_PROPERTY_VALUE = "setPropertyValue", TO_STRING_KEY_ONLY_METHOD_NAME = "toStringKeyOnly", TO_STRING_METHOD_NAME = "toString", HASHCODE_METHOD_NAME = "hashCode", diff --git a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java index 5f473cb..28dbd9e 100644 --- a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java @@ -189,6 +189,9 @@ public final class StorableGenerator { private static final String UPDATE_OP = "Update"; private static final String DELETE_OP = "Delete"; + // Different uses for generated property switch statements. + private static final int SWITCH_FOR_STATE = 1, SWITCH_FOR_GET = 2, SWITCH_FOR_SET = 3; + /** * Returns an abstract implementation of the given Storable type, which is * fully thread-safe. The Storable type itself may be an interface or a @@ -1863,6 +1866,10 @@ public final class StorableGenerator { b.returnValue(TypeDesc.BOOLEAN); } + // Define reflection-like method for manipulating property by name. + addGetPropertyValueMethod(); + addSetPropertyValueMethod(); + // Define standard object methods. addHashCodeMethod(); addEqualsMethod(EQUAL_FULL); @@ -2733,8 +2740,11 @@ public final class StorableGenerator { MethodInfo mi = mClassFile.addMethod(Modifiers.PRIVATE, PROPERTY_STATE_EXTRACT_METHOD_NAME, TypeDesc.INT, new TypeDesc[] {TypeDesc.STRING}); - CodeBuilder b = new CodeBuilder(mi); + + addPropertySwitch(new CodeBuilder(mi), SWITCH_FOR_STATE); + } + private void addPropertySwitch(CodeBuilder b, int switchFor) { // Generate big switch statement that operates on Strings. See also // org.cojen.util.BeanPropertyAccessor, which also generates this kind of // switch. @@ -2795,6 +2805,8 @@ public final class StorableGenerator { Label derivedMatch = null; Label joinMatch = null; + Label unreadable = null; + Label unwritable = null; for (int i=0; i> matches = caseMatches[i]; @@ -2824,29 +2836,59 @@ public final class StorableGenerator { b.ifZeroComparisonBranch(notEqual, "=="); } - if (prop.isDerived()) { - if (derivedMatch == null) { - derivedMatch = b.createLabel(); + if (switchFor == SWITCH_FOR_STATE) { + if (prop.isDerived()) { + if (derivedMatch == null) { + derivedMatch = b.createLabel(); + } + b.branch(derivedMatch); + } else if (prop.isJoin()) { + if (joinMatch == null) { + joinMatch = b.createLabel(); + } + b.branch(joinMatch); + } else { + int ordinal = ordinalMap.get(prop); + + b.loadThis(); + b.loadField(PROPERTY_STATE_FIELD_NAME + (ordinal >> 4), TypeDesc.INT); + int shift = (ordinal & 0xf) * 2; + if (shift != 0) { + b.loadConstant(shift); + b.math(Opcode.ISHR); + } + b.loadConstant(PROPERTY_STATE_MASK); + b.math(Opcode.IAND); + b.returnValue(TypeDesc.INT); } - b.branch(derivedMatch); - } else if (prop.isJoin()) { - if (joinMatch == null) { - joinMatch = b.createLabel(); + } else if (switchFor == SWITCH_FOR_GET) { + if (prop.getReadMethod() == null) { + if (unreadable == null) { + unreadable = b.createLabel(); + } + b.branch(unreadable); + } else { + b.loadThis(); + b.invoke(prop.getReadMethod()); + TypeDesc type = TypeDesc.forClass(prop.getType()); + b.convert(type, type.toObjectType()); + b.returnValue(TypeDesc.OBJECT); } - b.branch(joinMatch); - } else { - int ordinal = ordinalMap.get(prop); - - b.loadThis(); - b.loadField(PROPERTY_STATE_FIELD_NAME + (ordinal >> 4), TypeDesc.INT); - int shift = (ordinal & 0xf) * 2; - if (shift != 0) { - b.loadConstant(shift); - b.math(Opcode.ISHR); + } else if (switchFor == SWITCH_FOR_SET) { + if (prop.getWriteMethod() == null) { + if (unwritable == null) { + unwritable = b.createLabel(); + } + b.branch(unwritable); + } else { + b.loadThis(); + b.loadLocal(b.getParameter(1)); + TypeDesc type = TypeDesc.forClass(prop.getType()); + b.checkCast(type.toObjectType()); + b.convert(type.toObjectType(), type); + b.invoke(prop.getWriteMethod()); + b.returnVoid(); } - b.loadConstant(PROPERTY_STATE_MASK); - b.math(Opcode.IAND); - b.returnValue(TypeDesc.INT); } if (notEqual != null) { @@ -2855,42 +2897,44 @@ public final class StorableGenerator { } } - TypeDesc exceptionType = TypeDesc.forClass(IllegalArgumentException.class); - params = new TypeDesc[] {TypeDesc.STRING}; - noMatch.setLocation(); - - b.newObject(exceptionType); - b.dup(); - b.loadConstant("Unknown property: "); - b.loadLocal(b.getParameter(0)); - b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params); - b.invokeConstructor(exceptionType, params); - b.throwObject(); + throwIllegalArgException(b, "Unknown property: ", b.getParameter(0)); if (derivedMatch != null) { derivedMatch.setLocation(); - - b.newObject(exceptionType); - b.dup(); - b.loadConstant("Cannot get state for derived property: "); - b.loadLocal(b.getParameter(0)); - b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params); - b.invokeConstructor(exceptionType, params); - b.throwObject(); + throwIllegalArgException + (b, "Cannot get state for derived property: ", b.getParameter(0)); } if (joinMatch != null) { joinMatch.setLocation(); + throwIllegalArgException(b, "Cannot get state for join property: ", b.getParameter(0)); + } - b.newObject(exceptionType); - b.dup(); - b.loadConstant("Cannot get state for join property: "); - b.loadLocal(b.getParameter(0)); - b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params); - b.invokeConstructor(exceptionType, params); - b.throwObject(); + if (unreadable != null) { + unreadable.setLocation(); + throwIllegalArgException(b, "Property cannot be read: ", b.getParameter(0)); } + + if (unwritable != null) { + unwritable.setLocation(); + throwIllegalArgException(b, "Property cannot be written: ", b.getParameter(0)); + } + } + + private static void throwIllegalArgException(CodeBuilder b, String message, + LocalVariable concatStr) + { + TypeDesc exceptionType = TypeDesc.forClass(IllegalArgumentException.class); + TypeDesc[] params = {TypeDesc.STRING}; + + b.newObject(exceptionType); + b.dup(); + b.loadConstant(message); + b.loadLocal(concatStr); + b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params); + b.invokeConstructor(exceptionType, params); + b.throwObject(); } /** @@ -2955,6 +2999,42 @@ public final class StorableGenerator { b.returnValue(TypeDesc.BOOLEAN); } + private void addGetPropertyValueMethod() { + MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, GET_PROPERTY_VALUE, + TypeDesc.OBJECT, new TypeDesc[] {TypeDesc.STRING}); + + if (mi == null) { + return; + } + + CodeBuilder b = new CodeBuilder(mi); + + if (mGenMode == GEN_WRAPPED) { + callWrappedStorable(mi, b); + return; + } + + addPropertySwitch(b, SWITCH_FOR_GET); + } + + private void addSetPropertyValueMethod() { + MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, SET_PROPERTY_VALUE, null, + new TypeDesc[] {TypeDesc.STRING, TypeDesc.OBJECT}); + + if (mi == null) { + return; + } + + CodeBuilder b = new CodeBuilder(mi); + + if (mGenMode == GEN_WRAPPED) { + callWrappedStorable(mi, b); + return; + } + + addPropertySwitch(b, SWITCH_FOR_SET); + } + /** * Defines a hashCode method. */ diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java b/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java index ce37f5f..98454f1 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java @@ -22,8 +22,6 @@ import java.util.Arrays; import java.util.ArrayList; import java.util.List; -import org.cojen.util.BeanPropertyAccessor; - import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Query; @@ -49,7 +47,6 @@ class DependentStorableFetcher { private final IndexEntryAccessor[] mIndexEntryAccessors; private final Query mQuery; private final String[] mJoinProperties; - private final BeanPropertyAccessor mPropertyAccessor; /** * @param derivedTo special chained property from StorableProperty.getDerivedToProperties @@ -116,7 +113,6 @@ class DependentStorableFetcher { mIndexEntryAccessors = accessorList.toArray(new IndexEntryAccessor[accessorList.size()]); mQuery = repository.storageFor(dType).query(dFilter); mJoinProperties = joinProperties; - mPropertyAccessor = BeanPropertyAccessor.forClass(sType); } public Transaction enterTransaction() { @@ -126,7 +122,7 @@ class DependentStorableFetcher { public Cursor fetchDependenentStorables(S storable) throws FetchException { Query query = mQuery; for (String property : mJoinProperties) { - query = query.with(mPropertyAccessor.getPropertyValue(storable, property)); + query = query.with(storable.getPropertyValue(property)); } return query.fetch(); } diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java index 5f27aa8..42ce214 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java @@ -33,8 +33,7 @@ import com.amazon.carbonado.capability.IndexInfo; */ public interface IndexEntryAccessor extends IndexInfo { /** - * Returns the index entry storage. Index entry properties can only be - * accessed via reflection. + * Returns the index entry storage. */ Storage getIndexEntryStorage(); diff --git a/src/main/java/com/amazon/carbonado/spi/LobEngineTrigger.java b/src/main/java/com/amazon/carbonado/spi/LobEngineTrigger.java index 4b5ac5f..d278556 100644 --- a/src/main/java/com/amazon/carbonado/spi/LobEngineTrigger.java +++ b/src/main/java/com/amazon/carbonado/spi/LobEngineTrigger.java @@ -20,8 +20,6 @@ package com.amazon.carbonado.spi; import java.util.List; -import org.cojen.util.BeanPropertyAccessor; - import com.amazon.carbonado.FetchException; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Storable; @@ -38,14 +36,12 @@ import com.amazon.carbonado.lob.Lob; class LobEngineTrigger extends Trigger { final LobEngine mEngine; private final int mBlockSize; - private final BeanPropertyAccessor mAccessor; private final LobProperty[] mLobProperties; LobEngineTrigger(LobEngine engine, Class type, int blockSize, List> lobProperties) { mEngine = engine; - mAccessor = BeanPropertyAccessor.forClass(type); mBlockSize = blockSize; mLobProperties = new LobProperty[lobProperties.size()]; @@ -59,11 +55,11 @@ class LobEngineTrigger extends Trigger { Object[] userLobs = new Object[length]; for (int i=0; i prop = mLobProperties[i]; - Object userLob = mAccessor.getPropertyValue(storable, prop.mName); + Object userLob = storable.getPropertyValue(prop.mName); userLobs[i] = userLob; if (userLob != null) { Object lob = prop.createNewLob(mBlockSize); - mAccessor.setPropertyValue(storable, prop.mName, lob); + storable.setPropertyValue(prop.mName, lob); } } return userLobs; @@ -79,7 +75,7 @@ class LobEngineTrigger extends Trigger { Object userLob = userLobs[i]; if (userLob != null) { LobProperty prop = mLobProperties[i]; - Lob lob = (Lob) mAccessor.getPropertyValue(storable, prop.mName); + Lob lob = (Lob) storable.getPropertyValue(prop.mName); prop.setLobValue(mEngine.getLocator(lob), (Lob) userLob); } } @@ -112,9 +108,9 @@ class LobEngineTrigger extends Trigger { throw e.toPersistException(); } - Object userLob = mAccessor.getPropertyValue(storable, prop.mName); + Object userLob = storable.getPropertyValue(prop.mName); userLobs[i] = userLob; - Lob existingLob = (Lob) mAccessor.getPropertyValue(existing, prop.mName); + Lob existingLob = (Lob) existing.getPropertyValue(prop.mName); if (userLob == null) { if (existingLob != null) { // User is setting existing lob to null, so delete it. @@ -126,7 +122,7 @@ class LobEngineTrigger extends Trigger { existingLob = prop.createNewLob(mBlockSize); } prop.setLobValue(mEngine.getLocator(existingLob), (Lob) userLob); - mAccessor.setPropertyValue(storable, prop.mName, existingLob); + storable.setPropertyValue(prop.mName, existingLob); } } @@ -154,7 +150,7 @@ class LobEngineTrigger extends Trigger { if (existing != null) { // After successful delete of master storable, delete all the lobs. for (LobProperty prop : mLobProperties) { - Lob lob = (Lob) mAccessor.getPropertyValue(existing, prop.mName); + Lob lob = (Lob) ((S) existing).getPropertyValue(prop.mName); mEngine.deleteLob(lob); } } @@ -175,7 +171,7 @@ class LobEngineTrigger extends Trigger { for (int i=0; i