Hi,

This is still a work in progress, pending inclusion of the JDONullIdentityFIeldException. But the ObjectIdentity class is mostly done.

Craig

Index: test/java/javax/jdo/identity/StringIdentityTest.java
===================================================================
--- test/java/javax/jdo/identity/StringIdentityTest.java        (revision 
209512)
+++ test/java/javax/jdo/identity/StringIdentityTest.java        (working copy)
@@ -70,4 +70,10 @@
         assertFalse ("Not equal StringIdentity instances compare equal.", 
sc1.equals(sc3));
         assertFalse ("Not equal StringIdentity instances compare equal.", 
sc3.equals(sc1));
     }
+
+    public void testGetKeyAsObject() {
+        StringIdentity c1 = new StringIdentity(Object.class, "1");
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), "1");
+    }
+
 }
Index: test/java/javax/jdo/identity/ObjectIdentityTest.java
===================================================================
--- test/java/javax/jdo/identity/ObjectIdentityTest.java        (revision 0)
+++ test/java/javax/jdo/identity/ObjectIdentityTest.java        (revision 0)
@@ -0,0 +1,384 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * 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.
+ */
+
+/*
+ * ObjectIdentityTest.java
+ *
+ */
+
+package javax.jdo.identity;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import java.io.Serializable;
+
+import java.math.BigDecimal;
+import java.util.Currency;
+import java.util.Date;
+import java.util.Locale;
+
+import javax.jdo.JDOUserException;
+
+import javax.jdo.util.BatchTestRunner;
+
+/**
+ *
+ */
+public class ObjectIdentityTest extends SingleFieldIdentityTest {
+    
+    /** Creates a new instance of ObjectIdentityTest */
+    public ObjectIdentityTest() {
+    }
+    
+    /**
+     * @param args the command line arguments
+     */
+    public static void main(String[] args) {
+        BatchTestRunner.run(ObjectIdentityTest.class);
+    }
+    
+    public void testConstructor() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, new IdClass(1));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, new IdClass(1));
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, new IdClass(2));
+        assertEquals("Equal ObjectIdentity instances compare not equal.", c1, 
c2);
+        assertFalse ("Not equal ObjectIdentity instances compare equal", 
c1.equals(c3));
+    }
+    
+    public void testIntegerConstructor() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, new Integer(1));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, new Integer(1));
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, new Integer(2));
+        assertEquals("Equal ObjectIdentity instances compare not equal.", c1, 
c2);
+        assertFalse ("Not equal ObjectIdentity instances compare equal", 
c1.equals(c3));
+    }
+    
+    public void testLongConstructor() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, new Long(1));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, new Long(1));
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, new Long(2));
+        assertEquals("Equal ObjectIdentity instances compare not equal.", c1, 
c2);
+        assertFalse ("Not equal ObjectIdentity instances compare equal", 
c1.equals(c3));
+    }
+    
+    public void testDateConstructor() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, new Date(1));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, new Date(1));
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, new Date(2));
+        assertEquals("Equal ObjectIdentity instances compare not equal.", c1, 
c2);
+        assertFalse ("Not equal ObjectIdentity instances compare equal", 
c1.equals(c3));
+    }
+    
+    public void testLocaleConstructor() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, Locale.US);
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, Locale.US);
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, Locale.GERMANY);
+        assertEquals("Equal ObjectIdentity instances compare not equal.", c1, 
c2);
+        assertFalse ("Not equal ObjectIdentity instances compare equal", 
c1.equals(c3));
+    }
+    
+    public void testCurrencyConstructor() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, 
+                Currency.getInstance(Locale.US));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, 
+                Currency.getInstance(Locale.US));
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, 
+                Currency.getInstance(Locale.GERMANY));
+        assertEquals("Equal ObjectIdentity instances compare not equal.", c1, 
c2);
+        assertFalse ("Not equal ObjectIdentity instances compare equal", 
c1.equals(c3));
+    }
+    
+    public void testStringConstructor() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, 
+                "javax.jdo.identity.ObjectIdentityTest$IdClass:1");        
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, 
+                "javax.jdo.identity.ObjectIdentityTest$IdClass:1");        
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, 
+                "javax.jdo.identity.ObjectIdentityTest$IdClass:2");        
+        assertEquals("Equal ObjectIdentity instances compare not equal.", c1, 
c2);
+        assertFalse ("Not equal ObjectIdentity instances compare equal", 
c1.equals(c3));
+    }
+    
+    public void testToStringConstructor() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, new IdClass(1));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, c1.toString());
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c1, 
c2);
+    }
+
+    public void testBadStringConstructorNullClass() {
+        try {
+            ObjectIdentity c1 = new ObjectIdentity(null, "1");
+        } catch (NullPointerException ex) {
+            return;
+        }
+        fail ("Failed to catch expected exception.");
+    }
+    
+    public void testBadStringConstructorNullParam() {
+        try {
+            ObjectIdentity c1 = new ObjectIdentity(Object.class, null);
+        } catch (NullPointerException ex) {
+            return;
+        }
+        fail ("Failed to catch expected exception.");
+    }
+    
+    public void testBadStringConstructorTooShort() {
+        try {
+            ObjectIdentity c1 = new ObjectIdentity(Object.class, "xx");
+        } catch (JDOUserException ex) {
+            return;
+        }
+        fail ("Failed to catch expected exception.");
+    }
+    
+    public void testBadStringConstructorNoDelimiter() {
+        try {
+            ObjectIdentity c1 = new ObjectIdentity(Object.class, "xxxxxxxxx");
+        } catch (JDOUserException ex) {
+            return;
+        }
+        fail ("Failed to catch expected exception.");
+    }
+    
+    public void testBadStringConstructorBadClassName() {
+        try {
+            ObjectIdentity c1 = new ObjectIdentity(Object.class, "xx:yy");
+        } catch (JDOUserException ex) {
+            validateNestedException(ex, ClassNotFoundException.class);
+            return;
+        }
+        fail ("Failed to catch expected ClassNotFoundException.");
+    }
+    
+    public void testBadStringConstructorNoStringConstructor() {
+        try {
+            ObjectIdentity c1 = new ObjectIdentity(Object.class, 
+                    
"javax.jdo.identity.ObjectIdentityTest$BadIdClassNoStringConstructor:yy");
+        } catch (JDOUserException ex) {
+            validateNestedException(ex, NoSuchMethodException.class);
+            return;
+        }
+        fail ("Failed to catch expected NoSuchMethodException.");
+    }
+    
+    public void testBadStringConstructorNoPublicStringConstructor() {
+        try {
+            ObjectIdentity c1 = new ObjectIdentity(Object.class, 
+                    
"javax.jdo.identity.ObjectIdentityTest$BadIdClassNoPublicStringConstructor:yy");
+        } catch (JDOUserException ex) {
+            validateNestedException(ex, NoSuchMethodException.class);
+            return;
+        }
+        fail ("Failed to catch expected NoSuchMethodException.");
+    }
+    
+    public void testBadStringConstructorIllegalArgument() {
+        try {
+            ObjectIdentity c1 = new ObjectIdentity(Object.class, 
+                    "javax.jdo.identity.ObjectIdentityTest$IdClass:yy");
+        } catch (JDOUserException ex) {
+            validateNestedException(ex, InvocationTargetException.class);
+            return;
+        }
+        fail ("Failed to catch expected InvocationTargetException.");
+    }
+
+    public void testStringDateConstructor() {
+        Object c1;
+        try {
+            c1 = new ObjectIdentity(Object.class, 
+                "java.util.Date:Jan 01, 1970 00:00:00 AM");
+        } catch (JDOUserException ex) {
+            fail ("Unexpected Exception " + ex);
+        }
+    }
+
+    public void testBadStringDateConstructor() {
+        try {
+            ObjectIdentity c1 = new ObjectIdentity(Object.class, 
+                "java.util.Date:Jop 1, 1970 00:00:00");
+        } catch (JDOUserException ex) {
+            return;
+        }
+        fail ("Failed to catch expected Exception.");
+    }
+
+    public void testStringLocaleConstructor() {
+        Object c1;
+        try {
+            c1 = new ObjectIdentity(Object.class, 
+                    "java.util.Locale:en_us");
+        } catch (JDOUserException ex) {
+            fail ("Unexpected Exception " + ex);
+        }
+    }
+
+    public void testStringCurrencyConstructor() {
+        Object c1;
+        try {
+            c1 = new ObjectIdentity(Object.class, 
+                    "java.util.Currency:USD");
+        } catch (JDOUserException ex) {
+            fail ("Unexpected Exception " + ex);
+        }
+    }
+
+    public void testBadStringCurrencyConstructor() {
+        try {
+            ObjectIdentity c1 = new ObjectIdentity(Object.class, 
+                    "java.util.Currency:NowhereInTheWorld");
+        } catch (JDOUserException ex) {
+            validateNestedException(ex, IllegalArgumentException.class);
+            return;
+        }
+        fail ("Failed to catch expected IllegalArgumentException.");
+    }
+
+    public void testSerializedIdClass() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, new IdClass(1));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, new IdClass(1));
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, new IdClass(2));
+        Object[] scis = writeReadSerialized(new Object[] {c1, c2, c3});
+        Object sc1 = scis[0];
+        Object sc2 = scis[1];
+        Object sc3 = scis[2];
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c1, 
sc1);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c2, 
sc2);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", 
sc1, c2);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", 
sc2, c1);
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
c1.equals(sc3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc1.equals(c3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc1.equals(sc3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc3.equals(sc1));
+    }
+    
+    public void testSerializedBigDecimal() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, new 
BigDecimal("123456789.012"));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, new 
BigDecimal("123456789.012"));
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, new 
BigDecimal("123456789.01"));
+        Object[] scis = writeReadSerialized(new Object[] {c1, c2, c3});
+        Object sc1 = scis[0];
+        Object sc2 = scis[1];
+        Object sc3 = scis[2];
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c1, 
sc1);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c2, 
sc2);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", 
sc1, c2);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", 
sc2, c1);
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
c1.equals(sc3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc1.equals(c3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc1.equals(sc3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc3.equals(sc1));
+    }
+    
+    public void testSerializedCurrency() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, 
Currency.getInstance(Locale.US));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, 
Currency.getInstance(Locale.US));
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, 
Currency.getInstance(Locale.GERMANY));
+        Object[] scis = writeReadSerialized(new Object[] {c1, c2, c3});
+        Object sc1 = scis[0];
+        Object sc2 = scis[1];
+        Object sc3 = scis[2];
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c1, 
sc1);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c2, 
sc2);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", 
sc1, c2);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", 
sc2, c1);
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
c1.equals(sc3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc1.equals(c3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc1.equals(sc3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc3.equals(sc1));
+    }
+    
+    public void testSerializedDate() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, new Date(1));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, new Date(1));
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, new Date(2));
+        Object[] scis = writeReadSerialized(new Object[] {c1, c2, c3});
+        Object sc1 = scis[0];
+        Object sc2 = scis[1];
+        Object sc3 = scis[2];
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c1, 
sc1);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c2, 
sc2);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", 
sc1, c2);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", 
sc2, c1);
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
c1.equals(sc3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc1.equals(c3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc1.equals(sc3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc3.equals(sc1));
+    }
+    
+    public void testSerializedLocale() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, new 
Locale("EN_US"));
+        ObjectIdentity c2 = new ObjectIdentity(Object.class, new 
Locale("EN_US"));
+        ObjectIdentity c3 = new ObjectIdentity(Object.class, new 
Locale("EN_GB"));
+        Object[] scis = writeReadSerialized(new Object[] {c1, c2, c3});
+        Object sc1 = scis[0];
+        Object sc2 = scis[1];
+        Object sc3 = scis[2];
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c1, 
sc1);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", c2, 
sc2);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", 
sc1, c2);
+        assertEquals ("Equal ObjectIdentity instances compare not equal.", 
sc2, c1);
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
c1.equals(sc3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc1.equals(c3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc1.equals(sc3));
+        assertFalse ("Not equal ObjectIdentity instances compare equal.", 
sc3.equals(sc1));
+    }
+    
+    public void testGetKeyAsObject() {
+        ObjectIdentity c1 = new ObjectIdentity(Object.class, new IdClass(1));
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
IdClass(1));
+    }
+
+    private void validateNestedException(JDOUserException ex, Class expected) {
+        Throwable[] nesteds = ex.getNestedExceptions();
+        if (nesteds == null || nesteds.length == 0) {
+            fail ("Nested exception is null or length 0");
+        }
+        Throwable nested = nesteds[0];
+        if (!(expected.isAssignableFrom(nested.getClass()))) {
+            fail ("Wrong nested exception. Expected ClassNotFoundException, 
got "
+                    + nested.toString());
+        }
+        return;
+    }
+    public static class IdClass implements Serializable {
+        public int value;
+        public IdClass() {value = 0;}
+        public IdClass(int value) {this.value = value;}
+        public IdClass(String str) {this.value = Integer.parseInt(str);}
+        public String toString() {return Integer.toString(value);}
+        public int hashCode() {
+            return value;
+        }
+        public boolean equals (Object obj) {
+            if (this == obj) {
+                return true;
+            } else {
+                IdClass other = (IdClass) obj;
+                return value == other.value;
+            }
+        }
+    }
+    
+    public static class BadIdClassNoStringConstructor {
+    }
+    
+    public static class BadIdClassNoPublicStringConstructor {
+        private BadIdClassNoPublicStringConstructor(String str) {}
+    }
+}
Index: test/java/javax/jdo/identity/IntIdentityTest.java
===================================================================
--- test/java/javax/jdo/identity/IntIdentityTest.java   (revision 209512)
+++ test/java/javax/jdo/identity/IntIdentityTest.java   (working copy)
@@ -95,4 +95,14 @@
         assertFalse ("Not equal IntIdentity instances compare equal.", 
sc1.equals(sc3));
         assertFalse ("Not equal IntIdentity instances compare equal.", 
sc3.equals(sc1));
     }
