Add a unique id to every @Column annotation

This is necessary to know how to encode an entity into the Thrift
or the protobuf wire format.

Change-Id: I094a41b562a314582bf7cb888bfd41be907e2a1c
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gwtorm/client/Column.java b/src/main/java/com/google/gwtorm/client/Column.java
index 5d1392d..8ed6f0d 100644
--- a/src/main/java/com/google/gwtorm/client/Column.java
+++ b/src/main/java/com/google/gwtorm/client/Column.java
@@ -33,6 +33,9 @@
   /** Special value for {@link #name()} to indicate the name is empty. */
   public static final String NONE = "--NONE--";
 
+  /** @return unique identity of this field within its parent object. */
+  int id();
+
   /**
    * @return name of the column in the data store. Defaults to the field name.
    */
diff --git a/src/main/java/com/google/gwtorm/schema/ColumnModel.java b/src/main/java/com/google/gwtorm/schema/ColumnModel.java
index 1cb84e9..58afd5d 100644
--- a/src/main/java/com/google/gwtorm/schema/ColumnModel.java
+++ b/src/main/java/com/google/gwtorm/schema/ColumnModel.java
@@ -20,10 +20,13 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 public abstract class ColumnModel {
   protected ColumnModel parent;
   private String origName;
+  protected int columnId;
   protected String columnName;
   protected Column column;
   protected Collection<ColumnModel> nestedColumns;
@@ -44,6 +47,10 @@
     column = col;
     origName = Util.any(column.name(), Util.makeSqlFriendly(fieldName));
     columnName = origName;
+    columnId = column.id();
+    if (columnId < 1) {
+      throw new OrmException("Field " + fieldName + " cannot have id < 1");
+    }
     notNull = column.notNull();
   }
 
@@ -64,6 +71,14 @@
         c.notNull = false;
       }
     }
+
+    Set<Integer> ids = new HashSet<Integer>();
+    for (final ColumnModel c : nestedColumns) {
+      if (!ids.add(c.columnId)) {
+        throw new OrmException("Duplicate @Column id " + c.columnId + " in "
+            + c.getPathToFieldName());
+      }
+    }
   }
 
   private void recomputeColumnNames() {
@@ -119,6 +134,10 @@
     return getParent().getPathToFieldName() + "." + getFieldName();
   }
 
+  public int getColumnID() {
+    return columnId;
+  }
+
   public String getColumnName() {
     return columnName;
   }
