Allow registration of custom SqlDialects

This permits using an odd driver type whose URL isn't standard,
a subclass of SqlDialect can be created and registered, making
it available to any Database instance.

Change-Id: Ia04ee41154161be73c28f35783ad29e9cfc78f46
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/pom.xml b/pom.xml
index 4852e57..f19f4e6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
   <groupId>gwtorm</groupId>
   <artifactId>gwtorm</artifactId>
   <packaging>jar</packaging>
-  <version>1.1.5</version>
+  <version>1.1.6</version>
   <name>gwtorm</name>
   <description>Tiny ORM</description>
   <url>http://android.git.kernel.org/?p=tools/gwtorm.git</url>
diff --git a/src/main/java/com/google/gwtorm/jdbc/Database.java b/src/main/java/com/google/gwtorm/jdbc/Database.java
index e3d71c1..65d0929 100644
--- a/src/main/java/com/google/gwtorm/jdbc/Database.java
+++ b/src/main/java/com/google/gwtorm/jdbc/Database.java
@@ -23,9 +23,6 @@
 import com.google.gwtorm.jdbc.gen.SchemaGen;
 import com.google.gwtorm.schema.SchemaModel;
 import com.google.gwtorm.schema.java.JavaSchemaModel;
-import com.google.gwtorm.schema.sql.DialectH2;
-import com.google.gwtorm.schema.sql.DialectMySQL;
-import com.google.gwtorm.schema.sql.DialectPostgreSQL;
 import com.google.gwtorm.schema.sql.SqlDialect;
 import com.google.gwtorm.server.StandardKeyEncoder;
 
@@ -106,28 +103,14 @@
 
     SqlDialect dialect;
     try {
-      final Connection c = ds.getConnection();
+      Connection c = ds.getConnection();
       try {
-        final String url = c.getMetaData().getURL();
-        if (url.startsWith("jdbc:postgresql:")) {
-          dialect = new DialectPostgreSQL();
-
-        } else if (url.startsWith("jdbc:h2:")) {
-          dialect = new DialectH2();
-
-        } else if (url.startsWith("jdbc:mysql:")) {
-          dialect = new DialectMySQL();
-
-        } else {
-          throw new OrmException("No dialect known for " + url);
-        }
-
-        dialect = dialect.refine(c);
+        dialect = SqlDialect.getDialectFor(c);
       } finally {
         c.close();
       }
     } catch (SQLException e) {
-      throw new OrmException("Unable to determine driver URL", e);
+      throw new OrmException("Unable to determine SqlDialect", e);
     }
 
     schemaModel = new JavaSchemaModel(schema);
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java b/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
index 971118b..5d0fa59 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
@@ -29,6 +29,11 @@
 /** Dialect for <a href="http://www.h2database.com/">H2</a> */
 public class DialectH2 extends SqlDialect {
   @Override
+  public boolean handles(String url, Connection c) {
+    return url.startsWith("jdbc:h2:");
+  }
+
+  @Override
   public OrmException convertError(final String op, final String entity,
       final SQLException err) {
     switch (getSQLStateInt(err)) {
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java b/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
index bc787da..f81ad33 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
@@ -77,6 +77,11 @@
   }
 
   @Override
+  public boolean handles(String url, Connection c) {
+    return url.startsWith("jdbc:mysql:");
+  }
+
+  @Override
   public String getCreateSequenceSql(final SequenceModel seq) {
     final StringBuilder r = new StringBuilder();
     r.append("CREATE TABLE ");
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java b/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
index e9b323d..32ea333 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
@@ -36,6 +36,11 @@
   }
 
   @Override
+  public boolean handles(String url, Connection c) {
+    return url.startsWith("jdbc:postgresql:");
+  }
+
+  @Override
   public SqlDialect refine(final Connection c) throws SQLException {
     final int major = c.getMetaData().getDatabaseMajorVersion();
     final int minor = c.getMetaData().getDatabaseMinorVersion();
diff --git a/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java b/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
index 0e51db3..e1ea7c3 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
@@ -29,10 +29,36 @@
 import java.sql.Types;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 public abstract class SqlDialect {
+  private static final List<SqlDialect> DIALECTS =
+      new CopyOnWriteArrayList<SqlDialect>();
+
+  static {
+    DIALECTS.add(new DialectH2());
+    DIALECTS.add(new DialectPostgreSQL());
+    DIALECTS.add(new DialectMySQL());
+  }
+
+  public static void register(SqlDialect dialect) {
+    DIALECTS.add(0, dialect);
+  }
+
+  public static SqlDialect getDialectFor(Connection c)
+      throws SQLException, OrmException {
+    String url = c.getMetaData().getURL();
+    for (SqlDialect d : DIALECTS) {
+      if (d.handles(url, c)) {
+        return d.refine(c);
+      }
+    }
+    throw new OrmException("No dialect known for " + url);
+  }
+
   protected final Map<Class<?>, SqlTypeInfo> types;
   protected final Map<Integer, String> typeNames;
 
@@ -58,6 +84,8 @@
     typeNames.put(Types.TIMESTAMP, "TIMESTAMP");
   }
 
+  public abstract boolean handles(String url, Connection c) throws SQLException;
+
   /** Select a better dialect definition for this connection */
   public SqlDialect refine(final Connection c) throws SQLException {
     return this;