+    public void testGetKeyAsObjectPrimitive() {
+        IntIdentity c1 = new IntIdentity(Object.class, 1);
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
Integer(1));
+    }
+
+    public void testGetKeyAsObject() {
+        IntIdentity c1 = new IntIdentity(Object.class, new Integer(1));
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
Integer(1));
+    }
+
 }
Index: test/java/javax/jdo/identity/CharIdentityTest.java
===================================================================
--- test/java/javax/jdo/identity/CharIdentityTest.java  (revision 209512)
+++ test/java/javax/jdo/identity/CharIdentityTest.java  (working copy)
@@ -105,4 +105,14 @@
         assertFalse ("Not equal CharIdentity instances compare equal.", 
sc1.equals(sc3));
         assertFalse ("Not equal CharIdentity instances compare equal.", 
sc3.equals(sc1));
     }
+    public void testGetKeyAsObjectPrimitive() {
+        CharIdentity c1 = new CharIdentity(Object.class, '1');
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
Character('1'));
+    }
+
+    public void testGetKeyAsObject() {
+        CharIdentity c1 = new CharIdentity(Object.class, new Character('1'));
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
Character('1'));
+    }
+
 }
Index: test/java/javax/jdo/identity/ShortIdentityTest.java
===================================================================
--- test/java/javax/jdo/identity/ShortIdentityTest.java (revision 209512)
+++ test/java/javax/jdo/identity/ShortIdentityTest.java (working copy)
@@ -95,4 +95,14 @@
         assertFalse ("Not equal ShortIdentity instances compare equal.", 
sc1.equals(sc3));
         assertFalse ("Not equal ShortIdentity instances compare equal.", 
sc3.equals(sc1));
     }