@@ -153,6 +172,7 @@
   public String toString() {
     final StringBuilder r = new StringBuilder();
     r.append("Column[\n");
+    r.append("  id:       " + getColumnID() + "\n");
     r.append("  field:    " + getPathToFieldName() + "\n");
     r.append("  column:   " + getColumnName() + "\n");
     if (isSqlPrimitive()) {
diff --git a/src/main/java/com/google/gwtorm/schema/RelationModel.java b/src/main/java/com/google/gwtorm/schema/RelationModel.java
index 726ebbf..d656ae4 100644
--- a/src/main/java/com/google/gwtorm/schema/RelationModel.java
+++ b/src/main/java/com/google/gwtorm/schema/RelationModel.java
@@ -26,6 +26,8 @@
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
 
 public abstract class RelationModel {
   protected String methodName;
@@ -33,6 +35,7 @@
   protected Relation relation;
   protected final LinkedHashMap<String, ColumnModel> fieldsByFieldName;
   protected final LinkedHashMap<String, ColumnModel> columnsByColumnName;
+  protected final SortedMap<Integer, ColumnModel> columnsById;
   protected KeyModel primaryKey;
   protected Collection<KeyModel> secondaryKeys;
   protected Collection<QueryModel> queries;
@@ -40,6 +43,7 @@
   protected RelationModel() {
     fieldsByFieldName = new LinkedHashMap<String, ColumnModel>();
     columnsByColumnName = new LinkedHashMap<String, ColumnModel>();
+    columnsById = new TreeMap<Integer, ColumnModel>();
     secondaryKeys = new ArrayList<KeyModel>();
     queries = new ArrayList<QueryModel>();
   }
@@ -61,6 +65,10 @@
       if (fieldsByFieldName.put(field.getFieldName(), field) != null) {
         throw new OrmException("Duplicate fields " + field.getFieldName());
       }
+      if (columnsById.put(field.getColumnID(), field) != null) {
+        throw new OrmException("Duplicate @Column id " + field.getColumnID()
+            + " in " + field.getPathToFieldName());
+      }
 
       if (field.isNested()) {
         for (final ColumnModel newCol : field.getAllLeafColumns()) {
@@ -73,10 +81,10 @@
   }
 
   private void registerColumn(final ColumnModel nc) throws OrmException {
-    final ColumnModel oc = columnsByColumnName.put(nc.getColumnName(), nc);
-    if (oc != null) {
+    final ColumnModel name = columnsByColumnName.put(nc.getColumnName(), nc);
+    if (name != null) {
       throw new OrmException("Duplicate columns " + nc.getColumnName() + " in "
-          + getMethodName() + ":\n" + "prior " + oc.getPathToFieldName()
+          + getMethodName() + ":\n" + "prior " + name.getPathToFieldName()
           + "\n next  " + nc.getPathToFieldName());
     }
   }
diff --git a/src/test/java/com/google/gwtorm/client/IntKeyTestCase.java b/src/test/java/com/google/gwtorm/client/IntKeyTestCase.java
index 38e85f0..c0bbe25 100644
--- a/src/test/java/com/google/gwtorm/client/IntKeyTestCase.java
+++ b/src/test/java/com/google/gwtorm/client/IntKeyTestCase.java
@@ -21,7 +21,7 @@
 
 public class IntKeyTestCase extends TestCase {
   private abstract static class IntKeyImpl<T extends Key<?>> extends IntKey<T> {
-    @Column
+    @Column(id = 1)
     int id;
 
     public IntKeyImpl(int n) {
diff --git a/src/test/java/com/google/gwtorm/client/LongKeyTestCase.java b/src/test/java/com/google/gwtorm/client/LongKeyTestCase.java
index 45ab727..46f04a0 100644
--- a/src/test/java/com/google/gwtorm/client/LongKeyTestCase.java
+++ b/src/test/java/com/google/gwtorm/client/LongKeyTestCase.java
@@ -22,7 +22,7 @@
 public class LongKeyTestCase extends TestCase {
   private abstract static class LongKeyImpl<T extends Key<?>> extends
       LongKey<T> {
-    @Column
+    @Column(id = 1)
     long id;
 
     public LongKeyImpl(long n) {
diff --git a/src/test/java/com/google/gwtorm/client/StringKeyTestCase.java b/src/test/java/com/google/gwtorm/client/StringKeyTestCase.java
index 6bdd4c1..5922823 100644
--- a/src/test/java/com/google/gwtorm/client/StringKeyTestCase.java
+++ b/src/test/java/com/google/gwtorm/client/StringKeyTestCase.java
@@ -22,7 +22,7 @@
 public class StringKeyTestCase extends TestCase {
   private abstract static class StringKeyImpl<T extends Key<?>> extends
       StringKey<T> {
-    @Column
+    @Column(id = 1)
     String name;
 
     public StringKeyImpl(String n) {
diff --git a/src/test/java/com/google/gwtorm/data/TestAddress.java b/src/test/java/com/google/gwtorm/data/TestAddress.java
index 4ba551f..f3df900 100644
--- a/src/test/java/com/google/gwtorm/data/TestAddress.java
+++ b/src/test/java/com/google/gwtorm/data/TestAddress.java
@@ -19,10 +19,10 @@
 
 public class TestAddress {
   public static class Key extends StringKey<TestPerson.Key> {
-    @Column
+    @Column(id = 1)
     protected TestPerson.Key owner;
 
-    @Column
+    @Column(id = 2)
     protected String name;
 
     protected Key() {
@@ -50,13 +50,13 @@
     }
   }
 
-  @Column
+  @Column(id = 1)
   protected Key city;
 
-  @Column(length = Integer.MAX_VALUE)
+  @Column(id = 2, length = Integer.MAX_VALUE)
   protected String location;
 
-  @Column(notNull = false)
+  @Column(id = 3, notNull = false)
   protected byte[] photo;
 
   protected TestAddress() {
diff --git a/src/test/java/com/google/gwtorm/data/TestPerson.java b/src/test/java/com/google/gwtorm/data/TestPerson.java
index dc511ba..3f6f3f9 100644
--- a/src/test/java/com/google/gwtorm/data/TestPerson.java
+++ b/src/test/java/com/google/gwtorm/data/TestPerson.java
@@ -20,7 +20,7 @@
 
 public class TestPerson {
   public static class Key extends StringKey<com.google.gwtorm.client.Key<?>> {
-    @Column(length = 20)
+    @Column(id = 1, length = 20)
     protected String name;
 
     protected Key() {
@@ -41,13 +41,13 @@
     }
   }
 
-  @Column
+  @Column(id = 1)
   protected Key name;
 
-  @Column
+  @Column(id = 2)
   protected int age;
 
-  @Column
+  @Column(id = 3)
   protected boolean registered;
 
   protected TestPerson() {
diff --git a/src/test/java/com/google/gwtorm/data/TestPerson2.java b/src/test/java/com/google/gwtorm/data/TestPerson2.java
index 31628d4..7c780bf 100644
--- a/src/test/java/com/google/gwtorm/data/TestPerson2.java
+++ b/src/test/java/com/google/gwtorm/data/TestPerson2.java
@@ -18,12 +18,12 @@
 
 
 public class TestPerson2 {
-  @Column
+  @Column(id = 1)
   protected TestPerson.Key name;
 
-  @Column
+  @Column(id = 2)
   protected int age;
 
-  @Column
+  @Column(id = 3)
   protected boolean isRegistered;
 }
diff --git a/src/test/java/com/google/gwtorm/schema/QueryParserTest.java b/src/test/java/com/google/gwtorm/schema/QueryParserTest.java
index 3f5bd90..c050ede 100644
--- a/src/test/java/com/google/gwtorm/schema/QueryParserTest.java
+++ b/src/test/java/com/google/gwtorm/schema/QueryParserTest.java
@@ -27,7 +27,8 @@
   private final class DummyColumn extends ColumnModel {
     private String name;
 
-    DummyColumn(final String n) {
+    DummyColumn(final int id, final String n) {
+      columnId = id;
       name = n;
       columnName = n;
     }
@@ -53,10 +54,10 @@
       {
         final Collection<ColumnModel> c = new ArrayList<ColumnModel>();
         try {
-          c.add(new DummyColumn("name"));
-          c.add(new DummyColumn("a"));
-          c.add(new DummyColumn("b"));
-          c.add(new DummyColumn("c"));
+          c.add(new DummyColumn(1, "name"));
+          c.add(new DummyColumn(2, "a"));
+          c.add(new DummyColumn(3, "b"));
+          c.add(new DummyColumn(4, "c"));
           initColumns(c);
         } catch (OrmException e) {
           throw new RuntimeException("init columns failure", e);