Expose all relations and all entities

This permits someone to write a tool that iterates through the
schema, such as to dump it to an external backup file.

Change-Id: I58049e5e66ff827c59959823df5dca787cc7db2e
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gwtorm/client/Access.java b/src/main/java/com/google/gwtorm/client/Access.java
index 2f1fff9..c989135 100644
--- a/src/main/java/com/google/gwtorm/client/Access.java
+++ b/src/main/java/com/google/gwtorm/client/Access.java
@@ -47,6 +47,16 @@
  *        implementation. Entity specific key subclasses are recommended.
  */
 public interface Access<T extends Object, K extends Key<?>> {
+  /** @return the name of this relation. */
+  String getRelationName();
+
+  /**
+   * Iterate through all members of the relation.
+   *
+   * @return an iterator over all members. This is most likely not fast.
+   */
+  ResultSet<T> iterateAllEntities() throws OrmException;
+
   /**
    * Obtain the primary key of an entity instance.
    *
diff --git a/src/main/java/com/google/gwtorm/client/Schema.java b/src/main/java/com/google/gwtorm/client/Schema.java
index 28bfd25..95aa9e4 100644
--- a/src/main/java/com/google/gwtorm/client/Schema.java
+++ b/src/main/java/com/google/gwtorm/client/Schema.java
@@ -78,6 +78,11 @@
   void pruneSchema(StatementExecutor e) throws OrmException;
 
   /**
+   * @return access interface for each declared relation.
+   */
+  Access<?, ?>[] allRelations();
+
+  /**
    * Close the schema and release all resources.
    */
   void close();
diff --git a/src/main/java/com/google/gwtorm/jdbc/AccessGen.java b/src/main/java/com/google/gwtorm/jdbc/AccessGen.java
index 819dafe..573e8d2 100644
--- a/src/main/java/com/google/gwtorm/jdbc/AccessGen.java
+++ b/src/main/java/com/google/gwtorm/jdbc/AccessGen.java
@@ -116,6 +116,7 @@
     for (final QueryModel q : model.getQueries()) {
       implementQuery(q);
     }
+    implementQuery(new QueryModel(model, "iterateAllEntities", ""));
 
     cw.visitEnd();
     classLoader.defineClass(implClassName, cw.toByteArray());
diff --git a/src/main/java/com/google/gwtorm/jdbc/JdbcAccess.java b/src/main/java/com/google/gwtorm/jdbc/JdbcAccess.java
index 4105d75..afcab77 100644
--- a/src/main/java/com/google/gwtorm/jdbc/JdbcAccess.java
+++ b/src/main/java/com/google/gwtorm/jdbc/JdbcAccess.java
@@ -303,8 +303,6 @@
 
   protected abstract T newEntityInstance();
 
-  protected abstract String getRelationName();
-
   protected abstract String getInsertOneSql();
 
   protected abstract String getUpdateOneSql();
diff --git a/src/main/java/com/google/gwtorm/nosql/AccessGen.java b/src/main/java/com/google/gwtorm/nosql/AccessGen.java
index eb72b15..71828c4 100644
--- a/src/main/java/com/google/gwtorm/nosql/AccessGen.java
+++ b/src/main/java/com/google/gwtorm/nosql/AccessGen.java
@@ -107,6 +107,7 @@
     for (final QueryModel q : model.getQueries()) {
       implementQuery(q);
     }
+    implementQuery(new QueryModel(model, "iterateAllEntities", ""));
 
     cw.visitEnd();
     classLoader.defineClass(implClassName, cw.toByteArray());
diff --git a/src/main/java/com/google/gwtorm/nosql/NoSqlAccess.java b/src/main/java/com/google/gwtorm/nosql/NoSqlAccess.java
index 822bd60..291279b 100644
--- a/src/main/java/com/google/gwtorm/nosql/NoSqlAccess.java
+++ b/src/main/java/com/google/gwtorm/nosql/NoSqlAccess.java
@@ -62,9 +62,6 @@
 
   // -- These are all provided by AccessGen when it builds a subclass --
 
-  /** @return name of the relation in the schema. */
-  protected abstract String getRelationName();
-
   /** @return encoder/decoder for the object data. */
   protected abstract ProtobufCodec<T> getObjectCodec();
 
diff --git a/src/main/java/com/google/gwtorm/server/SchemaGen.java b/src/main/java/com/google/gwtorm/server/SchemaGen.java
index 91bc24b..24854bf 100644
--- a/src/main/java/com/google/gwtorm/server/SchemaGen.java
+++ b/src/main/java/com/google/gwtorm/server/SchemaGen.java
@@ -14,6 +14,7 @@
 
 package com.google.gwtorm.server;
 
+import com.google.gwtorm.client.Access;
 import com.google.gwtorm.client.OrmException;
 import com.google.gwtorm.client.Schema;
 import com.google.gwtorm.schema.RelationModel;
@@ -27,6 +28,8 @@
 import org.objectweb.asm.Type;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /** Generates a concrete implementation of a {@link Schema} extension. */
@@ -64,6 +67,8 @@
     implementConstructor();
     implementSequenceMethods();
     implementRelationMethods();
+    implementAllRelationsMethod();
+
     cw.visitEnd();
     classLoader.defineClass(getImplClassName(), cw.toByteArray());
     return loadClass();
@@ -97,6 +102,17 @@
       final Class<?> a = accessGen.create(classLoader, rel);
       relations.add(new RelationGen(rel, a));
     }