+    public void testGetKeyAsObjectPrimitive() {
+        ShortIdentity c1 = new ShortIdentity(Object.class, (short)1);
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
Short((short)1));
+    }
+
+    public void testGetKeyAsObject() {
+        ShortIdentity c1 = new ShortIdentity(Object.class, new 
Short((short)1));
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
Short((short)1));
+    }
+
 }
Index: test/java/javax/jdo/identity/LongIdentityTest.java
===================================================================
--- test/java/javax/jdo/identity/LongIdentityTest.java  (revision 209512)
+++ test/java/javax/jdo/identity/LongIdentityTest.java  (working copy)
@@ -95,4 +95,15 @@
         assertFalse ("Not equal LongIdentity instances compare equal.", 
sc1.equals(sc3));
         assertFalse ("Not equal LongIdentity instances compare equal.", 
sc3.equals(sc1));
     }
+    
+    public void testGetKeyAsObjectPrimitive() {
+        LongIdentity c1 = new LongIdentity(Object.class, 1L);
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
Long(1L));
+    }
+
+    public void testGetKeyAsObject() {
+        LongIdentity c1 = new LongIdentity(Object.class, new Long(1L));
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
Long(1L));
+    }
+
 }
Index: test/java/javax/jdo/identity/ByteIdentityTest.java
===================================================================
--- test/java/javax/jdo/identity/ByteIdentityTest.java  (revision 209512)
+++ test/java/javax/jdo/identity/ByteIdentityTest.java  (working copy)
@@ -95,4 +95,15 @@
         assertFalse ("Not equal ByteIdentity instances compare equal.", 
sc1.equals(sc3));
         assertFalse ("Not equal ByteIdentity instances compare equal.", 
sc3.equals(sc1));
     }
