From 97af4be638e371a2f693bde2798fc233a143f3f9 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 29 Apr 2007 17:47:50 +0000 Subject: Merged in support for derived properties. --- .../com/amazon/carbonado/gen/CodeBuilderUtil.java | 50 ++++-- .../carbonado/gen/MasterStorableGenerator.java | 178 ++++++++++++++++---- .../amazon/carbonado/gen/StorableGenerator.java | 181 ++++++++++++--------- .../amazon/carbonado/gen/StorableSerializer.java | 4 +- 4 files changed, 290 insertions(+), 123 deletions(-) (limited to 'src/main/java/com/amazon/carbonado/gen') diff --git a/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java b/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java index c1f26a0..1b754fc 100644 --- a/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java +++ b/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java @@ -267,7 +267,9 @@ public class CodeBuilderUtil { /** * Generates code to compare two values on the stack, and branch to the - * provided Label if they are not equal. Both values must be of the same type. + * provided Label if they are not equal. Both values must be of the same + * type. If they are floating point values, NaN is considered equal to NaN, + * which is inconsistent with the usual treatment for NaN. * *

The generated instruction consumes both values on the stack. * @@ -286,18 +288,25 @@ public class CodeBuilderUtil { final boolean choice) { if (valueType.getTypeCode() != TypeDesc.OBJECT_CODE) { - b.ifComparisonBranch(label, choice ? "==" : "!=", valueType); + if (valueType.getTypeCode() == TypeDesc.FLOAT_CODE) { + // Special treatment to handle NaN. + b.invokeStatic(TypeDesc.FLOAT.toObjectType(), "compare", TypeDesc.INT, + new TypeDesc[] {TypeDesc.FLOAT, TypeDesc.FLOAT}); + b.ifZeroComparisonBranch(label, choice ? "==" : "!="); + } else if (valueType.getTypeCode() == TypeDesc.DOUBLE_CODE) { + // Special treatment to handle NaN. + b.invokeStatic(TypeDesc.DOUBLE.toObjectType(), "compare", TypeDesc.INT, + new TypeDesc[] {TypeDesc.DOUBLE, TypeDesc.DOUBLE}); + b.ifZeroComparisonBranch(label, choice ? "==" : "!="); + } else { + b.ifComparisonBranch(label, choice ? "==" : "!=", valueType); + } return; } - // Equals method returns zero for false, so if choice is true, branch - // if not zero. Note that operator selection is opposite when invoking - // a direct ifComparisonBranch method. - String equalsBranchOp = choice ? "!=" : "=="; - if (!testForNull) { - addEqualsCallTo(b, valueType); - b.ifZeroComparisonBranch(label, equalsBranchOp); + String op = addEqualsCallTo(b, valueType, choice); + b.ifZeroComparisonBranch(label, op); return; } @@ -318,14 +327,19 @@ public class CodeBuilderUtil { isNotNull.setLocation(); b.loadLocal(value); b.swap(); - addEqualsCallTo(b, valueType); - b.ifZeroComparisonBranch(label, equalsBranchOp); + String op = addEqualsCallTo(b, valueType, choice); + b.ifZeroComparisonBranch(label, op); cont.setLocation(); } - public static void addEqualsCallTo(CodeBuilder b, TypeDesc fieldType) { + /** + * @param fieldType must be an object type + * @return zero comparison branch operator + */ + private static String addEqualsCallTo(CodeBuilder b, TypeDesc fieldType, boolean choice) { if (fieldType.isArray()) { + // FIXME: Array comparisons don't handle desired comparison of NaN. if (!fieldType.getComponentType().isPrimitive()) { TypeDesc type = TypeDesc.forClass(Object[].class); b.invokeStatic("java.util.Arrays", "deepEquals", @@ -334,6 +348,17 @@ public class CodeBuilderUtil { b.invokeStatic("java.util.Arrays", "equals", TypeDesc.BOOLEAN, new TypeDesc[] {fieldType, fieldType}); } + return choice ? "!=" : "=="; + } else if (fieldType.toPrimitiveType() == TypeDesc.FLOAT) { + // Special treatment to handle NaN. + b.invokeVirtual(TypeDesc.FLOAT.toObjectType(), "compareTo", TypeDesc.INT, + new TypeDesc[] {TypeDesc.FLOAT.toObjectType()}); + return choice ? "==" : "!="; + } else if (fieldType.toPrimitiveType() == TypeDesc.DOUBLE) { + // Special treatment to handle NaN. + b.invokeVirtual(TypeDesc.DOUBLE.toObjectType(), "compareTo", TypeDesc.INT, + new TypeDesc[] {TypeDesc.DOUBLE.toObjectType()}); + return choice ? "==" : "!="; } else { TypeDesc[] params = {TypeDesc.OBJECT}; if (fieldType.toClass() != null) { @@ -345,6 +370,7 @@ public class CodeBuilderUtil { } else { b.invokeVirtual(TypeDesc.OBJECT, "equals", TypeDesc.BOOLEAN, params); } + return choice ? "!=" : "=="; } } diff --git a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java index 17efb70..581a7d6 100644 --- a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java @@ -126,7 +126,7 @@ public final class MasterStorableGenerator { anySequences: if (features.contains(MasterFeature.INSERT_SEQUENCES)) { for (StorableProperty property : info.getAllProperties().values()) { - if (property.getSequenceName() != null) { + if (!property.isDerived() && property.getSequenceName() != null) { break anySequences; } } @@ -266,7 +266,7 @@ public final class MasterStorableGenerator { int ordinal = 0; for (StorableProperty property : mAllProperties.values()) { - if (property.getSequenceName() != null) { + if (!property.isDerived() && property.getSequenceName() != null) { // Check the state of this property, to see if it is // uninitialized. Uninitialized state has value zero. @@ -362,14 +362,16 @@ public final class MasterStorableGenerator { Label tryStart = addEnterTransaction(b, INSERT_OP, txnVar); if (mFeatures.contains(MasterFeature.VERSIONING)) { - // Only set if uninitialized. - b.loadThis(); - b.invokeVirtual(StorableGenerator.IS_VERSION_INITIALIZED_METHOD_NAME, - TypeDesc.BOOLEAN, null); - Label isInitialized = b.createLabel(); - b.ifZeroComparisonBranch(isInitialized, "!="); - addAdjustVersionProperty(b, null, 1); - isInitialized.setLocation(); + if (!mInfo.getVersionProperty().isDerived()) { + // Only set if uninitialized. + b.loadThis(); + b.invokeVirtual(StorableGenerator.IS_VERSION_INITIALIZED_METHOD_NAME, + TypeDesc.BOOLEAN, null); + Label isInitialized = b.createLabel(); + b.ifZeroComparisonBranch(isInitialized, "!="); + addAdjustVersionProperty(b, null, 1); + isInitialized.setLocation(); + } } if (mFeatures.contains(MasterFeature.INSERT_CHECK_REQUIRED)) { @@ -410,7 +412,8 @@ public final class MasterStorableGenerator { for (StorableProperty property : mAllProperties.values()) { ordinal++; - if (property.isJoin() || property.isPrimaryKeyMember() + if (property.isDerived() + || property.isJoin() || property.isPrimaryKeyMember() || property.isNullable() || property.isAutomatic() || property.isVersion()) { @@ -548,33 +551,109 @@ public final class MasterStorableGenerator { b.ifZeroComparisonBranch(failed, "=="); // if (version support enabled) { - // if (this.getVersionNumber() != saved.getVersionNumber()) { - // throw new OptimisticLockException - // (this.getVersionNumber(), saved.getVersionNumber(), this); + // if (!derived version) { + // if (this.getVersionNumber() != saved.getVersionNumber()) { + // throw new OptimisticLockException + // (this.getVersionNumber(), saved.getVersionNumber(), this); + // } + // } else { + // if (this.getVersionNumber() <= saved.getVersionNumber()) { + // throw new OptimisticLockException + // (saved.getVersionNumber(), this, this.getVersionNumber()); + // } // } // } if (mFeatures.contains(MasterFeature.VERSIONING)) { - TypeDesc versionType = TypeDesc.forClass(mInfo.getVersionProperty().getType()); - b.loadThis(); - b.invoke(mInfo.getVersionProperty().getReadMethod()); - b.loadLocal(savedVar); - b.invoke(mInfo.getVersionProperty().getReadMethod()); - Label sameVersion = b.createLabel(); - CodeBuilderUtil.addValuesEqualCall(b, versionType, true, sameVersion, true); - b.newObject(optimisticLockType); - b.dup(); - b.loadThis(); - b.invoke(mInfo.getVersionProperty().getReadMethod()); - b.convert(versionType, TypeDesc.OBJECT); - b.loadLocal(savedVar); - b.invoke(mInfo.getVersionProperty().getReadMethod()); - b.convert(versionType, TypeDesc.OBJECT); - b.loadThis(); - b.invokeConstructor - (optimisticLockType, - new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT, storableType}); - b.throwObject(); - sameVersion.setLocation(); + StorableProperty versionProperty = mInfo.getVersionProperty(); + TypeDesc versionType = TypeDesc.forClass(versionProperty.getType()); + + Label allowedVersion = b.createLabel(); + + if (!versionProperty.isDerived()) { + b.loadThis(); + b.invoke(versionProperty.getReadMethod()); + b.loadLocal(savedVar); + b.invoke(versionProperty.getReadMethod()); + CodeBuilderUtil.addValuesEqualCall + (b, versionType, true, allowedVersion, true); + + b.newObject(optimisticLockType); + b.dup(); + b.loadThis(); + b.invoke(versionProperty.getReadMethod()); + b.convert(versionType, TypeDesc.OBJECT); + b.loadLocal(savedVar); + b.invoke(versionProperty.getReadMethod()); + b.convert(versionType, TypeDesc.OBJECT); + b.loadThis(); + b.invokeConstructor + (optimisticLockType, + new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT, storableType}); + b.throwObject(); + } else { + b.loadThis(); + b.invoke(versionProperty.getReadMethod()); + LocalVariable newVersion = b.createLocalVariable(null, versionType); + b.storeLocal(newVersion); + + b.loadLocal(savedVar); + b.invoke(versionProperty.getReadMethod()); + LocalVariable savedVersion = b.createLocalVariable(null, versionType); + b.storeLocal(savedVersion); + + // Skip check if new or saved version is null. + branchIfNull(b, newVersion, allowedVersion); + branchIfNull(b, savedVersion, allowedVersion); + + TypeDesc primVersionType = versionType.toPrimitiveType(); + if (primVersionType != null) { + if (versionType != primVersionType) { + b.loadLocal(newVersion); + b.convert(versionType, primVersionType); + newVersion = b.createLocalVariable(null, primVersionType); + b.storeLocal(newVersion); + + b.loadLocal(savedVersion); + b.convert(versionType, primVersionType); + savedVersion = b.createLocalVariable(null, primVersionType); + b.storeLocal(savedVersion); + } + + // Skip check if new or saved version is NaN. + branchIfNaN(b, newVersion, allowedVersion); + branchIfNaN(b, savedVersion, allowedVersion); + + b.loadLocal(newVersion); + b.loadLocal(savedVersion); + b.ifComparisonBranch(allowedVersion, ">", primVersionType); + } else if (Comparable.class.isAssignableFrom(versionProperty.getType())) { + b.loadLocal(newVersion); + b.loadLocal(savedVersion); + b.invokeInterface(TypeDesc.forClass(Comparable.class), "compareTo", + TypeDesc.INT, new TypeDesc[] {TypeDesc.OBJECT}); + b.ifZeroComparisonBranch(allowedVersion, ">"); + } else { + throw new SupportException + ("Derived version property must be Comparable: " + + versionProperty); + } + + b.newObject(optimisticLockType); + b.dup(); + b.loadLocal(savedVar); + b.invoke(versionProperty.getReadMethod()); + b.convert(versionType, TypeDesc.OBJECT); + b.loadThis(); + b.loadThis(); + b.invoke(versionProperty.getReadMethod()); + b.convert(versionType, TypeDesc.OBJECT); + b.invokeConstructor + (optimisticLockType, + new TypeDesc[] {TypeDesc.OBJECT, storableType, TypeDesc.OBJECT}); + b.throwObject(); + } + + allowedVersion.setLocation(); } // this.copyDirtyProperties(saved); @@ -585,7 +664,9 @@ public final class MasterStorableGenerator { b.loadLocal(savedVar); b.invokeVirtual(COPY_DIRTY_PROPERTIES, null, new TypeDesc[] {storableType}); if (mFeatures.contains(MasterFeature.VERSIONING)) { - addAdjustVersionProperty(b, savedVar, -1); + if (!mInfo.getVersionProperty().isDerived()) { + addAdjustVersionProperty(b, savedVar, -1); + } } // if (!saved.doTryUpdateMaster()) { @@ -661,6 +742,31 @@ public final class MasterStorableGenerator { } } + private void branchIfNull(CodeBuilder b, LocalVariable value, Label isNull) { + if (!value.getType().isPrimitive()) { + b.loadLocal(value); + b.ifNullBranch(isNull, true); + } + } + + private void branchIfNaN(CodeBuilder b, LocalVariable value, Label isNaN) { + TypeDesc type = value.getType(); + if (type == TypeDesc.FLOAT || type == TypeDesc.DOUBLE) { + b.loadLocal(value); + if (type == TypeDesc.FLOAT) { + b.invokeStatic(TypeDesc.FLOAT.toObjectType(), + "isNaN", TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.FLOAT}); + b.ifZeroComparisonBranch(isNaN, "!="); + } else { + b.invokeStatic(TypeDesc.DOUBLE.toObjectType(), + "isNaN", TypeDesc.BOOLEAN, + new TypeDesc[] {TypeDesc.DOUBLE}); + b.ifZeroComparisonBranch(isNaN, "!="); + } + } + } + /** * Generates code to enter a transaction, if required. * diff --git a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java index 4934db3..a28f7e6 100644 --- a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java @@ -193,7 +193,7 @@ public final class StorableGenerator { * fully thread-safe. The Storable type itself may be an interface or a * class. If it is a class, then it must not be final, and it must have a * public, no-arg constructor. The constructor signature for the returned - * abstract is defined as follows: + * abstract class is defined as follows: * *

      * /**
@@ -569,27 +569,29 @@ public final class StorableGenerator {
             for (StorableProperty property : mAllProperties.values()) {
                 ordinal++;
 
-                if (property.isVersion()) {
+                if (!property.isDerived() && property.isVersion()) {
                     versionOrdinal = ordinal;
                 }
 
                 final String name = property.getName();
                 final TypeDesc type = TypeDesc.forClass(property.getType());
 
-                if (property.isJoin()) {
-                    // If generating wrapper, property access is not guarded by
-                    // synchronization. Mark as volatile instead.
-                    mClassFile.addField(Modifiers.PRIVATE.toVolatile(mGenMode == GEN_WRAPPED),
-                                        name, type);
-                    requireStateField = true;
-                } else if (mGenMode == GEN_ABSTRACT) {
-                    // Only define regular property fields if abstract
-                    // class. Wrapped class doesn't reference them. Double
-                    // words are volatile to prevent word tearing without
-                    // explicit synchronization.
-                    mClassFile.addField(Modifiers.PROTECTED.toVolatile(type.isDoubleWord()),
-                                        name, type);
-                    requireStateField = true;
+                if (!property.isDerived()) {
+                    if (property.isJoin()) {
+                        // If generating wrapper, property access is not guarded by
+                        // synchronization. Mark as volatile instead.
+                        mClassFile.addField(Modifiers.PRIVATE.toVolatile(mGenMode == GEN_WRAPPED),
+                                            name, type);
+                        requireStateField = true;
+                    } else if (mGenMode == GEN_ABSTRACT) {
+                        // Only define regular property fields if abstract
+                        // class. Wrapped class doesn't reference them. Double
+                        // words are volatile to prevent word tearing without
+                        // explicit synchronization.
+                        mClassFile.addField(Modifiers.PROTECTED.toVolatile(type.isDoubleWord()),
+                                            name, type);
+                        requireStateField = true;
+                    }
                 }
 
                 final String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
@@ -605,7 +607,7 @@ public final class StorableGenerator {
                 }
 
                 // Add read method.
-                buildReadMethod: {
+                buildReadMethod: if (!property.isDerived()) {
                     Method readMethod = property.getReadMethod();
 
                     MethodInfo mi;
@@ -849,7 +851,7 @@ public final class StorableGenerator {
                 }
 
                 // Add write method.
-                if (!property.isQuery()) {
+                buildWriteMethod: if (!property.isDerived() && !property.isQuery()) {
                     Method writeMethod = property.getWriteMethod();
 
                     MethodInfo mi;
@@ -1901,7 +1903,8 @@ public final class StorableGenerator {
                     new HashMap>();
 
                 for (StorableProperty property : mAllProperties.values()) {
-                    if (!property.isPrimaryKeyMember() &&
+                    if (!property.isDerived() &&
+                        !property.isPrimaryKeyMember() &&
                         !property.isJoin() &&
                         !property.isNullable()) {
 
@@ -2111,7 +2114,7 @@ public final class StorableGenerator {
 
         for (StorableProperty property : mAllProperties.values()) {
             // Decide if property should be part of the copy.
-            boolean shouldCopy = !property.isJoin() &&
+            boolean shouldCopy = !property.isDerived() && !property.isJoin() &&
                 (property.isPrimaryKeyMember() && pkProperties ||
                  property.isVersion() && versionProperty ||
                  !property.isPrimaryKeyMember() && dataProperties);
@@ -2309,7 +2312,7 @@ public final class StorableGenerator {
         int ordinal = 0;
         int mask = 0;
         for (StorableProperty property : mAllProperties.values()) {
-            if (property != joinProperty && !property.isJoin()) {
+            if (property != joinProperty && !property.isDerived() && !property.isJoin()) {
                 // Check to see if property is an internal member of joinProperty.
                 for (int i=joinProperty.getJoinElementCount(); --i>=0; ) {
                     if (property == joinProperty.getInternalJoinElement(i)) {
@@ -2343,19 +2346,21 @@ public final class StorableGenerator {
         for (StorableProperty property : mAllProperties.values()) {
             ordinal++;
 
-            if (property.isJoin() || mGenMode == GEN_ABSTRACT) {
-                requireStateField = true;
-            }
+            if (!property.isDerived()) {
+                if (property.isJoin() || mGenMode == GEN_ABSTRACT) {
+                    requireStateField = true;
+                }
 
-            if (ordinal == maxOrdinal || ((ordinal & 0xf) == 0xf)) {
-                if (requireStateField) {
-                    String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
+                if (ordinal == maxOrdinal || ((ordinal & 0xf) == 0xf)) {
+                    if (requireStateField) {
+                        String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
 
-                    b.loadThis();
-                    b.loadConstant(0);
-                    b.storeField(stateFieldName, TypeDesc.INT);
+                        b.loadThis();
+                        b.loadConstant(0);
+                        b.storeField(stateFieldName, TypeDesc.INT);
+                    }
+                    requireStateField = false;
                 }
-                requireStateField = false;
             }
         }
     }
@@ -2382,17 +2387,19 @@ public final class StorableGenerator {
         int orMask = 0;
 
         for (StorableProperty property : mAllProperties.values()) {
-            if (property.isQuery()) {
-                // Don't erase cached query.
-                andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
-            } else if (!property.isJoin()) {
-                if (name == MARK_ALL_PROPERTIES_CLEAN) {
-                    // Force clean state (1) always.
-                    orMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
-                } else if (name == MARK_PROPERTIES_CLEAN) {
-                    // Mask will convert dirty (3) to clean (1). State 2, which
-                    // is illegal, is converted to 0.
-                    andMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
+            if (!property.isDerived()) {
+                if (property.isQuery()) {
+                    // Don't erase cached query.
+                    andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
+                } else if (!property.isJoin()) {
+                    if (name == MARK_ALL_PROPERTIES_CLEAN) {
+                        // Force clean state (1) always.
+                        orMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
+                    } else if (name == MARK_PROPERTIES_CLEAN) {
+                        // Mask will convert dirty (3) to clean (1). State 2, which
+                        // is illegal, is converted to 0.
+                        andMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
+                    }
                 }
             }
 
@@ -2443,14 +2450,16 @@ public final class StorableGenerator {
         int orMask = 0;
 
         for (StorableProperty property : mAllProperties.values()) {
-            if (property.isJoin()) {
-                // Erase cached join properties, but don't erase cached query.
-                if (!property.isQuery()) {
-                    andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
+            if (!property.isDerived()) {
+                if (property.isJoin()) {
+                    // Erase cached join properties, but don't erase cached query.
+                    if (!property.isQuery()) {
+                        andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
+                    }
+                } else if (name == MARK_ALL_PROPERTIES_DIRTY) {
+                    // Force dirty state (3).
+                    orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
                 }
-            } else if (name == MARK_ALL_PROPERTIES_DIRTY) {
-                // Force dirty state (3).
-                orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
             }
 
             ordinal++;
@@ -2514,16 +2523,18 @@ public final class StorableGenerator {
         int andMask = 0xffffffff;
         int orMask = 0;
         for (StorableProperty property : mAllProperties.values()) {
-            if (property == ordinaryProperty) {
-                if (mGenMode == GEN_ABSTRACT) {
-                    // Only GEN_ABSTRACT mode uses these state bits.
-                    orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
-                }
-            } else if (property.isJoin()) {
-                // Check to see if ordinary is an internal member of join property.
-                for (int i=property.getJoinElementCount(); --i>=0; ) {
-                    if (ordinaryProperty == property.getInternalJoinElement(i)) {
-                        andMask &= ~(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2));
+            if (!property.isDerived()) {
+                if (property == ordinaryProperty) {
+                    if (mGenMode == GEN_ABSTRACT) {
+                        // Only GEN_ABSTRACT mode uses these state bits.
+                        orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
+                    }
+                } else if (property.isJoin()) {
+                    // Check to see if ordinary is an internal member of join property.
+                    for (int i=property.getJoinElementCount(); --i>=0; ) {
+                        if (ordinaryProperty == property.getInternalJoinElement(i)) {
+                            andMask &= ~(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2));
+                        }
                     }
                 }
             }
@@ -2556,13 +2567,15 @@ public final class StorableGenerator {
         int ordinal = 0;
         int andMask = 0;
         for (StorableProperty property : mAllProperties.values()) {
-            if (!property.isJoin() && (!property.isPrimaryKeyMember() || includePk)) {
-                // Logical 'and' will convert state 1 (clean) to state 0, so
-                // that it will be ignored. State 3 (dirty) is what we're
-                // looking for, and it turns into 2. Essentially, we leave the
-                // high order bit on, since there is no state which has the
-                // high order bit on unless the low order bit is also on.
-                andMask |= 2 << ((ordinal & 0xf) * 2);
+            if (!property.isDerived()) {
+                if (!property.isJoin() && (!property.isPrimaryKeyMember() || includePk)) {
+                    // Logical 'and' will convert state 1 (clean) to state 0, so
+                    // that it will be ignored. State 3 (dirty) is what we're
+                    // looking for, and it turns into 2. Essentially, we leave the
+                    // high order bit on, since there is no state which has the
+                    // high order bit on unless the low order bit is also on.
+                    andMask |= 2 << ((ordinal & 0xf) * 2);
+                }
             }
             ordinal++;
             if ((ordinal & 0xf) == 0 || ordinal >= count) {
@@ -2623,8 +2636,10 @@ public final class StorableGenerator {
         int ordinal = 0;
         int mask = 0;
         for (StorableProperty property : mAllProperties.values()) {
-            if (properties.containsKey(property.getName())) {
-                mask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
+            if (!property.isDerived()) {
+                if (properties.containsKey(property.getName())) {
+                    mask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
+                }
             }
             ordinal++;
             if (((ordinal & 0xf) == 0 || ordinal >= mAllProperties.size()) && mask != 0) {
@@ -2772,6 +2787,7 @@ public final class StorableGenerator {
         // Params to invoke String.equals.
         TypeDesc[] params = {TypeDesc.OBJECT};
 
+        Label derivedMatch = null;
         Label joinMatch = null;
 
         for (int i=0; i {
                     b.ifZeroComparisonBranch(notEqual, "==");
                 }
 
-                if (prop.isJoin()) {
+                if (prop.isDerived()) {
+                    if (derivedMatch == null) {
+                        derivedMatch = b.createLabel();
+                    }
+                    b.branch(derivedMatch);
+                } else if (prop.isJoin()) {
                     if (joinMatch == null) {
                         joinMatch = b.createLabel();
                     }
@@ -2841,6 +2862,18 @@ public final class StorableGenerator {
         b.invokeConstructor(exceptionType, params);
         b.throwObject();
 
+        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();
+        }
+
         if (joinMatch != null) {
             joinMatch.setLocation();
 
@@ -2936,7 +2969,7 @@ public final class StorableGenerator {
 
         boolean mixIn = false;
         for (StorableProperty property : mAllProperties.values()) {
-            if (property.isJoin()) {
+            if (property.isDerived() || property.isJoin()) {
                 continue;
             }
             addHashCodeCall(b, property.getName(),
@@ -3105,7 +3138,7 @@ public final class StorableGenerator {
         b.storeLocal(other);
 
         for (StorableProperty property : mAllProperties.values()) {
-            if (property.isJoin()) {
+            if (property.isDerived() || property.isJoin()) {
                 continue;
             }
             // If we're only comparing keys, and this isn't a key, skip it
@@ -3211,8 +3244,10 @@ public final class StorableGenerator {
         // Second pass, print non-primary keys.
         if (!keyOnly) {
             for (StorableProperty property : mAllProperties.values()) {
-                // Don't print join properties if they may throw an exception.
-                if (!property.isPrimaryKeyMember() && (!property.isJoin())) {
+                // Don't print any derived or join properties since they may throw an exception.
+                if (!property.isPrimaryKeyMember() &&
+                    (!property.isDerived()) && (!property.isJoin()))
+                {
                     Label skipPrint = b.createLabel();
 
                     // Check if independent property is supported, and skip if not.
diff --git a/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java b/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java
index 5115854..09eff96 100644
--- a/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java
+++ b/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java
@@ -116,11 +116,11 @@ public abstract class StorableSerializer {
 
         StorableProperty[] properties;
         {
-            // Exclude joins.
+            // Exclude derived properties and joins.
             List> list =
                 new ArrayList>(propertyMap.size());
             for (StorableProperty property : propertyMap.values()) {
-                if (!property.isJoin()) {
+                if (!property.isDerived() && !property.isJoin()) {
                     list.add(property);
                 }
             }
-- 
cgit v1.2.3