+
+    Collections.sort(relations, new Comparator<RelationGen>() {
+      @Override
+      public int compare(RelationGen a, RelationGen b) {
+        int cmp = a.model.getRelationID() - b.model.getRelationID();
+        if (cmp == 0) {
+          cmp = a.model.getRelationName().compareTo(b.model.getRelationName());
+        }
+        return cmp;
+      }
+    });
   }
 
   private void init() {
@@ -182,6 +198,37 @@
     }
   }
 
+  private void implementAllRelationsMethod() {
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, "allRelations", Type
+            .getMethodDescriptor(Type.getType(Access[].class), new Type[] {}),
+            null, null);
+    mv.visitCode();
+
+    final int r = 1;
+    CodeGenSupport cgs = new CodeGenSupport(mv);
+    cgs.push(relations.size());
+    mv.visitTypeInsn(ANEWARRAY, Type.getType(Access.class).getInternalName());
+    mv.visitVarInsn(ASTORE, r);
+
+    int index = 0;
+    for (RelationGen info : relations) {
+      mv.visitVarInsn(ALOAD, r);
+      cgs.push(index++);
+
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKEVIRTUAL, getImplTypeName(), info.model
+          .getMethodName(), info.getDescriptor());
+
+      mv.visitInsn(AASTORE);
+    }
+
+    mv.visitVarInsn(ALOAD, r);
+    mv.visitInsn(ARETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
   private class RelationGen {
     final RelationModel model;
     final Type accessType;
@@ -202,10 +249,8 @@
 
     void implementMethod() {
       final MethodVisitor mv =
-          cw.visitMethod(ACC_PUBLIC | ACC_FINAL, model.getMethodName(), Type
-              .getMethodDescriptor(Type.getObjectType(model
-                  .getAccessInterfaceName().replace('.', '/')), new Type[] {}),
-              null, null);
+          cw.visitMethod(ACC_PUBLIC | ACC_FINAL, model.getMethodName(),
+              getDescriptor(), null, null);
       mv.visitCode();
       mv.visitVarInsn(ALOAD, 0);
       mv.visitFieldInsn(GETFIELD, implTypeName, getAccessInstanceFieldName(),
@@ -214,5 +259,10 @@
       mv.visitMaxs(-1, -1);
       mv.visitEnd();
     }
+
+    String getDescriptor() {
+      return Type.getMethodDescriptor(Type.getObjectType(model
+          .getAccessInterfaceName().replace('.', '/')), new Type[] {});
+    }
   }
 }
diff --git a/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java b/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java
index c3f5c08..7c221b5 100644
--- a/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java
+++ b/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java
@@ -14,6 +14,7 @@
 
 package com.google.gwtorm.server;
 
+import com.google.gwtorm.client.Access;
 import com.google.gwtorm.client.OrmConcurrencyException;
 import com.google.gwtorm.client.OrmException;
 import com.google.gwtorm.data.PersonAccess;
@@ -100,11 +101,22 @@
   public void testGetPeopleAccess() throws Exception {
     final PhoneBookDb schema = open();
     assertNotNull(schema.people());
+    assertEquals("people", schema.people().getRelationName());
   }
 
   public void testGetAddressAccess() throws Exception {
     final PhoneBookDb schema = open();
     assertNotNull(schema.addresses());
+    assertEquals("addresses", schema.addresses().getRelationName());
+  }
+
+  public void testGetAllRelations() throws Exception {
+    final PhoneBookDb schema = open();
+    Access<?, ?>[] all = schema.allRelations();
+    assertNotNull(all);
+    assertEquals(2, all.length);
+    assertSame(schema.people(), all[0]);
+    assertSame(schema.addresses(), all[1]);
   }
 
   public void testCreateSchema() throws Exception {