+    
+    public void testGetKeyAsObjectPrimitive() {
+        ByteIdentity c1 = new ByteIdentity(Object.class, (byte)1);
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
Byte((byte)1));
+    }
+
+    public void testGetKeyAsObject() {
+        ByteIdentity c1 = new ByteIdentity(Object.class, new Byte((byte)1));
+        assertEquals("keyAsObject doesn't match.", c1.getKeyAsObject(), new 
Byte((byte)1));
+    }
+
 }
Index: src/java/javax/jdo/identity/SingleFieldIdentity.java
===================================================================
--- src/java/javax/jdo/identity/SingleFieldIdentity.java        (revision 
209512)
+++ src/java/javax/jdo/identity/SingleFieldIdentity.java        (working copy)
@@ -26,6 +26,10 @@
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
 
+import javax.jdo.JDOFatalInternalException;
+
+import javax.jdo.spi.I18NHelper;
+
 /** This class is the abstract base class for all single field identity
  * classes. A common case of application identity uses exactly one 
  * persistent field in the class to represent identity. In this case, 
@@ -36,6 +40,10 @@
 public abstract class SingleFieldIdentity
     implements Externalizable {
     
+    /** The Internationalization message helper.
+     */
+    protected static I18NHelper msg = I18NHelper.getInstance 
("javax.jdo.Bundle"); //NOI18N
+
     /** The class of the target object.
      */
     transient private Class targetClass;
@@ -47,6 +55,10 @@
     /** The hashCode.
      */
     protected int hashCode;
