Add Key.fromString to parse and update a key instance from a string

This utility makes it easier to read a key object from a string
created by the toString() method on Key.

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gwtorm/client/CompoundKey.java b/src/main/java/com/google/gwtorm/client/CompoundKey.java
index 214c96d..52d2513 100644
--- a/src/main/java/com/google/gwtorm/client/CompoundKey.java
+++ b/src/main/java/com/google/gwtorm/client/CompoundKey.java
@@ -91,6 +91,17 @@
     return r.toString();
   }
 
+  public void fromString(final String in) {
+    final String[] parts = in.split(",");
+    int p = 0;
+    if (getParentKey() != null) {
+      getParentKey().fromString(parts[p++]);
+    }
+    for (final Key<?> k : members()) {
+      k.fromString(parts[p++]);
+    }
+  }
+
   @SuppressWarnings("unchecked")
   private static <A extends Key<?>> CompoundKey<A> cast(final Object b) {
     return (CompoundKey<A>) b;
diff --git a/src/main/java/com/google/gwtorm/client/IntKey.java b/src/main/java/com/google/gwtorm/client/IntKey.java
index 2d70972..883ca3d 100644
--- a/src/main/java/com/google/gwtorm/client/IntKey.java
+++ b/src/main/java/com/google/gwtorm/client/IntKey.java
@@ -30,6 +30,9 @@
    */
   public abstract int get();
 
+  /** @param newValue the new value of this key. */
+  protected abstract void set(int newValue);
+
   /**
    * @return the parent key instance; null if this is a root level key.
    */
@@ -68,6 +71,10 @@
     return r.toString();
   }
 
+  public void fromString(final String in) {
+    set(Integer.parseInt(KeyUtil.parseFromString(getParentKey(), in)));
+  }
+
   @SuppressWarnings("unchecked")
   private static <A extends Key<?>> IntKey<A> cast(final Object b) {
     return (IntKey<A>) b;
diff --git a/src/main/java/com/google/gwtorm/client/Key.java b/src/main/java/com/google/gwtorm/client/Key.java
index 0505075..504b358 100644
--- a/src/main/java/com/google/gwtorm/client/Key.java
+++ b/src/main/java/com/google/gwtorm/client/Key.java
@@ -30,4 +30,14 @@
    * @return the parent key; null if this entity key is a root-level key.
    */
   public P getParentKey();
+
+  public int hashCode();
+
+  public boolean equals(Object o);
+
+  /** @return the key, encoded in a string format . */
+  public String toString();
+
+  /** Reset this key instance to represent the data in the supplied string. */
+  public void fromString(String in);
 }
diff --git a/src/main/java/com/google/gwtorm/client/KeyUtil.java b/src/main/java/com/google/gwtorm/client/KeyUtil.java
index 4f64f84..caab29b 100644
--- a/src/main/java/com/google/gwtorm/client/KeyUtil.java
+++ b/src/main/java/com/google/gwtorm/client/KeyUtil.java
@@ -84,6 +84,25 @@
     return ENCODER_IMPL.decode(e);
   }
 
+  /**
+   * Split a string along the last comma and parse into the parent.
+   * 
+   * @param parent parent key; <code>parent.fromString(in[0..comma])</code>.
+   * @param in the input string.
+   * @return text (if any) after the last comma in the input.
+   */
+  public static String parseFromString(final Key<?> parent, final String in) {
+    final int comma = in.lastIndexOf(',');
+    if (comma < 0 && parent == null) {
+      return decode(in);
+    }
+    if (comma < 0 && parent != null) {
+      throw new IllegalArgumentException("Not enough components: " + in);
+    }
+    parent.fromString(in.substring(0, comma));
+    return decode(in.substring(comma + 1));
+  }
+
   public static abstract class Encoder {
     public abstract String encode(String e);
 
diff --git a/src/main/java/com/google/gwtorm/client/LongKey.java b/src/main/java/com/google/gwtorm/client/LongKey.java
index 4bd5bae..c249e87 100644
--- a/src/main/java/com/google/gwtorm/client/LongKey.java
+++ b/src/main/java/com/google/gwtorm/client/LongKey.java
@@ -30,6 +30,9 @@
    */
   public abstract long get();
 
+  /** @param newValue the new value of this key. */
+  protected abstract void set(long newValue);
+
   /**
    * @return the parent key instance; null if this is a root level key.
    */
@@ -68,6 +71,10 @@
     return r.toString();
   }
 