+    
+    /** The key as an Object.
+     */
+    protected Object keyAsObject;
 
     /** Constructor with target class.
      * @param pcClass the class of the target
@@ -80,6 +92,27 @@
         return targetClassName;
     }
 
+    /** Return the key as an Object. The method is synchronized to avoid
+     * race conditions in multi-threaded environments.
+     * @return the key as an Object.
+     * @since 2.0
+     */
+    public synchronized Object getKeyAsObject() {
+        if (keyAsObject == null) {
+            keyAsObject = createKeyAsObject();
+        }
+        return keyAsObject;
+    }
+    
+    /** Create the key as an Object.
+     * @return the key as an Object;
+     * @since 2.0
+     */
+    protected Object createKeyAsObject() {
+        throw new JDOFatalInternalException
+                (msg.msg("EXC_CreateKeyAsObjectMustNotBeCalled"));
+    }
+    
     /** Check the class and class name and object type. If restored
      * from serialization, class will be null so compare class name.
      * @param obj the other object
Index: src/java/javax/jdo/identity/StringIdentity.java
===================================================================
--- src/java/javax/jdo/identity/StringIdentity.java     (revision 209512)
+++ src/java/javax/jdo/identity/StringIdentity.java     (working copy)
@@ -30,11 +30,9 @@
  */
 public class StringIdentity extends SingleFieldIdentity {
     
-    /** The key.
+    /** The key is stored in the superclass field keyAsObject.
      */
-    private String key;
-
-
+    
     /** Constructor with class and key.
      * @param pcClass the class
      * @param key the key
@@ -43,8 +41,8 @@
         super (pcClass);
         if (key == null)
             throw new NullPointerException ();
-        this.key = key;
-        hashCode = hashClassName() ^ key.hashCode();
+        this.keyAsObject = key;
+        hashCode = hashClassName() ^ keyAsObject.hashCode();
     }
 
     /** Constructor only for Externalizable.
@@ -56,14 +54,14 @@
      * @return the key
      */
     public String getKey () {
-        return key;
+        return (String)keyAsObject;
     }
 
     /** Return the String form of the key.
      * @return the String form of the key
      */
     public String toString () {
-        return key;
+        return (String)keyAsObject;
     }
 
     /** Determine if the other object represents the same object id.
@@ -77,7 +75,7 @@
             return false;
         } else {
             StringIdentity other = (StringIdentity) obj;
-            return key.equals(other.key);
+            return keyAsObject.equals(other.keyAsObject);
         }
     }
 
@@ -86,7 +84,7 @@
      */
     public void writeExternal(ObjectOutput out) throws IOException {
         super.writeExternal (out);
-        out.writeObject(key);
+        out.writeObject(keyAsObject);
     }
 
     /** Read this object. Read the superclass first.
@@ -95,7 +93,6 @@
     public void readExternal(ObjectInput in)
                throws IOException, ClassNotFoundException {
         super.readExternal (in);
-        key = (String)in.readObject();
-        hashCode = hashClassName() ^ key.hashCode();
+        keyAsObject = (String)in.readObject();
     }
 }
Index: src/java/javax/jdo/identity/ObjectIdentity.java
===================================================================
--- src/java/javax/jdo/identity/ObjectIdentity.java     (revision 0)
+++ src/java/javax/jdo/identity/ObjectIdentity.java     (revision 0)
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * 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.
+ */
+
+/*
+ * ObjectIdentity.java
+ *
+ */
+ 
+package javax.jdo.identity;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import java.util.Currency;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.jdo.JDOException;
+import javax.jdo.JDOUserException;
+import javax.jdo.spi.JDOImplHelper;
+/** This class is for identity with a single Object type field.
+ * @version 2.0
+ */
+public class ObjectIdentity extends SingleFieldIdentity {
+    
+    /** The key is stored in the superclass field keyAsObject.
+     */
+    
+
+    /** The JDOImplHelper instance used for parsing the String to an Object.
+     */
+    JDOImplHelper helper = JDOImplHelper.getInstance();
+    
+    /** The delimiter for String constructor.
+     */
+    public String STRING_DELIMITER = ":";
+    
+    /** Constructor with class and key.
+     * @param pcClass the class
+     * @param param the key
+     */
+    public ObjectIdentity (Class pcClass, Object param) {
+        super (pcClass);
+        String paramString = null;
+        String keyString = null;
+        String className = null;
+        if (param == null | pcClass == null)
+            throw new NullPointerException ();
+        if (param instanceof String) {
+            /* The paramString is of the form "<className>:<keyString>" */
+            paramString = (String)param;
+            if (paramString.length() < 3) {
+                throw new JDOUserException(
+                        
msg.msg("EXC_ObjectIdentityStringConstructionTooShort") + //NOI18N
+                        msg.msg("EXC_ObjectIdentityStringConstructionUsage", 
//NOI18N
+                            paramString));
+            }
+            int indexOfDelimiter = paramString.indexOf(STRING_DELIMITER);
+            if (indexOfDelimiter < 0) {
+                throw new JDOUserException(
+                        
msg.msg("EXC_ObjectIdentityStringConstructionNoDelimiter") + //NOI18N
+                        msg.msg("EXC_ObjectIdentityStringConstructionUsage", 
//NOI18N
+                            paramString));
+            }
+            keyString = paramString.substring(indexOfDelimiter+1);
+            className = paramString.substring(0, indexOfDelimiter);
+            try {
+                Class keyClass = Class.forName(className);
+                keyAsObject = helper.construct(keyClass, keyString);
+                if (keyAsObject == null) {
+                    Constructor keyConstructor = keyClass.getConstructor(new 
Class[]{String.class});
+                    keyAsObject = keyConstructor.newInstance(new 
Object[]{keyString});
+                }
+            } catch (JDOUserException ex) {
+                throw ex;
+            } catch (Exception ex) {
+                 /* ClassNotFoundException,
+                    NoSuchMethodException,
+                    InstantiationException,
+                    IllegalAccessException,
+                    InvocationTargetException */
+                throw new JDOUserException(
+                        msg.msg("EXC_ObjectIdentityStringConstruction",  
//NOI18N
+                        new Object[] 
+                        {paramString, ex.toString(), className, keyString}), 
ex);
+            }
+        } else {
+            keyAsObject = param;
+        }
+        hashCode = hashClassName() ^ keyAsObject.hashCode();
+    }
+
+    /** Constructor only for Externalizable.
+     */
+    public ObjectIdentity () {
+    }
+
+    /** Return the key.
+     * @return the key
+     */
+    public Object getKey () {
+        return keyAsObject;
+    }
+
+    /** Return the String form of the object id. The class of the
+     * object id is written as the first part of the result so that
+     * the class can be reconstructed later. Then the toString
+     * of the key instance is appended. During construction, 
+     * this process is reversed. The class is extracted from 
+     * the first part of the String, and the String constructor
+     * of the key is used to construct the key itself.
+     * @return the String form of the key
+     */
+    public String toString () {
+        return keyAsObject.getClass().getName()
+                + STRING_DELIMITER
+                + keyAsObject.toString();
+    }
+
+    /** Determine if the other object represents the same object id.
+     * @param obj the other object
+     * @return true if both objects represent the same object id
+     */
+    public boolean equals (Object obj) {
+        if (this == obj) {
+            return true;
+        } else if (!super.equals (obj)) {
+            return false;
+        } else {
+            ObjectIdentity other = (ObjectIdentity) obj;
+            return keyAsObject.equals(other.keyAsObject);
+        }
+    }
+
+    /** Write this object. Write the superclass first.
+     * @param out the output
+     */
+    public void writeExternal(ObjectOutput out) throws IOException {
+        super.writeExternal (out);
+        out.writeObject(keyAsObject);
+    }
+
+    /** Read this object. Read the superclass first.
+     * @param in the input
+     */
+    public void readExternal(ObjectInput in)
+               throws IOException, ClassNotFoundException {
+        super.readExternal (in);
+        keyAsObject = in.readObject();
+//        hashCode = hashClassName() ^ keyAsObject.hashCode();
+    }
+    
+}
Index: src/java/javax/jdo/identity/IntIdentity.java
===================================================================
--- src/java/javax/jdo/identity/IntIdentity.java        (revision 209512)
+++ src/java/javax/jdo/identity/IntIdentity.java        (working copy)
@@ -47,6 +47,7 @@
      */
     public IntIdentity (Class pcClass, Integer key) {
         this (pcClass, key.intValue ());
+        keyAsObject = key;
     }
 
 
@@ -92,6 +93,14 @@
         }
     }
 
+    /** Create the key as an Object.
+     * @return the key as an Object
+     * @since 2.0
+     */
+    protected Object createKeyAsObject() {
+        return new Integer(key);
+    }
+
     /** Write this object. Write the superclass first.
      * @param out the output
      */
@@ -107,6 +116,5 @@
                throws IOException, ClassNotFoundException {
         super.readExternal (in);
         key = in.readInt();
-        hashCode = hashClassName() ^ key;
     }
 }
Index: src/java/javax/jdo/identity/CharIdentity.java
===================================================================
--- src/java/javax/jdo/identity/CharIdentity.java       (revision 209512)
+++ src/java/javax/jdo/identity/CharIdentity.java       (working copy)
@@ -56,6 +56,7 @@
      */
     public CharIdentity (Class pcClass, Character key) {
         this (pcClass, key.charValue ());
+        keyAsObject = key;
     }
 
     /** Constructor with class and key. The String must have exactly one
@@ -106,6 +107,14 @@
         }
     }
 
+    /** Create the key as an Object.
+     * @return the key as an Object
+     * @since 2.0
+     */
+    protected Object createKeyAsObject() {
+        return new Character(key);
+    }
+
     /** Write this object. Write the superclass first.
      * @param out the output
      */
Index: src/java/javax/jdo/identity/ShortIdentity.java
===================================================================
--- src/java/javax/jdo/identity/ShortIdentity.java      (revision 209512)
+++ src/java/javax/jdo/identity/ShortIdentity.java      (working copy)
@@ -50,6 +50,7 @@
      */
     public ShortIdentity (Class pcClass, Short key) {
         this (pcClass, key.shortValue ());
+        keyAsObject = key;
     }
 
     /** Constructor with class and key.
@@ -94,6 +95,14 @@
         }
     }
 
+    /** Create the key as an Object.
+     * @return the key as an Object
+     * @since 2.0
+     */
+    protected Object createKeyAsObject() {
+        return new Short(key);
+    }
+
     /** Write this object. Write the superclass first.
      * @param out the output
      */