+  public void fromString(final String in) {
+    set(Long.parseLong(KeyUtil.parseFromString(getParentKey(), in)));
+  }
+
   @SuppressWarnings("unchecked")
   private static <A extends Key<?>> LongKey<A> cast(final Object b) {
     return (LongKey<A>) b;
diff --git a/src/main/java/com/google/gwtorm/client/ShortKey.java b/src/main/java/com/google/gwtorm/client/ShortKey.java
index 30daf88..b004170 100644
--- a/src/main/java/com/google/gwtorm/client/ShortKey.java
+++ b/src/main/java/com/google/gwtorm/client/ShortKey.java
@@ -31,6 +31,9 @@
    */
   public abstract short get();
 
+  /** @param newValue the new value of this key. */
+  protected abstract void set(short newValue);
+
   /**
    * @return the parent key instance; null if this is a root level key.
    */
@@ -69,6 +72,10 @@
     return r.toString();
   }
 
+  public void fromString(final String in) {
+    set(Short.parseShort(KeyUtil.parseFromString(getParentKey(), in)));
+  }
+
   @SuppressWarnings("unchecked")
   private static <A extends Key<?>> ShortKey<A> cast(final Object b) {
     return (ShortKey<A>) b;
diff --git a/src/main/java/com/google/gwtorm/client/StringKey.java b/src/main/java/com/google/gwtorm/client/StringKey.java
index a906142..0ba70c4 100644
--- a/src/main/java/com/google/gwtorm/client/StringKey.java
+++ b/src/main/java/com/google/gwtorm/client/StringKey.java
@@ -31,6 +31,9 @@
    */
   public abstract String get();
 
+  /** @param newValue the new value of this key. */
+  protected abstract void set(String newValue);
+
   /**
    * @return the parent key instance; null if this is a root level key.
    */
@@ -70,6 +73,10 @@
     return r.toString();
   }
 
+  public void fromString(final String in) {
+    set(KeyUtil.parseFromString(getParentKey(), in));
+  }
+
   @SuppressWarnings("unchecked")
   private static <A extends Key<?>> StringKey<A> cast(final Object b) {
     return (StringKey<A>) b;
diff --git a/src/test/java/com/google/gwtorm/client/IntKeyTestCase.java b/src/test/java/com/google/gwtorm/client/IntKeyTestCase.java
index 8147ca4..38e85f0 100644
--- a/src/test/java/com/google/gwtorm/client/IntKeyTestCase.java
+++ b/src/test/java/com/google/gwtorm/client/IntKeyTestCase.java
@@ -14,6 +14,8 @@
 
 package com.google.gwtorm.client;
 
+import com.google.gwtorm.server.StandardKeyEncoder;
+
 import junit.framework.TestCase;
 
 
@@ -30,6 +32,11 @@
     public int get() {
       return id;
     }
+
+    @Override
+    protected void set(int newValue) {
+      id = newValue;
+    }
   }
 
   private static class Parent extends IntKeyImpl<Key<?>> {
@@ -58,6 +65,11 @@
     }
   }
 
+  @Override
+  protected void setUp() throws Exception {
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+  }
+
   public void testHashCodeWhenNull() {
     final Parent p = new Parent(0);
     assertEquals(0, p.hashCode());
@@ -109,4 +121,22 @@
     assertFalse(c1.equals(u));
     assertFalse(u.equals(c1));
   }
+
+  public void testParentString() {
+    final Parent p1 = new Parent(1);
+    assertEquals("1", p1.toString());
+
+    final Parent p2 = new Parent(0);
+    p2.fromString(p1.toString());
+    assertEquals(p1, p2);
+  }
+
+  public void testChildString() {
+    final Child c1 = new Child(new Parent(1), 2);
+    assertEquals("1,2", c1.toString());
+
+    final Child c2 = new Child(new Parent(0), 0);
+    c2.fromString(c1.toString());
+    assertEquals(c1, c2);
+  }
 }
diff --git a/src/test/java/com/google/gwtorm/client/LongKeyTestCase.java b/src/test/java/com/google/gwtorm/client/LongKeyTestCase.java
index 34958cf..45ab727 100644
--- a/src/test/java/com/google/gwtorm/client/LongKeyTestCase.java
+++ b/src/test/java/com/google/gwtorm/client/LongKeyTestCase.java
@@ -14,6 +14,8 @@
 
 package com.google.gwtorm.client;
 
+import com.google.gwtorm.server.StandardKeyEncoder;
+
 import junit.framework.TestCase;
 
 