@@ -109,6 +118,5 @@
                throws IOException, ClassNotFoundException {
         super.readExternal (in);
         key = in.readShort();
-        hashCode = hashClassName() ^ key;
     }
 }
Index: src/java/javax/jdo/identity/LongIdentity.java
===================================================================
--- src/java/javax/jdo/identity/LongIdentity.java       (revision 209512)
+++ src/java/javax/jdo/identity/LongIdentity.java       (working copy)
@@ -50,6 +50,7 @@
      */
     public LongIdentity (Class pcClass, Long key) {
         this (pcClass, key.longValue ());
+        keyAsObject = key;
     }
 
     /** Constructor with class and key.
@@ -94,6 +95,14 @@
         }
     }
 
+    /** Create the key as an Object.
+     * @return the key as an Object
+     * @since 2.0
+     */
+    protected Object createKeyAsObject() {
+        return new Long(key);
+    }
+
     /** Write this object. Write the superclass first.
      * @param out the output
      */
@@ -109,6 +118,6 @@
                throws IOException, ClassNotFoundException {
         super.readExternal (in);
         key = in.readLong();
-        hashCode = hashClassName() ^ (int)key;
     }
+
 }
Index: src/java/javax/jdo/identity/ByteIdentity.java
===================================================================
--- src/java/javax/jdo/identity/ByteIdentity.java       (revision 209512)
+++ src/java/javax/jdo/identity/ByteIdentity.java       (working copy)
@@ -50,6 +50,7 @@
      */
     public ByteIdentity(Class pcClass, Byte key) {
         this (pcClass, key.byteValue());
+        keyAsObject = key;
     }
 
     /** Constructor with class and key.
@@ -94,6 +95,14 @@
         }
     }
 
+    /** Create the key as an Object.
+     * @return the key as an Object
+     * @since 2.0
+     */
+    protected Object createKeyAsObject() {
+        return new Byte(key);
+    }
+
     /** Write this object. Write the superclass first.
      * @param out the output
      */
@@ -109,6 +118,5 @@
                throws IOException, ClassNotFoundException {
         super.readExternal (in);
         key = in.readByte ();
-        hashCode = super.hashCode() ^ key;
     }
 }
Index: src/java/javax/jdo/spi/JDOImplHelper.java
===================================================================
--- src/java/javax/jdo/spi/JDOImplHelper.java   (revision 209536)
+++ src/java/javax/jdo/spi/JDOImplHelper.java   (working copy)
@@ -21,18 +21,26 @@
 
 package javax.jdo.spi;
 
+import java.text.DateFormat;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Currency;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.WeakHashMap;
 
 import javax.jdo.JDOFatalInternalException;
 import javax.jdo.JDOFatalUserException;
+import javax.jdo.JDOUserException;
 import javax.jdo.spi.JDOPermission;
 
 /** This class is a helper class for JDO implementations.  It contains methods
@@ -267,7 +275,7 @@
             byte[] fieldFlags, Class persistenceCapableSuperclass,
             PersistenceCapable pc) {
         if (pcClass == null) 
-            throw new NullPointerException(msg.msg("ERR_NullClass"));
+            throw new NullPointerException(msg.msg("ERR_NullClass")); //NOI18N
         Meta meta = new Meta (fieldNames, fieldTypes, 
             fieldFlags, persistenceCapableSuperclass, pc);
         registeredClasses.put (pcClass, meta);
@@ -413,7 +421,7 @@
     public static void registerAuthorizedStateManagerClass (Class smClass) 
         throws SecurityException {
         if (smClass == null) 
-            throw new NullPointerException(msg.msg("ERR_NullClass"));
+            throw new NullPointerException(msg.msg("ERR_NullClass")); //NOI18N
         SecurityManager sm = System.getSecurityManager();
         if (sm != null) {
             sm.checkPermission(JDOPermission.SET_STATE_MANAGER);
@@ -442,7 +450,7 @@
                     Object smClass = it.next();
                     if (!(smClass instanceof Class)) {
                         throw new ClassCastException(
-                            msg.msg("ERR_StateManagerClassCast", 
+                            msg.msg("ERR_StateManagerClassCast", //NOI18N
                                 smClass.getClass().getName()));
                     }
                     registerAuthorizedStateManagerClass((Class)it.next());
@@ -489,6 +497,106 @@
         scm.checkPermission(JDOPermission.SET_STATE_MANAGER);
     }
 
+    /** The default DateFormat instance.
+     */
+    static DateFormat dateFormat = DateFormat.getDateTimeInstance();
+
+    /** The DateFormat pattern, set to the default.
+     */
+    static String dateFormatPattern = "MMM d, yyyy hh:mm:ss a";  //NOI18N
+
+    /** Register a DateFormat instance for use with constructing Date 
+     * instances. The default is the default DateFormat instance.
+     * If the new instance implements SimpleDateFormat, set the pattern
+     * for error messages.
+     */
+    synchronized void registerDateFormat(DateFormat df) {
+        dateFormat = df;
+        if (df instanceof SimpleDateFormat) {
+            dateFormatPattern = ((SimpleDateFormat)df).toPattern();
+        }
+    }
+
+    /** 
+     * Construct an instance of a key class using a String as input.
+     * This is a helper interface for use with ObjectIdentity.
+     * Classes without a String constructor (such as those in java.lang
+     * and java.util) will use this interface for constructing new instances.
+     */
+    public interface StringConstructor {
+        public Object construct(String s);
+    }
+    
+    /** 
+     * Special StringConstructor instances for use with specific
+     * classes that have no public String constructor. The Map is
+     * keyed on class instance and the value is an instance of 
+     * StringConstructor.
+     */
+    static Map stringConstructorMap = new HashMap();
+
+    /** 
+     * Register special StringConstructor instances. These instances
+     * are for constructing instances from String parameters where there
+     * is no String constructor for them.
+     */
+    public Object registerStringConstructor(Class cls, StringConstructor sc) {
+        return stringConstructorMap.put(cls, sc);
+    }
+
+    /** Register the default special StringConstructor instances.
+     */
+    static {
+        JDOImplHelper helper = getInstance();
+        helper.registerStringConstructor(Currency.class, new 
StringConstructor() {
+            public Object construct(String s) {
+                try {
+                    return Currency.getInstance(s);
+                } catch (IllegalArgumentException ex) {
+                    throw new javax.jdo.JDOUserException(
+                        
msg.msg("EXC_CurrencyStringConstructorIllegalArgument", s), ex); //NOI18N
+                } catch (Exception ex) {
+                    throw new JDOUserException(
+                        msg.msg("EXC_CurrencyStringConstructorException"), 
ex); //NOI18N
+                }
+            }
+        });
+        helper.registerStringConstructor(Locale.class, new StringConstructor() 
{
+            public Object construct(String s) {
+                try {
+                    return new Locale(s);
+                } catch (Exception ex) {
+                    throw new JDOUserException(
+                        msg.msg("EXC_LocaleStringConstructorException"), ex); 
//NOI18N
+                }
+            }
+        });
+        helper.registerStringConstructor(Date.class, new StringConstructor() {
+            public synchronized Object construct(String s) {
+                ParsePosition pp = new ParsePosition(0);
+                Date result = dateFormat.parse(s, pp);
+                if (result == null) {
+                    throw new JDOUserException (
+                        msg.msg("EXC_DateStringConstructor", new Object[] 
//NOI18N
+                        {s, new Integer(pp.getErrorIndex()), 
dateFormatPattern}));
+                }
+                return result;
+            }
+        });
+    }
+    
+    public Object construct(Class cls, String s) {
+        synchronized(stringConstructorMap) {
+            StringConstructor sc = (StringConstructor) 
stringConstructorMap.get(cls);
+            if (sc == null) {
+                // no special treatment for this class
+                return null;
+            } else {
+                return sc.construct(s);
+            }
+        }
+    }
+
     /** This is a helper class to manage metadata per persistence-capable
      * class.  The information is used at runtime to provide field names and
      * field types to the JDO Model.
Index: src/java/javax/jdo/Bundle.properties
===================================================================
--- src/java/javax/jdo/Bundle.properties        (revision 209514)
+++ src/java/javax/jdo/Bundle.properties        (working copy)
@@ -66,3 +66,11 @@
 \nThe parameter String is of the form "<className>:<keyString>".
 EXC_CreateKeyAsObjectMustNotBeCalled: The method createKeyAsObject must not be 
called \
 because the keyAsObject field must never be null for this class.
+EXC_CurrencyStringConstructorIllegalArgument: The instance could not be 
constructed \
+with the argument "{0}". Try "USD".
+EXC_CurrencyStringConstructorException: An exception was thrown during 
construction \
+of the Currency instance.
+EXC_LocaleStringConstructorException: An exception was thrown during 
construction \
+of the Locale instance.
+EXC_DateStringConstructor: Error parsing Date string "{0}" at position {1} \
+using date format "{2}".
\ No newline at end of file

Craig Russell

Architect, Sun Java Enterprise System http://java.sun.com/products/jdo

408 276-5638 mailto:[EMAIL PROTECTED]

P.S. A good JDO? O, Gasp!


Attachment: smime.p7s
Description: S/MIME cryptographic signature



Reply via email to