@@ -31,6 +33,11 @@
     public long get() {
       return id;
     }
+
+    @Override
+    protected void set(long newValue) {
+      id = newValue;
+    }
   }
 
   private static class Parent extends LongKeyImpl<Key<?>> {
@@ -59,6 +66,11 @@
     }
   }
 
+  @Override
+  protected void setUp() throws Exception {
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+  }
+
   public void testHashCodeWhenNull() {
     final Parent p = new Parent(0);
     assertEquals(0, p.hashCode());
@@ -110,4 +122,25 @@
     assertFalse(c1.equals(u));
     assertFalse(u.equals(c1));
   }
+
+  public void testParentString() {
+    final long pId = 21281821821821881L;
+    final Parent p1 = new Parent(pId);
+    assertEquals("" + pId, p1.toString());
+
+    final Parent p2 = new Parent(0);
+    p2.fromString(p1.toString());
+    assertEquals(p1, p2);
+  }
+
+  public void testChildString() {
+    final long pId = 21281821821821881L;
+    final long cId = 18218181281818888L;
+    final Child c1 = new Child(new Parent(pId), cId);
+    assertEquals(pId + "," + cId, c1.toString());
+
+    final Child c2 = new Child(new Parent(0), 0);
+    c2.fromString(c1.toString());
+    assertEquals(c1, c2);
+  }
 }
diff --git a/src/test/java/com/google/gwtorm/client/StringKeyTestCase.java b/src/test/java/com/google/gwtorm/client/StringKeyTestCase.java
index 62dfbd0..6bdd4c1 100644
--- a/src/test/java/com/google/gwtorm/client/StringKeyTestCase.java
+++ b/src/test/java/com/google/gwtorm/client/StringKeyTestCase.java
@@ -14,6 +14,8 @@
 
 package com.google.gwtorm.client;
 
+import com.google.gwtorm.server.StandardKeyEncoder;
+
 import junit.framework.TestCase;
 
 
@@ -31,6 +33,11 @@
     public String get() {
       return name;
     }
+
+    @Override
+    protected void set(String newValue) {
+      name = newValue;
+    }
   }
 
   private static class Parent extends StringKeyImpl<Key<?>> {
@@ -59,6 +66,11 @@
     }
   }
 
+  @Override
+  protected void setUp() throws Exception {
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+  }
+
   public void testHashCodeWhenNull() {
     final Parent p = new Parent(null);
     assertEquals(0, p.hashCode());
@@ -110,4 +122,25 @@
     assertFalse(c1.equals(u));
     assertFalse(u.equals(c1));
   }
+
+  public void testParentString() {
+    final String pv = "foo,bar/ at %here";
+    final Parent p1 = new Parent(pv);
+    assertEquals("foo%2Cbar/+at+%25here", p1.toString());
+
+    final Parent p2 = new Parent("");
+    p2.fromString(p1.toString());
+    assertEquals(p1, p2);
+  }
+
+  public void testChildString() {
+    final String pv = "foo,bar/ at %here";
+    final String cv = "x";
+    final Child c1 = new Child(new Parent(pv), cv);
+    assertEquals("foo%2Cbar/+at+%25here,x", c1.toString());
+
+    final Child c2 = new Child(new Parent(""), "");
+    c2.fromString(c1.toString());
+    assertEquals(c1, c2);
+  }
 }
diff --git a/src/test/java/com/google/gwtorm/data/TestAddress.java b/src/test/java/com/google/gwtorm/data/TestAddress.java
index bc45104..4ba551f 100644
--- a/src/test/java/com/google/gwtorm/data/TestAddress.java
+++ b/src/test/java/com/google/gwtorm/data/TestAddress.java
@@ -43,6 +43,11 @@
     public TestPerson.Key getParentKey() {
       return owner;
     }
+
+    @Override
+    protected void set(String newValue) {
+      name = newValue;
+    }
   }
 
   @Column
diff --git a/src/test/java/com/google/gwtorm/data/TestPerson.java b/src/test/java/com/google/gwtorm/data/TestPerson.java
index e130d0b..dc511ba 100644
--- a/src/test/java/com/google/gwtorm/data/TestPerson.java
+++ b/src/test/java/com/google/gwtorm/data/TestPerson.java
@@ -34,6 +34,11 @@
     public String get() {
       return name;
     }
+
+    @Override
+    protected void set(String newValue) {
+      name = newValue;
+    }
   }
 
   @Column