aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/iciql
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/iciql')
-rw-r--r--src/main/java/com/iciql/CompareType.java45
-rw-r--r--src/main/java/com/iciql/Condition.java55
-rw-r--r--src/main/java/com/iciql/ConditionAndOr.java37
-rw-r--r--src/main/java/com/iciql/Constants.java38
-rw-r--r--src/main/java/com/iciql/Db.java774
-rw-r--r--src/main/java/com/iciql/DbInspector.java204
-rw-r--r--src/main/java/com/iciql/DbUpgrader.java81
-rw-r--r--src/main/java/com/iciql/DbVersion.java55
-rw-r--r--src/main/java/com/iciql/Define.java145
-rw-r--r--src/main/java/com/iciql/Filter.java25
-rw-r--r--src/main/java/com/iciql/Function.java149
-rw-r--r--src/main/java/com/iciql/Iciql.java731
-rw-r--r--src/main/java/com/iciql/IciqlException.java177
-rw-r--r--src/main/java/com/iciql/ModelUtils.java499
-rw-r--r--src/main/java/com/iciql/OrderExpression.java55
-rw-r--r--src/main/java/com/iciql/Query.java947
-rw-r--r--src/main/java/com/iciql/QueryBetween.java60
-rw-r--r--src/main/java/com/iciql/QueryCondition.java128
-rw-r--r--src/main/java/com/iciql/QueryJoin.java75
-rw-r--r--src/main/java/com/iciql/QueryJoinCondition.java83
-rw-r--r--src/main/java/com/iciql/QueryWhere.java501
-rw-r--r--src/main/java/com/iciql/RuntimeParameter.java49
-rw-r--r--src/main/java/com/iciql/RuntimeToken.java57
-rw-r--r--src/main/java/com/iciql/SQLDialect.java206
-rw-r--r--src/main/java/com/iciql/SQLDialectDefault.java445
-rw-r--r--src/main/java/com/iciql/SQLDialectDerby.java71
-rw-r--r--src/main/java/com/iciql/SQLDialectH2.java135
-rw-r--r--src/main/java/com/iciql/SQLDialectHSQL.java149
-rw-r--r--src/main/java/com/iciql/SQLDialectMSSQL.java53
-rw-r--r--src/main/java/com/iciql/SQLDialectMySQL.java93
-rw-r--r--src/main/java/com/iciql/SQLDialectPostgreSQL.java103
-rw-r--r--src/main/java/com/iciql/SQLStatement.java190
-rw-r--r--src/main/java/com/iciql/SelectColumn.java57
-rw-r--r--src/main/java/com/iciql/SelectTable.java112
-rw-r--r--src/main/java/com/iciql/SubQuery.java32
-rw-r--r--src/main/java/com/iciql/SubQueryCondition.java41
-rw-r--r--src/main/java/com/iciql/TableDefinition.java1233
-rw-r--r--src/main/java/com/iciql/TableInspector.java723
-rw-r--r--src/main/java/com/iciql/TestCondition.java115
-rw-r--r--src/main/java/com/iciql/Token.java35
-rw-r--r--src/main/java/com/iciql/UpdateColumn.java35
-rw-r--r--src/main/java/com/iciql/UpdateColumnIncrement.java55
-rw-r--r--src/main/java/com/iciql/UpdateColumnSet.java63
-rw-r--r--src/main/java/com/iciql/ValidationRemark.java127
-rw-r--r--src/main/java/com/iciql/bytecode/And.java46
-rw-r--r--src/main/java/com/iciql/bytecode/ArrayGet.java49
-rw-r--r--src/main/java/com/iciql/bytecode/CaseWhen.java62
-rw-r--r--src/main/java/com/iciql/bytecode/ClassReader.java1457
-rw-r--r--src/main/java/com/iciql/bytecode/Constant.java38
-rw-r--r--src/main/java/com/iciql/bytecode/ConstantNumber.java70
-rw-r--r--src/main/java/com/iciql/bytecode/ConstantString.java55
-rw-r--r--src/main/java/com/iciql/bytecode/Function.java47
-rw-r--r--src/main/java/com/iciql/bytecode/Not.java55
-rw-r--r--src/main/java/com/iciql/bytecode/Null.java44
-rw-r--r--src/main/java/com/iciql/bytecode/Operation.java111
-rw-r--r--src/main/java/com/iciql/bytecode/Or.java47
-rw-r--r--src/main/java/com/iciql/bytecode/Variable.java51
-rw-r--r--src/main/java/com/iciql/bytecode/package.html25
-rw-r--r--src/main/java/com/iciql/package.html25
-rw-r--r--src/main/java/com/iciql/util/GenerateModels.java193
-rw-r--r--src/main/java/com/iciql/util/IciqlLogger.java214
-rw-r--r--src/main/java/com/iciql/util/JdbcUtils.java254
-rw-r--r--src/main/java/com/iciql/util/Slf4jIciqlListener.java92
-rw-r--r--src/main/java/com/iciql/util/StatementBuilder.java166
-rw-r--r--src/main/java/com/iciql/util/StringUtils.java382
-rw-r--r--src/main/java/com/iciql/util/Utils.java459
-rw-r--r--src/main/java/com/iciql/util/WeakIdentityHashMap.java243
-rw-r--r--src/main/java/com/iciql/util/package.html25
68 files changed, 13253 insertions, 0 deletions
diff --git a/src/main/java/com/iciql/CompareType.java b/src/main/java/com/iciql/CompareType.java
new file mode 100644
index 0000000..84e29fe
--- /dev/null
+++ b/src/main/java/com/iciql/CompareType.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * An enumeration of compare operations.
+ */
+
+enum CompareType {
+ EQUAL("=", true), EXCEEDS(">", true), AT_LEAST(">=", true), LESS_THAN("<", true), AT_MOST("<=", true), NOT_EQUAL(
+ "<>", true), IS_NOT_NULL("IS NOT NULL", false), IS_NULL("IS NULL", false), LIKE("LIKE", true), BETWEEN(
+ "BETWEEN", true);
+
+ private String text;
+ private boolean hasRightExpression;
+
+ CompareType(String text, boolean hasRightExpression) {
+ this.text = text;
+ this.hasRightExpression = hasRightExpression;
+ }
+
+ String getString() {
+ return text;
+ }
+
+ boolean hasRightExpression() {
+ return hasRightExpression;
+ }
+
+}
diff --git a/src/main/java/com/iciql/Condition.java b/src/main/java/com/iciql/Condition.java
new file mode 100644
index 0000000..17cb117
--- /dev/null
+++ b/src/main/java/com/iciql/Condition.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * A condition contains one or two operands and a compare operation.
+ *
+ * @param <A>
+ * the operand type
+ */
+
+class Condition<A> implements Token {
+ CompareType compareType;
+ A x, y, z;
+
+ Condition(A x, A y, CompareType compareType) {
+ this(x, y, null, compareType);
+ }
+
+ Condition(A x, A y, A z, CompareType compareType) {
+ this.compareType = compareType;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ query.appendSQL(stat, null, x);
+ stat.appendSQL(" ");
+ stat.appendSQL(compareType.getString());
+ if (compareType.hasRightExpression()) {
+ stat.appendSQL(" ");
+ if (z == null) {
+ query.appendSQL(stat, x, y);
+ } else {
+ query.appendSQL(stat, x, y, z, compareType);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/iciql/ConditionAndOr.java b/src/main/java/com/iciql/ConditionAndOr.java
new file mode 100644
index 0000000..4d1cd0e
--- /dev/null
+++ b/src/main/java/com/iciql/ConditionAndOr.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * An OR or an AND condition.
+ */
+
+enum ConditionAndOr implements Token {
+ AND("AND"), OR("OR");
+
+ private String text;
+
+ ConditionAndOr(String text) {
+ this.text = text;
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL(text);
+ }
+
+}
diff --git a/src/main/java/com/iciql/Constants.java b/src/main/java/com/iciql/Constants.java
new file mode 100644
index 0000000..8b54493
--- /dev/null
+++ b/src/main/java/com/iciql/Constants.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * Iciql constants.
+ */
+public class Constants {
+
+ public static final String NAME = "iciql";
+
+ // The build script extracts this exact line so be careful editing it
+ // and only use A-Z a-z 0-9 .-_ in the string.
+ public static final String VERSION = "1.2.0-SNAPSHOT";
+
+ // The build script extracts this exact line so be careful editing it
+ // and only use A-Z a-z 0-9 .-_ in the string.
+ public static final String VERSION_DATE = "PENDING";
+
+ // The build script extracts this exact line so be careful editing it
+ // and only use A-Z a-z 0-9 .-_ in the string.
+ public static final String API_CURRENT = "15";
+
+}
diff --git a/src/main/java/com/iciql/Db.java b/src/main/java/com/iciql/Db.java
new file mode 100644
index 0000000..ecd373c
--- /dev/null
+++ b/src/main/java/com/iciql/Db.java
@@ -0,0 +1,774 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard.
+ * Copyright 2012 Alex Telepov.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.sql.DataSource;
+
+import com.iciql.DbUpgrader.DefaultDbUpgrader;
+import com.iciql.Iciql.IQTable;
+import com.iciql.Iciql.IQVersion;
+import com.iciql.Iciql.IQView;
+import com.iciql.util.IciqlLogger;
+import com.iciql.util.JdbcUtils;
+import com.iciql.util.StringUtils;
+import com.iciql.util.Utils;
+import com.iciql.util.WeakIdentityHashMap;
+
+/**
+ * This class represents a connection to a database.
+ */
+
+public class Db {
+
+ /**
+ * This map It holds unique tokens that are generated by functions such as
+ * Function.sum(..) in "db.from(p).select(Function.sum(p.unitPrice))". It
+ * doesn't actually hold column tokens, as those are bound to the query
+ * itself.
+ */
+ private static final Map<Object, Token> TOKENS;
+
+ private static final Map<String, Class<? extends SQLDialect>> DIALECTS;
+
+ private final Connection conn;
+ private final Map<Class<?>, TableDefinition<?>> classMap = Collections
+ .synchronizedMap(new HashMap<Class<?>, TableDefinition<?>>());
+ private final SQLDialect dialect;
+ private DbUpgrader dbUpgrader = new DefaultDbUpgrader();
+ private final Set<Class<?>> upgradeChecked = Collections.synchronizedSet(new HashSet<Class<?>>());
+
+ private boolean skipCreate;
+ private boolean autoSavePoint = true;
+
+ static {
+ TOKENS = Collections.synchronizedMap(new WeakIdentityHashMap<Object, Token>());
+ DIALECTS = Collections.synchronizedMap(new HashMap<String, Class<? extends SQLDialect>>());
+ // can register by...
+ // 1. Connection class name
+ // 2. DatabaseMetaData.getDatabaseProductName()
+ DIALECTS.put("Apache Derby", SQLDialectDerby.class);
+ DIALECTS.put("H2", SQLDialectH2.class);
+ DIALECTS.put("HSQL Database Engine", SQLDialectHSQL.class);
+ DIALECTS.put("MySQL", SQLDialectMySQL.class);
+ DIALECTS.put("PostgreSQL", SQLDialectPostgreSQL.class);
+ DIALECTS.put("Microsoft SQL Server", SQLDialectMSSQL.class);
+ }
+
+ private Db(Connection conn) {
+ this.conn = conn;
+ String databaseName = null;
+ DatabaseMetaData data = null;
+ try {
+ data = conn.getMetaData();
+ databaseName = data.getDatabaseProductName();
+ } catch (SQLException s) {
+ throw new IciqlException(s, "failed to retrieve database metadata!");
+ }
+ dialect = getDialect(databaseName, conn.getClass().getName());
+ dialect.configureDialect(databaseName, data);
+ }
+
+ /**
+ * Register a new/custom dialect class. You can use this method to replace
+ * any existing dialect or to add a new one.
+ *
+ * @param token
+ * the fully qualified name of the connection class or the
+ * expected result of DatabaseMetaData.getDatabaseProductName()
+ * @param dialectClass
+ * the dialect class to register
+ */
+ public static void registerDialect(String token, Class<? extends SQLDialect> dialectClass) {
+ DIALECTS.put(token, dialectClass);
+ }
+
+ SQLDialect getDialect(String databaseName, String className) {
+ Class<? extends SQLDialect> dialectClass = null;
+ if (DIALECTS.containsKey(className)) {
+ // dialect registered by connection class name
+ dialectClass = DIALECTS.get(className);
+ } else if (DIALECTS.containsKey(databaseName)) {
+ // dialect registered by database name
+ dialectClass = DIALECTS.get(databaseName);
+ } else {
+ // did not find a match, use default
+ dialectClass = SQLDialectDefault.class;
+ }
+ return instance(dialectClass);
+ }
+
+ static <X> X registerToken(X x, Token token) {
+ TOKENS.put(x, token);
+ return x;
+ }
+
+ static Token getToken(Object x) {
+ return TOKENS.get(x);
+ }
+
+ static <T> T instance(Class<T> clazz) {
+ try {
+ return clazz.newInstance();
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public static Db open(String url) {
+ try {
+ Connection conn = JdbcUtils.getConnection(null, url, null, null);
+ return new Db(conn);
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public static Db open(String url, String user, String password) {
+ try {
+ Connection conn = JdbcUtils.getConnection(null, url, user, password);
+ return new Db(conn);
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public static Db open(String url, String user, char[] password) {
+ try {
+ Connection conn = JdbcUtils.getConnection(null, url, user, password == null ? null : new String(password));
+ return new Db(conn);
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ /**
+ * Create a new database instance using a data source. This method is fast,
+ * so that you can always call open() / close() on usage.
+ *
+ * @param ds
+ * the data source
+ * @return the database instance.
+ */
+ public static Db open(DataSource ds) {
+ try {
+ return new Db(ds.getConnection());
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public static Db open(Connection conn) {
+ return new Db(conn);
+ }
+
+
+
+ /**
+ * Convenience function to avoid import statements in application code.
+ */
+ public void activateConsoleLogger() {
+ IciqlLogger.activateConsoleLogger();
+ }
+
+ /**
+ * Convenience function to avoid import statements in application code.
+ */
+ public void deactivateConsoleLogger() {
+ IciqlLogger.deactivateConsoleLogger();
+ }
+
+ public <T> void insert(T t) {
+ Class<?> clazz = t.getClass();
+ long rc = define(clazz).createIfRequired(this).insert(this, t, false);
+ if (rc == 0) {
+ throw new IciqlException("Failed to insert {0}. Affected rowcount == 0.", t);
+ }
+ }
+
+ public <T> long insertAndGetKey(T t) {
+ Class<?> clazz = t.getClass();
+ return define(clazz).createIfRequired(this).insert(this, t, true);
+ }
+
+ /**
+ * Merge INSERTS if the record does not exist or UPDATES the record if it
+ * does exist. Not all databases support MERGE and the syntax varies with
+ * the database.
+ *
+ * If the database does not support a MERGE syntax the dialect can try to
+ * simulate a merge by implementing:
+ * <p>
+ * INSERT INTO foo... (SELECT ?,... FROM foo WHERE pk=? HAVING count(*)=0)
+ * <p>
+ * iciql will check the affected row count returned by the internal merge
+ * method and if the affected row count = 0, it will issue an update.
+ * <p>
+ * See the Derby dialect for an implementation of this technique.
+ * <p>
+ * If the dialect does not support merge an IciqlException will be thrown.
+ *
+ * @param t
+ */
+ public <T> void merge(T t) {
+ Class<?> clazz = t.getClass();
+ TableDefinition<?> def = define(clazz).createIfRequired(this);
+ int rc = def.merge(this, t);
+ if (rc == 0) {
+ rc = def.update(this, t);
+ }
+ if (rc == 0) {
+ throw new IciqlException("merge failed");
+ }
+ }
+
+ public <T> int update(T t) {
+ Class<?> clazz = t.getClass();
+ return define(clazz).createIfRequired(this).update(this, t);
+ }
+
+ public <T> int delete(T t) {
+ Class<?> clazz = t.getClass();
+ return define(clazz).createIfRequired(this).delete(this, t);
+ }
+
+ public <T extends Object> Query<T> from(T alias) {
+ Class<?> clazz = alias.getClass();
+ define(clazz).createIfRequired(this);
+ return Query.from(this, alias);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> int dropTable(Class<? extends T> modelClass) {
+ TableDefinition<T> def = (TableDefinition<T>) define(modelClass);
+ SQLStatement stat = new SQLStatement(this);
+ getDialect().prepareDropTable(stat, def);
+ IciqlLogger.drop(stat.getSQL());
+ int rc = 0;
+ try {
+ rc = stat.executeUpdate();
+ } catch (IciqlException e) {
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) {
+ throw e;
+ }
+ }
+ // remove this model class from the table definition cache
+ classMap.remove(modelClass);
+ // remove this model class from the upgrade checked cache
+ upgradeChecked.remove(modelClass);
+ return rc;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> int dropView(Class<? extends T> modelClass) {
+ TableDefinition<T> def = (TableDefinition<T>) define(modelClass);
+ SQLStatement stat = new SQLStatement(this);
+ getDialect().prepareDropView(stat, def);
+ IciqlLogger.drop(stat.getSQL());
+ int rc = 0;
+ try {
+ rc = stat.executeUpdate();
+ } catch (IciqlException e) {
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_NOT_FOUND) {
+ throw e;
+ }
+ }
+ // remove this model class from the table definition cache
+ classMap.remove(modelClass);
+ // remove this model class from the upgrade checked cache
+ upgradeChecked.remove(modelClass);
+ return rc;
+ }
+
+ public <T> List<T> buildObjects(Class<? extends T> modelClass, ResultSet rs) {
+ return buildObjects(modelClass, false, rs);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> List<T> buildObjects(Class<? extends T> modelClass, boolean wildcardSelect, ResultSet rs) {
+ List<T> result = new ArrayList<T>();
+ TableDefinition<T> def = (TableDefinition<T>) define(modelClass);
+ try {
+ int[] columns = def.mapColumns(wildcardSelect, rs);
+ while (rs.next()) {
+ T item = Utils.newObject(modelClass);
+ def.readRow(item, rs, columns);
+ result.add(item);
+ }
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ return result;
+ }
+
+ Db upgradeDb() {
+ if (!upgradeChecked.contains(dbUpgrader.getClass())) {
+ // flag as checked immediately because calls are nested.
+ upgradeChecked.add(dbUpgrader.getClass());
+
+ IQVersion model = dbUpgrader.getClass().getAnnotation(IQVersion.class);
+ if (model.value() == 0) {
+ // try superclass
+ Class<?> superClass = dbUpgrader.getClass().getSuperclass();
+ if (superClass.isAnnotationPresent(IQVersion.class)) {
+ model = superClass.getAnnotation(IQVersion.class);
+ }
+ }
+ if (model.value() > 0) {
+ DbVersion v = new DbVersion();
+ // (SCHEMA="" && TABLE="") == DATABASE
+ DbVersion dbVersion = from(v).where(v.schemaName).is("").and(v.tableName).is("")
+ .selectFirst();
+ if (dbVersion == null) {
+ // database has no version registration, but model specifies
+ // version: insert DbVersion entry and return.
+ DbVersion newDb = new DbVersion(model.value());
+ // database is an older version than the model
+ boolean success = dbUpgrader.upgradeDatabase(this, 0, newDb.version);
+ if (success) {
+ insert(newDb);
+ }
+ } else {
+ // database has a version registration:
+ // check to see if upgrade is required.
+ if ((model.value() > dbVersion.version) && (dbUpgrader != null)) {
+ // database is an older version than the model
+ boolean success = dbUpgrader.upgradeDatabase(this, dbVersion.version, model.value());
+ if (success) {
+ dbVersion.version = model.value();
+ update(dbVersion);
+ }
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+ <T> void upgradeTable(TableDefinition<T> model) {
+ if (!upgradeChecked.contains(model.getModelClass())) {
+ // flag is checked immediately because calls are nested
+ upgradeChecked.add(model.getModelClass());
+
+ if (model.tableVersion > 0) {
+ // table is using iciql version tracking.
+ DbVersion v = new DbVersion();
+ String schema = StringUtils.isNullOrEmpty(model.schemaName) ? "" : model.schemaName;
+ DbVersion dbVersion = from(v).where(v.schemaName).is(schema).and(v.tableName)
+ .is(model.tableName).selectFirst();
+ if (dbVersion == null) {
+ // table has no version registration, but model specifies
+ // version: insert DbVersion entry
+ DbVersion newTable = new DbVersion(model.tableVersion);
+ newTable.schemaName = schema;
+ newTable.tableName = model.tableName;
+ insert(newTable);
+ } else {
+ // table has a version registration:
+ // check if upgrade is required
+ if ((model.tableVersion > dbVersion.version) && (dbUpgrader != null)) {
+ // table is an older version than model
+ boolean success = dbUpgrader.upgradeTable(this, schema, model.tableName,
+ dbVersion.version, model.tableVersion);
+ if (success) {
+ dbVersion.version = model.tableVersion;
+ update(dbVersion);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ <T> TableDefinition<T> define(Class<T> clazz) {
+ TableDefinition<T> def = getTableDefinition(clazz);
+ if (def == null) {
+ upgradeDb();
+ def = new TableDefinition<T>(clazz);
+ def.mapFields();
+ classMap.put(clazz, def);
+ if (Iciql.class.isAssignableFrom(clazz)) {
+ T t = instance(clazz);
+ Iciql table = (Iciql) t;
+ Define.define(def, table);
+ } else if (clazz.isAnnotationPresent(IQTable.class)) {
+ // annotated classes skip the Define().define() static
+ // initializer
+ T t = instance(clazz);
+ def.mapObject(t);
+ } else if (clazz.isAnnotationPresent(IQView.class)) {
+ // annotated classes skip the Define().define() static
+ // initializer
+ T t = instance(clazz);
+ def.mapObject(t);
+ }
+ }
+ return def;
+ }
+
+ <T> boolean hasCreated(Class<T> clazz) {
+ return upgradeChecked.contains(clazz);
+ }
+
+ public synchronized void setDbUpgrader(DbUpgrader upgrader) {
+ if (!upgrader.getClass().isAnnotationPresent(IQVersion.class)) {
+ throw new IciqlException("DbUpgrader must be annotated with " + IQVersion.class.getSimpleName());
+ }
+ this.dbUpgrader = upgrader;
+ upgradeChecked.clear();
+ }
+
+ public SQLDialect getDialect() {
+ return dialect;
+ }
+
+ public Connection getConnection() {
+ return conn;
+ }
+
+ public void close() {
+ try {
+ conn.close();
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public <A> TestCondition<A> test(A x) {
+ return new TestCondition<A>(x);
+ }
+
+ public <T> void insertAll(List<T> list) {
+ if (list.size() == 0) {
+ return;
+ }
+ Savepoint savepoint = null;
+ try {
+ Class<?> clazz = list.get(0).getClass();
+ TableDefinition<?> def = define(clazz).createIfRequired(this);
+ savepoint = prepareSavepoint();
+ for (T t : list) {
+ PreparedStatement ps = def.createInsertStatement(this, t, false);
+ int rc = ps.executeUpdate();
+ if (rc == 0) {
+ throw new IciqlException("Failed to insert {0}. Affected rowcount == 0.", t);
+ }
+ }
+ commit(savepoint);
+ } catch (SQLException e) {
+ rollback(savepoint);
+ throw new IciqlException(e);
+ } catch (IciqlException e) {
+ rollback(savepoint);
+ throw e;
+ }
+ }
+
+ public <T> List<Long> insertAllAndGetKeys(List<T> list) {
+ List<Long> identities = new ArrayList<Long>();
+ if (list.size() == 0) {
+ return identities;
+ }
+ Savepoint savepoint = null;
+ try {
+ Class<?> clazz = list.get(0).getClass();
+ TableDefinition<?> def = define(clazz).createIfRequired(this);
+ savepoint = prepareSavepoint();
+ for (T t : list) {
+ long key = def.insert(this, t, true);
+ identities.add(key);
+ }
+ commit(savepoint);
+ } catch (IciqlException e) {
+ rollback(savepoint);
+ throw e;
+ }
+ return identities;
+ }
+
+ public <T> void updateAll(List<T> list) {
+ if (list.size() == 0) {
+ return;
+ }
+ Savepoint savepoint = null;
+ try {
+ Class<?> clazz = list.get(0).getClass();
+ TableDefinition<?> def = define(clazz).createIfRequired(this);
+ savepoint = prepareSavepoint();
+ for (T t : list) {
+ def.update(this, t);
+ }
+ commit(savepoint);
+ } catch (IciqlException e) {
+ rollback(savepoint);
+ throw e;
+ }
+ }
+
+ public <T> void deleteAll(List<T> list) {
+ if (list.size() == 0) {
+ return;
+ }
+ Savepoint savepoint = null;
+ try {
+ Class<?> clazz = list.get(0).getClass();
+ TableDefinition<?> def = define(clazz).createIfRequired(this);
+ savepoint = prepareSavepoint();
+ for (T t : list) {
+ def.delete(this, t);
+ }
+ commit(savepoint);
+ } catch (IciqlException e) {
+ rollback(savepoint);
+ throw e;
+ }
+ }
+
+ PreparedStatement prepare(String sql, boolean returnGeneratedKeys) {
+ IciqlException.checkUnmappedField(sql);
+ try {
+ if (returnGeneratedKeys) {
+ return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
+ }
+ return conn.prepareStatement(sql);
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(sql, e);
+ }
+ }
+
+ Savepoint prepareSavepoint() {
+ // don't change auto-commit mode.
+ // don't create save point.
+ if (!autoSavePoint) {
+ return null;
+ }
+ // create a savepoint
+ Savepoint savepoint = null;
+ try {
+ conn.setAutoCommit(false);
+ savepoint = conn.setSavepoint();
+ } catch (SQLFeatureNotSupportedException e) {
+ // jdbc driver does not support save points
+ } catch (SQLException e) {
+ throw new IciqlException(e, "Could not create save point");
+ }
+ return savepoint;
+ }
+
+ void commit(Savepoint savepoint) {
+ if (savepoint != null) {
+ try {
+ conn.commit();
+ conn.setAutoCommit(true);
+ } catch (SQLException e) {
+ throw new IciqlException(e, "Failed to commit pending transactions");
+ }
+ }
+ }
+
+ void rollback(Savepoint savepoint) {
+ if (savepoint != null) {
+ try {
+ conn.rollback(savepoint);
+ conn.setAutoCommit(true);
+ } catch (SQLException s) {
+ throw new IciqlException(s, "Failed to rollback transactions");
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ <T> TableDefinition<T> getTableDefinition(Class<T> clazz) {
+ return (TableDefinition<T>) classMap.get(clazz);
+ }
+
+ /**
+ * Run a SQL query directly against the database.
+ *
+ * Be sure to close the ResultSet with
+ *
+ * <pre>
+ * JdbcUtils.closeSilently(rs, true);
+ * </pre>
+ *
+ * @param sql
+ * the SQL statement
+ * @param args
+ * optional object arguments for x=? tokens in query
+ * @return the result set
+ */
+ public ResultSet executeQuery(String sql, List<?> args) {
+ return executeQuery(sql, args.toArray());
+ }
+
+ /**
+ * Run a SQL query directly against the database.
+ *
+ * Be sure to close the ResultSet with
+ *
+ * <pre>
+ * JdbcUtils.closeSilently(rs, true);
+ * </pre>
+ *
+ * @param sql
+ * the SQL statement
+ * @param args
+ * optional object arguments for x=? tokens in query
+ * @return the result set
+ */
+ public ResultSet executeQuery(String sql, Object... args) {
+ try {
+ if (args.length == 0) {
+ return conn.createStatement().executeQuery(sql);
+ } else {
+ PreparedStatement stat = conn.prepareStatement(sql);
+ int i = 1;
+ for (Object arg : args) {
+ stat.setObject(i++, arg);
+ }
+ return stat.executeQuery();
+ }
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ /**
+ * Run a SQL query directly against the database and map the results to the
+ * model class.
+ *
+ * @param modelClass
+ * the model class to bind the query ResultSet rows into.
+ * @param sql
+ * the SQL statement
+ * @return the result set
+ */
+ public <T> List<T> executeQuery(Class<? extends T> modelClass, String sql, List<?> args) {
+ return executeQuery(modelClass, sql, args.toArray());
+ }
+
+ /**
+ * Run a SQL query directly against the database and map the results to the
+ * model class.
+ *
+ * @param modelClass
+ * the model class to bind the query ResultSet rows into.
+ * @param sql
+ * the SQL statement
+ * @return the result set
+ */
+ public <T> List<T> executeQuery(Class<? extends T> modelClass, String sql, Object... args) {
+ ResultSet rs = null;
+ try {
+ if (args.length == 0) {
+ rs = conn.createStatement().executeQuery(sql);
+ } else {
+ PreparedStatement stat = conn.prepareStatement(sql);
+ int i = 1;
+ for (Object arg : args) {
+ stat.setObject(i++, arg);
+ }
+ rs = stat.executeQuery();
+ }
+ boolean wildcardSelect = sql.toLowerCase().startsWith("select *")
+ || sql.toLowerCase().startsWith("select distinct *");
+ return buildObjects(modelClass, wildcardSelect, rs);
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ } finally {
+ JdbcUtils.closeSilently(rs, true);
+ }
+ }
+
+ /**
+ * Run a SQL statement directly against the database.
+ *
+ * @param sql
+ * the SQL statement
+ * @return the update count
+ */
+ public int executeUpdate(String sql, Object... args) {
+ Statement stat = null;
+ try {
+ int updateCount;
+ if (args.length == 0) {
+ stat = conn.createStatement();
+ updateCount = stat.executeUpdate(sql);
+ } else {
+ PreparedStatement ps = conn.prepareStatement(sql);
+ int i = 1;
+ for (Object arg : args) {
+ ps.setObject(i++, arg);
+ }
+ updateCount = ps.executeUpdate();
+ stat = ps;
+ }
+ return updateCount;
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ } finally {
+ JdbcUtils.closeSilently(stat);
+ }
+ }
+
+ /**
+ * Allow to enable/disable globally createIfRequired in TableDefinition.
+ * For advanced user wanting to gain full control of transactions.
+ * Default value is false.
+ * @param skipCreate
+ */
+ public void setSkipCreate(boolean skipCreate) {
+ this.skipCreate = skipCreate;
+ }
+
+ public boolean getSkipCreate() {
+ return this.skipCreate;
+ }
+
+ /**
+ * Allow to enable/disable usage of save point.
+ * For advanced user wanting to gain full control of transactions.
+ * Default value is false.
+ * @param autoSavePoint
+ */
+ public void setAutoSavePoint(boolean autoSavePoint) {
+ this.autoSavePoint = autoSavePoint;
+ }
+
+ public boolean getAutoSavePoint() {
+ return this.autoSavePoint;
+ }
+
+}
diff --git a/src/main/java/com/iciql/DbInspector.java b/src/main/java/com/iciql/DbInspector.java
new file mode 100644
index 0000000..acaceea
--- /dev/null
+++ b/src/main/java/com/iciql/DbInspector.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.iciql.Iciql.IQTable;
+import com.iciql.util.JdbcUtils;
+import com.iciql.util.StringUtils;
+import com.iciql.util.Utils;
+
+/**
+ * Class to inspect a model and a database for the purposes of model validation
+ * and automatic model generation. This class finds the available schemas and
+ * tables and serves as the entry point for model generation and validation.
+ */
+public class DbInspector {
+
+ private Db db;
+ private DatabaseMetaData metaData;
+ private Class<? extends java.util.Date> dateTimeClass = java.util.Date.class;
+
+ public DbInspector(Db db) {
+ this.db = db;
+ setPreferredDateTimeClass(db.getDialect().getDateTimeClass());
+ }
+
+ /**
+ * Set the preferred class to store date and time. Possible values are:
+ * java.util.Date (default) and java.sql.Timestamp.
+ *
+ * @param dateTimeClass
+ * the new class
+ */
+ public void setPreferredDateTimeClass(Class<? extends java.util.Date> dateTimeClass) {
+ this.dateTimeClass = dateTimeClass;
+ }
+
+ /**
+ * Generates models class skeletons for schemas and tables. If the table
+ * name is undefined, models will be generated for every table within the
+ * specified schema. Additionally, if no schema is defined, models will be
+ * generated for all schemas and all tables.
+ *
+ * @param schema
+ * the schema name (optional)
+ * @param table
+ * the table name (optional)
+ * @param packageName
+ * the package name (optional)
+ * @param annotateSchema
+ * (includes schema name in annotation)
+ * @param trimStrings
+ * (trims strings to maxLength of column)
+ * @return a list of complete model classes as strings, each element a class
+ */
+ public List<String> generateModel(String schema, String table, String packageName,
+ boolean annotateSchema, boolean trimStrings) {
+ try {
+ List<String> models = Utils.newArrayList();
+ List<TableInspector> tables = getTables(schema, table);
+ for (TableInspector t : tables) {
+ t.read(metaData);
+ String model = t.generateModel(packageName, annotateSchema, trimStrings);
+ models.add(model);
+ }
+ return models;
+ } catch (SQLException s) {
+ throw new IciqlException(s);
+ }
+ }
+
+ /**
+ * Validates a model.
+ *
+ * @param model
+ * an instance of the model class
+ * @param throwOnError
+ * if errors should cause validation to fail
+ * @return a list of validation remarks
+ */
+ public <T> List<ValidationRemark> validateModel(T model, boolean throwOnError) {
+ try {
+ TableInspector inspector = getTable(model);
+ inspector.read(metaData);
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) model.getClass();
+ TableDefinition<T> def = db.define(clazz);
+ return inspector.validate(def, throwOnError);
+ } catch (SQLException s) {
+ throw new IciqlException(s);
+ }
+ }
+
+ private DatabaseMetaData getMetaData() throws SQLException {
+ if (metaData == null) {
+ metaData = db.getConnection().getMetaData();
+ }
+ return metaData;
+ }
+
+ /**
+ * Get the table in the database based on the model definition.
+ *
+ * @param model
+ * an instance of the model class
+ * @return the table inspector
+ */
+ private <T> TableInspector getTable(T model) throws SQLException {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) model.getClass();
+ TableDefinition<T> def = db.define(clazz);
+ boolean forceUpperCase = getMetaData().storesUpperCaseIdentifiers();
+ String schema = (forceUpperCase && def.schemaName != null) ? def.schemaName.toUpperCase()
+ : def.schemaName;
+ String table = forceUpperCase ? def.tableName.toUpperCase() : def.tableName;
+ List<TableInspector> tables = getTables(schema, table);
+ return tables.get(0);
+ }
+
+ /**
+ * Returns a list of tables. This method always returns at least one
+ * element. If no table is found, an exception is thrown.
+ *
+ * @param schema
+ * the schema name
+ * @param table
+ * the table name
+ * @return a list of table inspectors (always contains at least one element)
+ */
+ private List<TableInspector> getTables(String schema, String table) throws SQLException {
+ ResultSet rs = null;
+ try {
+ rs = getMetaData().getSchemas();
+ ArrayList<String> schemaList = Utils.newArrayList();
+ while (rs.next()) {
+ schemaList.add(rs.getString("TABLE_SCHEM"));
+ }
+ JdbcUtils.closeSilently(rs);
+
+ String iciqlTables = DbVersion.class.getAnnotation(IQTable.class).name();
+
+ List<TableInspector> tables = Utils.newArrayList();
+ if (schemaList.size() == 0) {
+ schemaList.add(null);
+ }
+ for (String s : schemaList) {
+ rs = getMetaData().getTables(null, s, null, new String[] { "TABLE" });
+ while (rs.next()) {
+ String t = rs.getString("TABLE_NAME");
+ if (t.charAt(0) == '"') {
+ t = t.substring(1);
+ }
+ if (t.charAt(t.length() - 1) == '"') {
+ t = t.substring(0, t.length() - 1);
+ }
+ if (!t.equalsIgnoreCase(iciqlTables)) {
+ tables.add(new TableInspector(s, t, dateTimeClass));
+ }
+ }
+ }
+
+ if (StringUtils.isNullOrEmpty(schema) && StringUtils.isNullOrEmpty(table)) {
+ // all schemas and tables
+ return tables;
+ }
+ // schema subset OR table subset OR exact match
+ List<TableInspector> matches = Utils.newArrayList();
+ for (TableInspector t : tables) {
+ if (t.matches(schema, table)) {
+ matches.add(t);
+ }
+ }
+ if (matches.size() == 0) {
+ throw new IciqlException(MessageFormat.format("Failed to find schema={0} table={1}",
+ schema == null ? "" : schema, table == null ? "" : table));
+ }
+ return matches;
+ } finally {
+ JdbcUtils.closeSilently(rs);
+ }
+ }
+
+}
diff --git a/src/main/java/com/iciql/DbUpgrader.java b/src/main/java/com/iciql/DbUpgrader.java
new file mode 100644
index 0000000..1303f4e
--- /dev/null
+++ b/src/main/java/com/iciql/DbUpgrader.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.Iciql.IQVersion;
+
+/**
+ * Interface which defines a class to handle table changes based on model
+ * versions. An implementation of <i>DbUpgrader</i> must be annotated with the
+ * <i>IQDatabase</i> annotation, which defines the expected database version
+ * number.
+ */
+public interface DbUpgrader {
+
+ /**
+ * Defines method interface to handle database upgrades. This method is only
+ * called if your <i>DbUpgrader</i> implementation is annotated with
+ * IQDatabase.
+ *
+ * @param db
+ * the database
+ * @param fromVersion
+ * the old version
+ * @param toVersion
+ * the new version
+ * @return true for successful upgrade. If the upgrade is successful, the
+ * version registry is automatically updated.
+ */
+ boolean upgradeDatabase(Db db, int fromVersion, int toVersion);
+
+ /**
+ * Defines method interface to handle table upgrades.
+ *
+ * @param db
+ * the database
+ * @param schema
+ * the schema
+ * @param table
+ * the table
+ * @param fromVersion
+ * the old version
+ * @param toVersion
+ * the new version
+ * @return true for successful upgrade. If the upgrade is successful, the
+ * version registry is automatically updated.
+ */
+ boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion);
+
+ /**
+ * The default database upgrader. It throws runtime exception instead of
+ * handling upgrade requests.
+ */
+ @IQVersion(0)
+ public static class DefaultDbUpgrader implements DbUpgrader {
+
+ public boolean upgradeDatabase(Db db, int fromVersion, int toVersion) {
+ throw new IciqlException("Please provide your own DbUpgrader implementation.");
+ }
+
+ public boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion) {
+ throw new IciqlException("Please provide your own DbUpgrader implementation.");
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/iciql/DbVersion.java b/src/main/java/com/iciql/DbVersion.java
new file mode 100644
index 0000000..6270e14
--- /dev/null
+++ b/src/main/java/com/iciql/DbVersion.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.Iciql.IQColumn;
+import com.iciql.Iciql.IQTable;
+
+/**
+ * A system table to track database and table versions.
+ */
+@IQTable(name = "iq_versions", primaryKey = { "schemaName", "tableName" }, memoryTable = true)
+public class DbVersion {
+
+ @IQColumn(length = 255)
+ String schemaName = "";
+
+ @IQColumn(length = 255)
+ String tableName = "";
+
+ @IQColumn
+ Integer version;
+
+ public DbVersion() {
+ // nothing to do
+ }
+
+ /**
+ * Constructor for defining a version entry. Both the schema and the table
+ * are empty strings, which means this is the row for the 'database'.
+ *
+ * @param version
+ * the database version
+ */
+ public DbVersion(int version) {
+ this.schemaName = "";
+ this.tableName = "";
+ this.version = version;
+ }
+
+}
diff --git a/src/main/java/com/iciql/Define.java b/src/main/java/com/iciql/Define.java
new file mode 100644
index 0000000..1810a4b
--- /dev/null
+++ b/src/main/java/com/iciql/Define.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.Iciql.IndexType;
+
+/**
+ * This class provides utility methods to define primary keys, indexes, and set
+ * the name of the table.
+ */
+
+public class Define {
+
+ private static TableDefinition<?> currentTableDefinition;
+ private static Iciql currentTable;
+
+ public static void skipCreate() {
+ checkInDefine();
+ currentTableDefinition.defineSkipCreate();
+ }
+
+ public static void index(IndexType type, Object... columns) {
+ checkInDefine();
+ currentTableDefinition.defineIndex(null, type, columns);
+ }
+
+ public static void index(String name, IndexType type, Object... columns) {
+ checkInDefine();
+ currentTableDefinition.defineIndex(name, type, columns);
+ }
+
+ public static void constraintUnique(String name, Object... columns) {
+ checkInDefine();
+ currentTableDefinition.defineConstraintUnique(name, columns);
+ }
+
+ /*
+ * The variable argument type Object can't be used twice :-)
+ */
+// public static void constraintForeignKey(String name, String refTableName,
+// ConstraintDeleteType deleteType, ConstraintUpdateType updateType,
+// ConstraintDeferrabilityType deferrabilityType, Object... columns, Object... refColumns) {
+// checkInDefine();
+// currentTableDefinition.defineForeignKey(name, columns, refTableName, Columns, deleteType, updateType, deferrabilityType);
+// }
+
+ public static void primaryKey(Object... columns) {
+ checkInDefine();
+ currentTableDefinition.definePrimaryKey(columns);
+ }
+
+ public static void schemaName(String schemaName) {
+ checkInDefine();
+ currentTableDefinition.defineSchemaName(schemaName);
+ }
+
+ public static void tableName(String tableName) {
+ checkInDefine();
+ currentTableDefinition.defineTableName(tableName);
+ }
+
+ public static void viewTableName(String viewTableName) {
+ checkInDefine();
+ currentTableDefinition.defineViewTableName(viewTableName);
+ }
+
+ public static void memoryTable() {
+ checkInDefine();
+ currentTableDefinition.defineMemoryTable();
+ }
+
+ public static void columnName(Object column, String columnName) {
+ checkInDefine();
+ currentTableDefinition.defineColumnName(column, columnName);
+ }
+
+ public static void autoIncrement(Object column) {
+ checkInDefine();
+ currentTableDefinition.defineAutoIncrement(column);
+ }
+
+ public static void length(Object column, int length) {
+ checkInDefine();
+ currentTableDefinition.defineLength(column, length);
+ }
+
+ public static void scale(Object column, int scale) {
+ checkInDefine();
+ currentTableDefinition.defineScale(column, scale);
+ }
+
+ public static void trim(Object column) {
+ checkInDefine();
+ currentTableDefinition.defineTrim(column);
+ }
+
+ public static void nullable(Object column, boolean isNullable) {
+ checkInDefine();
+ currentTableDefinition.defineNullable(column, isNullable);
+ }
+
+ public static void defaultValue(Object column, String defaultValue) {
+ checkInDefine();
+ currentTableDefinition.defineDefaultValue(column, defaultValue);
+ }
+
+ public static void constraint(Object column, String constraint) {
+ checkInDefine();
+ currentTableDefinition.defineConstraint(column, constraint);
+ }
+
+ static synchronized <T> void define(TableDefinition<T> tableDefinition, Iciql table) {
+ currentTableDefinition = tableDefinition;
+ currentTable = table;
+ tableDefinition.mapObject(table);
+ table.defineIQ();
+ currentTable = null;
+ currentTableDefinition = null;
+ }
+
+ private static void checkInDefine() {
+ if (currentTable == null) {
+ throw new IciqlException("This method may only be called "
+ + "from within the define() method, and the define() method "
+ + "is called by the framework.");
+ }
+ }
+
+}
diff --git a/src/main/java/com/iciql/Filter.java b/src/main/java/com/iciql/Filter.java
new file mode 100644
index 0000000..99dbdc3
--- /dev/null
+++ b/src/main/java/com/iciql/Filter.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * Represents the WHERE clause of a query.
+ */
+public interface Filter {
+ boolean where();
+}
diff --git a/src/main/java/com/iciql/Function.java b/src/main/java/com/iciql/Function.java
new file mode 100644
index 0000000..3faddb7
--- /dev/null
+++ b/src/main/java/com/iciql/Function.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.util.Utils;
+
+/**
+ * This class provides static methods that represents common SQL functions.
+ */
+public class Function implements Token {
+
+ // must be a new instance
+ private static final Long COUNT_STAR = new Long(0);
+
+ protected Object[] x;
+ private String name;
+
+ protected Function(String name, Object... x) {
+ this.name = name;
+ this.x = x;
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL(name).appendSQL("(");
+ int i = 0;
+ for (Object o : x) {
+ if (i++ > 0) {
+ stat.appendSQL(",");
+ }
+ query.appendSQL(stat, null, o);
+ }
+ stat.appendSQL(")");
+ }
+
+ public static Long count() {
+ return COUNT_STAR;
+ }
+
+ public static Integer length(Object x) {
+ return Db.registerToken(Utils.newObject(Integer.class), new Function("LENGTH", x));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T extends Number> T sum(T x) {
+ return (T) Db.registerToken(Utils.newObject(x.getClass()), new Function("SUM", x));
+ }
+
+ public static Long count(Object x) {
+ return Db.registerToken(Utils.newObject(Long.class), new Function("COUNT", x));
+ }
+
+ public static Boolean isNull(Object x) {
+ return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ query.appendSQL(stat, null, x[0]);
+ stat.appendSQL(" IS NULL");
+ }
+ });
+ }
+
+ public static Boolean isNotNull(Object x) {
+ return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ query.appendSQL(stat, null, x[0]);
+ stat.appendSQL(" IS NOT NULL");
+ }
+ });
+ }
+
+ public static Boolean not(Boolean x) {
+ return Db.registerToken(Utils.newObject(Boolean.class), new Function("", x) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL("NOT ");
+ query.appendSQL(stat, null, x[0]);
+ }
+ });
+ }
+
+ public static Boolean or(Boolean... x) {
+ return Db.registerToken(Utils.newObject(Boolean.class), new Function("", (Object[]) x) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ int i = 0;
+ for (Object o : x) {
+ if (i++ > 0) {
+ stat.appendSQL(" OR ");
+ }
+ query.appendSQL(stat, null, o);
+ }
+ }
+ });
+ }
+
+ public static Boolean and(Boolean... x) {
+ return Db.registerToken(Utils.newObject(Boolean.class), new Function("", (Object[]) x) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ int i = 0;
+ for (Object o : x) {
+ if (i++ > 0) {
+ stat.appendSQL(" AND ");
+ }
+ query.appendSQL(stat, null, o);
+ }
+ }
+ });
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <X> X min(X x) {
+ Class<X> clazz = (Class<X>) x.getClass();
+ X o = Utils.newObject(clazz);
+ return Db.registerToken(o, new Function("MIN", x));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <X> X max(X x) {
+ Class<X> clazz = (Class<X>) x.getClass();
+ X o = Utils.newObject(clazz);
+ return Db.registerToken(o, new Function("MAX", x));
+ }
+
+ public static Boolean like(String x, String pattern) {
+ Boolean o = Utils.newObject(Boolean.class);
+ return Db.registerToken(o, new Function("LIKE", x, pattern) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL("(");
+ query.appendSQL(stat, null, x[0]);
+ stat.appendSQL(" LIKE ");
+ query.appendSQL(stat, x[0], x[1]);
+ stat.appendSQL(")");
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/com/iciql/Iciql.java b/src/main/java/com/iciql/Iciql.java
new file mode 100644
index 0000000..9f73ffa
--- /dev/null
+++ b/src/main/java/com/iciql/Iciql.java
@@ -0,0 +1,731 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A class that implements this interface can be used as a database table.
+ * <p>
+ * You may implement the Table interface on your model object and optionally use
+ * IQColumn annotations (which imposes a compile-time and runtime-dependency on
+ * iciql), or may choose to use the IQTable and IQColumn annotations only (which
+ * imposes a compile-time and runtime-dependency on this file only).
+ * <p>
+ * If a class is annotated with IQTable and at the same time implements Table,
+ * the define() method is not called.
+ * <p>
+ * Fully Supported Data Types:
+ * <table>
+ * <tr>
+ * <th colspan="2">All Databases</th>
+ * </tr>
+ * <tr>
+ * <td>java.lang.String</td>
+ * <td>VARCHAR (length > 0) or CLOB (length == 0)</td>
+ * </tr>
+ * <tr>
+ * <td>java.lang.Boolean</td>
+ * <td>BIT</td>
+ * </tr>
+ * <tr>
+ * <td>java.lang.Byte</td>
+ * <td>TINYINT</td>
+ * </tr>
+ * <tr>
+ * <td>java.lang.Short</td>
+ * <td>SMALLINT</td>
+ * </tr>
+ * <tr>
+ * <td>java.lang.Integer</td>
+ * <td>INT</td>
+ * </tr>
+ * <tr>
+ * <td>java.lang.Long</td>
+ * <td>BIGINT</td>
+ * </tr>
+ * <tr>
+ * <td>java.lang.Float</td>
+ * <td>REAL</td>
+ * </tr>
+ * <tr>
+ * <td>java.lang.Double</td>
+ * <td>DOUBLE</td>
+ * </tr>
+ * <tr>
+ * <td>java.math.BigDecimal</td>
+ * <td>DECIMAL (length == 0)<br/>
+ * DECIMAL(length, scale) (length > 0)</td>
+ * </tr>
+ * <tr>
+ * <td>java.sql.Date</td>
+ * <td>DATE</td>
+ * </tr>
+ * <tr>
+ * <td>java.sql.Time</td>
+ * <td>TIME</td>
+ * </tr>
+ * <tr>
+ * <td>java.sql.Timestamp</td>
+ * <td>TIMESTAMP</td>
+ * </tr>
+ * <tr>
+ * <td>java.util.Date</td>
+ * <td>TIMESTAMP</td>
+ * </tr>
+ * <tr>
+ * <td>java.lang.Enum.name()</td>
+ * <td>VARCHAR (length > 0) or CLOB (length == 0)<br/>
+ * EnumType.NAME</td>
+ * </tr>
+ * <tr>
+ * <td>java.lang.Enum.ordinal()</td>
+ * <td>INT<br/>
+ * EnumType.ORDINAL</td>
+ * </tr>
+ * <tr>
+ * <td>java.lang.Enum implements<br/>
+ * com.iciql.Iciql.EnumID.enumId()</td>
+ * <td>INT<br/>
+ * EnumType.ENUMID</td>
+ * </tr>
+ * <tr>
+ * <th colspan="2">H2 Databases</th>
+ * </tr>
+ * <tr>
+ * <td>java.util.UUID</td>
+ * <td>UUID</td>
+ * </tr>
+ * </table>
+ * <p>
+ * Partially Supported Data Types:
+ * <p>
+ * The following data types can be mapped to columns for all general statements
+ * BUT these field types may not be used to specify compile-time clauses or
+ * constraints.
+ * <table>
+ * <tr>
+ * <td>byte []</td>
+ * <td>BLOB</td>
+ * </tr>
+ * <tr>
+ * <td>boolean</td>
+ * <td>BIT</td>
+ * </tr>
+ * <tr>
+ * <td>byte</td>
+ * <td>TINYINT</td>
+ * </tr>
+ * <tr>
+ * <td>short</td>
+ * <td>SMALLINT</td>
+ * </tr>
+ * <tr>
+ * <td>int</td>
+ * <td>INT</td>
+ * </tr>
+ * <tr>
+ * <td>long</td>
+ * <td>BIGINT</td>
+ * </tr>
+ * <tr>
+ * <td>float</td>
+ * <td>REAL</td>
+ * </tr>
+ * <tr>
+ * <td>double</td>
+ * <td>DOUBLE</td>
+ * </tr>
+ * </table>
+ * <p>
+ * Table and field mapping: by default, the mapped table name is the class name
+ * and the public fields are reflectively mapped, by their name, to columns. As
+ * an alternative, you may specify both the table and column definition by
+ * annotations.
+ * <p>
+ * Table Interface: you may set additional parameters such as table name,
+ * primary key, and indexes in the define() method.
+ * <p>
+ * Annotations: you may use the annotations with or without implementing the
+ * Table interface. The annotations allow you to decouple your model completely
+ * from iciql other than this file.
+ * <p>
+ * Automatic model generation: you may automatically generate model classes as
+ * strings with the Db and DbInspector objects:
+ *
+ * <pre>
+ * Db db = Db.open(&quot;jdbc:h2:mem:&quot;, &quot;sa&quot;, &quot;sa&quot;);
+ * DbInspector inspector = new DbInspector(db);
+ * List&lt;String&gt; models =
+ * inspector.generateModel(schema, table, packageName,
+ * annotateSchema, trimStrings)
+ * </pre>
+ *
+ * Or you may use the GenerateModels tool to generate and save your classes to
+ * the file system:
+ *
+ * <pre>
+ * java -jar iciql.jar
+ * -url &quot;jdbc:h2:mem:&quot;
+ * -user sa -password sa -schema schemaName -table tableName
+ * -package packageName -folder destination
+ * -annotateSchema false -trimStrings true
+ * </pre>
+ *
+ * Model validation: you may validate your model class with DbInspector object.
+ * The DbInspector will report errors, warnings, and suggestions:
+ *
+ * <pre>
+ * Db db = Db.open(&quot;jdbc:h2:mem:&quot;, &quot;sa&quot;, &quot;sa&quot;);
+ * DbInspector inspector = new DbInspector(db);
+ * List&lt;Validation&gt; remarks = inspector.validateModel(new MyModel(), throwOnError);
+ * for (Validation remark : remarks) {
+ * System.out.println(remark);
+ * }
+ * </pre>
+ */
+public interface Iciql {
+
+ /**
+ * An annotation for an iciql version.
+ * <p>
+ *
+ * @IQVersion(1)
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQVersion {
+
+ /**
+ * If set to a non-zero value, iciql maintains a "iq_versions" table
+ * within your database. The version number is used to call to a
+ * registered DbUpgrader implementation to perform relevant ALTER
+ * statements. Default: 0. You must specify a DbUpgrader on your Db
+ * object to use this parameter.
+ */
+ int value() default 0;
+
+ }
+
+ /**
+ * An annotation for a schema.
+ * <p>
+ *
+ * @IQSchema("PUBLIC")
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQSchema {
+
+ /**
+ * The schema may be optionally specified. Default: unspecified.
+ */
+ String value() default "";
+
+ }
+
+ /**
+ * Enumeration defining the four index types.
+ */
+ public static enum IndexType {
+ STANDARD, UNIQUE, HASH, UNIQUE_HASH;
+ }
+
+ /**
+ * An index annotation.
+ * <p>
+ * <ul>
+ * <li>@IQIndex("name")
+ * <li>@IQIndex({"street", "city"})
+ * <li>@IQIndex(name="streetidx", value={"street", "city"})
+ * <li>@IQIndex(name="addressidx", type=IndexType.UNIQUE,
+ * value={"house_number", "street", "city"})
+ * </ul>
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQIndex {
+
+ /**
+ * Index name. If null or empty, iciql will generate one.
+ */
+ String name() default "";
+
+ /**
+ * Type of the index.
+ * <ul>
+ * <li>com.iciql.iciql.IndexType.STANDARD
+ * <li>com.iciql.iciql.IndexType.UNIQUE
+ * <li>com.iciql.iciql.IndexType.HASH
+ * <li>com.iciql.iciql.IndexType.UNIQUE_HASH
+ * </ul>
+ *
+ * HASH indexes may only be valid for single column indexes.
+ *
+ */
+ IndexType type() default IndexType.STANDARD;
+
+ /**
+ * Columns to include in index.
+ * <ul>
+ * <li>single column index: value = "id"
+ * <li>multiple column index: value = { "id", "name", "date" }
+ * </ul>
+ */
+ String[] value() default {};
+ }
+
+ /**
+ * Enumeration defining the ON DELETE actions.
+ */
+ public static enum ConstraintDeleteType {
+ UNSET, CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT;
+ }
+
+ /**
+ * Enumeration defining the ON UPDATE actions.
+ */
+ public static enum ConstraintUpdateType {
+ UNSET, CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT;
+ }
+
+ /**
+ * Enumeration defining the deferrability.
+ */
+ public static enum ConstraintDeferrabilityType {
+ UNSET, DEFERRABLE_INITIALLY_DEFERRED, DEFERRABLE_INITIALLY_IMMEDIATE, NOT_DEFERRABLE;
+ }
+
+ /**
+ * A foreign key constraint annotation.
+ * <p>
+ * <ul>
+ * <li>@IQContraintForeignKey(
+ * foreignColumns = { "idaccount"},
+ * referenceName = "account",
+ * referenceColumns = { "id" },
+ * deleteType = ConstrainDeleteType.CASCADE,
+ * updateType = ConstraintUpdateType.NO_ACTION )
+ * </ul>
+ * Note : reference columns should have a unique constraint defined in referenceName table,
+ * some database used to define a unique index instead of a unique constraint
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQContraintForeignKey {
+
+ /**
+ * Constraint name. If null or empty, iciql will generate one.
+ */
+ String name() default "";
+
+ /**
+ * Type of the action on delete, default to unspecified.
+ * <ul>
+ * <li>com.iciql.iciql.ConstrainDeleteType.CASCADE
+ * <li>com.iciql.iciql.ConstrainDeleteType.RESTRICT
+ * <li>com.iciql.iciql.ConstrainDeleteType.SET_NULL
+ * <li>com.iciql.iciql.ConstrainDeleteType.NO_ACTION
+ * <li>com.iciql.iciql.ConstrainDeleteType.SET_DEFAULT
+ * </ul>
+ */
+ ConstraintDeleteType deleteType() default ConstraintDeleteType.UNSET;
+
+ /**
+ * Type of the action on update, default to unspecified.
+ * <ul>
+ * <li>com.iciql.iciql.ConstrainUpdateType.CASCADE
+ * <li>com.iciql.iciql.ConstrainUpdateType.RESTRICT
+ * <li>com.iciql.iciql.ConstrainUpdateType.SET_NULL
+ * <li>com.iciql.iciql.ConstrainUpdateType.NO_ACTION
+ * <li>com.iciql.iciql.ConstrainUpdateType.SET_DEFAULT
+ * </ul>
+ */
+ ConstraintUpdateType updateType() default ConstraintUpdateType.UNSET;
+
+ /**
+ * Type of the deferrability mode, default to unspecified
+ * <ul>
+ * <li>com.iciql.iciql.ConstrainUpdateType.CASCADE
+ * <li>ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_DEFERRED
+ * <li>ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_IMMEDIATE
+ * <li>ConstraintDeferrabilityType.NOT_DEFERRABLE
+ * </ul>
+ */
+ ConstraintDeferrabilityType deferrabilityType() default ConstraintDeferrabilityType.UNSET;
+
+ /**
+ * The source table for the columns defined as foreign.
+ */
+ String tableName() default "";
+
+ /**
+ * Columns defined as 'foreign'.
+ * <ul>
+ * <li>single column : foreignColumns = "id"
+ * <li>multiple column : foreignColumns = { "id", "name", "date" }
+ * </ul>
+ */
+ String[] foreignColumns() default {};
+
+ /**
+ * The reference table for the columns defined as references.
+ */
+ String referenceName() default "";
+
+ /**
+ * Columns defined as 'references'.
+ * <ul>
+ * <li>single column : referenceColumns = "id"
+ * <li>multiple column : referenceColumns = { "id", "name", "date" }
+ * </ul>
+ */
+ String[] referenceColumns() default {};
+ }
+
+ /**
+ * Annotation to specify multiple foreign keys constraints.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQContraintsForeignKey {
+ IQContraintForeignKey[] value() default {};
+ }
+
+ /**
+ * A unique constraint annotation.
+ * <p>
+ * <ul>
+ * <li>@IQContraintUnique(uniqueColumns = { "street", "city" })
+ * <li>@IQContraintUnique(name="streetconstraint", uniqueColumns = { "street", "city" })
+ * </ul>
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQContraintUnique {
+
+ /**
+ * Constraint name. If null or empty, iciql will generate one.
+ */
+ String name() default "";
+
+ /**
+ * Columns defined as 'unique'.
+ * <ul>
+ * <li>single column : uniqueColumns = "id"
+ * <li>multiple column : uniqueColumns = { "id", "name", "date" }
+ * </ul>
+ */
+ String[] uniqueColumns() default {};
+
+ }
+
+ /**
+ * Annotation to specify multiple unique constraints.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQContraintsUnique {
+ IQContraintUnique[] value() default {};
+ }
+
+ /**
+ * Annotation to define a view.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQView {
+
+ /**
+ * The view name. If not specified the class name is used as the view
+ * name.
+ * <p>
+ * The view name may still be overridden in the define() method if the
+ * model class is not annotated with IQView. Default: unspecified.
+ */
+ String name() default "";
+
+ /**
+ * The source table for the view.
+ * <p>
+ * The view name may still be overridden in the define() method if the
+ * model class is not annotated with IQView. Default: unspecified.
+ */
+ String tableName() default "";
+
+ /**
+ * The inherit columns allows this model class to inherit columns from
+ * its super class. Any IQTable annotation present on the super class is
+ * ignored. Default: false.
+ */
+ boolean inheritColumns() default false;
+
+ /**
+ * Whether or not iciql tries to create the view. Default:
+ * true.
+ */
+ boolean create() default true;
+
+ /**
+ * If true, only fields that are explicitly annotated as IQColumn are
+ * mapped. Default: true.
+ */
+ boolean annotationsOnly() default true;
+ }
+
+ /**
+ * String snippet defining SQL constraints for a field. Use "this" as
+ * a placeholder for the column name. "this" will be substituted at
+ * runtime.
+ * <p>
+ * IQConstraint("this > 2 AND this <= 7")
+ * <p>
+ * This snippet may still be overridden in the define() method if the
+ * model class is not annotated with IQTable or IQView. Default: unspecified.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public @interface IQConstraint {
+
+ String value() default "";
+ }
+
+ /**
+ * Annotation to specify multiple indexes.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQIndexes {
+ IQIndex[] value() default {};
+ }
+
+ /**
+ * Annotation to define a table.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQTable {
+
+ /**
+ * The table name. If not specified the class name is used as the table
+ * name.
+ * <p>
+ * The table name may still be overridden in the define() method if the
+ * model class is not annotated with IQTable. Default: unspecified.
+ */
+ String name() default "";
+
+ /**
+ * The primary key may be optionally specified. If it is not specified,
+ * then no primary key is set by the IQTable annotation. You may specify
+ * a composite primary key.
+ * <ul>
+ * <li>single column primaryKey: value = "id"
+ * <li>compound primary key: value = { "id", "name" }
+ * </ul>
+ * The primary key may still be overridden in the define() method if the
+ * model class is not annotated with IQTable. Default: unspecified.
+ */
+ String[] primaryKey() default {};
+
+ /**
+ * The inherit columns allows this model class to inherit columns from
+ * its super class. Any IQTable annotation present on the super class is
+ * ignored. Default: false.
+ */
+ boolean inheritColumns() default false;
+
+ /**
+ * Whether or not iciql tries to create the table and indexes. Default:
+ * true.
+ */
+ boolean create() default true;
+
+ /**
+ * If true, only fields that are explicitly annotated as IQColumn are
+ * mapped. Default: true.
+ */
+ boolean annotationsOnly() default true;
+
+ /**
+ * If true, this table is created as a memory table where data is
+ * persistent, but index data is kept in main memory. Valid only for H2
+ * and HSQL databases. Default: false.
+ */
+ boolean memoryTable() default false;
+ }
+
+ /**
+ * Annotation to define a column. Annotated fields may have any scope
+ * (however, the JVM may raise a SecurityException if the SecurityManager
+ * doesn't allow iciql to access the field.)
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public @interface IQColumn {
+
+ /**
+ * If not specified, the field name is used as the column name. Default:
+ * the field name.
+ */
+ String name() default "";
+
+ /**
+ * This column is the primary key. Default: false.
+ */
+ boolean primaryKey() default false;
+
+ /**
+ * The column is created with a sequence as the default value. Default:
+ * false.
+ */
+ boolean autoIncrement() default false;
+
+ /**
+ * Length is used to define the length of a VARCHAR column or to define
+ * the precision of a DECIMAL(precision, scale) expression.
+ * <p>
+ * If larger than zero, it is used during the CREATE TABLE phase. For
+ * string values it may also be used to prevent database exceptions on
+ * INSERT and UPDATE statements (see trim).
+ * <p>
+ * Any length set in define() may override this annotation setting if
+ * the model class is not annotated with IQTable. Default: 0.
+ */
+ int length() default 0;
+
+ /**
+ * Scale is used during the CREATE TABLE phase to define the scale of a
+ * DECIMAL(precision, scale) expression.
+ * <p>
+ * Any scale set in define() may override this annotation setting if the
+ * model class is not annotated with IQTable. Default: 0.
+ */
+ int scale() default 0;
+
+ /**
+ * If true, iciql will automatically trim the string if it exceeds
+ * length (value.substring(0, length)). Default: false.
+ */
+ boolean trim() default false;
+
+ /**
+ * If false, iciql will set the column NOT NULL during the CREATE TABLE
+ * phase. Default: true.
+ */
+ boolean nullable() default true;
+
+ /**
+ * The default value assigned to the column during the CREATE TABLE
+ * phase. This field could contain a literal single-quoted value, or a
+ * function call. Empty strings are considered NULL. Examples:
+ * <ul>
+ * <li>defaultValue="" (null)
+ * <li>defaultValue="CURRENT_TIMESTAMP"
+ * <li>defaultValue="''" (empty string)
+ * <li>defaultValue="'0'"
+ * <li>defaultValue="'1970-01-01 00:00:01'"
+ * </ul>
+ * if the default value is specified, and auto increment is disabled,
+ * and primary key is disabled, then this value is included in the
+ * "DEFAULT ..." phrase of a column during the CREATE TABLE process.
+ * <p>
+ * Alternatively, you may specify a default object value on the field
+ * and this will be converted to a properly formatted DEFAULT expression
+ * during the CREATE TABLE process.
+ * <p>
+ * Default: unspecified (null).
+ */
+ String defaultValue() default "";
+
+ }
+
+ /**
+ * Interface for using the EnumType.ENUMID enumeration mapping strategy.
+ * <p>
+ * Enumerations wishing to use EnumType.ENUMID must implement this
+ * interface.
+ */
+ public interface EnumId {
+ int enumId();
+ }
+
+ /**
+ * Enumeration representing how to map a java.lang.Enum to a column.
+ * <p>
+ * <ul>
+ * <li>NAME - name() : string
+ * <li>ORDINAL - ordinal() : int
+ * <li>ENUMID - enumId() : int
+ * </ul>
+ *
+ * @see com.iciql.Iciql.EnumId interface
+ */
+ public enum EnumType {
+ NAME, ORDINAL, ENUMID;
+
+ public static final EnumType DEFAULT_TYPE = NAME;
+ }
+
+ /**
+ * Annotation to define how a java.lang.Enum is mapped to a column.
+ * <p>
+ * This annotation can be used on:
+ * <ul>
+ * <li>a field instance of an enumeration type
+ * <li>on the enumeration class declaration
+ * </ul>
+ * If you choose to annotate the class declaration, that will be the default
+ * mapping strategy for all @IQColumn instances of the enum. This can still
+ * be overridden for an individual field by specifying the IQEnum
+ * annotation.
+ * <p>
+ * The default mapping is by NAME.
+ *
+ * <pre>
+ * IQEnum(EnumType.NAME)
+ * </pre>
+ *
+ * A string mapping will generate either a VARCHAR, if IQColumn.length > 0
+ * or a TEXT column if IQColumn.length == 0
+ *
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ ElementType.FIELD, ElementType.TYPE })
+ public @interface IQEnum {
+ EnumType value() default EnumType.NAME;
+ }
+
+ /**
+ * Annotation to define an ignored field.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public @interface IQIgnore{
+ }
+
+ /**
+ * This method is called to let the table define the primary key, indexes,
+ * and the table name.
+ */
+ void defineIQ();
+}
diff --git a/src/main/java/com/iciql/IciqlException.java b/src/main/java/com/iciql/IciqlException.java
new file mode 100644
index 0000000..3f27b73
--- /dev/null
+++ b/src/main/java/com/iciql/IciqlException.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.sql.SQLException;
+import java.text.MessageFormat;
+import java.util.regex.Pattern;
+
+/**
+ * Iciql wraps all exceptions with this class.
+ */
+public class IciqlException extends RuntimeException {
+
+ public static final int CODE_UNMAPPED_FIELD = 1;
+ public static final int CODE_DUPLICATE_KEY = 2;
+ public static final int CODE_OBJECT_NOT_FOUND = 3;
+ public static final int CODE_OBJECT_ALREADY_EXISTS = 4;
+ public static final int CODE_CONSTRAINT_VIOLATION = 5;
+ public static final int CODE_UNCHARACTERIZED = 6;
+
+ private static final String TOKEN_UNMAPPED_FIELD = "\\? (=|\\>|\\<|\\<\\>|!=|\\>=|\\<=|LIKE|BETWEEN) \\?";
+
+ private static final long serialVersionUID = 1L;
+
+ private String sql;
+
+ private int iciqlCode;
+
+ public IciqlException(Throwable t) {
+ super(t.getMessage(), t);
+ configureCode(t);
+ }
+
+ public IciqlException(String message, Object... parameters) {
+ super(parameters.length > 0 ? MessageFormat.format(message, parameters) : message);
+ }
+
+ public IciqlException(Throwable t, String message, Object... parameters) {
+ super(parameters.length > 0 ? MessageFormat.format(message, parameters) : message, t);
+ configureCode(t);
+ }
+
+ public static void checkUnmappedField(String sql) {
+ if (Pattern.compile(IciqlException.TOKEN_UNMAPPED_FIELD).matcher(sql).find()) {
+ IciqlException e = new IciqlException("unmapped field in statement!");
+ e.sql = sql;
+ e.iciqlCode = CODE_UNMAPPED_FIELD;
+ throw e;
+ }
+ }
+
+ public static IciqlException fromSQL(String sql, Throwable t) {
+ if (Pattern.compile(TOKEN_UNMAPPED_FIELD).matcher(sql).find()) {
+ IciqlException e = new IciqlException(t, "unmapped field in statement!");
+ e.sql = sql;
+ e.iciqlCode = CODE_UNMAPPED_FIELD;
+ return e;
+ } else {
+ IciqlException e = new IciqlException(t, t.getMessage());
+ e.sql = sql;
+ return e;
+ }
+ }
+
+ public void setSQL(String sql) {
+ this.sql = sql;
+ }
+
+ public String getSQL() {
+ return sql;
+ }
+
+ public int getIciqlCode() {
+ return iciqlCode;
+ }
+
+ private void configureCode(Throwable t) {
+ if (t == null) {
+ return;
+ }
+ if (t instanceof SQLException) {
+ // http://developer.mimer.com/documentation/html_92/Mimer_SQL_Mobile_DocSet/App_Return_Codes2.html
+ SQLException s = (SQLException) t;
+ String state = s.getSQLState();
+ if ("23000".equals(state)) {
+ // MySQL duplicate primary key on insert
+ iciqlCode = CODE_DUPLICATE_KEY;
+ if (s.getErrorCode() == 1217) {
+ iciqlCode = CODE_CONSTRAINT_VIOLATION;
+ }
+ } else if ("23505".equals(state)) {
+ // Derby duplicate primary key on insert
+ iciqlCode = CODE_DUPLICATE_KEY;
+ } else if ("42000".equals(state)) {
+ // MySQL duplicate unique index value on insert
+ iciqlCode = CODE_DUPLICATE_KEY;
+ } else if ("42Y07".equals(state)) {
+ // Derby schema not found
+ iciqlCode = CODE_OBJECT_NOT_FOUND;
+ } else if ("42X05".equals(state)) {
+ // Derby table not found
+ iciqlCode = CODE_OBJECT_NOT_FOUND;
+ } else if ("42Y55".equals(state)) {
+ // Derby table not found
+ iciqlCode = CODE_OBJECT_NOT_FOUND;
+ } else if ("42S02".equals(state)) {
+ // H2 table not found
+ iciqlCode = CODE_OBJECT_NOT_FOUND;
+ } else if ("42501".equals(state)) {
+ // HSQL table not found
+ iciqlCode = CODE_OBJECT_NOT_FOUND;
+ } else if ("42P01".equals(state)) {
+ // PostgreSQL table not found
+ iciqlCode = CODE_OBJECT_NOT_FOUND;
+ } else if ("X0X05".equals(state)) {
+ // Derby view/table not found exists
+ iciqlCode = CODE_OBJECT_NOT_FOUND;
+ } else if ("X0Y32".equals(state)) {
+ // Derby table already exists
+ iciqlCode = CODE_OBJECT_ALREADY_EXISTS;
+ } else if ("42P07".equals(state)) {
+ // PostgreSQL table or index already exists
+ iciqlCode = CODE_OBJECT_ALREADY_EXISTS;
+ } else if ("42S01".equals(state)) {
+ // MySQL view already exists
+ iciqlCode = CODE_OBJECT_ALREADY_EXISTS;
+ } else if ("42S11".equals(state)) {
+ // H2 index already exists
+ iciqlCode = CODE_OBJECT_ALREADY_EXISTS;
+ } else if ("42504".equals(state)) {
+ // HSQL index already exists
+ iciqlCode = CODE_OBJECT_ALREADY_EXISTS;
+ } else if ("2BP01".equals(state)) {
+ // PostgreSQL constraint violation
+ iciqlCode = CODE_CONSTRAINT_VIOLATION;
+ } else if ("42533".equals(state)) {
+ // HSQL constraint violation
+ iciqlCode = CODE_CONSTRAINT_VIOLATION;
+ } else if ("X0Y25".equals(state)) {
+ // Derby constraint violation
+ iciqlCode = CODE_CONSTRAINT_VIOLATION;
+ } else {
+ // uncharacterized SQL code, we can always rely on iciqlCode != 0 in IciqlException
+ iciqlCode = s.getErrorCode() == 0 ? CODE_UNCHARACTERIZED : s.getErrorCode();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getName());
+ String message = getLocalizedMessage();
+ if (message != null) {
+ sb.append(": ").append(message);
+ }
+ if (sql != null) {
+ sb.append('\n').append(sql);
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/iciql/ModelUtils.java b/src/main/java/com/iciql/ModelUtils.java
new file mode 100644
index 0000000..56e6440
--- /dev/null
+++ b/src/main/java/com/iciql/ModelUtils.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import static com.iciql.util.StringUtils.isNullOrEmpty;
+
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import com.iciql.TableDefinition.FieldDefinition;
+import com.iciql.util.StringUtils;
+
+/**
+ * Utility methods for models related to type mapping, default value validation,
+ * and class or field name creation.
+ */
+class ModelUtils {
+
+ /**
+ * The list of supported data types. It is used by the runtime mapping for
+ * CREATE statements.
+ */
+ private static final Map<Class<?>, String> SUPPORTED_TYPES = new HashMap<Class<?>, String>();
+
+ static {
+ Map<Class<?>, String> m = SUPPORTED_TYPES;
+ m.put(String.class, "VARCHAR");
+ m.put(Boolean.class, "BOOLEAN");
+ m.put(Byte.class, "TINYINT");
+ m.put(Short.class, "SMALLINT");
+ m.put(Integer.class, "INT");
+ m.put(Long.class, "BIGINT");
+ m.put(Float.class, "REAL");
+ m.put(Double.class, "DOUBLE");
+ m.put(BigDecimal.class, "DECIMAL");
+ m.put(java.sql.Timestamp.class, "TIMESTAMP");
+ m.put(java.util.Date.class, "TIMESTAMP");
+ m.put(java.sql.Date.class, "DATE");
+ m.put(java.sql.Time.class, "TIME");
+ m.put(byte[].class, "BLOB");
+ m.put(UUID.class, "UUID");
+
+ // map primitives
+ m.put(boolean.class, m.get(Boolean.class));
+ m.put(byte.class, m.get(Byte.class));
+ m.put(short.class, m.get(Short.class));
+ m.put(int.class, m.get(Integer.class));
+ m.put(long.class, m.get(Long.class));
+ m.put(float.class, m.get(Float.class));
+ m.put(double.class, m.get(Double.class));
+ }
+
+ /**
+ * Convert SQL type aliases to the list of supported types. This map is used
+ * by generation and validation.
+ */
+ private static final Map<String, String> SQL_TYPES = new HashMap<String, String>();
+
+ static {
+ Map<String, String> m = SQL_TYPES;
+ m.put("CHAR", "VARCHAR");
+ m.put("CHARACTER", "VARCHAR");
+ m.put("NCHAR", "VARCHAR");
+ m.put("VARCHAR_CASESENSITIVE", "VARCHAR");
+ m.put("VARCHAR_IGNORECASE", "VARCHAR");
+ m.put("LONGVARCHAR", "VARCHAR");
+ m.put("VARCHAR2", "VARCHAR");
+ m.put("NVARCHAR", "VARCHAR");
+ m.put("NVARCHAR2", "VARCHAR");
+ m.put("TEXT", "VARCHAR");
+ m.put("NTEXT", "VARCHAR");
+ m.put("TINYTEXT", "VARCHAR");
+ m.put("MEDIUMTEXT", "VARCHAR");
+ m.put("LONGTEXT", "VARCHAR");
+ m.put("CLOB", "VARCHAR");
+ m.put("NCLOB", "VARCHAR");
+
+ // logic
+ m.put("BIT", "BOOLEAN");
+ m.put("BOOL", "BOOLEAN");
+
+ // numeric
+ m.put("BYTE", "TINYINT");
+ m.put("INT2", "SMALLINT");
+ m.put("YEAR", "SMALLINT");
+ m.put("INTEGER", "INT");
+ m.put("MEDIUMINT", "INT");
+ m.put("INT4", "INT");
+ m.put("SIGNED", "INT");
+ m.put("INT8", "BIGINT");
+ m.put("IDENTITY", "BIGINT");
+ m.put("SERIAL", "INT");
+ m.put("BIGSERIAL", "BIGINT");
+
+ // decimal
+ m.put("NUMBER", "DECIMAL");
+ m.put("DEC", "DECIMAL");
+ m.put("NUMERIC", "DECIMAL");
+ m.put("FLOAT", "DOUBLE");
+ m.put("FLOAT4", "DOUBLE");
+ m.put("FLOAT8", "DOUBLE");
+ m.put("DOUBLE PRECISION", "DOUBLE");
+
+ // date
+ m.put("DATETIME", "TIMESTAMP");
+ m.put("SMALLDATETIME", "TIMESTAMP");
+
+ // binary types
+ m.put("TINYBLOB", "BLOB");
+ m.put("MEDIUMBLOB", "BLOB");
+ m.put("LONGBLOB", "BLOB");
+ m.put("IMAGE", "BLOB");
+ m.put("OID", "BLOB");
+ }
+
+ private static final List<String> KEYWORDS = Arrays.asList("abstract", "assert", "boolean", "break",
+ "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else",
+ "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import",
+ "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected",
+ "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this",
+ "throw", "throws", "transient", "try", "void", "volatile", "while", "false", "null", "true");
+
+ /**
+ * Returns a SQL type mapping for a Java class.
+ *
+ * @param fieldDef
+ * the field to map
+ * @return
+ */
+ static String getDataType(FieldDefinition fieldDef) {
+ Class<?> fieldClass = fieldDef.field.getType();
+ if (fieldClass.isEnum()) {
+ switch (fieldDef.enumType) {
+ case ORDINAL:
+ case ENUMID:
+ return "INT";
+ case NAME:
+ default:
+ return "VARCHAR";
+ }
+ }
+ if (SUPPORTED_TYPES.containsKey(fieldClass)) {
+ return SUPPORTED_TYPES.get(fieldClass);
+ }
+ throw new IciqlException("Unsupported type " + fieldClass.getName());
+ }
+
+ /**
+ * Returns the Java class for a given SQL type.
+ *
+ * @param sqlType
+ * @param dateTimeClass
+ * the preferred date class (java.util.Date or
+ * java.sql.Timestamp)
+ * @return
+ */
+ static Class<?> getClassForSqlType(String sqlType, Class<? extends java.util.Date> dateTimeClass) {
+ sqlType = sqlType.toUpperCase();
+ // XXX dropping "UNSIGNED" or parts like that could be trouble
+ sqlType = sqlType.split(" ")[0].trim();
+
+ if (SQL_TYPES.containsKey(sqlType)) {
+ // convert the sqlType to a standard type
+ sqlType = SQL_TYPES.get(sqlType);
+ }
+ Class<?> mappedClass = null;
+ for (Class<?> clazz : SUPPORTED_TYPES.keySet()) {
+ if (clazz.isPrimitive()) {
+ // do not map from SQL TYPE to primitive type
+ continue;
+ }
+ if (SUPPORTED_TYPES.get(clazz).equalsIgnoreCase(sqlType)) {
+ mappedClass = clazz;
+
+ break;
+ }
+ }
+ if (mappedClass != null) {
+ if (mappedClass.equals(java.util.Date.class) || mappedClass.equals(java.sql.Timestamp.class)) {
+ return dateTimeClass;
+ }
+ return mappedClass;
+ }
+ return null;
+ }
+
+ /**
+ * Tries to create a convert a SQL table name to a camel case class name.
+ *
+ * @param tableName
+ * the SQL table name
+ * @return the class name
+ */
+ static String convertTableToClassName(String tableName) {
+ String[] chunks = StringUtils.arraySplit(tableName, '_', false);
+ StringBuilder className = new StringBuilder();
+ for (String chunk : chunks) {
+ if (chunk.length() == 0) {
+ // leading or trailing _
+ continue;
+ }
+ String[] subchunks = StringUtils.arraySplit(chunk, ' ', false);
+ for (String subchunk : subchunks) {
+ if (subchunk.length() == 0) {
+ // leading or trailing space
+ continue;
+ }
+ className.append(Character.toUpperCase(subchunk.charAt(0)));
+ className.append(subchunk.substring(1).toLowerCase());
+ }
+ }
+ return className.toString();
+ }
+
+ /**
+ * Ensures that SQL column names don't collide with Java keywords.
+ *
+ * @param columnName
+ * the column name
+ * @return the Java field name
+ */
+ static String convertColumnToFieldName(String columnName) {
+ String lower = columnName.toLowerCase();
+ if (KEYWORDS.contains(lower)) {
+ lower += "Value";
+ }
+ return lower;
+ }
+
+ /**
+ * Converts a DEFAULT clause value into an object.
+ *
+ * @param field
+ * definition
+ * @return object
+ */
+ static Object getDefaultValue(FieldDefinition def, Class<? extends Date> dateTimeClass) {
+ Class<?> valueType = getClassForSqlType(def.dataType, dateTimeClass);
+ if (String.class.isAssignableFrom(valueType)) {
+ if (StringUtils.isNullOrEmpty(def.defaultValue)) {
+ // literal default must be specified within single quotes
+ return null;
+ }
+ if (def.defaultValue.charAt(0) == '\''
+ && def.defaultValue.charAt(def.defaultValue.length() - 1) == '\'') {
+ // strip leading and trailing single quotes
+ return def.defaultValue.substring(1, def.defaultValue.length() - 1).trim();
+ }
+ return def.defaultValue;
+ }
+
+ if (StringUtils.isNullOrEmpty(def.defaultValue)) {
+ // can not create object from empty string
+ return null;
+ }
+
+ // strip leading and trailing single quotes
+ String content = def.defaultValue;
+ if (content.charAt(0) == '\'') {
+ content = content.substring(1);
+ }
+ if (content.charAt(content.length() - 1) == '\'') {
+ content = content.substring(0, content.length() - 2);
+ }
+
+ if (StringUtils.isNullOrEmpty(content)) {
+ // can not create object from empty string
+ return null;
+ }
+
+ if (Boolean.class.isAssignableFrom(valueType) || boolean.class.isAssignableFrom(valueType)) {
+ return Boolean.parseBoolean(content);
+ }
+
+ if (Number.class.isAssignableFrom(valueType)) {
+ try {
+ // delegate to static valueOf() method to parse string
+ Method m = valueType.getMethod("valueOf", String.class);
+ return m.invoke(null, content);
+ } catch (NumberFormatException e) {
+ throw new IciqlException(e, "Failed to parse {0} as a number!", def.defaultValue);
+ } catch (Throwable t) {
+ }
+ }
+
+ String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}";
+ String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}";
+
+ if (java.sql.Date.class.isAssignableFrom(valueType)) {
+ // this may be a little loose....
+ // 00-00-00
+ // 00/00/00
+ // 00.00.00
+ Pattern pattern = Pattern.compile(dateRegex);
+ if (pattern.matcher(content).matches()) {
+ DateFormat df = DateFormat.getDateInstance();
+ try {
+ return df.parse(content);
+ } catch (Exception e) {
+ throw new IciqlException(e, "Failed to parse {0} as a date!", def.defaultValue);
+ }
+ }
+ }
+
+ if (java.sql.Time.class.isAssignableFrom(valueType)) {
+ // 00:00:00
+ Pattern pattern = Pattern.compile(timeRegex);
+ if (pattern.matcher(content).matches()) {
+ DateFormat df = DateFormat.getTimeInstance();
+ try {
+ return df.parse(content);
+ } catch (Exception e) {
+ throw new IciqlException(e, "Failed to parse {0} as a time!", def.defaultValue);
+ }
+ }
+ }
+
+ if (java.util.Date.class.isAssignableFrom(valueType)) {
+ // this may be a little loose....
+ // 00-00-00 00:00:00
+ // 00/00/00T00:00:00
+ // 00.00.00T00:00:00
+ Pattern pattern = Pattern.compile(dateRegex + "." + timeRegex);
+ if (pattern.matcher(content).matches()) {
+ DateFormat df = DateFormat.getDateTimeInstance();
+ try {
+ return df.parse(content);
+ } catch (Exception e) {
+ throw new IciqlException(e, "Failed to parse {0} as a datetimestamp!", def.defaultValue);
+ }
+ }
+ }
+ return content;
+ }
+
+ /**
+ * Converts the object into a DEFAULT clause value.
+ *
+ * @param o
+ * the default object
+ * @return the value formatted for a DEFAULT clause
+ */
+ static String formatDefaultValue(Object o) {
+ Class<?> objectClass = o.getClass();
+ String value = null;
+ if (Number.class.isAssignableFrom(objectClass)) {
+ // NUMBER
+ return ((Number) o).toString();
+ } else if (Boolean.class.isAssignableFrom(objectClass)) {
+ // BOOLEAN
+ return o.toString();
+ } else if (java.sql.Date.class.isAssignableFrom(objectClass)) {
+ // DATE
+ value = new SimpleDateFormat("yyyy-MM-dd").format((Date) o);
+ } else if (java.sql.Time.class.isAssignableFrom(objectClass)) {
+ // TIME
+ value = new SimpleDateFormat("HH:mm:ss").format((Date) o);
+ } else if (Date.class.isAssignableFrom(objectClass)) {
+ // DATETIME
+ value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) o);
+ } else if (String.class.isAssignableFrom(objectClass)) {
+ // STRING
+ value = o.toString();
+ }
+ if (value == null) {
+ return "''";
+ }
+ return MessageFormat.format("''{0}''", value);
+ }
+
+ /**
+ * Checks the formatting of IQColumn.defaultValue().
+ *
+ * @param defaultValue
+ * the default value
+ * @return true if it is
+ */
+ static boolean isProperlyFormattedDefaultValue(String defaultValue) {
+ if (isNullOrEmpty(defaultValue)) {
+ return true;
+ }
+ Pattern literalDefault = Pattern.compile("'.*'");
+ Pattern functionDefault = Pattern.compile("[^'].*[^']");
+ return literalDefault.matcher(defaultValue).matches()
+ || functionDefault.matcher(defaultValue).matches();
+ }
+
+ /**
+ * Checks to see if the default value matches the class.
+ *
+ * @param modelClass
+ * the class
+ * @param defaultValue
+ * the value
+ * @return true if it does
+ */
+ static boolean isValidDefaultValue(Class<?> modelClass, String defaultValue) {
+
+ if (defaultValue == null) {
+ // NULL
+ return true;
+ }
+ if (defaultValue.trim().length() == 0) {
+ // NULL (effectively)
+ return true;
+ }
+
+ // function / variable
+ Pattern functionDefault = Pattern.compile("[^'].*[^']");
+ if (functionDefault.matcher(defaultValue).matches()) {
+ // hard to validate this since its in the database
+ // assume it is good
+ return true;
+ }
+
+ // STRING
+ if (modelClass == String.class) {
+ Pattern stringDefault = Pattern.compile("'(.|\\n)*'");
+ return stringDefault.matcher(defaultValue).matches();
+ }
+
+ String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}";
+ String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}";
+
+ // TIMESTAMP
+ if (modelClass == java.util.Date.class || modelClass == java.sql.Timestamp.class) {
+ // this may be a little loose....
+ // 00-00-00 00:00:00
+ // 00/00/00T00:00:00
+ // 00.00.00T00:00:00
+ Pattern pattern = Pattern.compile("'" + dateRegex + "." + timeRegex + "'");
+ return pattern.matcher(defaultValue).matches();
+ }
+
+ // DATE
+ if (modelClass == java.sql.Date.class) {
+ // this may be a little loose....
+ // 00-00-00
+ // 00/00/00
+ // 00.00.00
+ Pattern pattern = Pattern.compile("'" + dateRegex + "'");
+ return pattern.matcher(defaultValue).matches();
+ }
+
+ // TIME
+ if (modelClass == java.sql.Time.class) {
+ // 00:00:00
+ Pattern pattern = Pattern.compile("'" + timeRegex + "'");
+ return pattern.matcher(defaultValue).matches();
+ }
+
+ // NUMBER
+ if (Number.class.isAssignableFrom(modelClass)) {
+ // strip single quotes
+ String unquoted = defaultValue;
+ if (unquoted.charAt(0) == '\'') {
+ unquoted = unquoted.substring(1);
+ }
+ if (unquoted.charAt(unquoted.length() - 1) == '\'') {
+ unquoted = unquoted.substring(0, unquoted.length() - 1);
+ }
+
+ try {
+ // delegate to static valueOf() method to parse string
+ Method m = modelClass.getMethod("valueOf", String.class);
+ m.invoke(null, unquoted);
+ } catch (NumberFormatException ex) {
+ return false;
+ } catch (Throwable t) {
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/iciql/OrderExpression.java b/src/main/java/com/iciql/OrderExpression.java
new file mode 100644
index 0000000..f450bfb
--- /dev/null
+++ b/src/main/java/com/iciql/OrderExpression.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * An expression to order by in a query.
+ *
+ * @param <T>
+ * the query data type
+ */
+
+class OrderExpression<T> {
+ private Query<T> query;
+ private Object expression;
+ private boolean desc;
+ private boolean nullsFirst;
+ private boolean nullsLast;
+
+ OrderExpression(Query<T> query, Object expression, boolean desc, boolean nullsFirst, boolean nullsLast) {
+ this.query = query;
+ this.expression = expression;
+ this.desc = desc;
+ this.nullsFirst = nullsFirst;
+ this.nullsLast = nullsLast;
+ }
+
+ void appendSQL(SQLStatement stat) {
+ query.appendSQL(stat, null, expression);
+ if (desc) {
+ stat.appendSQL(" DESC");
+ }
+ if (nullsLast) {
+ stat.appendSQL(" NULLS LAST");
+ }
+ if (nullsFirst) {
+ stat.appendSQL(" NULLS FIRST");
+ }
+ }
+
+}
diff --git a/src/main/java/com/iciql/Query.java b/src/main/java/com/iciql/Query.java
new file mode 100644
index 0000000..5dc78a5
--- /dev/null
+++ b/src/main/java/com/iciql/Query.java
@@ -0,0 +1,947 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.lang.reflect.Field;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+
+import com.iciql.Iciql.EnumType;
+import com.iciql.bytecode.ClassReader;
+import com.iciql.util.JdbcUtils;
+import com.iciql.util.IciqlLogger;
+import com.iciql.util.Utils;
+
+/**
+ * This class represents a query.
+ *
+ * @param <T>
+ * the return type
+ */
+
+public class Query<T> {
+
+ private Db db;
+ private SelectTable<T> from;
+ private ArrayList<Token> conditions = Utils.newArrayList();
+ private ArrayList<UpdateColumn> updateColumnDeclarations = Utils.newArrayList();
+ private ArrayList<SelectTable<T>> joins = Utils.newArrayList();
+ private final IdentityHashMap<Object, SelectColumn<T>> aliasMap = Utils.newIdentityHashMap();
+ private ArrayList<OrderExpression<T>> orderByList = Utils.newArrayList();
+ private ArrayList<Object> groupByExpressions = Utils.newArrayList();
+ private long limit;
+ private long offset;
+
+ private Query(Db db) {
+ this.db = db;
+ }
+
+ /**
+ * from() is a static factory method to build a Query object.
+ *
+ * @param db
+ * @param alias
+ * @return a query object
+ */
+ @SuppressWarnings("unchecked")
+ static <T> Query<T> from(Db db, T alias) {
+ Query<T> query = new Query<T>(db);
+ TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());
+ query.from = new SelectTable<T>(db, query, alias, false);
+ def.initSelectObject(query.from, alias, query.aliasMap);
+ return query;
+ }
+
+ public long selectCount() {
+ SQLStatement stat = getSelectStatement(false);
+ stat.appendSQL("COUNT(*) ");
+ appendFromWhere(stat);
+ ResultSet rs = stat.executeQuery();
+ try {
+ rs.next();
+ long value = rs.getLong(1);
+ return value;
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(stat.getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(rs, true);
+ }
+ }
+
+ public List<T> select() {
+ return select(false);
+ }
+
+ public T selectFirst() {
+ return select(false).get(0);
+ }
+
+ public List<T> selectDistinct() {
+ return select(true);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <X, Z> X selectFirst(Z x) {
+ List<X> list = (List<X>) select(x);
+ return list.isEmpty() ? null : list.get(0);
+ }
+
+ public <X> void createView(Class<X> viewClass) {
+ TableDefinition<X> viewDef = db.define(viewClass);
+
+ SQLStatement fromWhere = new SQLStatement(db);
+ appendFromWhere(fromWhere, false);
+
+ SQLStatement stat = new SQLStatement(db);
+ db.getDialect().prepareCreateView(stat, viewDef, fromWhere.toSQL());
+ IciqlLogger.create(stat.toSQL());
+ stat.execute();
+ }
+
+ public <X> void replaceView(Class<X> viewClass) {
+ db.dropView(viewClass);
+ createView(viewClass);
+ }
+
+ public String getSQL() {
+ SQLStatement stat = getSelectStatement(false);
+ stat.appendSQL("*");
+ appendFromWhere(stat);
+ return stat.getSQL().trim();
+ }
+
+ /**
+ * toSQL returns a static string version of the query with runtime variables
+ * properly encoded. This method is also useful when combined with the where
+ * clause methods like isParameter() or atLeastParameter() which allows
+ * iciql to generate re-usable parameterized string statements.
+ *
+ * @return the sql query as plain text
+ */
+ public String toSQL() {
+ return toSQL(false);
+ }
+
+ /**
+ * toSQL returns a static string version of the query with runtime variables
+ * properly encoded. This method is also useful when combined with the where
+ * clause methods like isParameter() or atLeastParameter() which allows
+ * iciql to generate re-usable parameterized string statements.
+ *
+ * @param distinct
+ * if true SELECT DISTINCT is used for the query
+ * @return the sql query as plain text
+ */
+ public String toSQL(boolean distinct) {
+ return toSQL(distinct, null);
+ }
+
+ /**
+ * toSQL returns a static string version of the query with runtime variables
+ * properly encoded. This method is also useful when combined with the where
+ * clause methods like isParameter() or atLeastParameter() which allows
+ * iciql to generate re-usable parameterized string statements.
+ *
+ * @param distinct
+ * if true SELECT DISTINCT is used for the query
+ * @param k
+ * k is used to select only the columns of the specified alias
+ * for an inner join statement. An example of a generated
+ * statement is: SELECT DISTINCT t1.* FROM sometable AS t1 INNER
+ * JOIN othertable AS t2 ON t1.id = t2.id WHERE t2.flag = true
+ * without the alias parameter the statement would start with
+ * SELECT DISTINCT * FROM...
+ * @return the sql query as plain text
+ */
+ public <K> String toSQL(boolean distinct, K k) {
+ SQLStatement stat = new SQLStatement(getDb());
+ if (updateColumnDeclarations.size() > 0) {
+ stat.appendSQL("UPDATE ");
+ from.appendSQL(stat);
+ stat.appendSQL(" SET ");
+ int i = 0;
+ for (UpdateColumn declaration : updateColumnDeclarations) {
+ if (i++ > 0) {
+ stat.appendSQL(", ");
+ }
+ declaration.appendSQL(stat);
+ }
+ appendWhere(stat);
+ } else {
+ stat.appendSQL("SELECT ");
+ if (distinct) {
+ stat.appendSQL("DISTINCT ");
+ }
+ if (k != null) {
+ SelectTable<?> sel = getSelectTable(k);
+ if (sel == null) {
+ // unknown alias, use wildcard
+ IciqlLogger.warn("Alias {0} is not defined in the statement!", k.getClass());
+ stat.appendSQL("*");
+ } else if (isJoin()) {
+ // join query, use AS alias
+ String as = sel.getAs();
+ stat.appendSQL(as + ".*");
+ } else {
+ // schema.table.*
+ String schema = sel.getAliasDefinition().schemaName;
+ String table = sel.getAliasDefinition().tableName;
+ String as = getDb().getDialect().prepareTableName(schema, table);
+ stat.appendSQL(as + ".*");
+ }
+ } else {
+ // alias unspecified, use wildcard
+ stat.appendSQL("*");
+ }
+ appendFromWhere(stat);
+ }
+ return stat.toSQL().trim();
+ }
+
+ <Z> String toSubQuery(Z z) {
+ SQLStatement stat = getSelectStatement(false);
+ SelectColumn<T> col = aliasMap.get(z);
+ String columnName = col.getFieldDefinition().columnName;
+ stat.appendColumn(columnName);
+ appendFromWhere(stat);
+ return stat.toSQL();
+ }
+
+ private List<T> select(boolean distinct) {
+ List<T> result = Utils.newArrayList();
+ TableDefinition<T> def = from.getAliasDefinition();
+ SQLStatement stat = getSelectStatement(distinct);
+ def.appendSelectList(stat);
+ appendFromWhere(stat);
+ ResultSet rs = stat.executeQuery();
+ try {
+ int[] columns = def.mapColumns(false, rs);
+ while (rs.next()) {
+ T item = from.newObject();
+ def.readRow(item, rs, columns);
+ result.add(item);
+ }
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(stat.getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(rs, true);
+ }
+ return result;
+ }
+
+ public int delete() {
+ SQLStatement stat = new SQLStatement(db);
+ stat.appendSQL("DELETE FROM ");
+ from.appendSQL(stat);
+ appendWhere(stat);
+ IciqlLogger.delete(stat.getSQL());
+ return stat.executeUpdate();
+ }
+
+ public <A> UpdateColumnSet<T, A> set(A field) {
+ from.getAliasDefinition().checkMultipleEnums(field);
+ return new UpdateColumnSet<T, A>(this, field);
+ }
+
+ public UpdateColumnSet<T, Boolean> set(boolean field) {
+ from.getAliasDefinition().checkMultipleBooleans();
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Byte> set(byte field) {
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Short> set(short field) {
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Integer> set(int field) {
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Long> set(long field) {
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Float> set(float field) {
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Double> set(double field) {
+ return setPrimitive(field);
+ }
+
+ private <A> UpdateColumnSet<T, A> setPrimitive(A field) {
+ A alias = getPrimitiveAliasByValue(field);
+ if (alias == null) {
+ // this will result in an unmapped field exception
+ return set(field);
+ }
+ return set(alias);
+ }
+
+ public <A> UpdateColumnIncrement<T, A> increment(A field) {
+ return new UpdateColumnIncrement<T, A>(this, field);
+ }
+
+ public UpdateColumnIncrement<T, Byte> increment(byte field) {
+ return incrementPrimitive(field);
+ }
+
+ public UpdateColumnIncrement<T, Short> increment(short field) {
+ return incrementPrimitive(field);
+ }
+
+ public UpdateColumnIncrement<T, Integer> increment(int field) {
+ return incrementPrimitive(field);
+ }
+
+ public UpdateColumnIncrement<T, Long> increment(long field) {
+ return incrementPrimitive(field);
+ }
+
+ public UpdateColumnIncrement<T, Float> increment(float field) {
+ return incrementPrimitive(field);
+ }
+
+ public UpdateColumnIncrement<T, Double> increment(double field) {
+ return incrementPrimitive(field);
+ }
+
+ private <A> UpdateColumnIncrement<T, A> incrementPrimitive(A field) {
+ A alias = getPrimitiveAliasByValue(field);
+ if (alias == null) {
+ // this will result in an unmapped field exception
+ return increment(field);
+ }
+ return increment(alias);
+ }
+
+ public int update() {
+ if (updateColumnDeclarations.size() == 0) {
+ throw new IciqlException("Missing set or increment call.");
+ }
+ SQLStatement stat = new SQLStatement(db);
+ stat.appendSQL("UPDATE ");
+ from.appendSQL(stat);
+ stat.appendSQL(" SET ");
+ int i = 0;
+ for (UpdateColumn declaration : updateColumnDeclarations) {
+ if (i++ > 0) {
+ stat.appendSQL(", ");
+ }
+ declaration.appendSQL(stat);
+ }
+ appendWhere(stat);
+ IciqlLogger.update(stat.getSQL());
+ return stat.executeUpdate();
+ }
+
+ public <X, Z> List<X> selectDistinct(Z x) {
+ return select(x, true);
+ }
+
+ public <X, Z> List<X> select(Z x) {
+ return select(x, false);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <X, Z> List<X> select(Z x, boolean distinct) {
+ Class<?> clazz = x.getClass();
+ if (Utils.isSimpleType(clazz)) {
+ return selectSimple((X) x, distinct);
+ }
+ Class<?> enclosingClass = clazz.getEnclosingClass();
+ if (enclosingClass != null) {
+ // anonymous inner class
+ clazz = clazz.getSuperclass();
+ }
+ return select((Class<X>) clazz, (X) x, distinct);
+ }
+
+ private <X> List<X> select(Class<X> clazz, X x, boolean distinct) {
+ List<X> result = Utils.newArrayList();
+ TableDefinition<X> def = db.define(clazz);
+ SQLStatement stat = getSelectStatement(distinct);
+ def.appendSelectList(stat, this, x);
+ appendFromWhere(stat);
+ ResultSet rs = stat.executeQuery();
+ try {
+ int[] columns = def.mapColumns(false, rs);
+ while (rs.next()) {
+ X row = Utils.newObject(clazz);
+ def.readRow(row, rs, columns);
+ result.add(row);
+ }
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(stat.getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(rs, true);
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <X> List<X> selectSimple(X x, boolean distinct) {
+ SQLStatement stat = getSelectStatement(distinct);
+ appendSQL(stat, null, x);
+ appendFromWhere(stat);
+ ResultSet rs = stat.executeQuery();
+ List<X> result = Utils.newArrayList();
+ try {
+ while (rs.next()) {
+ X value;
+ Object o = rs.getObject(1);
+ // Convert CLOB and BLOB now because we close the resultset
+ if (Clob.class.isAssignableFrom(o.getClass())) {
+ value = (X) Utils.convert(o, String.class);
+ } else if (Blob.class.isAssignableFrom(o.getClass())) {
+ value = (X) Utils.convert(o, byte[].class);
+ } else {
+ value = (X) o;
+ }
+ result.add(value);
+ }
+ } catch (Exception e) {
+ throw IciqlException.fromSQL(stat.getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(rs, true);
+ }
+ return result;
+ }
+
+ private SQLStatement getSelectStatement(boolean distinct) {
+ SQLStatement stat = new SQLStatement(db);
+ stat.appendSQL("SELECT ");
+ if (distinct) {
+ stat.appendSQL("DISTINCT ");
+ }
+ return stat;
+ }
+
+ /**
+ * Begin a primitive boolean field condition clause.
+ *
+ * @param x
+ * the primitive boolean field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Boolean> where(boolean x) {
+ from.getAliasDefinition().checkMultipleBooleans();
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive short field condition clause.
+ *
+ * @param x
+ * the primitive short field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Byte> where(byte x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive short field condition clause.
+ *
+ * @param x
+ * the primitive short field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Short> where(short x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive int field condition clause.
+ *
+ * @param x
+ * the primitive int field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Integer> where(int x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive long field condition clause.
+ *
+ * @param x
+ * the primitive long field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Long> where(long x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive float field condition clause.
+ *
+ * @param x
+ * the primitive float field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Float> where(float x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive double field condition clause.
+ *
+ * @param x
+ * the primitive double field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Double> where(double x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begins a primitive field condition clause.
+ *
+ * @param value
+ * @return a query condition to continue building the condition
+ */
+ private <A> QueryCondition<T, A> wherePrimitive(A value) {
+ A alias = getPrimitiveAliasByValue(value);
+ if (alias == null) {
+ // this will result in an unmapped field exception
+ return where(value);
+ }
+ return where(alias);
+ }
+
+ /**
+ * Begin an Object field condition clause.
+ *
+ * @param x
+ * the mapped object to query
+ * @return a query condition to continue building the condition
+ */
+ public <A> QueryCondition<T, A> where(A x) {
+ from.getAliasDefinition().checkMultipleEnums(x);
+ return new QueryCondition<T, A>(this, x);
+ }
+
+ public <A> QueryWhere<T> where(Filter filter) {
+ HashMap<String, Object> fieldMap = Utils.newHashMap();
+ for (Field f : filter.getClass().getDeclaredFields()) {
+ f.setAccessible(true);
+ try {
+ Object obj = f.get(filter);
+ if (obj == from.getAlias()) {
+ List<TableDefinition.FieldDefinition> fields = from.getAliasDefinition().getFields();
+ String name = f.getName();
+ for (TableDefinition.FieldDefinition field : fields) {
+ String n = name + "." + field.field.getName();
+ Object o = field.field.get(obj);
+ fieldMap.put(n, o);
+ }
+ }
+ fieldMap.put(f.getName(), f.get(filter));
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+ Token filterCode = new ClassReader().decompile(filter, fieldMap, "where");
+ // String filterQuery = filterCode.toString();
+ conditions.add(filterCode);
+ return new QueryWhere<T>(this);
+ }
+
+ public QueryWhere<T> where(String fragment, List<?> args) {
+ return this.where(fragment, args.toArray());
+ }
+
+ public QueryWhere<T> where(String fragment, Object... args) {
+ conditions.add(new RuntimeToken(fragment, args));
+ return new QueryWhere<T>(this);
+ }
+
+ public QueryWhere<T> whereTrue(Boolean condition) {
+ Token token = new Function("", condition);
+ addConditionToken(token);
+ return new QueryWhere<T>(this);
+ }
+
+ /**
+ * Sets the Limit and Offset of a query.
+ *
+ * @return the query
+ */
+
+ public Query<T> limit(long limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ public Query<T> offset(long offset) {
+ this.offset = offset;
+ return this;
+ }
+
+ public Query<T> orderBy(boolean field) {
+ from.getAliasDefinition().checkMultipleBooleans();
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(byte field) {
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(short field) {
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(int field) {
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(long field) {
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(float field) {
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(double field) {
+ return orderByPrimitive(field);
+ }
+
+ Query<T> orderByPrimitive(Object field) {
+ Object alias = getPrimitiveAliasByValue(field);
+ if (alias == null) {
+ return orderBy(field);
+ }
+ return orderBy(alias);
+ }
+
+ public Query<T> orderBy(Object expr) {
+ from.getAliasDefinition().checkMultipleEnums(expr);
+ OrderExpression<T> e = new OrderExpression<T>(this, expr, false, false, false);
+ addOrderBy(e);
+ return this;
+ }
+
+ /**
+ * Order by a number of columns.
+ *
+ * @param expressions
+ * the columns
+ * @return the query
+ */
+
+ public Query<T> orderBy(Object... expressions) {
+ for (Object expr : expressions) {
+ from.getAliasDefinition().checkMultipleEnums(expr);
+ OrderExpression<T> e = new OrderExpression<T>(this, expr, false, false, false);
+ addOrderBy(e);
+ }
+ return this;
+ }
+
+ public Query<T> orderByDesc(Object expr) {
+ OrderExpression<T> e = new OrderExpression<T>(this, expr, true, false, false);
+ addOrderBy(e);
+ return this;
+ }
+
+ public Query<T> groupBy(boolean field) {
+ from.getAliasDefinition().checkMultipleBooleans();
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(byte field) {
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(short field) {
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(int field) {
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(long field) {
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(float field) {
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(double field) {
+ return groupByPrimitive(field);
+ }
+
+ Query<T> groupByPrimitive(Object field) {
+ Object alias = getPrimitiveAliasByValue(field);
+ if (alias == null) {
+ return groupBy(field);
+ }
+ return groupBy(alias);
+ }
+
+ public Query<T> groupBy(Object expr) {
+ from.getAliasDefinition().checkMultipleEnums(expr);
+ groupByExpressions.add(expr);
+ return this;
+ }
+
+ public Query<T> groupBy(Object... groupBy) {
+ this.groupByExpressions.addAll(Arrays.asList(groupBy));
+ return this;
+ }
+
+ /**
+ * INTERNAL
+ *
+ * @param stat
+ * the statement
+ * @param alias
+ * the alias object (can be null)
+ * @param value
+ * the value
+ */
+ public void appendSQL(SQLStatement stat, Object alias, Object value) {
+ if (Function.count() == value) {
+ stat.appendSQL("COUNT(*)");
+ return;
+ }
+ if (RuntimeParameter.PARAMETER == value) {
+ stat.appendSQL("?");
+ addParameter(stat, alias, value);
+ return;
+ }
+ Token token = Db.getToken(value);
+ if (token != null) {
+ token.appendSQL(stat, this);
+ return;
+ }
+ if (alias != null && value.getClass().isEnum()) {
+ // special case:
+ // value is first enum constant which is also the alias object.
+ // the first enum constant is used as the alias because we can not
+ // instantiate an enum reflectively.
+ stat.appendSQL("?");
+ addParameter(stat, alias, value);
+ return;
+ }
+ SelectColumn<T> col = getColumnByReference(value);
+ if (col != null) {
+ col.appendSQL(stat);
+ return;
+ }
+ stat.appendSQL("?");
+ addParameter(stat, alias, value);
+ }
+
+ /**
+ * INTERNAL
+ *
+ * @param stat
+ * the statement
+ * @param alias
+ * the alias object (can be null)
+ * @param valueLeft
+ * the value on the left of the compound clause
+ * @param valueRight
+ * the value on the right of the compound clause
+ * @param compareType
+ * the current compare type (e.g. BETWEEN)
+ */
+ public void appendSQL(SQLStatement stat, Object alias, Object valueLeft, Object valueRight,
+ CompareType compareType) {
+ stat.appendSQL("?");
+ stat.appendSQL(" ");
+ switch (compareType) {
+ case BETWEEN:
+ stat.appendSQL("AND");
+ break;
+ }
+ stat.appendSQL(" ");
+ stat.appendSQL("?");
+ addParameter(stat, alias, valueLeft);
+ addParameter(stat, alias, valueRight);
+ }
+
+ private void addParameter(SQLStatement stat, Object alias, Object value) {
+ if (alias != null && value.getClass().isEnum()) {
+ SelectColumn<T> col = getColumnByReference(alias);
+ EnumType type = col.getFieldDefinition().enumType;
+ Enum<?> anEnum = (Enum<?>) value;
+ Object y = Utils.convertEnum(anEnum, type);
+ stat.addParameter(y);
+ } else {
+ stat.addParameter(value);
+ }
+ }
+
+ void addConditionToken(Token condition) {
+ conditions.add(condition);
+ }
+
+ void addUpdateColumnDeclaration(UpdateColumn declaration) {
+ updateColumnDeclarations.add(declaration);
+ }
+
+ void appendWhere(SQLStatement stat) {
+ if (!conditions.isEmpty()) {
+ stat.appendSQL(" WHERE ");
+ for (Token token : conditions) {
+ token.appendSQL(stat, this);
+ stat.appendSQL(" ");
+ }
+ }
+ }
+
+ void appendFromWhere(SQLStatement stat) {
+ appendFromWhere(stat, true);
+ }
+
+ void appendFromWhere(SQLStatement stat, boolean log) {
+ stat.appendSQL(" FROM ");
+ from.appendSQL(stat);
+ for (SelectTable<T> join : joins) {
+ join.appendSQLAsJoin(stat, this);
+ }
+ appendWhere(stat);
+ if (!groupByExpressions.isEmpty()) {
+ stat.appendSQL(" GROUP BY ");
+ int i = 0;
+ for (Object obj : groupByExpressions) {
+ if (i++ > 0) {
+ stat.appendSQL(", ");
+ }
+ appendSQL(stat, null, obj);
+ stat.appendSQL(" ");
+ }
+ }
+ if (!orderByList.isEmpty()) {
+ stat.appendSQL(" ORDER BY ");
+ int i = 0;
+ for (OrderExpression<T> o : orderByList) {
+ if (i++ > 0) {
+ stat.appendSQL(", ");
+ }
+ o.appendSQL(stat);
+ stat.appendSQL(" ");
+ }
+ }
+ db.getDialect().appendLimitOffset(stat, limit, offset);
+ if (log) {
+ IciqlLogger.select(stat.getSQL());
+ }
+ }
+
+ /**
+ * Join another table.
+ *
+ * @param alias
+ * an alias for the table to join
+ * @return the joined query
+ */
+
+ public <A> QueryJoin<T> innerJoin(A alias) {
+ return join(alias, false);
+ }
+
+ public <A> QueryJoin<T> leftJoin(A alias) {
+ return join(alias, true);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private <A> QueryJoin<T> join(A alias, boolean outerJoin) {
+ TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());
+ SelectTable<T> join = new SelectTable(db, this, alias, outerJoin);
+ def.initSelectObject(join, alias, aliasMap);
+ joins.add(join);
+ return new QueryJoin(this, join);
+ }
+
+ Db getDb() {
+ return db;
+ }
+
+ SelectTable<T> getFrom() {
+ return from;
+ }
+
+ boolean isJoin() {
+ return !joins.isEmpty();
+ }
+
+ SelectTable<?> getSelectTable(Object alias) {
+ if (from.getAlias() == alias) {
+ return from;
+ } else {
+ for (SelectTable<?> join : joins) {
+ if (join.getAlias() == alias) {
+ return join;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This method returns a mapped Object field by its reference.
+ *
+ * @param obj
+ * @return
+ */
+ private SelectColumn<T> getColumnByReference(Object obj) {
+ SelectColumn<T> col = aliasMap.get(obj);
+ return col;
+ }
+
+ /**
+ * This method returns the alias of a mapped primitive field by its value.
+ *
+ * @param obj
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ <A> A getPrimitiveAliasByValue(A obj) {
+ for (Object alias : aliasMap.keySet()) {
+ if (alias.equals(obj)) {
+ SelectColumn<T> match = aliasMap.get(alias);
+ if (match.getFieldDefinition().isPrimitive) {
+ return (A) alias;
+ }
+ }
+ }
+ return null;
+ }
+
+ void addOrderBy(OrderExpression<T> expr) {
+ orderByList.add(expr);
+ }
+
+}
diff --git a/src/main/java/com/iciql/QueryBetween.java b/src/main/java/com/iciql/QueryBetween.java
new file mode 100644
index 0000000..72d19dc
--- /dev/null
+++ b/src/main/java/com/iciql/QueryBetween.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * This class represents a "between y and z" condition.
+ *
+ * @param <T>
+ * the return type of the query
+ * @param <A>
+ * the incomplete condition data type
+ */
+public class QueryBetween<T, A> {
+
+ private Query<T> query;
+ private A x;
+ private A y;
+
+ /**
+ * Construct a between condition.
+ *
+ * @param query
+ * the query
+ * @param x
+ * the alias
+ * @param y
+ * the lower bound of the between condition
+ */
+ public QueryBetween(Query<T> query, A x, A y) {
+ this.query = query;
+ this.x = x;
+ this.y = y;
+ }
+
+ /**
+ * Set the upper bound of the between condition.
+ *
+ * @param z
+ * the upper bound of the between condition
+ * @return the query
+ */
+ public QueryWhere<T> and(A z) {
+ query.addConditionToken(new Condition<A>(x, y, z, CompareType.BETWEEN));
+ return new QueryWhere<T>(query);
+ }
+}
diff --git a/src/main/java/com/iciql/QueryCondition.java b/src/main/java/com/iciql/QueryCondition.java
new file mode 100644
index 0000000..9613b1b
--- /dev/null
+++ b/src/main/java/com/iciql/QueryCondition.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * This class represents a query with an incomplete condition.
+ *
+ * @param <T>
+ * the return type of the query
+ * @param <A>
+ * the incomplete condition data type
+ */
+
+public class QueryCondition<T, A> {
+
+ private Query<T> query;
+ private A x;
+
+ QueryCondition(Query<T> query, A x) {
+ this.query = query;
+ this.x = x;
+ }
+
+ public <Q, Z> QueryWhere<T> in(SubQuery<Q, Z> q) {
+ query.addConditionToken(new SubQueryCondition<A, Q, Z>(x, q));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> is(A y) {
+ query.addConditionToken(new Condition<A>(x, y, CompareType.EQUAL));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> isNot(A y) {
+ query.addConditionToken(new Condition<A>(x, y, CompareType.NOT_EQUAL));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> isNull() {
+ query.addConditionToken(new Condition<A>(x, null, CompareType.IS_NULL));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> isNotNull() {
+ query.addConditionToken(new Condition<A>(x, null, CompareType.IS_NOT_NULL));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> exceeds(A y) {
+ query.addConditionToken(new Condition<A>(x, y, CompareType.EXCEEDS));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> atLeast(A y) {
+ query.addConditionToken(new Condition<A>(x, y, CompareType.AT_LEAST));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> lessThan(A y) {
+ query.addConditionToken(new Condition<A>(x, y, CompareType.LESS_THAN));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> atMost(A y) {
+ query.addConditionToken(new Condition<A>(x, y, CompareType.AT_MOST));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryBetween<T, A> between(A y) {
+ return new QueryBetween<T, A>(query, x, y);
+ }
+
+ public QueryWhere<T> like(A pattern) {
+ query.addConditionToken(new Condition<A>(x, pattern, CompareType.LIKE));
+ return new QueryWhere<T>(query);
+ }
+
+ /*
+ * These method allows you to generate "x=?", "x!=?", etc where conditions.
+ * Parameter substitution must be done manually later with db.executeQuery.
+ * This allows for building re-usable SQL string statements from your model
+ * classes.
+ */
+ public QueryWhere<T> isParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.EQUAL));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> isNotParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.NOT_EQUAL));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> exceedsParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.EXCEEDS));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> lessThanParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.LESS_THAN));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> atMostParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.AT_MOST));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> likeParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.LIKE));
+ return new QueryWhere<T>(query);
+ }
+}
diff --git a/src/main/java/com/iciql/QueryJoin.java b/src/main/java/com/iciql/QueryJoin.java
new file mode 100644
index 0000000..6d0484e
--- /dev/null
+++ b/src/main/java/com/iciql/QueryJoin.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * This class represents a query with a join.
+ */
+
+public class QueryJoin<T> {
+
+ private Query<T> query;
+ private SelectTable<T> join;
+
+ QueryJoin(Query<T> query, SelectTable<T> join) {
+ this.query = query;
+ this.join = join;
+ }
+
+ public QueryJoinCondition<T, Boolean> on(boolean x) {
+ query.getFrom().getAliasDefinition().checkMultipleBooleans();
+ return addPrimitive(x);
+ }
+
+ public QueryJoinCondition<T, Byte> on(byte x) {
+ return addPrimitive(x);
+ }
+
+ public QueryJoinCondition<T, Short> on(short x) {
+ return addPrimitive(x);
+ }
+
+ public QueryJoinCondition<T, Integer> on(int x) {
+ return addPrimitive(x);
+ }
+
+ public QueryJoinCondition<T, Long> on(long x) {
+ return addPrimitive(x);
+ }
+
+ public QueryJoinCondition<T, Float> on(float x) {
+ return addPrimitive(x);
+ }
+
+ public QueryJoinCondition<T, Double> on(double x) {
+ return addPrimitive(x);
+ }
+
+ private <A> QueryJoinCondition<T, A> addPrimitive(A x) {
+ A alias = query.getPrimitiveAliasByValue(x);
+ if (alias == null) {
+ // this will result in an unmapped field exception
+ return new QueryJoinCondition<T, A>(query, join, x);
+ }
+ return new QueryJoinCondition<T, A>(query, join, alias);
+ }
+
+ public <A> QueryJoinCondition<T, A> on(A x) {
+ return new QueryJoinCondition<T, A>(query, join, x);
+ }
+}
diff --git a/src/main/java/com/iciql/QueryJoinCondition.java b/src/main/java/com/iciql/QueryJoinCondition.java
new file mode 100644
index 0000000..6dfd218
--- /dev/null
+++ b/src/main/java/com/iciql/QueryJoinCondition.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+
+/**
+ * This class represents a query with join and an incomplete condition.
+ *
+ * @param <A>
+ * the incomplete condition data type
+ */
+
+public class QueryJoinCondition<T, A> {
+
+ private Query<T> query;
+ private SelectTable<T> join;
+ private A x;
+
+ QueryJoinCondition(Query<T> query, SelectTable<T> join, A x) {
+ this.query = query;
+ this.join = join;
+ this.x = x;
+ }
+
+ public Query<T> is(boolean y) {
+ return addPrimitive(y);
+ }
+
+ public Query<T> is(byte y) {
+ return addPrimitive(y);
+ }
+
+ public Query<T> is(short y) {
+ return addPrimitive(y);
+ }
+
+ public Query<T> is(int y) {
+ return addPrimitive(y);
+ }
+
+ public Query<T> is(long y) {
+ return addPrimitive(y);
+ }
+
+ public Query<T> is(float y) {
+ return addPrimitive(y);
+ }
+
+ public Query<T> is(double y) {
+ return addPrimitive(y);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Query<T> addPrimitive(Object o) {
+ A alias = query.getPrimitiveAliasByValue((A) o);
+ if (alias == null) {
+ join.addConditionToken(new Condition<A>(x, (A) o, CompareType.EQUAL));
+ } else {
+ join.addConditionToken(new Condition<A>(x, alias, CompareType.EQUAL));
+ }
+ return query;
+ }
+
+ public Query<T> is(A y) {
+ join.addConditionToken(new Condition<A>(x, y, CompareType.EQUAL));
+ return query;
+ }
+}
diff --git a/src/main/java/com/iciql/QueryWhere.java b/src/main/java/com/iciql/QueryWhere.java
new file mode 100644
index 0000000..5baa5ab
--- /dev/null
+++ b/src/main/java/com/iciql/QueryWhere.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.util.List;
+
+/**
+ * This class represents a query with a condition.
+ *
+ * @param <T>
+ * the return type
+ */
+
+public class QueryWhere<T> {
+
+ Query<T> query;
+
+ QueryWhere(Query<T> query) {
+ this.query = query;
+ }
+
+ /**
+ * Specify an AND condition with a mapped primitive boolean.
+ *
+ * @param x
+ * the primitive boolean field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Boolean> and(boolean x) {
+ query.getFrom().getAliasDefinition().checkMultipleBooleans();
+ return addPrimitive(ConditionAndOr.AND, x);
+ }
+
+ /**
+ * Specify an AND condition with a mapped primitive byte.
+ *
+ * @param x
+ * the primitive byte field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Byte> and(byte x) {
+ return addPrimitive(ConditionAndOr.AND, x);
+ }
+
+ /**
+ * Specify an AND condition with a mapped primitive short.
+ *
+ * @param x
+ * the primitive short field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Short> and(short x) {
+ return addPrimitive(ConditionAndOr.AND, x);
+ }
+
+ /**
+ * Specify an AND condition with a mapped primitive int.
+ *
+ * @param x
+ * the primitive int field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Integer> and(int x) {
+ return addPrimitive(ConditionAndOr.AND, x);
+ }
+
+ /**
+ * Specify an AND condition with a mapped primitive long.
+ *
+ * @param x
+ * the primitive long field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Long> and(long x) {
+ return addPrimitive(ConditionAndOr.AND, x);
+ }
+
+ /**
+ * Specify an AND condition with a mapped primitive float.
+ *
+ * @param x
+ * the primitive float field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Float> and(float x) {
+ return addPrimitive(ConditionAndOr.AND, x);
+ }
+
+ /**
+ * Specify an AND condition with a mapped primitive double.
+ *
+ * @param x
+ * the primitive double field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Double> and(double x) {
+ return addPrimitive(ConditionAndOr.AND, x);
+ }
+
+ private <A> QueryCondition<T, A> addPrimitive(ConditionAndOr condition, A x) {
+ query.addConditionToken(condition);
+ A alias = query.getPrimitiveAliasByValue(x);
+ if (alias == null) {
+ // this will result in an unmapped field exception
+ return new QueryCondition<T, A>(query, x);
+ }
+ return new QueryCondition<T, A>(query, alias);
+ }
+
+ /**
+ * Specify an AND condition with a mapped Object field.
+ *
+ * @param x
+ * the Object field to query
+ * @return a query condition to continue building the condition
+ */
+ public <A> QueryCondition<T, A> and(A x) {
+ query.getFrom().getAliasDefinition().checkMultipleEnums(x);
+ query.addConditionToken(ConditionAndOr.AND);
+ return new QueryCondition<T, A>(query, x);
+ }
+
+ /**
+ * Specify an OR condition with a mapped primitive boolean.
+ *
+ * @param x
+ * the primitive boolean field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Boolean> or(boolean x) {
+ query.getFrom().getAliasDefinition().checkMultipleBooleans();
+ return addPrimitive(ConditionAndOr.OR, x);
+ }
+
+ /**
+ * Specify an OR condition with a mapped primitive byte.
+ *
+ * @param x
+ * the primitive byte field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Byte> or(byte x) {
+ return addPrimitive(ConditionAndOr.OR, x);
+ }
+
+ /**
+ * Specify an OR condition with a mapped primitive short.
+ *
+ * @param x
+ * the primitive short field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Short> or(short x) {
+ return addPrimitive(ConditionAndOr.OR, x);
+ }
+
+ /**
+ * Specify an OR condition with a mapped primitive int.
+ *
+ * @param x
+ * the primitive int field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Integer> or(int x) {
+ return addPrimitive(ConditionAndOr.OR, x);
+ }
+
+ /**
+ * Specify an OR condition with a mapped primitive long.
+ *
+ * @param x
+ * the primitive long field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Long> or(long x) {
+ return addPrimitive(ConditionAndOr.OR, x);
+ }
+
+ /**
+ * Specify an OR condition with a mapped primitive float.
+ *
+ * @param x
+ * the primitive float field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Float> or(float x) {
+ return addPrimitive(ConditionAndOr.OR, x);
+ }
+
+ /**
+ * Specify an OR condition with a mapped primitive double.
+ *
+ * @param x
+ * the primitive double field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Double> or(double x) {
+ return addPrimitive(ConditionAndOr.OR, x);
+ }
+
+ /**
+ * Specify an OR condition with a mapped Object field.
+ *
+ * @param x
+ * the Object field to query
+ * @return a query condition to continue building the condition
+ */
+ public <A> QueryCondition<T, A> or(A x) {
+ query.getFrom().getAliasDefinition().checkMultipleEnums(x);
+ query.addConditionToken(ConditionAndOr.OR);
+ return new QueryCondition<T, A>(query, x);
+ }
+
+ public QueryWhere<T> limit(long limit) {
+ query.limit(limit);
+ return this;
+ }
+
+ public QueryWhere<T> offset(long offset) {
+ query.offset(offset);
+ return this;
+ }
+
+ public String getSQL() {
+ SQLStatement stat = new SQLStatement(query.getDb());
+ stat.appendSQL("SELECT *");
+ query.appendFromWhere(stat);
+ return stat.getSQL().trim();
+ }
+
+ /**
+ * toSQL returns a static string version of the query with runtime variables
+ * properly encoded. This method is also useful when combined with the where
+ * clause methods like isParameter() or atLeastParameter() which allows
+ * iciql to generate re-usable parameterized string statements.
+ *
+ * @return the sql query as plain text
+ */
+ public String toSQL() {
+ return query.toSQL(false);
+ }
+
+ /**
+ * toSQL returns a static string version of the query with runtime variables
+ * properly encoded. This method is also useful when combined with the where
+ * clause methods like isParameter() or atLeastParameter() which allows
+ * iciql to generate re-usable parameterized string statements.
+ *
+ * @param distinct
+ * if true SELECT DISTINCT is used for the query
+ * @return the sql query as plain text
+ */
+ public String toSQL(boolean distinct) {
+ return query.toSQL(distinct);
+ }
+
+ /**
+ * toSQL returns a static string version of the query with runtime variables
+ * properly encoded. This method is also useful when combined with the where
+ * clause methods like isParameter() or atLeastParameter() which allows
+ * iciql to generate re-usable parameterized string statements.
+ *
+ * @param distinct
+ * if true SELECT DISTINCT is used for the query
+ * @param k
+ * k is used to select only the columns of the specified alias
+ * for an inner join statement. An example of a generated
+ * statement is: SELECT DISTINCT t1.* FROM sometable AS t1 INNER
+ * JOIN othertable AS t2 ON t1.id = t2.id WHERE t2.flag = true
+ * without the alias parameter the statement would start with
+ * SELECT DISTINCT * FROM...
+ * @return the sql query as plain text
+ */
+ public <K> String toSQL(boolean distinct, K k) {
+ return query.toSQL(distinct, k);
+ }
+
+ public <Z> SubQuery<T, Z> subQuery(Z x) {
+ return new SubQuery<T, Z>(query, x);
+ }
+
+ public SubQuery<T, Boolean> subQuery(boolean x) {
+ return subQuery(query.getPrimitiveAliasByValue(x));
+ }
+
+ public SubQuery<T, Byte> subQuery(byte x) {
+ return subQuery(query.getPrimitiveAliasByValue(x));
+ }
+
+ public SubQuery<T, Short> subQuery(short x) {
+ return subQuery(query.getPrimitiveAliasByValue(x));
+ }
+
+ public SubQuery<T, Integer> subQuery(int x) {
+ return subQuery(query.getPrimitiveAliasByValue(x));
+ }
+
+ public SubQuery<T, Long> subQuery(long x) {
+ return subQuery(query.getPrimitiveAliasByValue(x));
+ }
+
+ public SubQuery<T, Float> subQuery(float x) {
+ return subQuery(query.getPrimitiveAliasByValue(x));
+ }
+
+ public SubQuery<T, Double> subQuery(double x) {
+ return subQuery(query.getPrimitiveAliasByValue(x));
+ }
+
+ public <X, Z> List<X> select(Z x) {
+ return query.select(x);
+ }
+
+ public <X, Z> List<X> selectDistinct(Z x) {
+ return query.selectDistinct(x);
+ }
+
+ public <X, Z> X selectFirst(Z x) {
+ List<X> list = query.select(x);
+ return list.isEmpty() ? null : list.get(0);
+ }
+
+ public List<T> select() {
+ return query.select();
+ }
+
+ public T selectFirst() {
+ List<T> list = select();
+ return list.isEmpty() ? null : list.get(0);
+ }
+
+ public List<T> selectDistinct() {
+ return query.selectDistinct();
+ }
+
+ public void createView(Class<?> viewClass) {
+ query.createView(viewClass);
+ }
+
+ public void replaceView(Class<?> viewClass) {
+ query.replaceView(viewClass);
+ }
+
+ /**
+ * Order by primitive boolean field
+ *
+ * @param field
+ * a primitive boolean field
+ * @return the query
+ */
+ public QueryWhere<T> orderBy(boolean field) {
+ query.getFrom().getAliasDefinition().checkMultipleBooleans();
+ return orderByPrimitive(field);
+ }
+
+ /**
+ * Order by primitive byte field
+ *
+ * @param field
+ * a primitive byte field
+ * @return the query
+ */
+ public QueryWhere<T> orderBy(byte field) {
+ return orderByPrimitive(field);
+ }
+
+ /**
+ * Order by primitive short field
+ *
+ * @param field
+ * a primitive short field
+ * @return the query
+ */
+ public QueryWhere<T> orderBy(short field) {
+ return orderByPrimitive(field);
+ }
+
+ public QueryWhere<T> orderBy(int field) {
+ return orderByPrimitive(field);
+ }
+
+ /**
+ * Order by primitive long field
+ *
+ * @param field
+ * a primitive long field
+ * @return the query
+ */
+ public QueryWhere<T> orderBy(long field) {
+ return orderByPrimitive(field);
+ }
+
+ /**
+ * Order by primitive float field
+ *
+ * @param field
+ * a primitive float field
+ * @return the query
+ */
+ public QueryWhere<T> orderBy(float field) {
+ return orderByPrimitive(field);
+ }
+
+ /**
+ * Order by primitive double field
+ *
+ * @param field
+ * a primitive double field
+ * @return the query
+ */
+ public QueryWhere<T> orderBy(double field) {
+ return orderByPrimitive(field);
+ }
+
+ private QueryWhere<T> orderByPrimitive(Object field) {
+ query.orderByPrimitive(field);
+ return this;
+ }
+
+ public QueryWhere<T> orderBy(Object field) {
+ query.getFrom().getAliasDefinition().checkMultipleEnums(field);
+ query.orderBy(field);
+ return this;
+ }
+
+ /**
+ * Order by a number of Object columns.
+ *
+ * @param expressions
+ * the order by expressions
+ * @return the query
+ */
+
+ public QueryWhere<T> orderBy(Object... expressions) {
+ query.orderBy(expressions);
+ return this;
+ }
+
+ public QueryWhere<T> orderByNullsFirst(Object expr) {
+ query.getFrom().getAliasDefinition().checkMultipleEnums(expr);
+ OrderExpression<T> e = new OrderExpression<T>(query, expr, false, true, false);
+ query.addOrderBy(e);
+ return this;
+ }
+
+ public QueryWhere<T> orderByNullsLast(Object expr) {
+ query.getFrom().getAliasDefinition().checkMultipleEnums(expr);
+ OrderExpression<T> e = new OrderExpression<T>(query, expr, false, false, true);
+ query.addOrderBy(e);
+ return this;
+ }
+
+ public QueryWhere<T> orderByDesc(Object expr) {
+ query.getFrom().getAliasDefinition().checkMultipleEnums(expr);
+ OrderExpression<T> e = new OrderExpression<T>(query, expr, true, false, false);
+ query.addOrderBy(e);
+ return this;
+ }
+
+ public QueryWhere<T> orderByDescNullsFirst(Object expr) {
+ query.getFrom().getAliasDefinition().checkMultipleEnums(expr);
+ OrderExpression<T> e = new OrderExpression<T>(query, expr, true, true, false);
+ query.addOrderBy(e);
+ return this;
+ }
+
+ public QueryWhere<T> orderByDescNullsLast(Object expr) {
+ query.getFrom().getAliasDefinition().checkMultipleEnums(expr);
+ OrderExpression<T> e = new OrderExpression<T>(query, expr, true, false, true);
+ query.addOrderBy(e);
+ return this;
+ }
+
+ public int delete() {
+ return query.delete();
+ }
+
+ public int update() {
+ return query.update();
+ }
+
+ public long selectCount() {
+ return query.selectCount();
+ }
+
+}
diff --git a/src/main/java/com/iciql/RuntimeParameter.java b/src/main/java/com/iciql/RuntimeParameter.java
new file mode 100644
index 0000000..0fbedba
--- /dev/null
+++ b/src/main/java/com/iciql/RuntimeParameter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * A runtime parameter is used to generate x=? conditions so that iciql can
+ * build re-usable dynamic queries with parameter substitution done manually at
+ * runtime.
+ *
+ * @param <A>
+ * the operand type
+ */
+
+class RuntimeParameter<A> implements Token {
+
+ public final static String PARAMETER = "";
+
+ A x;
+ CompareType compareType;
+
+ RuntimeParameter(A x, CompareType type) {
+ this.x = x;
+ this.compareType = type;
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ query.appendSQL(stat, null, x);
+ stat.appendSQL(" ");
+ stat.appendSQL(compareType.getString());
+ if (compareType.hasRightExpression()) {
+ stat.appendSQL(" ");
+ query.appendSQL(stat, x, PARAMETER);
+ }
+ }
+}
diff --git a/src/main/java/com/iciql/RuntimeToken.java b/src/main/java/com/iciql/RuntimeToken.java
new file mode 100644
index 0000000..cbfd882
--- /dev/null
+++ b/src/main/java/com/iciql/RuntimeToken.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+package com.iciql;
+
+import java.text.MessageFormat;
+
+import com.iciql.util.StringUtils;
+
+/**
+ * Represents a traditional PreparedStatment fragment like "id=?, name=?".
+ *
+ */
+public class RuntimeToken implements Token {
+
+ final String fragment;
+ final Object[] args;
+
+ public RuntimeToken(String fragment, Object... args) {
+ this.fragment = fragment;
+ this.args = args == null ? new Object[0] : args;
+ }
+
+ /**
+ * Append the SQL to the given statement using the given query.
+ *
+ * @param stat
+ * the statement to append the SQL to
+ * @param query
+ * the query to use
+ */
+ @Override
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ int tokenCount = StringUtils.count('?', fragment);
+ if (tokenCount != args.length) {
+ throw new IciqlException(MessageFormat.format(
+ "Fragment \"{0}\" specifies {1} tokens but you supplied {2} args", fragment, tokenCount,
+ args.length));
+ }
+ stat.appendSQL(fragment);
+ for (Object arg : args) {
+ stat.addParameter(arg);
+ }
+ }
+}
diff --git a/src/main/java/com/iciql/SQLDialect.java b/src/main/java/com/iciql/SQLDialect.java
new file mode 100644
index 0000000..f62168e
--- /dev/null
+++ b/src/main/java/com/iciql/SQLDialect.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.sql.DatabaseMetaData;
+
+import com.iciql.TableDefinition.ConstraintForeignKeyDefinition;
+import com.iciql.TableDefinition.ConstraintUniqueDefinition;
+import com.iciql.TableDefinition.IndexDefinition;
+
+/**
+ * This interface defines points where iciql can build different statements
+ * depending on the database used.
+ */
+public interface SQLDialect {
+
+ /**
+ * Configure the dialect from the database metadata.
+ *
+ * @param databaseName
+ * @param data
+ */
+ void configureDialect(String databaseName, DatabaseMetaData data);
+
+ /**
+ * Allows a dialect to substitute an SQL type.
+ *
+ * @param sqlType
+ * @return the dialect-safe type
+ */
+ String convertSqlType(String sqlType);
+
+ /**
+ * Returns a properly formatted table name for the dialect.
+ *
+ * @param schemaName
+ * the schema name, or null for no schema
+ * @param tableName
+ * the properly formatted table name
+ * @return the SQL snippet
+ */
+ String prepareTableName(String schemaName, String tableName);
+
+ /**
+ * Returns a properly formatted column name for the dialect.
+ *
+ * @param name
+ * the column name
+ * @return the properly formatted column name
+ */
+ String prepareColumnName(String name);
+
+ /**
+ * Get the CREATE TABLE statement.
+ *
+ * @param stat
+ * @param def
+ */
+ <T> void prepareCreateTable(SQLStatement stat, TableDefinition<T> def);
+
+ /**
+ * Get the DROP TABLE statement.
+ *
+ * @param stat
+ * @param def
+ */
+ <T> void prepareDropTable(SQLStatement stat, TableDefinition<T> def);
+
+
+ /**
+ * Get the CREATE VIEW statement.
+ *
+ * @param stat
+ * return the SQL statement
+ * @param def
+ * table definition
+ */
+ <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def);
+
+ /**
+ * Get the CREATE VIEW statement.
+ *
+ * @param stat
+ * return the SQL statement
+ * @param def
+ * table definition
+ * @param fromWhere
+ */
+ <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def, String fromWhere);
+
+ /**
+ * Get the DROP VIEW statement.
+ *
+ * @param stat
+ * return the SQL statement
+ * @param def
+ * table definition
+ */
+ <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def);
+
+ /**
+ * Get the CREATE INDEX statement.
+ *
+ * @param stat
+ * return the SQL statement
+ * @param schemaName
+ * the schema name
+ * @param tableName
+ * the table name
+ * @param index
+ * the index definition
+ */
+ void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName, IndexDefinition index);
+
+ /**
+ * Get the ALTER statement.
+ *
+ * @param stat
+ * return the SQL statement
+ * @param schemaName
+ * the schema name
+ * @param tableName
+ * the table name
+ * @param constraint
+ * the constraint definition
+ */
+ void prepareCreateConstraintForeignKey(SQLStatement stat, String schemaName, String tableName, ConstraintForeignKeyDefinition constraint);
+
+ /**
+ * Get the ALTER statement.
+ *
+ * @param stat
+ * return the SQL statement
+ * @param schemaName
+ * the schema name
+ * @param tableName
+ * the table name
+ * @param constraint
+ * the constraint definition
+ * return the SQL statement
+ */
+ void prepareCreateConstraintUnique(SQLStatement stat, String schemaName, String tableName, ConstraintUniqueDefinition constraint);
+
+ /**
+ * Get a MERGE or REPLACE INTO statement.
+ *
+ * @param stat
+ * return the SQL statement
+ * @param schemaName
+ * the schema name
+ * @param tableName
+ * the table name
+ * @param def
+ * the table definition
+ * @param obj
+ * values
+ */
+ <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition<T> def,
+ Object obj);
+
+ /**
+ * Append "LIMIT limit OFFSET offset" to the SQL statement.
+ *
+ * @param stat
+ * the statement
+ * @param limit
+ * the limit
+ * @param offset
+ * the offset
+ */
+ void appendLimitOffset(SQLStatement stat, long limit, long offset);
+
+ /**
+ * Returns the preferred DATETIME class for the database.
+ * <p>
+ * Either java.util.Date or java.sql.Timestamp
+ *
+ * @return preferred DATETIME class
+ */
+ Class<? extends java.util.Date> getDateTimeClass();
+
+ /**
+ * When building static string statements this method flattens an object to
+ * a string representation suitable for a static string statement.
+ *
+ * @param o
+ * @return the string equivalent of this object
+ */
+ String prepareParameter(Object o);
+}
diff --git a/src/main/java/com/iciql/SQLDialectDefault.java b/src/main/java/com/iciql/SQLDialectDefault.java
new file mode 100644
index 0000000..364db7b
--- /dev/null
+++ b/src/main/java/com/iciql/SQLDialectDefault.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+
+import com.iciql.Iciql.ConstraintDeleteType;
+import com.iciql.Iciql.ConstraintUpdateType;
+import com.iciql.TableDefinition.ConstraintForeignKeyDefinition;
+import com.iciql.TableDefinition.ConstraintUniqueDefinition;
+import com.iciql.TableDefinition.FieldDefinition;
+import com.iciql.TableDefinition.IndexDefinition;
+import com.iciql.util.IciqlLogger;
+import com.iciql.util.StatementBuilder;
+import com.iciql.util.StringUtils;
+
+/**
+ * Default implementation of an SQL dialect.
+ */
+public class SQLDialectDefault implements SQLDialect {
+
+ final String LITERAL = "'";
+
+ float databaseVersion;
+ String databaseName;
+ String productVersion;
+
+ @Override
+ public String toString() {
+ return getClass().getName() + ": " + databaseName + " " + productVersion;
+ }
+
+ @Override
+ public void configureDialect(String databaseName, DatabaseMetaData data) {
+ this.databaseName = databaseName;
+ try {
+ databaseVersion = Float.parseFloat(data.getDatabaseMajorVersion() + "."
+ + data.getDatabaseMinorVersion());
+ productVersion = data.getDatabaseProductVersion();
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ /**
+ * Allows subclasses to change the type of a column for a CREATE statement.
+ *
+ * @param sqlType
+ * @return the SQL type or a preferred alternative
+ */
+ @Override
+ public String convertSqlType(String sqlType) {
+ return sqlType;
+ }
+
+ @Override
+ public Class<? extends java.util.Date> getDateTimeClass() {
+ return java.util.Date.class;
+ }
+
+ @Override
+ public String prepareTableName(String schemaName, String tableName) {
+ if (StringUtils.isNullOrEmpty(schemaName)) {
+ return tableName;
+ }
+ return schemaName + "." + tableName;
+ }
+
+ @Override
+ public String prepareColumnName(String name) {
+ return name;
+ }
+
+ @Override
+ public <T> void prepareDropTable(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder("DROP TABLE IF EXISTS "
+ + prepareTableName(def.schemaName, def.tableName));
+ stat.setSQL(buff.toString());
+ return;
+ }
+
+ protected <T> String prepareCreateTable(TableDefinition<T> def) {
+ return "CREATE TABLE";
+ }
+
+ @Override
+ public <T> void prepareCreateTable(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append(prepareCreateTable(def));
+ buff.append(" ");
+ buff.append(prepareTableName(def.schemaName, def.tableName)).append('(');
+
+ boolean hasIdentityColumn = false;
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(field.columnName)).append(' ');
+ String dataType = field.dataType;
+ if (dataType.equals("VARCHAR")) {
+ // check to see if we should use VARCHAR or CLOB
+ if (field.length <= 0) {
+ dataType = "CLOB";
+ }
+ buff.append(convertSqlType(dataType));
+ if (field.length > 0) {
+ buff.append('(').append(field.length).append(')');
+ }
+ } else if (dataType.equals("DECIMAL")) {
+ // DECIMAL(precision,scale)
+ buff.append(convertSqlType(dataType));
+ if (field.length > 0) {
+ buff.append('(').append(field.length);
+ if (field.scale > 0) {
+ buff.append(',').append(field.scale);
+ }
+ buff.append(')');
+ }
+ } else {
+ // other
+ hasIdentityColumn |= prepareColumnDefinition(buff, convertSqlType(dataType),
+ field.isAutoIncrement, field.isPrimaryKey);
+ }
+
+ // default values
+ if (!field.isAutoIncrement && !field.isPrimaryKey) {
+ String dv = field.defaultValue;
+ if (!StringUtils.isNullOrEmpty(dv)) {
+ if (ModelUtils.isProperlyFormattedDefaultValue(dv)
+ && ModelUtils.isValidDefaultValue(field.field.getType(), dv)) {
+ buff.append(" DEFAULT " + dv);
+ }
+ }
+ }
+
+ if (!field.nullable) {
+ buff.append(" NOT NULL");
+ }
+ }
+
+ // if table does not have identity column then specify primary key
+ if (!hasIdentityColumn) {
+ if (def.primaryKeyColumnNames != null && def.primaryKeyColumnNames.size() > 0) {
+ buff.append(", PRIMARY KEY(");
+ buff.resetCount();
+ for (String n : def.primaryKeyColumnNames) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(n));
+ }
+ buff.append(')');
+ }
+ }
+ buff.append(')');
+ stat.setSQL(buff.toString());
+ }
+
+ @Override
+ public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder("DROP VIEW "
+ + prepareTableName(def.schemaName, def.tableName));
+ stat.setSQL(buff.toString());
+ return;
+ }
+
+ protected <T> String prepareCreateView(TableDefinition<T> def) {
+ return "CREATE VIEW";
+ }
+
+ @Override
+ public <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append(" FROM ");
+ buff.append(prepareTableName(def.schemaName, def.viewTableName));
+
+ StatementBuilder where = new StatementBuilder();
+ for (FieldDefinition field : def.fields) {
+ if (!StringUtils.isNullOrEmpty(field.constraint)) {
+ where.appendExceptFirst(", ");
+ String col = prepareColumnName(field.columnName);
+ String constraint = field.constraint.replace("{0}", col).replace("this", col);
+ where.append(constraint);
+ }
+ }
+ if (where.length() > 0) {
+ buff.append(" WHERE ");
+ buff.append(where.toString());
+ }
+
+ prepareCreateView(stat, def, buff.toString());
+ }
+
+ @Override
+ public <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def, String fromWhere) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append(prepareCreateView(def));
+ buff.append(" ");
+ buff.append(prepareTableName(def.schemaName, def.tableName));
+
+ buff.append(" AS SELECT ");
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(field.columnName));
+ }
+ buff.append(fromWhere);
+ stat.setSQL(buff.toString());
+ }
+
+ protected boolean isIntegerType(String dataType) {
+ if ("INT".equals(dataType)) {
+ return true;
+ } else if ("BIGINT".equals(dataType)) {
+ return true;
+ } else if ("TINYINT".equals(dataType)) {
+ return true;
+ } else if ("SMALLINT".equals(dataType)) {
+ return true;
+ }
+ return false;
+ }
+
+ protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,
+ boolean isAutoIncrement, boolean isPrimaryKey) {
+ buff.append(dataType);
+ if (isAutoIncrement) {
+ buff.append(" AUTO_INCREMENT");
+ }
+ return false;
+ }
+
+ @Override
+ public void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName,
+ IndexDefinition index) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append("CREATE ");
+ switch (index.type) {
+ case UNIQUE:
+ buff.append("UNIQUE ");
+ break;
+ case UNIQUE_HASH:
+ buff.append("UNIQUE ");
+ break;
+ default:
+ IciqlLogger.warn("{0} does not support hash indexes", getClass().getSimpleName());
+ }
+ buff.append("INDEX ");
+ buff.append(index.indexName);
+ buff.append(" ON ");
+ // FIXME maybe we can use schemaName ?
+ // buff.append(prepareTableName(schemaName, tableName));
+ buff.append(tableName);
+ buff.append("(");
+ for (String col : index.columnNames) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+
+ stat.setSQL(buff.toString().trim());
+ }
+
+ /**
+ * PostgreSQL and Derby do not support the SQL2003 MERGE syntax, but we can
+ * use a trick to insert a row if it does not exist and call update() in
+ * Db.merge() if the affected row count is 0.
+ * <p>
+ * Databases that do support a MERGE syntax should override this method.
+ * <p>
+ * http://stackoverflow.com/questions/407688
+ */
+ @Override
+ public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,
+ TableDefinition<T> def, Object obj) {
+ StatementBuilder buff = new StatementBuilder("INSERT INTO ");
+ buff.append(prepareTableName(schemaName, tableName));
+ buff.append(" (");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(field.columnName));
+ }
+ buff.append(") (SELECT ");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append('?');
+ Object value = def.getValue(obj, field);
+ stat.addParameter(value);
+ }
+ buff.append(" FROM ");
+ buff.append(prepareTableName(schemaName, tableName));
+ buff.append(" WHERE ");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ if (field.isPrimaryKey) {
+ buff.appendExceptFirst(" AND ");
+ buff.append(MessageFormat.format("{0} = ?", prepareColumnName(field.columnName)));
+ Object value = def.getValue(obj, field);
+ stat.addParameter(value);
+ }
+ }
+ buff.append(" HAVING count(*)=0)");
+ stat.setSQL(buff.toString());
+ }
+
+ @Override
+ public void appendLimitOffset(SQLStatement stat, long limit, long offset) {
+ if (limit > 0) {
+ stat.appendSQL(" LIMIT " + limit);
+ }
+ if (offset > 0) {
+ stat.appendSQL(" OFFSET " + offset);
+ }
+ }
+
+ @Override
+ public String prepareParameter(Object o) {
+ if (o instanceof String) {
+ return LITERAL + o.toString().replace(LITERAL, "''") + LITERAL;
+ } else if (o instanceof Character) {
+ return LITERAL + o.toString() + LITERAL;
+ } else if (o instanceof java.sql.Time) {
+ return LITERAL + new SimpleDateFormat("HH:mm:ss").format(o) + LITERAL;
+ } else if (o instanceof java.sql.Date) {
+ return LITERAL + new SimpleDateFormat("yyyy-MM-dd").format(o) + LITERAL;
+ } else if (o instanceof java.util.Date) {
+ return LITERAL + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(o) + LITERAL;
+ }
+ return o.toString();
+ }
+
+ @SuppressWarnings("incomplete-switch")
+ @Override
+ public void prepareCreateConstraintForeignKey(SQLStatement stat, String schemaName, String tableName, ConstraintForeignKeyDefinition constraint) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append("ALTER TABLE ");
+ buff.append(prepareTableName(schemaName, tableName));
+ buff.append(" ADD CONSTRAINT ");
+ buff.append(constraint.constraintName);
+ buff.append(" FOREIGN KEY ");
+ buff.append(" (");
+ for (String col : constraint.foreignColumns) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+ buff.append(" REFERENCES ");
+ buff.append(constraint.referenceTable);
+ buff.append(" (");
+ buff.resetCount();
+ for (String col : constraint.referenceColumns) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+ if (constraint.deleteType != ConstraintDeleteType.UNSET) {
+ buff.append(" ON DELETE ");
+ switch (constraint.deleteType) {
+ case CASCADE:
+ buff.append("CASCADE ");
+ break;
+ case RESTRICT:
+ buff.append("RESTRICT ");
+ break;
+ case SET_NULL:
+ buff.append("SET NULL ");
+ break;
+ case NO_ACTION:
+ buff.append("NO ACTION ");
+ break;
+ case SET_DEFAULT:
+ buff.append("SET DEFAULT ");
+ break;
+ }
+ }
+ if (constraint.updateType != ConstraintUpdateType.UNSET) {
+ buff.append(" ON UPDATE ");
+ switch (constraint.updateType) {
+ case CASCADE:
+ buff.append("CASCADE ");
+ break;
+ case RESTRICT:
+ buff.append("RESTRICT ");
+ break;
+ case SET_NULL:
+ buff.append("SET NULL ");
+ break;
+ case NO_ACTION:
+ buff.append("NO ACTION ");
+ break;
+ case SET_DEFAULT:
+ buff.append("SET DEFAULT ");
+ break;
+ }
+ }
+ switch (constraint.deferrabilityType) {
+ case DEFERRABLE_INITIALLY_DEFERRED:
+ buff.append("DEFERRABLE INITIALLY DEFERRED ");
+ break;
+ case DEFERRABLE_INITIALLY_IMMEDIATE:
+ buff.append("DEFERRABLE INITIALLY IMMEDIATE ");
+ break;
+ case NOT_DEFERRABLE:
+ buff.append("NOT DEFERRABLE ");
+ break;
+ case UNSET:
+ break;
+ }
+ stat.setSQL(buff.toString().trim());
+ }
+
+ @Override
+ public void prepareCreateConstraintUnique(SQLStatement stat, String schemaName, String tableName, ConstraintUniqueDefinition constraint) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append("ALTER TABLE ");
+ buff.append(prepareTableName(schemaName, tableName));
+ buff.append(" ADD CONSTRAINT ");
+ buff.append(constraint.constraintName);
+ buff.append(" UNIQUE ");
+ buff.append(" (");
+ for (String col : constraint.uniqueColumns) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+ stat.setSQL(buff.toString().trim());
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/com/iciql/SQLDialectDerby.java b/src/main/java/com/iciql/SQLDialectDerby.java
new file mode 100644
index 0000000..f954a7c
--- /dev/null
+++ b/src/main/java/com/iciql/SQLDialectDerby.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.util.StatementBuilder;
+
+/**
+ * Derby database dialect.
+ */
+public class SQLDialectDerby extends SQLDialectDefault {
+
+ @Override
+ public Class<? extends java.util.Date> getDateTimeClass() {
+ return java.sql.Timestamp.class;
+ }
+
+ @Override
+ public String convertSqlType(String sqlType) {
+ if ("TINYINT".equals(sqlType)) {
+ // Derby does not have a TINYINT/BYTE type
+ return "SMALLINT";
+ }
+ return sqlType;
+ }
+
+ @Override
+ public void appendLimitOffset(SQLStatement stat, long limit, long offset) {
+ // FETCH/OFFSET added in 10.5
+ if (databaseVersion >= 10.5f) {
+ if (offset > 0) {
+ stat.appendSQL(" OFFSET " + offset + (offset == 1 ? " ROW" : " ROWS"));
+ }
+ if (limit > 0) {
+ stat.appendSQL(" FETCH NEXT " + limit + (limit == 1 ? " ROW" : " ROWS") + " ONLY");
+ }
+ }
+ }
+
+ @Override
+ protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,
+ boolean isAutoIncrement, boolean isPrimaryKey) {
+ String convertedType = convertSqlType(dataType);
+ buff.append(convertedType);
+ if (isIntegerType(dataType) && isAutoIncrement) {
+ buff.append(" GENERATED BY DEFAULT AS IDENTITY");
+ }
+ return false;
+ }
+
+ @Override
+ public <T> void prepareDropTable(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder("DROP TABLE "
+ + prepareTableName(def.schemaName, def.tableName));
+ stat.setSQL(buff.toString());
+ return;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/iciql/SQLDialectH2.java b/src/main/java/com/iciql/SQLDialectH2.java
new file mode 100644
index 0000000..6b3bab1
--- /dev/null
+++ b/src/main/java/com/iciql/SQLDialectH2.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.TableDefinition.FieldDefinition;
+import com.iciql.TableDefinition.IndexDefinition;
+import com.iciql.util.StatementBuilder;
+
+/**
+ * H2 database dialect.
+ */
+public class SQLDialectH2 extends SQLDialectDefault {
+
+ /**
+ * CACHED tables are created by default. MEMORY tables are created upon
+ * request.
+ */
+ @Override
+ protected <T> String prepareCreateTable(TableDefinition<T> def) {
+ if (def.memoryTable) {
+ return "CREATE MEMORY TABLE IF NOT EXISTS";
+ } else {
+ return "CREATE CACHED TABLE IF NOT EXISTS";
+ }
+ }
+
+ @Override
+ protected <T> String prepareCreateView(TableDefinition<T> def) {
+ return "CREATE VIEW IF NOT EXISTS";
+ }
+
+ @Override
+ public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS "
+ + prepareTableName(def.schemaName, def.tableName));
+ stat.setSQL(buff.toString());
+ return;
+ }
+
+ @Override
+ protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,
+ boolean isAutoIncrement, boolean isPrimaryKey) {
+ String convertedType = convertSqlType(dataType);
+ boolean isIdentity = false;
+ if (isIntegerType(dataType)) {
+ if (isAutoIncrement && isPrimaryKey) {
+ buff.append("IDENTITY");
+ isIdentity = true;
+ } else if (isAutoIncrement) {
+ buff.append(convertedType);
+ buff.append(" AUTO_INCREMENT");
+ } else {
+ buff.append(convertedType);
+ }
+ } else {
+ buff.append(convertedType);
+ }
+ return isIdentity;
+ }
+
+ @Override
+ public void prepareCreateIndex(SQLStatement stat, String schema, String table, IndexDefinition index) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append("CREATE ");
+ switch (index.type) {
+ case STANDARD:
+ break;
+ case UNIQUE:
+ buff.append("UNIQUE ");
+ break;
+ case HASH:
+ buff.append("HASH ");
+ break;
+ case UNIQUE_HASH:
+ buff.append("UNIQUE HASH ");
+ break;
+ }
+ buff.append("INDEX IF NOT EXISTS ");
+ buff.append(index.indexName);
+ buff.append(" ON ");
+ buff.append(table);
+ buff.append("(");
+ for (String col : index.columnNames) {
+ buff.appendExceptFirst(", ");
+ buff.append(col);
+ }
+ buff.append(")");
+ stat.setSQL(buff.toString());
+ }
+
+ @Override
+ public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,
+ TableDefinition<T> def, Object obj) {
+ StatementBuilder buff = new StatementBuilder("MERGE INTO ");
+ buff.append(prepareTableName(schemaName, tableName)).append(" (");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(field.columnName);
+ }
+ buff.append(") KEY(");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ if (field.isPrimaryKey) {
+ buff.appendExceptFirst(", ");
+ buff.append(field.columnName);
+ }
+ }
+ buff.append(") ");
+ buff.resetCount();
+ buff.append("VALUES (");
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append('?');
+ Object value = def.getValue(obj, field);
+ stat.addParameter(value);
+ }
+ buff.append(')');
+ stat.setSQL(buff.toString());
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/iciql/SQLDialectHSQL.java b/src/main/java/com/iciql/SQLDialectHSQL.java
new file mode 100644
index 0000000..82e6833
--- /dev/null
+++ b/src/main/java/com/iciql/SQLDialectHSQL.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.text.MessageFormat;
+
+import com.iciql.TableDefinition.FieldDefinition;
+import com.iciql.util.StatementBuilder;
+
+/**
+ * HyperSQL database dialect.
+ */
+public class SQLDialectHSQL extends SQLDialectDefault {
+
+ /**
+ * CACHED tables are created by default. MEMORY tables are created upon
+ * request.
+ */
+ @Override
+ protected <T> String prepareCreateTable(TableDefinition<T> def) {
+ if (def.memoryTable) {
+ return "CREATE MEMORY TABLE IF NOT EXISTS";
+ } else {
+ return "CREATE CACHED TABLE IF NOT EXISTS";
+ }
+ }
+
+ @Override
+ public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS "
+ + prepareTableName(def.schemaName, def.tableName));
+ stat.setSQL(buff.toString());
+ return;
+ }
+
+ @Override
+ protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,
+ boolean isAutoIncrement, boolean isPrimaryKey) {
+ boolean isIdentity = false;
+ String convertedType = convertSqlType(dataType);
+ buff.append(convertedType);
+ if (isIntegerType(dataType) && isAutoIncrement && isPrimaryKey) {
+ buff.append(" IDENTITY");
+ isIdentity = true;
+ }
+ return isIdentity;
+ }
+
+ @Override
+ public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,
+ TableDefinition<T> def, Object obj) {
+ final String valuePrefix = "v";
+ StatementBuilder buff = new StatementBuilder("MERGE INTO ");
+ buff.append(prepareTableName(schemaName, tableName));
+ // a, b, c....
+ buff.append(" USING (VALUES(");
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append("CAST(? AS ");
+ String dataType = convertSqlType(field.dataType);
+ buff.append(dataType);
+ if ("VARCHAR".equals(dataType)) {
+ if (field.length > 0) {
+ // VARCHAR(x)
+ buff.append(MessageFormat.format("({0})", field.length));
+ }
+ } else if ("DECIMAL".equals(dataType)) {
+ if (field.length > 0) {
+ if (field.scale > 0) {
+ // DECIMAL(x,y)
+ buff.append(MessageFormat.format("({0},{1})", field.length, field.scale));
+ } else {
+ // DECIMAL(x)
+ buff.append(MessageFormat.format("({0})", field.length));
+ }
+ }
+ }
+ buff.append(')');
+ Object value = def.getValue(obj, field);
+ stat.addParameter(value);
+ }
+
+ // map to temporary table
+ buff.resetCount();
+ buff.append(")) AS vals (");
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(valuePrefix + field.columnName));
+ }
+
+ buff.append(") ON ");
+
+ // create the ON condition
+ // (va, vb) = (va,vb)
+ String[] prefixes = { "", valuePrefix };
+ for (int i = 0; i < prefixes.length; i++) {
+ String prefix = prefixes[i];
+ buff.resetCount();
+ buff.append('(');
+ for (FieldDefinition field : def.fields) {
+ if (field.isPrimaryKey) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(prefix + field.columnName));
+ }
+ }
+ buff.append(")");
+ if (i == 0) {
+ buff.append('=');
+ }
+ }
+
+ // UPDATE
+ // set a=va
+ buff.append(" WHEN MATCHED THEN UPDATE SET ");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(field.columnName));
+ buff.append('=');
+ buff.append(prepareColumnName(valuePrefix + field.columnName));
+ }
+
+ // INSERT
+ // insert va, vb, vc....
+ buff.append(" WHEN NOT MATCHED THEN INSERT ");
+ buff.resetCount();
+ buff.append(" VALUES (");
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(valuePrefix + field.columnName));
+ }
+ buff.append(')');
+ stat.setSQL(buff.toString());
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/iciql/SQLDialectMSSQL.java b/src/main/java/com/iciql/SQLDialectMSSQL.java
new file mode 100644
index 0000000..92b1297
--- /dev/null
+++ b/src/main/java/com/iciql/SQLDialectMSSQL.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 Alex Telepov.
+ * Copyright 2012 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * MS SQL Server database dialect.
+ */
+public class SQLDialectMSSQL extends SQLDialectDefault {
+
+ /**
+ * Append limit and offset rows
+ *
+ * @param stat Statement
+ * @param limit Limit rows
+ * @param offset Offset rows
+ */
+ @Override
+ public void appendLimitOffset(SQLStatement stat, long limit, long offset) {
+ if (offset > 0) {
+ throw new IciqlException("iciql does not support offset for MSSQL dialect!");
+ }
+ StringBuilder query = new StringBuilder(stat.getSQL());
+
+ // for databaseVersion >= 2012 need Offset
+ if (limit > 0) {
+ int indexSelect = query.indexOf("SELECT");
+
+ if (indexSelect >= 0) {
+ StringBuilder subPathQuery = new StringBuilder(" TOP ");
+ subPathQuery.append(Long.toString(limit));
+
+ query.insert(indexSelect + "SELECT".length(), subPathQuery);
+
+ stat.setSQL(query.toString());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/iciql/SQLDialectMySQL.java b/src/main/java/com/iciql/SQLDialectMySQL.java
new file mode 100644
index 0000000..52676d4
--- /dev/null
+++ b/src/main/java/com/iciql/SQLDialectMySQL.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.TableDefinition.FieldDefinition;
+import com.iciql.util.StatementBuilder;
+
+/**
+ * MySQL database dialect.
+ */
+public class SQLDialectMySQL extends SQLDialectDefault {
+
+ @Override
+ public String convertSqlType(String sqlType) {
+ if (sqlType.equals("CLOB")) {
+ return "TEXT";
+ }
+ return sqlType;
+ }
+
+ @Override
+ protected <T> String prepareCreateTable(TableDefinition<T> def) {
+ return "CREATE TABLE IF NOT EXISTS";
+ }
+
+ @Override
+ public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS "
+ + prepareTableName(def.schemaName, def.tableName));
+ stat.setSQL(buff.toString());
+ return;
+ }
+
+ @Override
+ public String prepareColumnName(String name) {
+ return "`" + name + "`";
+ }
+
+ @Override
+ protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType, boolean isAutoIncrement,
+ boolean isPrimaryKey) {
+ String convertedType = convertSqlType(dataType);
+ buff.append(convertedType);
+ if (isIntegerType(dataType) && isAutoIncrement) {
+ buff.append(" AUTO_INCREMENT");
+ }
+ return false;
+ }
+
+ @Override
+ public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,
+ TableDefinition<T> def, Object obj) {
+ StatementBuilder buff = new StatementBuilder("INSERT INTO ");
+ buff.append(prepareTableName(schemaName, tableName)).append(" (");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(field.columnName);
+ }
+ buff.resetCount();
+ buff.append(") VALUES (");
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append('?');
+ Object value = def.getValue(obj, field);
+ stat.addParameter(value);
+ }
+ buff.append(") ON DUPLICATE KEY UPDATE ");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(field.columnName);
+ buff.append("=VALUES(");
+ buff.append(field.columnName);
+ buff.append(')');
+ }
+ stat.setSQL(buff.toString());
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/iciql/SQLDialectPostgreSQL.java b/src/main/java/com/iciql/SQLDialectPostgreSQL.java
new file mode 100644
index 0000000..fc115ab
--- /dev/null
+++ b/src/main/java/com/iciql/SQLDialectPostgreSQL.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.TableDefinition.IndexDefinition;
+import com.iciql.util.StatementBuilder;
+
+/**
+ * PostgreSQL database dialect.
+ */
+public class SQLDialectPostgreSQL extends SQLDialectDefault {
+
+ @Override
+ public Class<? extends java.util.Date> getDateTimeClass() {
+ return java.sql.Timestamp.class;
+ }
+
+ @Override
+ public String convertSqlType(String sqlType) {
+ if ("DOUBLE".equals(sqlType)) {
+ return "DOUBLE PRECISION";
+ } else if ("TINYINT".equals(sqlType)) {
+ // PostgreSQL does not have a byte type
+ return "SMALLINT";
+ } else if ("CLOB".equals(sqlType)) {
+ return "TEXT";
+ } else if ("BLOB".equals(sqlType)) {
+ return "BYTEA";
+ }
+ return sqlType;
+ }
+
+ @Override
+ protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,
+ boolean isAutoIncrement, boolean isPrimaryKey) {
+ String convertedType = convertSqlType(dataType);
+ if (isIntegerType(dataType)) {
+ if (isAutoIncrement) {
+ if ("BIGINT".equals(dataType)) {
+ buff.append("BIGSERIAL");
+ } else {
+ buff.append("SERIAL");
+ }
+ } else {
+ buff.append(convertedType);
+ }
+ } else {
+ buff.append(convertedType);
+ }
+ return false;
+ }
+
+ @Override
+ public void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName,
+ IndexDefinition index) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append("CREATE ");
+ switch (index.type) {
+ case UNIQUE:
+ buff.append("UNIQUE ");
+ break;
+ case UNIQUE_HASH:
+ buff.append("UNIQUE ");
+ break;
+ }
+ buff.append("INDEX ");
+ buff.append(index.indexName);
+ buff.append(" ON ");
+ buff.append(tableName);
+
+ switch (index.type) {
+ case HASH:
+ buff.append(" USING HASH");
+ break;
+ case UNIQUE_HASH:
+ buff.append(" USING HASH");
+ break;
+ }
+
+ buff.append(" (");
+ for (String col : index.columnNames) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+
+ stat.setSQL(buff.toString().trim());
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/iciql/SQLStatement.java b/src/main/java/com/iciql/SQLStatement.java
new file mode 100644
index 0000000..394fc42
--- /dev/null
+++ b/src/main/java/com/iciql/SQLStatement.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+import com.iciql.util.JdbcUtils;
+
+/**
+ * This class represents a parameterized SQL statement.
+ */
+
+public class SQLStatement {
+ private Db db;
+ private StringBuilder buff = new StringBuilder();
+ private String sql;
+ private ArrayList<Object> params = new ArrayList<Object>();
+
+ SQLStatement(Db db) {
+ this.db = db;
+ }
+
+ public void setSQL(String sql) {
+ this.sql = sql;
+ buff = new StringBuilder(sql);
+ }
+
+ public SQLStatement appendSQL(String s) {
+ buff.append(s);
+ sql = null;
+ return this;
+ }
+
+ public SQLStatement appendTable(String schema, String table) {
+ return appendSQL(db.getDialect().prepareTableName(schema, table));
+ }
+
+ public SQLStatement appendColumn(String column) {
+ return appendSQL(db.getDialect().prepareColumnName(column));
+ }
+
+ /**
+ * getSQL returns a simple string representation of the parameterized
+ * statement which will be used later, internally, with prepareStatement.
+ *
+ * @return a simple sql statement
+ */
+ String getSQL() {
+ if (sql == null) {
+ sql = buff.toString();
+ }
+ return sql;
+ }
+
+ /**
+ * toSQL creates a static sql statement with the referenced parameters
+ * encoded in the statement.
+ *
+ * @return a complete sql statement
+ */
+ String toSQL() {
+ if (sql == null) {
+ sql = buff.toString();
+ }
+ if (params.size() == 0) {
+ return sql;
+ }
+ StringBuilder sb = new StringBuilder();
+ // TODO this needs to me more sophisticated
+ StringTokenizer st = new StringTokenizer(sql, "?", false);
+ int i = 0;
+ while (st.hasMoreTokens()) {
+ sb.append(st.nextToken());
+ if (i < params.size()) {
+ Object o = params.get(i);
+ if (RuntimeParameter.PARAMETER == o) {
+ // dynamic parameter
+ sb.append('?');
+ } else {
+ // static parameter
+ sb.append(db.getDialect().prepareParameter(o));
+ }
+ i++;
+ }
+ }
+ return sb.toString();
+ }
+
+ public SQLStatement addParameter(Object o) {
+ // Automatically convert java.util.Date to java.sql.Timestamp
+ // if the dialect requires java.sql.Timestamp objects (e.g. Derby)
+ if (o != null && o.getClass().equals(java.util.Date.class)
+ && db.getDialect().getDateTimeClass().equals(java.sql.Timestamp.class)) {
+ o = new java.sql.Timestamp(((java.util.Date) o).getTime());
+ }
+ params.add(o);
+ return this;
+ }
+
+ void execute() {
+ PreparedStatement ps = null;
+ try {
+ ps = prepare(false);
+ ps.execute();
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(ps);
+ }
+ }
+
+ ResultSet executeQuery() {
+ try {
+ return prepare(false).executeQuery();
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(getSQL(), e);
+ }
+ }
+
+ int executeUpdate() {
+ PreparedStatement ps = null;
+ try {
+ ps = prepare(false);
+ return ps.executeUpdate();
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(ps);
+ }
+ }
+
+ long executeInsert() {
+ PreparedStatement ps = null;
+ try {
+ ps = prepare(true);
+ ps.executeUpdate();
+ long identity = -1;
+ ResultSet rs = ps.getGeneratedKeys();
+ if (rs != null && rs.next()) {
+ identity = rs.getLong(1);
+ }
+ JdbcUtils.closeSilently(rs);
+ return identity;
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(ps);
+ }
+ }
+
+ private void setValue(PreparedStatement prep, int parameterIndex, Object x) {
+ try {
+ prep.setObject(parameterIndex, x);
+ } catch (SQLException e) {
+ IciqlException ix = new IciqlException(e, "error setting parameter {0} as {1}", parameterIndex, x
+ .getClass().getSimpleName());
+ ix.setSQL(getSQL());
+ throw ix;
+ }
+ }
+
+ PreparedStatement prepare(boolean returnGeneratedKeys) {
+ PreparedStatement prep = db.prepare(getSQL(), returnGeneratedKeys);
+ for (int i = 0; i < params.size(); i++) {
+ Object o = params.get(i);
+ setValue(prep, i + 1, o);
+ }
+ return prep;
+ }
+
+}
diff --git a/src/main/java/com/iciql/SelectColumn.java b/src/main/java/com/iciql/SelectColumn.java
new file mode 100644
index 0000000..43a1a93
--- /dev/null
+++ b/src/main/java/com/iciql/SelectColumn.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.TableDefinition.FieldDefinition;
+
+/**
+ * This class represents a column of a table in a query.
+ *
+ * @param <T>
+ * the table data type
+ */
+
+class SelectColumn<T> {
+ private SelectTable<T> selectTable;
+ private FieldDefinition fieldDef;
+
+ SelectColumn(SelectTable<T> table, FieldDefinition fieldDef) {
+ this.selectTable = table;
+ this.fieldDef = fieldDef;
+ }
+
+ void appendSQL(SQLStatement stat) {
+ if (selectTable.getQuery().isJoin()) {
+ stat.appendSQL(selectTable.getAs() + "." + fieldDef.columnName);
+ } else {
+ stat.appendColumn(fieldDef.columnName);
+ }
+ }
+
+ FieldDefinition getFieldDefinition() {
+ return fieldDef;
+ }
+
+ SelectTable<T> getSelectTable() {
+ return selectTable;
+ }
+
+ Object getCurrentValue() {
+ return fieldDef.getValue(selectTable.getCurrent());
+ }
+}
diff --git a/src/main/java/com/iciql/SelectTable.java b/src/main/java/com/iciql/SelectTable.java
new file mode 100644
index 0000000..37b42c4
--- /dev/null
+++ b/src/main/java/com/iciql/SelectTable.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.util.ArrayList;
+
+import com.iciql.util.Utils;
+
+/**
+ * This class represents a table in a query.
+ *
+ * @param <T>
+ * the table class
+ */
+
+class SelectTable<T> {
+
+ private Query<T> query;
+ private Class<T> clazz;
+ private T current;
+ private String as;
+ private TableDefinition<T> aliasDef;
+ private boolean outerJoin;
+ private ArrayList<Token> joinConditions = Utils.newArrayList();
+ private T alias;
+
+ @SuppressWarnings("unchecked")
+ SelectTable(Db db, Query<T> query, T alias, boolean outerJoin) {
+ this.alias = alias;
+ this.query = query;
+ this.outerJoin = outerJoin;
+ aliasDef = (TableDefinition<T>) db.getTableDefinition(alias.getClass());
+ clazz = Utils.getClass(alias);
+ as = "T" + Utils.nextAsCount();
+ }
+
+ T getAlias() {
+ return alias;
+ }
+
+ T newObject() {
+ return Utils.newObject(clazz);
+ }
+
+ TableDefinition<T> getAliasDefinition() {
+ return aliasDef;
+ }
+
+ void appendSQL(SQLStatement stat) {
+ if (query.isJoin()) {
+ stat.appendTable(aliasDef.schemaName, aliasDef.tableName).appendSQL(" AS " + as);
+ } else {
+ stat.appendTable(aliasDef.schemaName, aliasDef.tableName);
+ }
+ }
+
+ void appendSQLAsJoin(SQLStatement stat, Query<T> q) {
+ if (outerJoin) {
+ stat.appendSQL(" LEFT OUTER JOIN ");
+ } else {
+ stat.appendSQL(" INNER JOIN ");
+ }
+ appendSQL(stat);
+ if (!joinConditions.isEmpty()) {
+ stat.appendSQL(" ON ");
+ for (Token token : joinConditions) {
+ token.appendSQL(stat, q);
+ stat.appendSQL(" ");
+ }
+ }
+ }
+
+ boolean getOuterJoin() {
+ return outerJoin;
+ }
+
+ Query<T> getQuery() {
+ return query;
+ }
+
+ String getAs() {
+ return as;
+ }
+
+ void addConditionToken(Token condition) {
+ joinConditions.add(condition);
+ }
+
+ T getCurrent() {
+ return current;
+ }
+
+ void setCurrent(T current) {
+ this.current = current;
+ }
+
+}
diff --git a/src/main/java/com/iciql/SubQuery.java b/src/main/java/com/iciql/SubQuery.java
new file mode 100644
index 0000000..398d214
--- /dev/null
+++ b/src/main/java/com/iciql/SubQuery.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+public class SubQuery<T, Z> {
+
+ final Query<T> query;
+ final Z z;
+
+ public SubQuery(Query<T> query, Z x) {
+ this.query = query;
+ this.z = x;
+ }
+
+ public void appendSQL(SQLStatement stat) {
+ stat.appendSQL(query.toSubQuery(z));
+ }
+}
diff --git a/src/main/java/com/iciql/SubQueryCondition.java b/src/main/java/com/iciql/SubQueryCondition.java
new file mode 100644
index 0000000..effea3b
--- /dev/null
+++ b/src/main/java/com/iciql/SubQueryCondition.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * A condition that contains a subquery.
+ *
+ * @param <A>
+ * the operand type
+ */
+
+class SubQueryCondition<A, Y, Z> implements Token {
+ A x;
+ SubQuery<Y, Z> subquery;
+
+ SubQueryCondition(A x, SubQuery<Y, Z> subquery) {
+ this.x = x;
+ this.subquery = subquery;
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ query.appendSQL(stat, null, x);
+ stat.appendSQL(" in (");
+ subquery.appendSQL(stat);
+ stat.appendSQL(")");
+ }
+}
diff --git a/src/main/java/com/iciql/TableDefinition.java b/src/main/java/com/iciql/TableDefinition.java
new file mode 100644
index 0000000..6d8cb6e
--- /dev/null
+++ b/src/main/java/com/iciql/TableDefinition.java
@@ -0,0 +1,1233 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import java.lang.reflect.Field;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.iciql.Iciql.ConstraintDeferrabilityType;
+import com.iciql.Iciql.ConstraintDeleteType;
+import com.iciql.Iciql.ConstraintUpdateType;
+import com.iciql.Iciql.EnumId;
+import com.iciql.Iciql.EnumType;
+import com.iciql.Iciql.IQColumn;
+import com.iciql.Iciql.IQConstraint;
+import com.iciql.Iciql.IQContraintUnique;
+import com.iciql.Iciql.IQContraintsUnique;
+import com.iciql.Iciql.IQEnum;
+import com.iciql.Iciql.IQContraintForeignKey;
+import com.iciql.Iciql.IQContraintsForeignKey;
+import com.iciql.Iciql.IQIgnore;
+import com.iciql.Iciql.IQIndex;
+import com.iciql.Iciql.IQIndexes;
+import com.iciql.Iciql.IQSchema;
+import com.iciql.Iciql.IQTable;
+import com.iciql.Iciql.IQVersion;
+import com.iciql.Iciql.IQView;
+import com.iciql.Iciql.IndexType;
+import com.iciql.util.IciqlLogger;
+import com.iciql.util.StatementBuilder;
+import com.iciql.util.StringUtils;
+import com.iciql.util.Utils;
+
+/**
+ * A table definition contains the index definitions of a table, the field
+ * definitions, the table name, and other meta data.
+ *
+ * @param <T>
+ * the table type
+ */
+
+public class TableDefinition<T> {
+
+ /**
+ * The meta data of an index.
+ */
+
+ public static class IndexDefinition {
+ public IndexType type;
+ public String indexName;
+
+ public List<String> columnNames;
+ }
+
+ /**
+ * The meta data of a constraint on foreign key.
+ */
+
+ public static class ConstraintForeignKeyDefinition {
+
+ public String constraintName;
+ public List<String> foreignColumns;
+ public String referenceTable;
+ public List<String> referenceColumns;
+ public ConstraintDeleteType deleteType = ConstraintDeleteType.UNSET;
+ public ConstraintUpdateType updateType = ConstraintUpdateType.UNSET;
+ public ConstraintDeferrabilityType deferrabilityType = ConstraintDeferrabilityType.UNSET;
+ }
+
+ /**
+ * The meta data of a unique constraint.
+ */
+
+ public static class ConstraintUniqueDefinition {
+
+ public String constraintName;
+ public List<String> uniqueColumns;
+ }
+
+
+ /**
+ * The meta data of a field.
+ */
+
+ static class FieldDefinition {
+ String columnName;
+ Field field;
+ String dataType;
+ int length;
+ int scale;
+ boolean isPrimaryKey;
+ boolean isAutoIncrement;
+ boolean trim;
+ boolean nullable;
+ String defaultValue;
+ EnumType enumType;
+ boolean isPrimitive;
+ String constraint;
+
+ Object getValue(Object obj) {
+ try {
+ return field.get(obj);
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ private Object initWithNewObject(Object obj) {
+ Object o = Utils.newObject(field.getType());
+ setValue(obj, o);
+ return o;
+ }
+
+ private void setValue(Object obj, Object o) {
+ try {
+ if (!field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ Class<?> targetType = field.getType();
+ if (targetType.isEnum()) {
+ o = Utils.convertEnum(o, targetType, enumType);
+ } else {
+ o = Utils.convert(o, targetType);
+ }
+ field.set(obj, o);
+ } catch (IciqlException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ private Object read(ResultSet rs, int columnIndex) {
+ try {
+ return rs.getObject(columnIndex);
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return columnName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof FieldDefinition) {
+ return o.hashCode() == hashCode();
+ }
+ return false;
+ }
+ }
+
+ public ArrayList<FieldDefinition> fields = Utils.newArrayList();
+ String schemaName;
+ String tableName;
+ String viewTableName;
+ int tableVersion;
+ List<String> primaryKeyColumnNames;
+ boolean memoryTable;
+ boolean multiplePrimitiveBools;
+
+ private boolean createIfRequired = true;
+ private Class<T> clazz;
+ private IdentityHashMap<Object, FieldDefinition> fieldMap = Utils.newIdentityHashMap();
+ private ArrayList<IndexDefinition> indexes = Utils.newArrayList();
+ private ArrayList<ConstraintForeignKeyDefinition> constraintsForeignKey = Utils.newArrayList();
+ private ArrayList<ConstraintUniqueDefinition> constraintsUnique = Utils.newArrayList();
+
+ TableDefinition(Class<T> clazz) {
+ this.clazz = clazz;
+ schemaName = null;
+ tableName = clazz.getSimpleName();
+ }
+
+ Class<T> getModelClass() {
+ return clazz;
+ }
+
+ List<FieldDefinition> getFields() {
+ return fields;
+ }
+
+ void defineSchemaName(String schemaName) {
+ this.schemaName = schemaName;
+ }
+
+ void defineTableName(String tableName) {
+ this.tableName = tableName;
+ }
+
+ void defineViewTableName(String viewTableName) {
+ this.viewTableName = viewTableName;
+ }
+
+ void defineMemoryTable() {
+ this.memoryTable = true;
+ }
+
+ void defineSkipCreate() {
+ this.createIfRequired = false;
+ }
+
+ /**
+ * Define a primary key by the specified model fields.
+ *
+ * @param modelFields
+ * the ordered list of model fields
+ */
+ void definePrimaryKey(Object[] modelFields) {
+ List<String> columnNames = mapColumnNames(modelFields);
+ setPrimaryKey(columnNames);
+ }
+
+ /**
+ * Define a primary key by the specified column names.
+ *
+ * @param columnNames
+ * the ordered list of column names
+ */
+ private void setPrimaryKey(List<String> columnNames) {
+ primaryKeyColumnNames = Utils.newArrayList(columnNames);
+ List<String> pkNames = Utils.newArrayList();
+ for (String name : columnNames) {
+ pkNames.add(name.toLowerCase());
+ }
+ // set isPrimaryKey flag for all field definitions
+ for (FieldDefinition fieldDefinition : fieldMap.values()) {
+ fieldDefinition.isPrimaryKey = pkNames.contains(fieldDefinition.columnName.toLowerCase());
+ }
+ }
+
+ private <A> String getColumnName(A fieldObject) {
+ FieldDefinition def = fieldMap.get(fieldObject);
+ return def == null ? null : def.columnName;
+ }
+
+ private ArrayList<String> mapColumnNames(Object[] columns) {
+ ArrayList<String> columnNames = Utils.newArrayList();
+ for (Object column : columns) {
+ columnNames.add(getColumnName(column));
+ }
+ return columnNames;
+ }
+
+ /**
+ * Defines an index with the specified model fields.
+ *
+ * @param name
+ * the index name (optional)
+ * @param type
+ * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
+ * @param modelFields
+ * the ordered list of model fields
+ */
+ void defineIndex(String name, IndexType type, Object[] modelFields) {
+ List<String> columnNames = mapColumnNames(modelFields);
+ addIndex(name, type, columnNames);
+ }
+
+ /**
+ * Defines an index with the specified column names.
+ *
+ * @param type
+ * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
+ * @param columnNames
+ * the ordered list of column names
+ */
+ private void addIndex(String name, IndexType type, List<String> columnNames) {
+ IndexDefinition index = new IndexDefinition();
+ if (StringUtils.isNullOrEmpty(name)) {
+ index.indexName = tableName + "_idx_" + indexes.size();
+ } else {
+ index.indexName = name;
+ }
+ index.columnNames = Utils.newArrayList(columnNames);
+ index.type = type;
+ indexes.add(index);
+ }
+
+ /**
+ * Defines an unique constraint with the specified model fields.
+ *
+ * @param name
+ * the constraint name (optional)
+ * @param modelFields
+ * the ordered list of model fields
+ */
+ void defineConstraintUnique(String name, Object[] modelFields) {
+ List<String> columnNames = mapColumnNames(modelFields);
+ addConstraintUnique(name, columnNames);
+ }
+
+ /**
+ * Defines an unique constraint.
+ *
+ * @param name
+ * @param columnNames
+ */
+ private void addConstraintUnique(String name, List<String> columnNames) {
+ ConstraintUniqueDefinition constraint = new ConstraintUniqueDefinition();
+ if (StringUtils.isNullOrEmpty(name)) {
+ constraint.constraintName = tableName + "_unique_" + constraintsUnique.size();
+ } else {
+ constraint.constraintName = name;
+ }
+ constraint.uniqueColumns = Utils.newArrayList(columnNames);
+ constraintsUnique.add(constraint);
+ }
+
+ /**
+ * Defines a foreign key constraint with the specified model fields.
+ *
+ * @param name
+ * the constraint name (optional)
+ * @param modelFields
+ * the ordered list of model fields
+ */
+ void defineForeignKey(String name, Object[] modelFields, String refTableName, Object[] refModelFields,
+ ConstraintDeleteType deleteType, ConstraintUpdateType updateType,
+ ConstraintDeferrabilityType deferrabilityType) {
+ List<String> columnNames = mapColumnNames(modelFields);
+ List<String> referenceColumnNames = mapColumnNames(refModelFields);
+ addConstraintForeignKey(name, columnNames, refTableName, referenceColumnNames,
+ deleteType, updateType, deferrabilityType);
+ }
+
+ void defineColumnName(Object column, String columnName) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.columnName = columnName;
+ }
+ }
+
+ void defineAutoIncrement(Object column) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.isAutoIncrement = true;
+ }
+ }
+
+ void defineLength(Object column, int length) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.length = length;
+ }
+ }
+
+ void defineScale(Object column, int scale) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.scale = scale;
+ }
+ }
+
+ void defineTrim(Object column) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.trim = true;
+ }
+ }
+
+ void defineNullable(Object column, boolean isNullable) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.nullable = isNullable;
+ }
+ }
+
+ void defineDefaultValue(Object column, String defaultValue) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.defaultValue = defaultValue;
+ }
+ }
+
+ void defineConstraint(Object column, String constraint) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.constraint = constraint;
+ }
+ }
+
+ void mapFields() {
+ boolean byAnnotationsOnly = false;
+ boolean inheritColumns = false;
+ if (clazz.isAnnotationPresent(IQTable.class)) {
+ IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
+ byAnnotationsOnly = tableAnnotation.annotationsOnly();
+ inheritColumns = tableAnnotation.inheritColumns();
+ }
+
+ if (clazz.isAnnotationPresent(IQView.class)) {
+ IQView viewAnnotation = clazz.getAnnotation(IQView.class);
+ byAnnotationsOnly = viewAnnotation.annotationsOnly();
+ inheritColumns = viewAnnotation.inheritColumns();
+ }
+
+ List<Field> classFields = Utils.newArrayList();
+ classFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
+ if (inheritColumns) {
+ Class<?> superClass = clazz.getSuperclass();
+ classFields.addAll(Arrays.asList(superClass.getDeclaredFields()));
+
+ if (superClass.isAnnotationPresent(IQView.class)) {
+ IQView superView = superClass.getAnnotation(IQView.class);
+ if (superView.inheritColumns()) {
+ // inherit columns from super.super.class
+ Class<?> superSuperClass = superClass.getSuperclass();
+ classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields()));
+ }
+ } else if (superClass.isAnnotationPresent(IQTable.class)) {
+ IQTable superTable = superClass.getAnnotation(IQTable.class);
+ if (superTable.inheritColumns()) {
+ // inherit columns from super.super.class
+ Class<?> superSuperClass = superClass.getSuperclass();
+ classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields()));
+ }
+ }
+ }
+
+ Set<FieldDefinition> uniqueFields = new LinkedHashSet<FieldDefinition>();
+ T defaultObject = Db.instance(clazz);
+ for (Field f : classFields) {
+ // check if we should skip this field
+ if (f.isAnnotationPresent(IQIgnore.class)) {
+ continue;
+ }
+
+ // default to field name
+ String columnName = f.getName();
+ boolean isAutoIncrement = false;
+ boolean isPrimaryKey = false;
+ int length = 0;
+ int scale = 0;
+ boolean trim = false;
+ boolean nullable = !f.getType().isPrimitive();
+ EnumType enumType = null;
+ String defaultValue = "";
+ String constraint = "";
+ // configure Java -> SQL enum mapping
+ if (f.getType().isEnum()) {
+ enumType = EnumType.DEFAULT_TYPE;
+ if (f.getType().isAnnotationPresent(IQEnum.class)) {
+ // enum definition is annotated for all instances
+ IQEnum iqenum = f.getType().getAnnotation(IQEnum.class);
+ enumType = iqenum.value();
+ }
+ if (f.isAnnotationPresent(IQEnum.class)) {
+ // this instance of the enum is annotated
+ IQEnum iqenum = f.getAnnotation(IQEnum.class);
+ enumType = iqenum.value();
+ }
+ }
+
+ // try using default object
+ try {
+ f.setAccessible(true);
+ Object value = f.get(defaultObject);
+ if (value != null) {
+ if (value.getClass().isEnum()) {
+ // enum default, convert to target type
+ Enum<?> anEnum = (Enum<?>) value;
+ Object o = Utils.convertEnum(anEnum, enumType);
+ defaultValue = ModelUtils.formatDefaultValue(o);
+ } else {
+ // object default
+ defaultValue = ModelUtils.formatDefaultValue(value);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new IciqlException(e, "failed to get default object for {0}", columnName);
+ }
+
+ boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class);
+ if (hasAnnotation) {
+ IQColumn col = f.getAnnotation(IQColumn.class);
+ if (!StringUtils.isNullOrEmpty(col.name())) {
+ columnName = col.name();
+ }
+ isAutoIncrement = col.autoIncrement();
+ isPrimaryKey = col.primaryKey();
+ length = col.length();
+ scale = col.scale();
+ trim = col.trim();
+ nullable = col.nullable();
+
+ // annotation overrides
+ if (!StringUtils.isNullOrEmpty(col.defaultValue())) {
+ defaultValue = col.defaultValue();
+ }
+ }
+
+ boolean hasConstraint = f.isAnnotationPresent(IQConstraint.class);
+ if (hasConstraint) {
+ IQConstraint con = f.getAnnotation(IQConstraint.class);
+ // annotation overrides
+ if (!StringUtils.isNullOrEmpty(con.value())) {
+ constraint = con.value();
+ }
+ }
+
+ boolean reflectiveMatch = !byAnnotationsOnly;
+ if (reflectiveMatch || hasAnnotation || hasConstraint) {
+ FieldDefinition fieldDef = new FieldDefinition();
+ fieldDef.isPrimitive = f.getType().isPrimitive();
+ fieldDef.field = f;
+ fieldDef.columnName = columnName;
+ fieldDef.isAutoIncrement = isAutoIncrement;
+ fieldDef.isPrimaryKey = isPrimaryKey;
+ fieldDef.length = length;
+ fieldDef.scale = scale;
+ fieldDef.trim = trim;
+ fieldDef.nullable = nullable;
+ fieldDef.defaultValue = defaultValue;
+ fieldDef.enumType = enumType;
+ fieldDef.dataType = ModelUtils.getDataType(fieldDef);
+ fieldDef.constraint = constraint;
+ uniqueFields.add(fieldDef);
+ }
+ }
+ fields.addAll(uniqueFields);
+
+ List<String> primaryKey = Utils.newArrayList();
+ int primitiveBoolean = 0;
+ for (FieldDefinition fieldDef : fields) {
+ if (fieldDef.isPrimaryKey) {
+ primaryKey.add(fieldDef.columnName);
+ }
+ if (fieldDef.isPrimitive && fieldDef.field.getType().equals(boolean.class)) {
+ primitiveBoolean++;
+ }
+ }
+ if (primitiveBoolean > 1) {
+ multiplePrimitiveBools = true;
+ IciqlLogger
+ .warn("Model {0} has multiple primitive booleans! Possible where,set,join clause problem!");
+ }
+ if (primaryKey.size() > 0) {
+ setPrimaryKey(primaryKey);
+ }
+ }
+
+ void checkMultipleBooleans() {
+ if (multiplePrimitiveBools) {
+ throw new IciqlException(
+ "Can not explicitly reference a primitive boolean if there are multiple boolean fields in your model class!");
+ }
+ }
+
+ void checkMultipleEnums(Object o) {
+ if (o == null) {
+ return;
+ }
+ Class<?> clazz = o.getClass();
+ if (!clazz.isEnum()) {
+ return;
+ }
+
+ int fieldCount = 0;
+ for (FieldDefinition fieldDef : fields) {
+ Class<?> targetType = fieldDef.field.getType();
+ if (clazz.equals(targetType)) {
+ fieldCount++;
+ }
+ }
+
+ if (fieldCount > 1) {
+ throw new IciqlException(
+ "Can not explicitly reference {0} because there are {1} {0} fields in your model class!",
+ clazz.getSimpleName(), fieldCount);
+ }
+ }
+
+ /**
+ * Optionally truncates strings to the maximum length and converts
+ * java.lang.Enum types to Strings or Integers.
+ */
+ Object getValue(Object obj, FieldDefinition field) {
+ Object value = field.getValue(obj);
+ if (value == null) {
+ return value;
+ }
+ if (field.enumType != null) {
+ // convert enumeration to INT or STRING
+ Enum<?> iqenum = (Enum<?>) value;
+ switch (field.enumType) {
+ case NAME:
+ if (field.trim && field.length > 0) {
+ if (iqenum.name().length() > field.length) {
+ return iqenum.name().substring(0, field.length);
+ }
+ }
+ return iqenum.name();
+ case ORDINAL:
+ return iqenum.ordinal();
+ case ENUMID:
+ if (!EnumId.class.isAssignableFrom(value.getClass())) {
+ throw new IciqlException(field.field.getName() + " does not implement EnumId!");
+ }
+ EnumId enumid = (EnumId) value;
+ return enumid.enumId();
+ }
+ }
+
+ if (field.trim && field.length > 0) {
+ if (value instanceof String) {
+ // clip strings
+ String s = (String) value;
+ if (s.length() > field.length) {
+ return s.substring(0, field.length);
+ }
+ return s;
+ }
+ return value;
+ }
+
+ // return the value unchanged
+ return value;
+ }
+
+ PreparedStatement createInsertStatement(Db db, Object obj, boolean returnKey) {
+ SQLStatement stat = new SQLStatement(db);
+ StatementBuilder buff = new StatementBuilder("INSERT INTO ");
+ buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');
+ for (FieldDefinition field : fields) {
+ if (skipInsertField(field, obj)) {
+ continue;
+ }
+ buff.appendExceptFirst(", ");
+ buff.append(db.getDialect().prepareColumnName(field.columnName));
+ }
+ buff.append(") VALUES(");
+ buff.resetCount();
+ for (FieldDefinition field : fields) {
+ if (skipInsertField(field, obj)) {
+ continue;
+ }
+ buff.appendExceptFirst(", ");
+ buff.append('?');
+ Object value = getValue(obj, field);
+ if (value == null && !field.nullable) {
+ // try to interpret and instantiate a default value
+ value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());
+ }
+ stat.addParameter(value);
+ }
+ buff.append(')');
+ stat.setSQL(buff.toString());
+ IciqlLogger.insert(stat.getSQL());
+ return stat.prepare(returnKey);
+ }
+
+ long insert(Db db, Object obj, boolean returnKey) {
+ if (!StringUtils.isNullOrEmpty(viewTableName)) {
+ throw new IciqlException("Iciql does not support inserting rows into views!");
+ }
+ SQLStatement stat = new SQLStatement(db);
+ StatementBuilder buff = new StatementBuilder("INSERT INTO ");
+ buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');
+ for (FieldDefinition field : fields) {
+ if (skipInsertField(field, obj)) {
+ continue;
+ }
+ buff.appendExceptFirst(", ");
+ buff.append(db.getDialect().prepareColumnName(field.columnName));
+ }
+ buff.append(") VALUES(");
+ buff.resetCount();
+ for (FieldDefinition field : fields) {
+ if (skipInsertField(field, obj)) {
+ continue;
+ }
+ buff.appendExceptFirst(", ");
+ buff.append('?');
+ Object value = getValue(obj, field);
+ if (value == null && !field.nullable) {
+ // try to interpret and instantiate a default value
+ value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());
+ }
+ stat.addParameter(value);
+ }
+ buff.append(')');
+ stat.setSQL(buff.toString());
+ IciqlLogger.insert(stat.getSQL());
+ if (returnKey) {
+ return stat.executeInsert();
+ }
+ return stat.executeUpdate();
+ }
+
+ private boolean skipInsertField(FieldDefinition field, Object obj) {
+ if (field.isAutoIncrement) {
+ Object value = getValue(obj, field);
+ if (field.isPrimitive) {
+ // skip uninitialized primitive autoincrement values
+ if (value.toString().equals("0")) {
+ return true;
+ }
+ } else if (value == null) {
+ // skip null object autoincrement values
+ return true;
+ }
+ } else {
+ // conditionally skip insert of null
+ Object value = getValue(obj, field);
+ if (value == null) {
+ if (field.nullable) {
+ // skip null assignment, field is nullable
+ return true;
+ } else if (StringUtils.isNullOrEmpty(field.defaultValue)) {
+ IciqlLogger.warn("no default value, skipping null insert assignment for {0}.{1}",
+ tableName, field.columnName);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ int merge(Db db, Object obj) {
+ if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
+ throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()
+ + " - no update possible");
+ }
+ SQLStatement stat = new SQLStatement(db);
+ db.getDialect().prepareMerge(stat, schemaName, tableName, this, obj);
+ IciqlLogger.merge(stat.getSQL());
+ return stat.executeUpdate();
+ }
+
+ int update(Db db, Object obj) {
+ if (!StringUtils.isNullOrEmpty(viewTableName)) {
+ throw new IciqlException("Iciql does not support updating rows in views!");
+ }
+ if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
+ throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()
+ + " - no update possible");
+ }
+ SQLStatement stat = new SQLStatement(db);
+ StatementBuilder buff = new StatementBuilder("UPDATE ");
+ buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" SET ");
+ buff.resetCount();
+
+ for (FieldDefinition field : fields) {
+ if (!field.isPrimaryKey) {
+ Object value = getValue(obj, field);
+ if (value == null && !field.nullable) {
+ // try to interpret and instantiate a default value
+ value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());
+ }
+ buff.appendExceptFirst(", ");
+ buff.append(db.getDialect().prepareColumnName(field.columnName));
+ buff.append(" = ?");
+ stat.addParameter(value);
+ }
+ }
+ Object alias = Utils.newObject(obj.getClass());
+ Query<Object> query = Query.from(db, alias);
+ boolean firstCondition = true;
+ for (FieldDefinition field : fields) {
+ if (field.isPrimaryKey) {
+ Object fieldAlias = field.getValue(alias);
+ Object value = field.getValue(obj);
+ if (field.isPrimitive) {
+ fieldAlias = query.getPrimitiveAliasByValue(fieldAlias);
+ }
+ if (!firstCondition) {
+ query.addConditionToken(ConditionAndOr.AND);
+ }
+ firstCondition = false;
+ query.addConditionToken(new Condition<Object>(fieldAlias, value, CompareType.EQUAL));
+ }
+ }
+ stat.setSQL(buff.toString());
+ query.appendWhere(stat);
+ IciqlLogger.update(stat.getSQL());
+ return stat.executeUpdate();
+ }
+
+ int delete(Db db, Object obj) {
+ if (!StringUtils.isNullOrEmpty(viewTableName)) {
+ throw new IciqlException("Iciql does not support deleting rows from views!");
+ }
+ if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
+ throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()
+ + " - no update possible");
+ }
+ SQLStatement stat = new SQLStatement(db);
+ StatementBuilder buff = new StatementBuilder("DELETE FROM ");
+ buff.append(db.getDialect().prepareTableName(schemaName, tableName));
+ buff.resetCount();
+ Object alias = Utils.newObject(obj.getClass());
+ Query<Object> query = Query.from(db, alias);
+ boolean firstCondition = true;
+ for (FieldDefinition field : fields) {
+ if (field.isPrimaryKey) {
+ Object fieldAlias = field.getValue(alias);
+ Object value = field.getValue(obj);
+ if (field.isPrimitive) {
+ fieldAlias = query.getPrimitiveAliasByValue(fieldAlias);
+ }
+ if (!firstCondition) {
+ query.addConditionToken(ConditionAndOr.AND);
+ }
+ firstCondition = false;
+ query.addConditionToken(new Condition<Object>(fieldAlias, value, CompareType.EQUAL));
+ }
+ }
+ stat.setSQL(buff.toString());
+ query.appendWhere(stat);
+ IciqlLogger.delete(stat.getSQL());
+ return stat.executeUpdate();
+ }
+
+ TableDefinition<T> createIfRequired(Db db) {
+ // globally enable/disable check of create if required
+ if (db.getSkipCreate()) {
+ return this;
+ }
+ if (!createIfRequired) {
+ // skip table and index creation
+ // but still check for upgrades
+ db.upgradeTable(this);
+ return this;
+ }
+ if (db.hasCreated(clazz)) {
+ return this;
+ }
+ SQLStatement stat = new SQLStatement(db);
+ if (StringUtils.isNullOrEmpty(viewTableName)) {
+ db.getDialect().prepareCreateTable(stat, this);
+ } else {
+ db.getDialect().prepareCreateView(stat, this);
+ }
+ IciqlLogger.create(stat.getSQL());
+ try {
+ stat.executeUpdate();
+ } catch (IciqlException e) {
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS) {
+ throw e;
+ }
+ }
+
+ // create indexes
+ for (IndexDefinition index : indexes) {
+ stat = new SQLStatement(db);
+ db.getDialect().prepareCreateIndex(stat, schemaName, tableName, index);
+ IciqlLogger.create(stat.getSQL());
+ try {
+ stat.executeUpdate();
+ } catch (IciqlException e) {
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS
+ && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) {
+ throw e;
+ }
+ }
+ }
+
+ // create unique constraints
+ for (ConstraintUniqueDefinition constraint : constraintsUnique) {
+ stat = new SQLStatement(db);
+ db.getDialect().prepareCreateConstraintUnique(stat, schemaName, tableName, constraint);
+ IciqlLogger.create(stat.getSQL());
+ try {
+ stat.executeUpdate();
+ } catch (IciqlException e) {
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS
+ && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) {
+ throw e;
+ }
+ }
+ }
+
+ // create foreign keys constraints
+ for (ConstraintForeignKeyDefinition constraint : constraintsForeignKey) {
+ stat = new SQLStatement(db);
+ db.getDialect().prepareCreateConstraintForeignKey(stat, schemaName, tableName, constraint);
+ IciqlLogger.create(stat.getSQL());
+ try {
+ stat.executeUpdate();
+ } catch (IciqlException e) {
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS
+ && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) {
+ throw e;
+ }
+ }
+ }
+
+ // tables are created using IF NOT EXISTS
+ // but we may still need to upgrade
+ db.upgradeTable(this);
+ return this;
+ }
+
+ void mapObject(Object obj) {
+ fieldMap.clear();
+ initObject(obj, fieldMap);
+
+ if (clazz.isAnnotationPresent(IQSchema.class)) {
+ IQSchema schemaAnnotation = clazz.getAnnotation(IQSchema.class);
+ // setup schema name mapping, if properly annotated
+ if (!StringUtils.isNullOrEmpty(schemaAnnotation.value())) {
+ schemaName = schemaAnnotation.value();
+ }
+ }
+
+ if (clazz.isAnnotationPresent(IQTable.class)) {
+ IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
+
+ // setup table name mapping, if properly annotated
+ if (!StringUtils.isNullOrEmpty(tableAnnotation.name())) {
+ tableName = tableAnnotation.name();
+ }
+
+ // allow control over createTableIfRequired()
+ createIfRequired = tableAnnotation.create();
+
+ // model version
+ if (clazz.isAnnotationPresent(IQVersion.class)) {
+ IQVersion versionAnnotation = clazz.getAnnotation(IQVersion.class);
+ if (versionAnnotation.value() > 0) {
+ tableVersion = versionAnnotation.value();
+ }
+ }
+
+ // setup the primary index, if properly annotated
+ if (tableAnnotation.primaryKey().length > 0) {
+ List<String> primaryKey = Utils.newArrayList();
+ primaryKey.addAll(Arrays.asList(tableAnnotation.primaryKey()));
+ setPrimaryKey(primaryKey);
+ }
+ }
+
+ if (clazz.isAnnotationPresent(IQView.class)) {
+ IQView viewAnnotation = clazz.getAnnotation(IQView.class);
+
+ // setup view name mapping, if properly annotated
+ // set this as the table name so it fits in seemlessly with iciql
+ if (!StringUtils.isNullOrEmpty(viewAnnotation.name())) {
+ tableName = viewAnnotation.name();
+ } else {
+ tableName = clazz.getSimpleName();
+ }
+
+ // setup source table name mapping, if properly annotated
+ if (!StringUtils.isNullOrEmpty(viewAnnotation.tableName())) {
+ viewTableName = viewAnnotation.tableName();
+ } else {
+ // check for IQTable annotation on super class
+ Class<?> superClass = clazz.getSuperclass();
+ if (superClass.isAnnotationPresent(IQTable.class)) {
+ IQTable table = superClass.getAnnotation(IQTable.class);
+ if (StringUtils.isNullOrEmpty(table.name())) {
+ // super.SimpleClassName
+ viewTableName = superClass.getSimpleName();
+ } else {
+ // super.IQTable.name()
+ viewTableName = table.name();
+ }
+ } else if (superClass.isAnnotationPresent(IQView.class)) {
+ // super class is a view
+ IQView parentView = superClass.getAnnotation(IQView.class);
+ if (StringUtils.isNullOrEmpty(parentView.tableName())) {
+ // parent view does not define a tableName, must be inherited
+ Class<?> superParent = superClass.getSuperclass();
+ if (superParent != null && superParent.isAnnotationPresent(IQTable.class)) {
+ IQTable superParentTable = superParent.getAnnotation(IQTable.class);
+ if (StringUtils.isNullOrEmpty(superParentTable.name())) {
+ // super.super.SimpleClassName
+ viewTableName = superParent.getSimpleName();
+ } else {
+ // super.super.IQTable.name()
+ viewTableName = superParentTable.name();
+ }
+ }
+ } else {
+ // super.IQView.tableName()
+ viewTableName = parentView.tableName();
+ }
+ }
+
+ if (StringUtils.isNullOrEmpty(viewTableName)) {
+ // still missing view table name
+ throw new IciqlException("View model class \"{0}\" is missing a table name!", tableName);
+ }
+ }
+
+ // allow control over createTableIfRequired()
+ createIfRequired = viewAnnotation.create();
+ }
+
+ if (clazz.isAnnotationPresent(IQIndex.class)) {
+ // single table index
+ IQIndex index = clazz.getAnnotation(IQIndex.class);
+ addIndex(index);
+ }
+
+ if (clazz.isAnnotationPresent(IQIndexes.class)) {
+ // multiple table indexes
+ IQIndexes indexes = clazz.getAnnotation(IQIndexes.class);
+ for (IQIndex index : indexes.value()) {
+ addIndex(index);
+ }
+ }
+
+ if (clazz.isAnnotationPresent(IQContraintUnique.class)) {
+ // single table unique constraint
+ IQContraintUnique constraint = clazz.getAnnotation(IQContraintUnique.class);
+ addConstraintUnique(constraint);
+ }
+
+ if (clazz.isAnnotationPresent(IQContraintsUnique.class)) {
+ // multiple table unique constraints
+ IQContraintsUnique constraints = clazz.getAnnotation(IQContraintsUnique.class);
+ for (IQContraintUnique constraint : constraints.value()) {
+ addConstraintUnique(constraint);
+ }
+ }
+
+ if (clazz.isAnnotationPresent(IQContraintForeignKey.class)) {
+ // single table constraint
+ IQContraintForeignKey constraint = clazz.getAnnotation(IQContraintForeignKey.class);
+ addConstraintForeignKey(constraint);
+ }
+
+ if (clazz.isAnnotationPresent(IQContraintsForeignKey.class)) {
+ // multiple table constraints
+ IQContraintsForeignKey constraints = clazz.getAnnotation(IQContraintsForeignKey.class);
+ for (IQContraintForeignKey constraint : constraints.value()) {
+ addConstraintForeignKey(constraint);
+ }
+ }
+
+ }
+
+ private void addConstraintForeignKey(IQContraintForeignKey constraint) {
+ List<String> foreignColumns = Arrays.asList(constraint.foreignColumns());
+ List<String> referenceColumns = Arrays.asList(constraint.referenceColumns());
+ addConstraintForeignKey(constraint.name(), foreignColumns, constraint.referenceName(), referenceColumns, constraint.deleteType(), constraint.updateType(), constraint.deferrabilityType());
+ }
+
+ private void addConstraintUnique(IQContraintUnique constraint) {
+ List<String> uniqueColumns = Arrays.asList(constraint.uniqueColumns());
+ addConstraintUnique(constraint.name(), uniqueColumns);
+ }
+
+ /**
+ * Defines a foreign key constraint with the specified parameters.
+ *
+ * @param name
+ * name of the constraint
+ * @param foreignColumns
+ * list of columns declared as foreign
+ * @param referenceName
+ * reference table name
+ * @param referenceColumns
+ * list of columns used in reference table
+ * @param deleteType
+ * action on delete
+ * @param updateType
+ * action on update
+ * @param deferrabilityType
+ * deferrability mode
+ */
+ private void addConstraintForeignKey(String name,
+ List<String> foreignColumns, String referenceName,
+ List<String> referenceColumns, ConstraintDeleteType deleteType,
+ ConstraintUpdateType updateType, ConstraintDeferrabilityType deferrabilityType) {
+ ConstraintForeignKeyDefinition constraint = new ConstraintForeignKeyDefinition();
+ if (StringUtils.isNullOrEmpty(name)) {
+ constraint.constraintName = tableName + "_fkey_" + constraintsForeignKey.size();
+ } else {
+ constraint.constraintName = name;
+ }
+ constraint.foreignColumns = Utils.newArrayList(foreignColumns);
+ constraint.referenceColumns = Utils.newArrayList(referenceColumns);
+ constraint.referenceTable = referenceName;
+ constraint.deleteType = deleteType;
+ constraint.updateType = updateType;
+ constraint.deferrabilityType = deferrabilityType;
+ constraintsForeignKey.add(constraint);
+ }
+
+ private void addIndex(IQIndex index) {
+ List<String> columns = Arrays.asList(index.value());
+ addIndex(index.name(), index.type(), columns);
+ }
+
+ List<IndexDefinition> getIndexes() {
+ return indexes;
+ }
+
+ List<ConstraintUniqueDefinition> getContraintsUnique() {
+ return constraintsUnique;
+ }
+
+ List<ConstraintForeignKeyDefinition> getContraintsForeignKey() {
+ return constraintsForeignKey;
+ }
+
+ private void initObject(Object obj, Map<Object, FieldDefinition> map) {
+ for (FieldDefinition def : fields) {
+ Object newValue = def.initWithNewObject(obj);
+ map.put(newValue, def);
+ }
+ }
+
+ void initSelectObject(SelectTable<T> table, Object obj, Map<Object, SelectColumn<T>> map) {
+ for (FieldDefinition def : fields) {
+ Object newValue = def.initWithNewObject(obj);
+ SelectColumn<T> column = new SelectColumn<T>(table, def);
+ map.put(newValue, column);
+ }
+ }
+
+ /**
+ * Most queries executed by iciql have named select lists (select alpha,
+ * beta where...) but sometimes a wildcard select is executed (select *).
+ * When a wildcard query is executed on a table that has more columns than
+ * are mapped in your model object, this creates a column mapping issue.
+ * JaQu assumed that you can always use the integer index of the
+ * reflectively mapped field definition to determine position in the result
+ * set.
+ *
+ * This is not always true.
+ *
+ * iciql identifies when a select * query is executed and maps column names
+ * to a column index from the result set. If the select statement is
+ * explicit, then the standard assumed column index is used instead.
+ *
+ * @param rs
+ * @return
+ */
+ int[] mapColumns(boolean wildcardSelect, ResultSet rs) {
+ int[] columns = new int[fields.size()];
+ for (int i = 0; i < fields.size(); i++) {
+ try {
+ FieldDefinition def = fields.get(i);
+ int columnIndex;
+ if (wildcardSelect) {
+ // select *
+ // create column index by field name
+ columnIndex = rs.findColumn(def.columnName);
+ } else {
+ // select alpha, beta, gamma, etc
+ // explicit select order
+ columnIndex = i + 1;
+ }
+ columns[i] = columnIndex;
+ } catch (SQLException s) {
+ throw new IciqlException(s);
+ }
+ }
+ return columns;
+ }
+
+ void readRow(Object item, ResultSet rs, int[] columns) {
+ for (int i = 0; i < fields.size(); i++) {
+ FieldDefinition def = fields.get(i);
+ int index = columns[i];
+ Object o = def.read(rs, index);
+ def.setValue(item, o);
+ }
+ }
+
+ void appendSelectList(SQLStatement stat) {
+ for (int i = 0; i < fields.size(); i++) {
+ if (i > 0) {
+ stat.appendSQL(", ");
+ }
+ FieldDefinition def = fields.get(i);
+ stat.appendColumn(def.columnName);
+ }
+ }
+
+ <Y, X> void appendSelectList(SQLStatement stat, Query<Y> query, X x) {
+ // select t0.col1, t0.col2, t0.col3...
+ // select table1.col1, table1.col2, table1.col3...
+ String selectDot = "";
+ SelectTable<?> sel = query.getSelectTable(x);
+ if (sel != null) {
+ if (query.isJoin()) {
+ selectDot = sel.getAs() + ".";
+ } else {
+ String sn = sel.getAliasDefinition().schemaName;
+ String tn = sel.getAliasDefinition().tableName;
+ selectDot = query.getDb().getDialect().prepareTableName(sn, tn) + ".";
+ }
+ }
+
+ for (int i = 0; i < fields.size(); i++) {
+ if (i > 0) {
+ stat.appendSQL(", ");
+ }
+ stat.appendSQL(selectDot);
+ FieldDefinition def = fields.get(i);
+ if (def.isPrimitive) {
+ Object obj = def.getValue(x);
+ Object alias = query.getPrimitiveAliasByValue(obj);
+ query.appendSQL(stat, x, alias);
+ } else {
+ Object obj = def.getValue(x);
+ query.appendSQL(stat, x, obj);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/iciql/TableInspector.java b/src/main/java/com/iciql/TableInspector.java
new file mode 100644
index 0000000..b717203
--- /dev/null
+++ b/src/main/java/com/iciql/TableInspector.java
@@ -0,0 +1,723 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import static com.iciql.ValidationRemark.consider;
+import static com.iciql.ValidationRemark.error;
+import static com.iciql.ValidationRemark.warn;
+import static com.iciql.util.JdbcUtils.closeSilently;
+import static com.iciql.util.StringUtils.isNullOrEmpty;
+import static java.text.MessageFormat.format;
+
+import java.io.Serializable;
+import java.lang.reflect.Modifier;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.iciql.Iciql.IQColumn;
+import com.iciql.Iciql.IQIndex;
+import com.iciql.Iciql.IQIndexes;
+import com.iciql.Iciql.IQSchema;
+import com.iciql.Iciql.IQTable;
+import com.iciql.Iciql.IndexType;
+import com.iciql.TableDefinition.ConstraintForeignKeyDefinition;
+import com.iciql.TableDefinition.ConstraintUniqueDefinition;
+import com.iciql.TableDefinition.FieldDefinition;
+import com.iciql.TableDefinition.IndexDefinition;
+import com.iciql.util.StatementBuilder;
+import com.iciql.util.StringUtils;
+import com.iciql.util.Utils;
+
+/**
+ * Class to inspect the contents of a particular table including its indexes.
+ * This class does the bulk of the work in terms of model generation and model
+ * validation.
+ */
+public class TableInspector {
+
+ private String schema;
+ private String table;
+ private Class<? extends java.util.Date> dateTimeClass;
+ private List<String> primaryKeys = Utils.newArrayList();
+ private Map<String, IndexInspector> indexes;
+ private Map<String, ColumnInspector> columns;
+ private final String eol = "\n";
+
+ TableInspector(String schema, String table, Class<? extends java.util.Date> dateTimeClass) {
+ this.schema = schema;
+ this.table = table;
+ this.dateTimeClass = dateTimeClass;
+ }
+
+ /**
+ * Tests to see if this TableInspector represents schema.table.
+ * <p>
+ *
+ * @param schema
+ * the schema name
+ * @param table
+ * the table name
+ * @return true if the table matches
+ */
+ boolean matches(String schema, String table) {
+ if (isNullOrEmpty(schema)) {
+ // table name matching
+ return this.table.equalsIgnoreCase(table);
+ } else if (isNullOrEmpty(table)) {
+ // schema name matching
+ return this.schema.equalsIgnoreCase(schema);
+ } else {
+ // exact table matching
+ return this.schema.equalsIgnoreCase(schema) && this.table.equalsIgnoreCase(table);
+ }
+ }
+
+ /**
+ * Reads the DatabaseMetaData for the details of this table including
+ * primary keys and indexes.
+ *
+ * @param metaData
+ * the database meta data
+ */
+ void read(DatabaseMetaData metaData) throws SQLException {
+ ResultSet rs = null;
+
+ // primary keys
+ try {
+ rs = metaData.getPrimaryKeys(null, schema, table);
+ while (rs.next()) {
+ String c = rs.getString("COLUMN_NAME");
+ primaryKeys.add(c);
+ }
+ closeSilently(rs);
+
+ // indexes
+ rs = metaData.getIndexInfo(null, schema, table, false, true);
+ indexes = Utils.newHashMap();
+ while (rs.next()) {
+ IndexInspector info = new IndexInspector(rs);
+ if (info.type.equals(IndexType.UNIQUE)) {
+ String name = info.name.toLowerCase();
+ if (name.startsWith("primary") || name.startsWith("sys_idx_sys_pk")
+ || name.startsWith("sql") || name.endsWith("_pkey")) {
+ // skip primary key indexes
+ continue;
+ }
+ }
+ if (indexes.containsKey(info.name)) {
+ indexes.get(info.name).addColumn(rs);
+ } else {
+ indexes.put(info.name, info);
+ }
+ }
+ closeSilently(rs);
+
+ // columns
+ rs = metaData.getColumns(null, schema, table, null);
+ columns = Utils.newHashMap();
+ while (rs.next()) {
+ ColumnInspector col = new ColumnInspector();
+ col.name = rs.getString("COLUMN_NAME");
+ col.type = rs.getString("TYPE_NAME");
+ col.clazz = ModelUtils.getClassForSqlType(col.type, dateTimeClass);
+ col.size = rs.getInt("COLUMN_SIZE");
+ col.nullable = rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable;
+ try {
+ Object autoIncrement = rs.getObject("IS_AUTOINCREMENT");
+ if (autoIncrement instanceof Boolean) {
+ col.isAutoIncrement = (Boolean) autoIncrement;
+ } else if (autoIncrement instanceof String) {
+ String val = autoIncrement.toString().toLowerCase();
+ col.isAutoIncrement = val.equals("true") | val.equals("yes");
+ } else if (autoIncrement instanceof Number) {
+ Number n = (Number) autoIncrement;
+ col.isAutoIncrement = n.intValue() > 0;
+ }
+ } catch (SQLException s) {
+// throw s;
+ }
+ if (primaryKeys.size() == 1) {
+ if (col.name.equalsIgnoreCase(primaryKeys.get(0))) {
+ col.isPrimaryKey = true;
+ }
+ }
+ if (!col.isAutoIncrement) {
+ col.defaultValue = rs.getString("COLUMN_DEF");
+ }
+ columns.put(col.name.toLowerCase(), col);
+ }
+ } finally {
+ closeSilently(rs);
+ }
+ }
+
+ /**
+ * Generates a model (class definition) from this table. The model includes
+ * indexes, primary keys, default values, lengths, and nullables.
+ * information.
+ * <p>
+ * The caller may optionally set a destination package name, whether or not
+ * to include the schema name (setting schema can be a problem when using
+ * the model between databases), and if to automatically trim strings for
+ * those that have a maximum length.
+ * <p>
+ *
+ * @param packageName
+ * @param annotateSchema
+ * @param trimStrings
+ * @return a complete model (class definition) for this table as a string
+ */
+ String generateModel(String packageName, boolean annotateSchema, boolean trimStrings) {
+
+ // import statements
+ Set<String> imports = Utils.newHashSet();
+ imports.add(Serializable.class.getCanonicalName());
+ imports.add(IQSchema.class.getCanonicalName());
+ imports.add(IQTable.class.getCanonicalName());
+ imports.add(IQIndexes.class.getCanonicalName());
+ imports.add(IQIndex.class.getCanonicalName());
+ imports.add(IQColumn.class.getCanonicalName());
+ imports.add(IndexType.class.getCanonicalName());
+
+ // fields
+ StringBuilder fields = new StringBuilder();
+ List<ColumnInspector> sortedColumns = Utils.newArrayList(columns.values());
+ Collections.sort(sortedColumns);
+ for (ColumnInspector col : sortedColumns) {
+ fields.append(generateColumn(imports, col, trimStrings));
+ }
+
+ // build complete class definition
+ StringBuilder model = new StringBuilder();
+ if (!isNullOrEmpty(packageName)) {
+ // package
+ model.append("package " + packageName + ";");
+ model.append(eol).append(eol);
+ }
+
+ // imports
+ List<String> sortedImports = new ArrayList<String>(imports);
+ Collections.sort(sortedImports);
+ for (String imp : sortedImports) {
+ model.append("import ").append(imp).append(';').append(eol);
+ }
+ model.append(eol);
+
+ // @IQSchema
+ if (annotateSchema && !isNullOrEmpty(schema)) {
+ model.append('@').append(IQSchema.class.getSimpleName());
+ model.append('(');
+ AnnotationBuilder ap = new AnnotationBuilder();
+ ap.addParameter(null, schema);
+ model.append(ap);
+ model.append(')').append(eol);
+ }
+
+ // @IQTable
+ model.append('@').append(IQTable.class.getSimpleName());
+ model.append('(');
+
+ // IQTable annotation parameters
+ AnnotationBuilder ap = new AnnotationBuilder();
+ ap.addParameter("name", table);
+
+ if (primaryKeys.size() > 1) {
+ ap.addParameter("primaryKey", primaryKeys);
+ }
+
+ // finish @IQTable annotation
+ model.append(ap);
+ model.append(')').append(eol);
+
+ // @IQIndexes
+ // @IQIndex
+ String indexAnnotations = generateIndexAnnotations();
+ if (!StringUtils.isNullOrEmpty(indexAnnotations)) {
+ model.append(indexAnnotations);
+ }
+
+ // class declaration
+ String clazzName = ModelUtils.convertTableToClassName(table);
+ model.append(format("public class {0} implements Serializable '{'", clazzName)).append(eol);
+ model.append(eol);
+ model.append("\tprivate static final long serialVersionUID = 1L;").append(eol);
+ model.append(eol);
+
+ // field declarations
+ model.append(fields);
+
+ // default constructor
+ model.append("\t" + "public ").append(clazzName).append("() {").append(eol);
+ model.append("\t}").append(eol);
+
+ // end of class body
+ model.append('}');
+ model.trimToSize();
+ return model.toString();
+ }
+
+ /**
+ * Generates the specified index annotation.
+ *
+ * @param ap
+ */
+ String generateIndexAnnotations() {
+ if (indexes == null || indexes.size() == 0) {
+ // no matching indexes
+ return null;
+ }
+ AnnotationBuilder ap = new AnnotationBuilder();
+ if (indexes.size() == 1) {
+ // single index
+ IndexInspector index = indexes.values().toArray(new IndexInspector[1])[0];
+ ap.append(generateIndexAnnotation(index));
+ ap.append(eol);
+ } else {
+ // multiple indexes
+ ap.append('@').append(IQIndexes.class.getSimpleName());
+ ap.append("({");
+ ap.resetCount();
+ for (IndexInspector index : indexes.values()) {
+ ap.appendExceptFirst(", ");
+ ap.append(generateIndexAnnotation(index));
+ }
+ ap.append("})").append(eol);
+ }
+ return ap.toString();
+ }
+
+ private String generateIndexAnnotation(IndexInspector index) {
+ AnnotationBuilder ap = new AnnotationBuilder();
+ ap.append('@').append(IQIndex.class.getSimpleName());
+ ap.append('(');
+ ap.resetCount();
+ if (!StringUtils.isNullOrEmpty(index.name)) {
+ ap.addParameter("name", index.name);
+ }
+ if (!index.type.equals(IndexType.STANDARD)) {
+ ap.addEnum("type", index.type);
+ }
+ if (ap.getCount() > 0) {
+ // multiple fields specified
+ ap.addParameter("value", index.columns);
+ } else {
+ // default value
+ ap.addParameter(null, index.columns);
+ }
+ ap.append(')');
+ return ap.toString();
+ }
+
+ private StatementBuilder generateColumn(Set<String> imports, ColumnInspector col, boolean trimStrings) {
+ StatementBuilder sb = new StatementBuilder();
+ Class<?> clazz = col.clazz;
+ String column = ModelUtils.convertColumnToFieldName(col.name.toLowerCase());
+ sb.append('\t');
+ if (clazz == null) {
+ // unsupported type
+ clazz = Object.class;
+ sb.append("// unsupported type " + col.type);
+ } else {
+ // Imports
+ // don't import primitives, java.lang classes, or byte []
+ if (clazz.getPackage() == null) {
+ } else if (clazz.getPackage().getName().equals("java.lang")) {
+ } else if (clazz.equals(byte[].class)) {
+ } else {
+ imports.add(clazz.getCanonicalName());
+ }
+ // @IQColumn
+ sb.append('@').append(IQColumn.class.getSimpleName());
+
+ // IQColumn annotation parameters
+ AnnotationBuilder ap = new AnnotationBuilder();
+
+ // IQColumn.name
+ if (!col.name.equalsIgnoreCase(column)) {
+ ap.addParameter("name", col.name);
+ }
+
+ // IQColumn.primaryKey
+ // composite primary keys are annotated on the table
+ if (col.isPrimaryKey && primaryKeys.size() == 1) {
+ ap.addParameter("primaryKey=true");
+ }
+
+ // IQColumn.length
+ if ((clazz == String.class) && (col.size > 0) && (col.size < Integer.MAX_VALUE)) {
+ ap.addParameter("length", col.size);
+
+ // IQColumn.trim
+ if (trimStrings) {
+ ap.addParameter("trim=true");
+ }
+ } else {
+ // IQColumn.AutoIncrement
+ if (col.isAutoIncrement) {
+ ap.addParameter("autoIncrement=true");
+ }
+ }
+
+ // IQColumn.nullable
+ if (!col.nullable) {
+ ap.addParameter("nullable=false");
+ }
+
+ // IQColumn.defaultValue
+ if (!isNullOrEmpty(col.defaultValue)) {
+ ap.addParameter("defaultValue=\"" + col.defaultValue + "\"");
+ }
+
+ // add leading and trailing ()
+ if (ap.length() > 0) {
+ ap.insert(0, '(');
+ ap.append(')');
+ }
+ sb.append(ap);
+ }
+ sb.append(eol);
+
+ // variable declaration
+ sb.append("\t" + "public ");
+ sb.append(clazz.getSimpleName());
+ sb.append(' ');
+ sb.append(column);
+ sb.append(';');
+ sb.append(eol).append(eol);
+ return sb;
+ }
+
+ /**
+ * Validates that a table definition (annotated, interface, or both) matches
+ * the current state of the table and indexes in the database. Results are
+ * returned as a list of validation remarks which includes recommendations,
+ * warnings, and errors about the model. The caller may choose to have
+ * validate throw an exception on any validation ERROR.
+ *
+ * @param def
+ * the table definition
+ * @param throwError
+ * whether or not to throw an exception if an error was found
+ * @return a list if validation remarks
+ */
+ <T> List<ValidationRemark> validate(TableDefinition<T> def, boolean throwError) {
+ List<ValidationRemark> remarks = Utils.newArrayList();
+
+ // model class definition validation
+ if (!Modifier.isPublic(def.getModelClass().getModifiers())) {
+ remarks.add(error(table, "SCHEMA",
+ format("Class {0} MUST BE PUBLIC!", def.getModelClass().getCanonicalName())).throwError(
+ throwError));
+ }
+
+ // Schema Validation
+ if (!isNullOrEmpty(schema)) {
+ if (isNullOrEmpty(def.schemaName)) {
+ remarks.add(consider(table, "SCHEMA",
+ format("@{0}(\"{1}\")", IQSchema.class.getSimpleName(), schema)));
+ } else if (!schema.equalsIgnoreCase(def.schemaName)) {
+ remarks.add(error(
+ table,
+ "SCHEMA",
+ format("@{0}(\"{1}\") != {2}", IQSchema.class.getSimpleName(), def.schemaName, schema))
+ .throwError(throwError));
+ }
+ }
+
+ // index validation
+ for (IndexInspector index : indexes.values()) {
+ validate(remarks, def, index, throwError);
+ }
+
+ // field column validation
+ for (FieldDefinition fieldDef : def.getFields()) {
+ validate(remarks, fieldDef, throwError);
+ }
+ return remarks;
+ }
+
+ /**
+ * Validates an inspected index from the database against the
+ * IndexDefinition within the TableDefinition.
+ */
+ private <T> void validate(List<ValidationRemark> remarks, TableDefinition<T> def, IndexInspector index,
+ boolean throwError) {
+ List<IndexDefinition> defIndexes = def.getIndexes();
+ if (defIndexes.size() > indexes.size()) {
+ remarks.add(warn(table, IndexType.STANDARD.name(), "More model indexes than database indexes"));
+ } else if (defIndexes.size() < indexes.size()) {
+ remarks.add(warn(table, IndexType.STANDARD.name(), "Model class is missing indexes"));
+ }
+ // TODO complete index validation.
+ // need to actually compare index types and columns within each index.
+
+ // TODO add constraints validation
+ List<ConstraintUniqueDefinition> defContraintsU = def.getContraintsUnique();
+ List<ConstraintForeignKeyDefinition> defContraintsFK = def.getContraintsForeignKey();
+ }
+
+ /**
+ * Validates a column against the model's field definition. Checks for
+ * existence, supported type, type mapping, default value, defined lengths,
+ * primary key, autoincrement.
+ */
+ private void validate(List<ValidationRemark> remarks, FieldDefinition fieldDef, boolean throwError) {
+ // unknown field
+ if (!columns.containsKey(fieldDef.columnName.toLowerCase())) {
+ // unknown column mapping
+ remarks.add(error(table, fieldDef, "Does not exist in database!").throwError(throwError));
+ return;
+ }
+ ColumnInspector col = columns.get(fieldDef.columnName.toLowerCase());
+ Class<?> fieldClass = fieldDef.field.getType();
+ Class<?> jdbcClass = ModelUtils.getClassForSqlType(col.type, dateTimeClass);
+
+ // supported type check
+ // iciql maps to VARCHAR for unsupported types.
+ if (fieldDef.dataType.equals("VARCHAR") && (fieldClass != String.class)) {
+ remarks.add(error(table, fieldDef,
+ "iciql does not currently implement support for " + fieldClass.getName()).throwError(
+ throwError));
+ }
+ // number types
+ if (!fieldClass.equals(jdbcClass)) {
+ if (Number.class.isAssignableFrom(fieldClass)) {
+ remarks.add(warn(
+ table,
+ col,
+ format("Precision mismatch: ModelObject={0}, ColumnObject={1}",
+ fieldClass.getSimpleName(), jdbcClass.getSimpleName())));
+ } else {
+ if (!Date.class.isAssignableFrom(jdbcClass)) {
+ remarks.add(warn(
+ table,
+ col,
+ format("Object Mismatch: ModelObject={0}, ColumnObject={1}",
+ fieldClass.getSimpleName(), jdbcClass.getSimpleName())));
+ }
+ }
+ }
+
+ // string types
+ if (fieldClass == String.class) {
+ if ((fieldDef.length != col.size) && (col.size < Integer.MAX_VALUE)) {
+ remarks.add(warn(
+ table,
+ col,
+ format("{0}.length={1}, ColumnMaxLength={2}", IQColumn.class.getSimpleName(),
+ fieldDef.length, col.size)));
+ }
+ if (fieldDef.length > 0 && !fieldDef.trim) {
+ remarks.add(consider(table, col, format("{0}.trim=true will prevent IciqlExceptions on"
+ + " INSERT or UPDATE, but will clip data!", IQColumn.class.getSimpleName())));
+ }
+ }
+
+ // numeric autoIncrement
+ if (fieldDef.isAutoIncrement != col.isAutoIncrement) {
+ remarks.add(warn(
+ table,
+ col,
+ format("{0}.autoIncrement={1}" + " while Column autoIncrement={2}",
+ IQColumn.class.getSimpleName(), fieldDef.isAutoIncrement, col.isAutoIncrement)));
+ }
+ // default value
+ if (!col.isAutoIncrement && !col.isPrimaryKey) {
+ String defaultValue = null;
+ if (fieldDef.defaultValue != null && fieldDef.defaultValue instanceof String) {
+ defaultValue = fieldDef.defaultValue.toString();
+ }
+ // check Model.defaultValue format
+ if (!ModelUtils.isProperlyFormattedDefaultValue(defaultValue)) {
+ remarks.add(error(
+ table,
+ col,
+ format("{0}.defaultValue=\"{1}\"" + " is improperly formatted!",
+ IQColumn.class.getSimpleName(), defaultValue)).throwError(throwError));
+ // next field
+ return;
+ }
+ // compare Model.defaultValue to Column.defaultValue
+ if (isNullOrEmpty(defaultValue) && !isNullOrEmpty(col.defaultValue)) {
+ // Model.defaultValue is NULL, Column.defaultValue is NOT NULL
+ remarks.add(warn(
+ table,
+ col,
+ format("{0}.defaultValue=\"\"" + " while column default=\"{1}\"",
+ IQColumn.class.getSimpleName(), col.defaultValue)));
+ } else if (!isNullOrEmpty(defaultValue) && isNullOrEmpty(col.defaultValue)) {
+ // Column.defaultValue is NULL, Model.defaultValue is NOT NULL
+ remarks.add(warn(
+ table,
+ col,
+ format("{0}.defaultValue=\"{1}\"" + " while column default=\"\"",
+ IQColumn.class.getSimpleName(), defaultValue)));
+ } else if (!isNullOrEmpty(defaultValue) && !isNullOrEmpty(col.defaultValue)) {
+ if (!defaultValue.equals(col.defaultValue)) {
+ // Model.defaultValue != Column.defaultValue
+ remarks.add(warn(
+ table,
+ col,
+ format("{0}.defaultValue=\"{1}\"" + " while column default=\"{2}\"",
+ IQColumn.class.getSimpleName(), defaultValue, col.defaultValue)));
+ }
+ }
+
+ // sanity check Model.defaultValue literal value
+ if (!ModelUtils.isValidDefaultValue(fieldDef.field.getType(), defaultValue)) {
+ remarks.add(error(
+ table,
+ col,
+ format("{0}.defaultValue=\"{1}\" is invalid!", IQColumn.class.getSimpleName(),
+ defaultValue)));
+ }
+ }
+ }
+
+ /**
+ * Represents an index as it exists in the database.
+ */
+ private static class IndexInspector {
+
+ String name;
+ IndexType type;
+ private List<String> columns = new ArrayList<String>();
+
+ public IndexInspector(ResultSet rs) throws SQLException {
+ name = rs.getString("INDEX_NAME");
+
+ // determine index type
+ boolean hash = rs.getInt("TYPE") == DatabaseMetaData.tableIndexHashed;
+ boolean unique = !rs.getBoolean("NON_UNIQUE");
+
+ if (!hash && !unique) {
+ type = IndexType.STANDARD;
+ } else if (hash && unique) {
+ type = IndexType.UNIQUE_HASH;
+ } else if (unique) {
+ type = IndexType.UNIQUE;
+ } else if (hash) {
+ type = IndexType.HASH;
+ }
+ columns.add(rs.getString("COLUMN_NAME"));
+ }
+
+ public void addColumn(ResultSet rs) throws SQLException {
+ columns.add(rs.getString("COLUMN_NAME"));
+ }
+ }
+
+ /**
+ * Represents a column as it exists in the database.
+ */
+ static class ColumnInspector implements Comparable<ColumnInspector> {
+ String name;
+ String type;
+ int size;
+ boolean nullable;
+ Class<?> clazz;
+ boolean isPrimaryKey;
+ boolean isAutoIncrement;
+ String defaultValue;
+
+ public int compareTo(ColumnInspector o) {
+ if (isPrimaryKey && o.isPrimaryKey) {
+ // both primary sort by name
+ return name.compareTo(o.name);
+ } else if (isPrimaryKey && !o.isPrimaryKey) {
+ // primary first
+ return -1;
+ } else if (!isPrimaryKey && o.isPrimaryKey) {
+ // primary first
+ return 1;
+ } else {
+ // neither primary, sort by name
+ return name.compareTo(o.name);
+ }
+ }
+ }
+
+ /**
+ * Convenience class based on StatementBuilder for creating the annotation
+ * parameter list.
+ */
+ private static class AnnotationBuilder extends StatementBuilder {
+
+ AnnotationBuilder() {
+ super();
+ }
+
+ void addParameter(String parameter) {
+
+ appendExceptFirst(", ");
+ append(parameter);
+ }
+
+ <T> void addParameter(String parameter, T value) {
+ appendExceptFirst(", ");
+ if (!StringUtils.isNullOrEmpty(parameter)) {
+ append(parameter);
+ append('=');
+ }
+ if (value instanceof List) {
+ append("{ ");
+ List<?> list = (List<?>) value;
+ StatementBuilder flat = new StatementBuilder();
+ for (Object o : list) {
+ flat.appendExceptFirst(", ");
+ if (o instanceof String) {
+ flat.append('\"');
+ }
+ // TODO escape string
+ flat.append(o.toString().trim());
+ if (o instanceof String) {
+ flat.append('\"');
+ }
+ }
+ append(flat);
+ append(" }");
+ } else {
+ if (value instanceof String) {
+ append('\"');
+ }
+ // TODO escape
+ append(value.toString().trim());
+ if (value instanceof String) {
+ append('\"');
+ }
+ }
+ }
+
+ void addEnum(String parameter, Enum<?> value) {
+ appendExceptFirst(", ");
+ if (!StringUtils.isNullOrEmpty(parameter)) {
+ append(parameter);
+ append('=');
+ }
+ append(value.getClass().getSimpleName() + "." + value.name());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/iciql/TestCondition.java b/src/main/java/com/iciql/TestCondition.java
new file mode 100644
index 0000000..010f5a1
--- /dev/null
+++ b/src/main/java/com/iciql/TestCondition.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.util.Utils;
+
+/**
+ * This class represents an incomplete condition.
+ *
+ * @param <A>
+ * the incomplete condition data type
+ */
+
+public class TestCondition<A> {
+
+ private A x;
+
+ public TestCondition(A x) {
+ this.x = x;
+ }
+
+ public Boolean is(A y) {
+ Boolean o = Utils.newObject(Boolean.class);
+ return Db.registerToken(o, new Function("=", x, y) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL("(");
+ query.appendSQL(stat, null, x[0]);
+ stat.appendSQL(" = ");
+ query.appendSQL(stat, x[0], x[1]);
+ stat.appendSQL(")");
+ }
+ });
+ }
+
+ public Boolean exceeds(A y) {
+ Boolean o = Utils.newObject(Boolean.class);
+ return Db.registerToken(o, new Function(">", x, y) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL("(");
+ query.appendSQL(stat, null, x[0]);
+ stat.appendSQL(" > ");
+ query.appendSQL(stat, x[0], x[1]);
+ stat.appendSQL(")");
+ }
+ });
+ }
+
+ public Boolean atLeast(A y) {
+ Boolean o = Utils.newObject(Boolean.class);
+ return Db.registerToken(o, new Function(">=", x, y) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL("(");
+ query.appendSQL(stat, null, x[0]);
+ stat.appendSQL(" >= ");
+ query.appendSQL(stat, x[0], x[1]);
+ stat.appendSQL(")");
+ }
+ });
+ }
+
+ public Boolean lessThan(A y) {
+ Boolean o = Utils.newObject(Boolean.class);
+ return Db.registerToken(o, new Function("<", x, y) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL("(");
+ query.appendSQL(stat, null, x[0]);
+ stat.appendSQL(" < ");
+ query.appendSQL(stat, x[0], x[1]);
+ stat.appendSQL(")");
+ }
+ });
+ }
+
+ public Boolean atMost(A y) {
+ Boolean o = Utils.newObject(Boolean.class);
+ return Db.registerToken(o, new Function("<=", x, y) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL("(");
+ query.appendSQL(stat, null, x[0]);
+ stat.appendSQL(" <= ");
+ query.appendSQL(stat, x[0], x[1]);
+ stat.appendSQL(")");
+ }
+ });
+ }
+
+ public Boolean like(A pattern) {
+ Boolean o = Utils.newObject(Boolean.class);
+ return Db.registerToken(o, new Function("LIKE", x, pattern) {
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL("(");
+ query.appendSQL(stat, null, x[0]);
+ stat.appendSQL(" LIKE ");
+ query.appendSQL(stat, x[0], x[1]);
+ stat.appendSQL(")");
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/com/iciql/Token.java b/src/main/java/com/iciql/Token.java
new file mode 100644
index 0000000..cc2203c
--- /dev/null
+++ b/src/main/java/com/iciql/Token.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * Classes implementing this interface can be used as a token in a statement.
+ */
+public interface Token {
+ /**
+ * Append the SQL to the given statement using the given query.
+ *
+ * @param stat
+ * the statement to append the SQL to
+ * @param query
+ * the query to use
+ */
+
+ <T> void appendSQL(SQLStatement stat, Query<T> query);
+
+}
diff --git a/src/main/java/com/iciql/UpdateColumn.java b/src/main/java/com/iciql/UpdateColumn.java
new file mode 100644
index 0000000..1eaf14c
--- /dev/null
+++ b/src/main/java/com/iciql/UpdateColumn.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * Classes implementing this interface can be used as a declaration in an update
+ * statement.
+ */
+public interface UpdateColumn {
+
+ /**
+ * Append the SQL to the given statement using the given query.
+ *
+ * @param stat
+ * the statement to append the SQL to
+ */
+
+ void appendSQL(SQLStatement stat);
+
+}
diff --git a/src/main/java/com/iciql/UpdateColumnIncrement.java b/src/main/java/com/iciql/UpdateColumnIncrement.java
new file mode 100644
index 0000000..143ce48
--- /dev/null
+++ b/src/main/java/com/iciql/UpdateColumnIncrement.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * This class represents "SET column = (column + x)" in an UPDATE statement.
+ *
+ * @param <T>
+ * the query type
+ * @param <A>
+ * the new value data type
+ */
+
+public class UpdateColumnIncrement<T, A> implements UpdateColumn {
+
+ private Query<T> query;
+ private A x;
+ private A y;
+
+ UpdateColumnIncrement(Query<T> query, A x) {
+ this.query = query;
+ this.x = x;
+ }
+
+ public Query<T> by(A y) {
+ query.addUpdateColumnDeclaration(this);
+ this.y = y;
+ return query;
+ }
+
+ public void appendSQL(SQLStatement stat) {
+ query.appendSQL(stat, null, x);
+ stat.appendSQL("=(");
+ query.appendSQL(stat, null, x);
+ stat.appendSQL("+");
+ query.appendSQL(stat, x, y);
+ stat.appendSQL(")");
+ }
+
+}
diff --git a/src/main/java/com/iciql/UpdateColumnSet.java b/src/main/java/com/iciql/UpdateColumnSet.java
new file mode 100644
index 0000000..a961480
--- /dev/null
+++ b/src/main/java/com/iciql/UpdateColumnSet.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+/**
+ * This class represents "SET column = value" in an UPDATE statement.
+ *
+ * @param <T>
+ * the query type
+ * @param <A>
+ * the new value data type
+ */
+
+public class UpdateColumnSet<T, A> implements UpdateColumn {
+
+ private Query<T> query;
+ private A x;
+ private A y;
+ private boolean isParameter;
+
+ UpdateColumnSet(Query<T> query, A x) {
+ this.query = query;
+ this.x = x;
+ }
+
+ public Query<T> to(A y) {
+ query.addUpdateColumnDeclaration(this);
+ this.y = y;
+ return query;
+ }
+
+ public Query<T> toParameter() {
+ query.addUpdateColumnDeclaration(this);
+ isParameter = true;
+ return query;
+ }
+
+ public void appendSQL(SQLStatement stat) {
+ query.appendSQL(stat, null, x);
+ stat.appendSQL(" = ");
+ if (isParameter) {
+ query.appendSQL(stat, x, RuntimeParameter.PARAMETER);
+ } else {
+ query.appendSQL(stat, x, y);
+ }
+ }
+
+}
diff --git a/src/main/java/com/iciql/ValidationRemark.java b/src/main/java/com/iciql/ValidationRemark.java
new file mode 100644
index 0000000..33320ab
--- /dev/null
+++ b/src/main/java/com/iciql/ValidationRemark.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql;
+
+import com.iciql.TableDefinition.FieldDefinition;
+import com.iciql.TableInspector.ColumnInspector;
+import com.iciql.util.StringUtils;
+
+/**
+ * A validation remark is a result of running a model validation. Each remark
+ * has a level, associated component (schema, table, column, index), and a
+ * message.
+ */
+public class ValidationRemark {
+
+ /**
+ * The validation message level.
+ */
+ public static enum Level {
+ CONSIDER, WARN, ERROR;
+ }
+
+ public final Level level;
+ public final String table;
+ public final String fieldType;
+ public final String fieldName;
+ public final String message;
+
+ private ValidationRemark(Level level, String table, String type, String message) {
+ this.level = level;
+ this.table = table;
+ this.fieldType = type;
+ this.fieldName = "";
+ this.message = message;
+ }
+
+ private ValidationRemark(Level level, String table, FieldDefinition field, String message) {
+ this.level = level;
+ this.table = table;
+ this.fieldType = field.dataType;
+ this.fieldName = field.columnName;
+ this.message = message;
+ }
+
+ private ValidationRemark(Level level, String table, ColumnInspector col, String message) {
+ this.level = level;
+ this.table = table;
+ this.fieldType = col.type;
+ this.fieldName = col.name;
+ this.message = message;
+ }
+
+ public static ValidationRemark consider(String table, String type, String message) {
+ return new ValidationRemark(Level.CONSIDER, table, type, message);
+ }
+
+ public static ValidationRemark consider(String table, ColumnInspector col, String message) {
+ return new ValidationRemark(Level.CONSIDER, table, col, message);
+ }
+
+ public static ValidationRemark warn(String table, ColumnInspector col, String message) {
+ return new ValidationRemark(Level.WARN, table, col, message);
+ }
+
+ public static ValidationRemark warn(String table, String type, String message) {
+ return new ValidationRemark(Level.WARN, table, type, message);
+ }
+
+ public static ValidationRemark error(String table, ColumnInspector col, String message) {
+ return new ValidationRemark(Level.ERROR, table, col, message);
+ }
+
+ public static ValidationRemark error(String table, String type, String message) {
+ return new ValidationRemark(Level.ERROR, table, type, message);
+ }
+
+ public static ValidationRemark error(String table, FieldDefinition field, String message) {
+ return new ValidationRemark(Level.ERROR, table, field, message);
+ }
+
+ public ValidationRemark throwError(boolean throwOnError) {
+ if (throwOnError && isError()) {
+ throw new IciqlException(toString());
+ }
+ return this;
+ }
+
+ public boolean isError() {
+ return level.equals(Level.ERROR);
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(StringUtils.pad(level.name(), 9, " ", true));
+ sb.append(StringUtils.pad(table, 25, " ", true));
+ sb.append(StringUtils.pad(fieldName, 20, " ", true));
+ sb.append(' ');
+ sb.append(message);
+ return sb.toString();
+ }
+
+ public String toCSVString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(level.name()).append(',');
+ sb.append(table).append(',');
+ sb.append(fieldType).append(',');
+ sb.append(fieldName).append(',');
+ sb.append(message);
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/And.java b/src/main/java/com/iciql/bytecode/And.java
new file mode 100644
index 0000000..808a9c1
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/And.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+import com.iciql.Token;
+
+/**
+ * An AND expression.
+ */
+public class And implements Token {
+
+ private final Token left, right;
+
+ private And(Token left, Token right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ static And get(Token left, Token right) {
+ return new And(left, right);
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ left.appendSQL(stat, query);
+ stat.appendSQL(" AND ");
+ right.appendSQL(stat, query);
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/ArrayGet.java b/src/main/java/com/iciql/bytecode/ArrayGet.java
new file mode 100644
index 0000000..29516c2
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/ArrayGet.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+import com.iciql.Token;
+
+/**
+ * An array access operation.
+ */
+public class ArrayGet implements Token {
+
+ private final Token variable;
+ private final Token index;
+
+ private ArrayGet(Token variable, Token index) {
+ this.variable = variable;
+ this.index = index;
+ }
+
+ static ArrayGet get(Token variable, Token index) {
+ return new ArrayGet(variable, index);
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ // untested
+ variable.appendSQL(stat, query);
+ stat.appendSQL("[");
+ index.appendSQL(stat, query);
+ stat.appendSQL("]");
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/CaseWhen.java b/src/main/java/com/iciql/bytecode/CaseWhen.java
new file mode 100644
index 0000000..2a1d69e
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/CaseWhen.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+import com.iciql.Token;
+
+/**
+ * A conditional expression.
+ */
+public class CaseWhen implements Token {
+
+ private final Token condition, ifTrue, ifFalse;
+
+ private CaseWhen(Token condition, Token ifTrue, Token ifFalse) {
+ this.condition = condition;
+ this.ifTrue = ifTrue;
+ this.ifFalse = ifFalse;
+ }
+
+ static Token get(Token condition, Token ifTrue, Token ifFalse) {
+ if ("0".equals(ifTrue.toString()) && "1".equals(ifFalse.toString())) {
+ return Not.get(condition);
+ } else if ("1".equals(ifTrue.toString()) && "0".equals(ifFalse.toString())) {
+ return condition;
+ } else if ("0".equals(ifTrue.toString())) {
+ return And.get(Not.get(condition), ifFalse);
+ }
+ return new CaseWhen(condition, ifTrue, ifFalse);
+ }
+
+ public String toString() {
+ return "CASEWHEN(" + condition + ", " + ifTrue + ", " + ifFalse + ")";
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL("CASEWHEN ");
+ condition.appendSQL(stat, query);
+ stat.appendSQL(" THEN ");
+ ifTrue.appendSQL(stat, query);
+ stat.appendSQL(" ELSE ");
+ ifFalse.appendSQL(stat, query);
+ stat.appendSQL(" END");
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/ClassReader.java b/src/main/java/com/iciql/bytecode/ClassReader.java
new file mode 100644
index 0000000..38fd2f5
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/ClassReader.java
@@ -0,0 +1,1457 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+
+import com.iciql.IciqlException;
+import com.iciql.Token;
+
+/**
+ * This class converts a method to a SQL Token by interpreting (decompiling) the
+ * bytecode of the class.
+ */
+public class ClassReader {
+
+ private static final boolean DEBUG = false;
+
+ private byte[] data;
+ private int pos;
+ private Constant[] constantPool;
+ private int startByteCode;
+ private String methodName;
+
+ private String convertMethodName;
+ private Token result;
+ private Stack<Token> stack = new Stack<Token>();
+ private ArrayList<Token> variables = new ArrayList<Token>();
+ private boolean endOfMethod;
+ private boolean condition;
+ private int nextPc;
+ private Map<String, Object> fieldMap = new HashMap<String, Object>();
+
+ private static void debug(String s) {
+ if (DEBUG) {
+ System.out.println(s);
+ }
+ }
+
+ public Token decompile(Object instance, Map<String, Object> fields, String method) {
+ this.fieldMap = fields;
+ this.convertMethodName = method;
+ Class<?> clazz = instance.getClass();
+ String className = clazz.getName();
+ debug("class name " + className);
+ ByteArrayOutputStream buff = new ByteArrayOutputStream();
+ try {
+ InputStream in = clazz.getClassLoader().getResource(className.replace('.', '/') + ".class")
+ .openStream();
+ while (true) {
+ int x = in.read();
+ if (x < 0) {
+ break;
+ }
+ buff.write(x);
+ }
+ } catch (IOException e) {
+ throw new IciqlException("Could not read class bytecode", e);
+ }
+ data = buff.toByteArray();
+ int header = readInt();
+ debug("header: " + Integer.toHexString(header));
+ int minorVersion = readShort();
+ int majorVersion = readShort();
+ debug("version: " + majorVersion + "." + minorVersion);
+ int constantPoolCount = readShort();
+ constantPool = new Constant[constantPoolCount];
+ for (int i = 1; i < constantPoolCount; i++) {
+ int type = readByte();
+ switch (type) {
+ case 1:
+ constantPool[i] = ConstantString.get(readString());
+ break;
+ case 3: {
+ int x = readInt();
+ constantPool[i] = ConstantNumber.get(x);
+ break;
+ }
+ case 4: {
+ int x = readInt();
+ constantPool[i] = ConstantNumber.get("" + Float.intBitsToFloat(x), x, Constant.Type.FLOAT);
+ break;
+ }
+ case 5: {
+ long x = readLong();
+ constantPool[i] = ConstantNumber.get(x);
+ i++;
+ break;
+ }
+ case 6: {
+ long x = readLong();
+ constantPool[i] = ConstantNumber
+ .get("" + Double.longBitsToDouble(x), x, Constant.Type.DOUBLE);
+ i++;
+ break;
+ }
+ case 7: {
+ int x = readShort();
+ constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.CLASS_REF);
+ break;
+ }
+ case 8: {
+ int x = readShort();
+ constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.STRING_REF);
+ break;
+ }
+ case 9: {
+ int x = readInt();
+ constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.FIELD_REF);
+ break;
+ }
+ case 10: {
+ int x = readInt();
+ constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.METHOD_REF);
+ break;
+ }
+ case 11: {
+ int x = readInt();
+ constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.INTERFACE_METHOD_REF);
+ break;
+ }
+ case 12: {
+ int x = readInt();
+ constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.NAME_AND_TYPE);
+ break;
+ }
+ default:
+ throw new IciqlException("Unsupported constant pool tag: " + type);
+ }
+ }
+ int accessFlags = readShort();
+ debug("access flags: " + accessFlags);
+ int classRef = readShort();
+ debug("class: " + constantPool[constantPool[classRef].intValue()]);
+ int superClassRef = readShort();
+ debug(" extends " + constantPool[constantPool[superClassRef].intValue()]);
+ int interfaceCount = readShort();
+ for (int i = 0; i < interfaceCount; i++) {
+ int interfaceRef = readShort();
+ debug(" implements " + constantPool[constantPool[interfaceRef].intValue()]);
+ }
+ int fieldCount = readShort();
+ for (int i = 0; i < fieldCount; i++) {
+ readField();
+ }
+ int methodCount = readShort();
+ for (int i = 0; i < methodCount; i++) {
+ readMethod();
+ }
+ readAttributes();
+ return result;
+ }
+
+ private void readField() {
+ int accessFlags = readShort();
+ int nameIndex = readShort();
+ int descIndex = readShort();
+ debug(" " + constantPool[descIndex] + " " + constantPool[nameIndex] + " " + accessFlags);
+ readAttributes();
+ }
+
+ private void readMethod() {
+ int accessFlags = readShort();
+ int nameIndex = readShort();
+ int descIndex = readShort();
+ String desc = constantPool[descIndex].toString();
+ methodName = constantPool[nameIndex].toString();
+ debug(" " + desc + " " + methodName + " " + accessFlags);
+ readAttributes();
+ }
+
+ private void readAttributes() {
+ int attributeCount = readShort();
+ for (int i = 0; i < attributeCount; i++) {
+ int attributeNameIndex = readShort();
+ String attributeName = constantPool[attributeNameIndex].toString();
+ debug(" attribute " + attributeName);
+ int attributeLength = readInt();
+ int end = pos + attributeLength;
+ if ("Code".equals(attributeName)) {
+ readCode();
+ }
+ pos = end;
+ }
+ }
+
+ void decompile() {
+ int maxStack = readShort();
+ int maxLocals = readShort();
+ debug("stack: " + maxStack + " locals: " + maxLocals);
+ int codeLength = readInt();
+ startByteCode = pos;
+ int end = pos + codeLength;
+ while (pos < end) {
+ readByteCode();
+ }
+ debug("");
+ pos = startByteCode + codeLength;
+ int exceptionTableLength = readShort();
+ pos += 2 * exceptionTableLength;
+ readAttributes();
+ }
+
+ private void readCode() {
+ variables.clear();
+ stack.clear();
+ int maxStack = readShort();
+ int maxLocals = readShort();
+ debug("stack: " + maxStack + " locals: " + maxLocals);
+ int codeLength = readInt();
+ startByteCode = pos;
+ if (methodName.startsWith(convertMethodName)) {
+ result = getResult();
+ }
+ pos = startByteCode + codeLength;
+ int exceptionTableLength = readShort();
+ pos += 2 * exceptionTableLength;
+ readAttributes();
+ }
+
+ private Token getResult() {
+ while (true) {
+ readByteCode();
+ if (endOfMethod) {
+ return stack.pop();
+ }
+ if (condition) {
+ Token c = stack.pop();
+ Stack<Token> currentStack = new Stack<Token>();
+ currentStack.addAll(stack);
+ ArrayList<Token> currentVariables = new ArrayList<Token>();
+ currentVariables.addAll(variables);
+ int branch = nextPc;
+ Token a = getResult();
+ stack = currentStack;
+ variables = currentVariables;
+ pos = branch + startByteCode;
+ Token b = getResult();
+ if (a.equals("0") && b.equals("1")) {
+ return c;
+ } else if (a.equals("1") && b.equals("0")) {
+ return Not.get(c);
+ } else if (b.equals("0")) {
+ return And.get(Not.get(c), a);
+ } else if (a.equals("0")) {
+ return And.get(c, b);
+ } else if (b.equals("1")) {
+ return Or.get(c, a);
+ } else if (a.equals("1")) {
+ return And.get(Not.get(c), b);
+ }
+ return CaseWhen.get(c, b, a);
+ }
+ if (nextPc != 0) {
+ pos = nextPc + startByteCode;
+ }
+ }
+ }
+
+ private void readByteCode() {
+ int startPos = pos - startByteCode;
+ int opCode = readByte();
+ String op;
+ endOfMethod = false;
+ condition = false;
+ nextPc = 0;
+ switch (opCode) {
+ case 0:
+ op = "nop";
+ break;
+ case 1:
+ op = "aconst_null";
+ stack.push(Null.INSTANCE);
+ break;
+ case 2:
+ op = "iconst_m1";
+ stack.push(ConstantNumber.get("-1"));
+ break;
+ case 3:
+ op = "iconst_0";
+ stack.push(ConstantNumber.get("0"));
+ break;
+ case 4:
+ op = "iconst_1";
+ stack.push(ConstantNumber.get("1"));
+ break;
+ case 5:
+ op = "iconst_2";
+ stack.push(ConstantNumber.get("2"));
+ break;
+ case 6:
+ op = "iconst_3";
+ stack.push(ConstantNumber.get("3"));
+ break;
+ case 7:
+ op = "iconst_4";
+ stack.push(ConstantNumber.get("4"));
+ break;
+ case 8:
+ op = "iconst_5";
+ stack.push(ConstantNumber.get("5"));
+ break;
+ case 9:
+ op = "lconst_0";
+ stack.push(ConstantNumber.get("0"));
+ break;
+ case 10:
+ op = "lconst_1";
+ stack.push(ConstantNumber.get("1"));
+ break;
+ case 11:
+ op = "fconst_0";
+ stack.push(ConstantNumber.get("0.0"));
+ break;
+ case 12:
+ op = "fconst_1";
+ stack.push(ConstantNumber.get("1.0"));
+ break;
+ case 13:
+ op = "fconst_2";
+ stack.push(ConstantNumber.get("2.0"));
+ break;
+ case 14:
+ op = "dconst_0";
+ stack.push(ConstantNumber.get("0.0"));
+ break;
+ case 15:
+ op = "dconst_1";
+ stack.push(ConstantNumber.get("1.0"));
+ break;
+ case 16: {
+ int x = (byte) readByte();
+ op = "bipush " + x;
+ stack.push(ConstantNumber.get(x));
+ break;
+ }
+ case 17: {
+ int x = (short) readShort();
+ op = "sipush " + x;
+ stack.push(ConstantNumber.get(x));
+ break;
+ }
+ case 18: {
+ Token s = getConstant(readByte());
+ op = "ldc " + s;
+ stack.push(s);
+ break;
+ }
+ case 19: {
+ Token s = getConstant(readShort());
+ op = "ldc_w " + s;
+ stack.push(s);
+ break;
+ }
+ case 20: {
+ Token s = getConstant(readShort());
+ op = "ldc2_w " + s;
+ stack.push(s);
+ break;
+ }
+ case 21: {
+ int x = readByte();
+ op = "iload " + x;
+ stack.push(getVariable(x));
+ break;
+ }
+ case 22: {
+ int x = readByte();
+ op = "lload " + x;
+ stack.push(getVariable(x));
+ break;
+ }
+ case 23: {
+ int x = readByte();
+ op = "fload " + x;
+ stack.push(getVariable(x));
+ break;
+ }
+ case 24: {
+ int x = readByte();
+ op = "dload " + x;
+ stack.push(getVariable(x));
+ break;
+ }
+ case 25: {
+ int x = readByte();
+ op = "aload " + x;
+ stack.push(getVariable(x));
+ break;
+ }
+ case 26:
+ op = "iload_0";
+ stack.push(getVariable(0));
+ break;
+ case 27:
+ op = "iload_1";
+ stack.push(getVariable(1));
+ break;
+ case 28:
+ op = "iload_2";
+ stack.push(getVariable(2));
+ break;
+ case 29:
+ op = "iload_3";
+ stack.push(getVariable(3));
+ break;
+ case 30:
+ op = "lload_0";
+ stack.push(getVariable(0));
+ break;
+ case 31:
+ op = "lload_1";
+ stack.push(getVariable(1));
+ break;
+ case 32:
+ op = "lload_2";
+ stack.push(getVariable(2));
+ break;
+ case 33:
+ op = "lload_3";
+ stack.push(getVariable(3));
+ break;
+ case 34:
+ op = "fload_0";
+ stack.push(getVariable(0));
+ break;
+ case 35:
+ op = "fload_1";
+ stack.push(getVariable(1));
+ break;
+ case 36:
+ op = "fload_2";
+ stack.push(getVariable(2));
+ break;
+ case 37:
+ op = "fload_3";
+ stack.push(getVariable(3));
+ break;
+ case 38:
+ op = "dload_0";
+ stack.push(getVariable(0));
+ break;
+ case 39:
+ op = "dload_1";
+ stack.push(getVariable(1));
+ break;
+ case 40:
+ op = "dload_2";
+ stack.push(getVariable(2));
+ break;
+ case 41:
+ op = "dload_3";
+ stack.push(getVariable(3));
+ break;
+ case 42:
+ op = "aload_0";
+ stack.push(getVariable(0));
+ break;
+ case 43:
+ op = "aload_1";
+ stack.push(getVariable(1));
+ break;
+ case 44:
+ op = "aload_2";
+ stack.push(getVariable(2));
+ break;
+ case 45:
+ op = "aload_3";
+ stack.push(getVariable(3));
+ break;
+ case 46: {
+ Token index = stack.pop();
+ Token ref = stack.pop();
+ op = "iaload";
+ stack.push(ArrayGet.get(ref, index));
+ break;
+ }
+ case 47: {
+ Token index = stack.pop();
+ Token ref = stack.pop();
+ op = "laload";
+ stack.push(ArrayGet.get(ref, index));
+ break;
+ }
+ case 48: {
+ Token index = stack.pop();
+ Token ref = stack.pop();
+ op = "faload";
+ stack.push(ArrayGet.get(ref, index));
+ break;
+ }
+ case 49: {
+ Token index = stack.pop();
+ Token ref = stack.pop();
+ op = "daload";
+ stack.push(ArrayGet.get(ref, index));
+ break;
+ }
+ case 50: {
+ Token index = stack.pop();
+ Token ref = stack.pop();
+ op = "aaload";
+ stack.push(ArrayGet.get(ref, index));
+ break;
+ }
+ case 51: {
+ Token index = stack.pop();
+ Token ref = stack.pop();
+ op = "baload";
+ stack.push(ArrayGet.get(ref, index));
+ break;
+ }
+ case 52: {
+ Token index = stack.pop();
+ Token ref = stack.pop();
+ op = "caload";
+ stack.push(ArrayGet.get(ref, index));
+ break;
+ }
+ case 53: {
+ Token index = stack.pop();
+ Token ref = stack.pop();
+ op = "saload";
+ stack.push(ArrayGet.get(ref, index));
+ break;
+ }
+ case 54: {
+ int var = readByte();
+ op = "istore " + var;
+ setVariable(var, stack.pop());
+ break;
+ }
+ case 55: {
+ int var = readByte();
+ op = "lstore " + var;
+ setVariable(var, stack.pop());
+ break;
+ }
+ case 56: {
+ int var = readByte();
+ op = "fstore " + var;
+ setVariable(var, stack.pop());
+ break;
+ }
+ case 57: {
+ int var = readByte();
+ op = "dstore " + var;
+ setVariable(var, stack.pop());
+ break;
+ }
+ case 58: {
+ int var = readByte();
+ op = "astore " + var;
+ setVariable(var, stack.pop());
+ break;
+ }
+ case 59:
+ op = "istore_0";
+ setVariable(0, stack.pop());
+ break;
+ case 60:
+ op = "istore_1";
+ setVariable(1, stack.pop());
+ break;
+ case 61:
+ op = "istore_2";
+ setVariable(2, stack.pop());
+ break;
+ case 62:
+ op = "istore_3";
+ setVariable(3, stack.pop());
+ break;
+ case 63:
+ op = "lstore_0";
+ setVariable(0, stack.pop());
+ break;
+ case 64:
+ op = "lstore_1";
+ setVariable(1, stack.pop());
+ break;
+ case 65:
+ op = "lstore_2";
+ setVariable(2, stack.pop());
+ break;
+ case 66:
+ op = "lstore_3";
+ setVariable(3, stack.pop());
+ break;
+ case 67:
+ op = "fstore_0";
+ setVariable(0, stack.pop());
+ break;
+ case 68:
+ op = "fstore_1";
+ setVariable(1, stack.pop());
+ break;
+ case 69:
+ op = "fstore_2";
+ setVariable(2, stack.pop());
+ break;
+ case 70:
+ op = "fstore_3";
+ setVariable(3, stack.pop());
+ break;
+ case 71:
+ op = "dstore_0";
+ setVariable(0, stack.pop());
+ break;
+ case 72:
+ op = "dstore_1";
+ setVariable(1, stack.pop());
+ break;
+ case 73:
+ op = "dstore_2";
+ setVariable(2, stack.pop());
+ break;
+ case 74:
+ op = "dstore_3";
+ setVariable(3, stack.pop());
+ break;
+ case 75:
+ op = "astore_0";
+ setVariable(0, stack.pop());
+ break;
+ case 76:
+ op = "astore_1";
+ setVariable(1, stack.pop());
+ break;
+ case 77:
+ op = "astore_2";
+ setVariable(2, stack.pop());
+ break;
+ case 78:
+ op = "astore_3";
+ setVariable(3, stack.pop());
+ break;
+ case 79: {
+ // String value = stack.pop();
+ // String index = stack.pop();
+ // String ref = stack.pop();
+ op = "iastore";
+ // TODO side effect - not supported
+ break;
+ }
+ case 80:
+ op = "lastore";
+ // TODO side effect - not supported
+ break;
+ case 81:
+ op = "fastore";
+ // TODO side effect - not supported
+ break;
+ case 82:
+ op = "dastore";
+ // TODO side effect - not supported
+ break;
+ case 83:
+ op = "aastore";
+ // TODO side effect - not supported
+ break;
+ case 84:
+ op = "bastore";
+ // TODO side effect - not supported
+ break;
+ case 85:
+ op = "castore";
+ // TODO side effect - not supported
+ break;
+ case 86:
+ op = "sastore";
+ // TODO side effect - not supported
+ break;
+ case 87:
+ op = "pop";
+ stack.pop();
+ break;
+ case 88:
+ op = "pop2";
+ // TODO currently we don't know the stack types
+ stack.pop();
+ stack.pop();
+ break;
+ case 89: {
+ op = "dup";
+ Token x = stack.pop();
+ stack.push(x);
+ stack.push(x);
+ break;
+ }
+ case 90: {
+ op = "dup_x1";
+ Token a = stack.pop();
+ Token b = stack.pop();
+ stack.push(a);
+ stack.push(b);
+ stack.push(a);
+ break;
+ }
+ case 91: {
+ // TODO currently we don't know the stack types
+ op = "dup_x2";
+ Token a = stack.pop();
+ Token b = stack.pop();
+ Token c = stack.pop();
+ stack.push(a);
+ stack.push(c);
+ stack.push(b);
+ stack.push(a);
+ break;
+ }
+ case 92: {
+ // TODO currently we don't know the stack types
+ op = "dup2";
+ Token a = stack.pop();
+ Token b = stack.pop();
+ stack.push(b);
+ stack.push(a);
+ stack.push(b);
+ stack.push(a);
+ break;
+ }
+ case 93: {
+ // TODO currently we don't know the stack types
+ op = "dup2_x1";
+ Token a = stack.pop();
+ Token b = stack.pop();
+ Token c = stack.pop();
+ stack.push(b);
+ stack.push(a);
+ stack.push(c);
+ stack.push(b);
+ stack.push(a);
+ break;
+ }
+ case 94: {
+ // TODO currently we don't know the stack types
+ op = "dup2_x2";
+ Token a = stack.pop();
+ Token b = stack.pop();
+ Token c = stack.pop();
+ Token d = stack.pop();
+ stack.push(b);
+ stack.push(a);
+ stack.push(d);
+ stack.push(c);
+ stack.push(b);
+ stack.push(a);
+ break;
+ }
+ case 95: {
+ op = "swap";
+ Token a = stack.pop();
+ Token b = stack.pop();
+ stack.push(a);
+ stack.push(b);
+ break;
+ }
+ case 96: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "iadd";
+ stack.push(Operation.get(a, Operation.Type.ADD, b));
+ break;
+ }
+ case 97: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "ladd";
+ stack.push(Operation.get(a, Operation.Type.ADD, b));
+ break;
+ }
+ case 98: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "fadd";
+ stack.push(Operation.get(a, Operation.Type.ADD, b));
+ break;
+ }
+ case 99: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "dadd";
+ stack.push(Operation.get(a, Operation.Type.ADD, b));
+ break;
+ }
+ case 100: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "isub";
+ stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
+ break;
+ }
+ case 101: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "lsub";
+ stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
+ break;
+ }
+ case 102: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "fsub";
+ stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
+ break;
+ }
+ case 103: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "dsub";
+ stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
+ break;
+ }
+ case 104: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "imul";
+ stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
+ break;
+ }
+ case 105: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "lmul";
+ stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
+ break;
+ }
+ case 106: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "fmul";
+ stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
+ break;
+ }
+ case 107: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "dmul";
+ stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
+ break;
+ }
+ case 108: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "idiv";
+ stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
+ break;
+ }
+ case 109: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "ldiv";
+ stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
+ break;
+ }
+ case 110: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "fdiv";
+ stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
+ break;
+ }
+ case 111: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "ddiv";
+ stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
+ break;
+ }
+ case 112: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "irem";
+ stack.push(Operation.get(a, Operation.Type.MOD, b));
+ break;
+ }
+ case 113: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "lrem";
+ stack.push(Operation.get(a, Operation.Type.MOD, b));
+ break;
+ }
+ case 114: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "frem";
+ stack.push(Operation.get(a, Operation.Type.MOD, b));
+ break;
+ }
+ case 115: {
+ Token b = stack.pop();
+ Token a = stack.pop();
+ op = "drem";
+ stack.push(Operation.get(a, Operation.Type.MOD, b));
+ break;
+ }
+ // case 116:
+ // op = "ineg";
+ // break;
+ // case 117:
+ // op = "lneg";
+ // break;
+ // case 118:
+ // op = "fneg";
+ // break;
+ // case 119:
+ // op = "dneg";
+ // break;
+ // case 120:
+ // op = "ishl";
+ // break;
+ // case 121:
+ // op = "lshl";
+ // break;
+ // case 122:
+ // op = "ishr";
+ // break;
+ // case 123:
+ // op = "lshr";
+ // break;
+ // case 124:
+ // op = "iushr";
+ // break;
+ // case 125:
+ // op = "lushr";
+ // break;
+ // case 126:
+ // op = "iand";
+ // break;
+ // case 127:
+ // op = "land";
+ // break;
+ // case 128:
+ // op = "ior";
+ // break;
+ // case 129:
+ // op = "lor";
+ // break;
+ // case 130:
+ // op = "ixor";
+ // break;
+ // case 131:
+ // op = "lxor";
+ // break;
+ // case 132: {
+ // int var = readByte();
+ // int off = (byte) readByte();
+ // op = "iinc " + var + " " + off;
+ // break;
+ // }
+ // case 133:
+ // op = "i2l";
+ // break;
+ // case 134:
+ // op = "i2f";
+ // break;
+ // case 135:
+ // op = "i2d";
+ // break;
+ // case 136:
+ // op = "l2i";
+ // break;
+ // case 137:
+ // op = "l2f";
+ // break;
+ // case 138:
+ // op = "l2d";
+ // break;
+ // case 139:
+ // op = "f2i";
+ // break;
+ // case 140:
+ // op = "f2l";
+ // break;
+ // case 141:
+ // op = "f2d";
+ // break;
+ // case 142:
+ // op = "d2i";
+ // break;
+ // case 143:
+ // op = "d2l";
+ // break;
+ // case 144:
+ // op = "d2f";
+ // break;
+ // case 145:
+ // op = "i2b";
+ // break;
+ // case 146:
+ // op = "i2c";
+ // break;
+ // case 147:
+ // op = "i2s";
+ // break;
+ case 148: {
+ Token b = stack.pop(), a = stack.pop();
+ stack.push(new Function("SIGN", Operation.get(a, Operation.Type.SUBTRACT, b)));
+ op = "lcmp";
+ break;
+ }
+ // case 149:
+ // op = "fcmpl";
+ // break;
+ // case 150:
+ // op = "fcmpg";
+ // break;
+ // case 151:
+ // op = "dcmpl";
+ // break;
+ // case 152:
+ // op = "dcmpg";
+ // break;
+ case 153:
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ stack.push(Operation.get(stack.pop(), Operation.Type.EQUALS, ConstantNumber.get(0)));
+ op = "ifeq " + nextPc;
+ break;
+ case 154:
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ stack.push(Operation.get(stack.pop(), Operation.Type.NOT_EQUALS, ConstantNumber.get(0)));
+ op = "ifne " + nextPc;
+ break;
+ case 155:
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ stack.push(Operation.get(stack.pop(), Operation.Type.SMALLER, ConstantNumber.get(0)));
+ op = "iflt " + nextPc;
+ break;
+ case 156:
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ stack.push(Operation.get(stack.pop(), Operation.Type.BIGGER_EQUALS, ConstantNumber.get(0)));
+ op = "ifge " + nextPc;
+ break;
+ case 157:
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ stack.push(Operation.get(stack.pop(), Operation.Type.BIGGER, ConstantNumber.get(0)));
+ op = "ifgt " + nextPc;
+ break;
+ case 158:
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ stack.push(Operation.get(stack.pop(), Operation.Type.SMALLER_EQUALS, ConstantNumber.get(0)));
+ op = "ifle " + nextPc;
+ break;
+ case 159: {
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ Token b = stack.pop(), a = stack.pop();
+ stack.push(Operation.get(a, Operation.Type.EQUALS, b));
+ op = "if_icmpeq " + nextPc;
+ break;
+ }
+ case 160: {
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ Token b = stack.pop(), a = stack.pop();
+ stack.push(Operation.get(a, Operation.Type.NOT_EQUALS, b));
+ op = "if_icmpne " + nextPc;
+ break;
+ }
+ case 161: {
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ Token b = stack.pop(), a = stack.pop();
+ stack.push(Operation.get(a, Operation.Type.SMALLER, b));
+ op = "if_icmplt " + nextPc;
+ break;
+ }
+ case 162: {
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ Token b = stack.pop(), a = stack.pop();
+ stack.push(Operation.get(a, Operation.Type.BIGGER_EQUALS, b));
+ op = "if_icmpge " + nextPc;
+ break;
+ }
+ case 163: {
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ Token b = stack.pop(), a = stack.pop();
+ stack.push(Operation.get(a, Operation.Type.BIGGER, b));
+ op = "if_icmpgt " + nextPc;
+ break;
+ }
+ case 164: {
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ Token b = stack.pop(), a = stack.pop();
+ stack.push(Operation.get(a, Operation.Type.SMALLER_EQUALS, b));
+ op = "if_icmple " + nextPc;
+ break;
+ }
+ case 165: {
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ Token b = stack.pop(), a = stack.pop();
+ stack.push(Operation.get(a, Operation.Type.EQUALS, b));
+ op = "if_acmpeq " + nextPc;
+ break;
+ }
+ case 166: {
+ condition = true;
+ nextPc = getAbsolutePos(pos, readShort());
+ Token b = stack.pop(), a = stack.pop();
+ stack.push(Operation.get(a, Operation.Type.NOT_EQUALS, b));
+ op = "if_acmpne " + nextPc;
+ break;
+ }
+ case 167:
+ nextPc = getAbsolutePos(pos, readShort());
+ op = "goto " + nextPc;
+ break;
+ // case 168:
+ // // TODO not supported yet
+ // op = "jsr " + getAbsolutePos(pos, readShort());
+ // break;
+ // case 169:
+ // // TODO not supported yet
+ // op = "ret " + readByte();
+ // break;
+ // case 170: {
+ // int start = pos;
+ // pos += 4 - ((pos - startByteCode) & 3);
+ // int def = readInt();
+ // int low = readInt(), high = readInt();
+ // int n = high - low + 1;
+ // op = "tableswitch default:" + getAbsolutePos(start, def);
+ // StringBuilder buff = new StringBuilder();
+ // for (int i = 0; i < n; i++) {
+ // buff.append(' ').append(low++).
+ // append(":").
+ // append(getAbsolutePos(start, readInt()));
+ // }
+ // op += buff.toString();
+ // // pos += n * 4;
+ // break;
+ // }
+ // case 171: {
+ // int start = pos;
+ // pos += 4 - ((pos - startByteCode) & 3);
+ // int def = readInt();
+ // int n = readInt();
+ // op = "lookupswitch default:" + getAbsolutePos(start, def);
+ // StringBuilder buff = new StringBuilder();
+ // for (int i = 0; i < n; i++) {
+ // buff.append(' ').
+ // append(readInt()).
+ // append(":").
+ // append(getAbsolutePos(start, readInt()));
+ // }
+ // op += buff.toString();
+ // // pos += n * 8;
+ // break;
+ // }
+ case 172:
+ op = "ireturn";
+ endOfMethod = true;
+ break;
+ case 173:
+ op = "lreturn";
+ endOfMethod = true;
+ break;
+ case 174:
+ op = "freturn";
+ endOfMethod = true;
+ break;
+ case 175:
+ op = "dreturn";
+ endOfMethod = true;
+ break;
+ case 176:
+ op = "areturn";
+ endOfMethod = true;
+ break;
+ case 177:
+ op = "return";
+ // no value returned
+ stack.push(null);
+ endOfMethod = true;
+ break;
+ // case 178:
+ // op = "getstatic " + getField(readShort());
+ // break;
+ // case 179:
+ // op = "putstatic " + getField(readShort());
+ // break;
+ case 180: {
+ String field = getField(readShort());
+ Token p = stack.pop();
+ String s = p + "." + field.substring(field.lastIndexOf('.') + 1, field.indexOf(' '));
+ if (s.startsWith("this.")) {
+ s = s.substring(5);
+ }
+ stack.push(Variable.get(s, fieldMap.get(s)));
+ op = "getfield " + field;
+ break;
+ }
+ // case 181:
+ // op = "putfield " + getField(readShort());
+ // break;
+ case 182: {
+ String method = getMethod(readShort());
+ op = "invokevirtual " + method;
+ if (method.equals("java/lang/String.equals (Ljava/lang/Object;)Z")) {
+ Token a = stack.pop();
+ Token b = stack.pop();
+ stack.push(Operation.get(a, Operation.Type.EQUALS, b));
+ } else if (method.equals("java/lang/Integer.intValue ()I")) {
+ // ignore
+ } else if (method.equals("java/lang/Long.longValue ()J")) {
+ // ignore
+ }
+ break;
+ }
+ case 183: {
+ String method = getMethod(readShort());
+ op = "invokespecial " + method;
+ break;
+ }
+ case 184:
+ op = "invokestatic " + getMethod(readShort());
+ break;
+ // case 185: {
+ // int methodRef = readShort();
+ // readByte();
+ // readByte();
+ // op = "invokeinterface " + getMethod(methodRef);
+ // break;
+ // }
+ case 187: {
+ String className = constantPool[constantPool[readShort()].intValue()].toString();
+ op = "new " + className;
+ break;
+ }
+ // case 188:
+ // op = "newarray " + readByte();
+ // break;
+ // case 189:
+ // op = "anewarray " + cpString[readShort()];
+ // break;
+ // case 190:
+ // op = "arraylength";
+ // break;
+ // case 191:
+ // op = "athrow";
+ // break;
+ // case 192:
+ // op = "checkcast " + cpString[readShort()];
+ // break;
+ // case 193:
+ // op = "instanceof " + cpString[readShort()];
+ // break;
+ // case 194:
+ // op = "monitorenter";
+ // break;
+ // case 195:
+ // op = "monitorexit";
+ // break;
+ // case 196: {
+ // opCode = readByte();
+ // switch (opCode) {
+ // case 21:
+ // op = "wide iload " + readShort();
+ // break;
+ // case 22:
+ // op = "wide lload " + readShort();
+ // break;
+ // case 23:
+ // op = "wide fload " + readShort();
+ // break;
+ // case 24:
+ // op = "wide dload " + readShort();
+ // break;
+ // case 25:
+ // op = "wide aload " + readShort();
+ // break;
+ // case 54:
+ // op = "wide istore " + readShort();
+ // break;
+ // case 55:
+ // op = "wide lstore " + readShort();
+ // break;
+ // case 56:
+ // op = "wide fstore " + readShort();
+ // break;
+ // case 57:
+ // op = "wide dstore " + readShort();
+ // break;
+ // case 58:
+ // op = "wide astore " + readShort();
+ // break;
+ // case 132: {
+ // int var = readShort();
+ // int off = (short) readShort();
+ // op = "wide iinc " + var + " " + off;
+ // break;
+ // }
+ // case 169:
+ // op = "wide ret " + readShort();
+ // break;
+ // default:
+ // throw new IciqlException(
+ // "Unsupported wide opCode " + opCode);
+ // }
+ // break;
+ // }
+ // case 197:
+ // op = "multianewarray " + cpString[readShort()] + " " + readByte();
+ // break;
+ // case 198: {
+ // condition = true;
+ // nextPc = getAbsolutePos(pos, readShort());
+ // Token a = stack.pop();
+ // stack.push("(" + a + " IS NULL)");
+ // op = "ifnull " + nextPc;
+ // break;
+ // }
+ // case 199: {
+ // condition = true;
+ // nextPc = getAbsolutePos(pos, readShort());
+ // Token a = stack.pop();
+ // stack.push("(" + a + " IS NOT NULL)");
+ // op = "ifnonnull " + nextPc;
+ // break;
+ // }
+ case 200:
+ op = "goto_w " + getAbsolutePos(pos, readInt());
+ break;
+ case 201:
+ op = "jsr_w " + getAbsolutePos(pos, readInt());
+ break;
+ default:
+ throw new IciqlException("Unsupported opCode " + opCode);
+ }
+ debug(" " + startPos + ": " + op);
+ }
+
+ private void setVariable(int x, Token value) {
+ while (x >= variables.size()) {
+ variables.add(Variable.get("p" + variables.size(), null));
+ }
+ variables.set(x, value);
+ }
+
+ private Token getVariable(int x) {
+ if (x == 0) {
+ return Variable.THIS;
+ }
+ while (x >= variables.size()) {
+ variables.add(Variable.get("p" + variables.size(), null));
+ }
+ return variables.get(x);
+ }
+
+ private String getField(int fieldRef) {
+ int field = constantPool[fieldRef].intValue();
+ int classIndex = field >>> 16;
+ int nameAndType = constantPool[field & 0xffff].intValue();
+ String className = constantPool[constantPool[classIndex].intValue()] + "."
+ + constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff];
+ return className;
+ }
+
+ private String getMethod(int methodRef) {
+ int method = constantPool[methodRef].intValue();
+ int classIndex = method >>> 16;
+ int nameAndType = constantPool[method & 0xffff].intValue();
+ String className = constantPool[constantPool[classIndex].intValue()] + "."
+ + constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff];
+ return className;
+ }
+
+ private Constant getConstant(int constantRef) {
+ Constant c = constantPool[constantRef];
+ switch (c.getType()) {
+ case INT:
+ case FLOAT:
+ case DOUBLE:
+ case LONG:
+ return c;
+ case STRING_REF:
+ return constantPool[c.intValue()];
+ default:
+ throw new IciqlException("Not a constant: " + constantRef);
+ }
+ }
+
+ private String readString() {
+ int size = readShort();
+ byte[] buff = data;
+ int p = pos, end = p + size;
+ char[] chars = new char[size];
+ int j = 0;
+ for (; p < end; j++) {
+ int x = buff[p++] & 0xff;
+ if (x < 0x80) {
+ chars[j] = (char) x;
+ } else if (x >= 0xe0) {
+ chars[j] = (char) (((x & 0xf) << 12) + ((buff[p++] & 0x3f) << 6) + (buff[p++] & 0x3f));
+ } else {
+ chars[j] = (char) (((x & 0x1f) << 6) + (buff[p++] & 0x3f));
+ }
+ }
+ pos = p;
+ return new String(chars, 0, j);
+ }
+
+ private int getAbsolutePos(int start, int offset) {
+ return start - startByteCode - 1 + (short) offset;
+ }
+
+ private int readByte() {
+ return data[pos++] & 0xff;
+ }
+
+ private int readShort() {
+ byte[] buff = data;
+ return ((buff[pos++] & 0xff) << 8) + (buff[pos++] & 0xff);
+ }
+
+ private int readInt() {
+ byte[] buff = data;
+ return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8)
+ + (buff[pos++] & 0xff);
+ }
+
+ private long readLong() {
+ return ((long) (readInt()) << 32) + (readInt() & 0xffffffffL);
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/Constant.java b/src/main/java/com/iciql/bytecode/Constant.java
new file mode 100644
index 0000000..65cd66b
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/Constant.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Token;
+
+/**
+ * An expression in the constant pool.
+ */
+public interface Constant extends Token {
+
+ /**
+ * The constant pool type.
+ */
+ enum Type {
+ STRING, INT, FLOAT, DOUBLE, LONG, CLASS_REF, STRING_REF, FIELD_REF, METHOD_REF, INTERFACE_METHOD_REF, NAME_AND_TYPE
+ }
+
+ Constant.Type getType();
+
+ int intValue();
+
+}
diff --git a/src/main/java/com/iciql/bytecode/ConstantNumber.java b/src/main/java/com/iciql/bytecode/ConstantNumber.java
new file mode 100644
index 0000000..934de3d
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/ConstantNumber.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+
+/**
+ * A literal number.
+ */
+public class ConstantNumber implements Constant {
+
+ private final String value;
+ private final Type type;
+ private final long longValue;
+
+ private ConstantNumber(String value, long longValue, Type type) {
+ this.value = value;
+ this.longValue = longValue;
+ this.type = type;
+ }
+
+ static ConstantNumber get(String v) {
+ return new ConstantNumber(v, 0, Type.STRING);
+ }
+
+ static ConstantNumber get(int v) {
+ return new ConstantNumber("" + v, v, Type.INT);
+ }
+
+ static ConstantNumber get(long v) {
+ return new ConstantNumber("" + v, v, Type.LONG);
+ }
+
+ static ConstantNumber get(String s, long x, Type type) {
+ return new ConstantNumber(s, x, type);
+ }
+
+ public int intValue() {
+ return (int) longValue;
+ }
+
+ public String toString() {
+ return value;
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL(toString());
+ }
+
+ public Constant.Type getType() {
+ return type;
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/ConstantString.java b/src/main/java/com/iciql/bytecode/ConstantString.java
new file mode 100644
index 0000000..985f97d
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/ConstantString.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+import com.iciql.util.StringUtils;
+
+/**
+ * A string constant.
+ */
+public class ConstantString implements Constant {
+
+ private final String value;
+
+ private ConstantString(String value) {
+ this.value = value;
+ }
+
+ static ConstantString get(String v) {
+ return new ConstantString(v);
+ }
+
+ public String toString() {
+ return value;
+ }
+
+ public int intValue() {
+ return 0;
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL(StringUtils.quoteStringSQL(value));
+ }
+
+ public Constant.Type getType() {
+ return Constant.Type.STRING;
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/Function.java b/src/main/java/com/iciql/bytecode/Function.java
new file mode 100644
index 0000000..56a55ea
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/Function.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+import com.iciql.Token;
+
+/**
+ * A method call.
+ */
+class Function implements Token {
+
+ private final String name;
+ private final Token expr;
+
+ Function(String name, Token expr) {
+ this.name = name;
+ this.expr = expr;
+ }
+
+ public String toString() {
+ return name + "(" + expr + ")";
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ // untested
+ stat.appendSQL(name + "(");
+ expr.appendSQL(stat, query);
+ stat.appendSQL(")");
+ }
+}
diff --git a/src/main/java/com/iciql/bytecode/Not.java b/src/main/java/com/iciql/bytecode/Not.java
new file mode 100644
index 0000000..ab5ab84
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/Not.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+import com.iciql.Token;
+
+/**
+ * A NOT condition.
+ */
+public class Not implements Token {
+
+ private Token expr;
+
+ private Not(Token expr) {
+ this.expr = expr;
+ }
+
+ static Token get(Token expr) {
+ if (expr instanceof Not) {
+ return ((Not) expr).expr;
+ } else if (expr instanceof Operation) {
+ return ((Operation) expr).reverse();
+ }
+ return new Not(expr);
+ }
+
+ Token not() {
+ return expr;
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ // untested
+ stat.appendSQL("NOT(");
+ expr.appendSQL(stat, query);
+ stat.appendSQL(")");
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/Null.java b/src/main/java/com/iciql/bytecode/Null.java
new file mode 100644
index 0000000..a28de56
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/Null.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+import com.iciql.Token;
+
+/**
+ * The Java 'null'.
+ */
+public class Null implements Token {
+
+ static final Null INSTANCE = new Null();
+
+ private Null() {
+ // don't allow to create new instances
+ }
+
+ public String toString() {
+ return "null";
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ // untested
+ stat.appendSQL("NULL");
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/Operation.java b/src/main/java/com/iciql/bytecode/Operation.java
new file mode 100644
index 0000000..7cd42d9
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/Operation.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+import com.iciql.Token;
+
+/**
+ * A mathematical or comparison operation.
+ */
+class Operation implements Token {
+
+ /**
+ * The operation type.
+ */
+ enum Type {
+ EQUALS("=") {
+ Type reverse() {
+ return NOT_EQUALS;
+ }
+ },
+ NOT_EQUALS("<>") {
+ Type reverse() {
+ return EQUALS;
+ }
+ },
+ BIGGER(">") {
+ Type reverse() {
+ return SMALLER_EQUALS;
+ }
+ },
+ BIGGER_EQUALS(">=") {
+ Type reverse() {
+ return SMALLER;
+ }
+ },
+ SMALLER_EQUALS("<=") {
+ Type reverse() {
+ return BIGGER;
+ }
+ },
+ SMALLER("<") {
+ Type reverse() {
+ return BIGGER_EQUALS;
+ }
+ },
+ ADD("+"), SUBTRACT("-"), MULTIPLY("*"), DIVIDE("/"), MOD("%");
+
+ private String name;
+
+ Type(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ Type reverse() {
+ return null;
+ }
+
+ }
+
+ private final Token left, right;
+ private final Type op;
+
+ private Operation(Token left, Type op, Token right) {
+ this.left = left;
+ this.op = op;
+ this.right = right;
+ }
+
+ static Token get(Token left, Type op, Token right) {
+ if (op == Type.NOT_EQUALS && "0".equals(right.toString())) {
+ return left;
+ }
+ return new Operation(left, op, right);
+ }
+
+ public String toString() {
+ return left + " " + op + " " + right;
+ }
+
+ public Token reverse() {
+ return get(left, op.reverse(), right);
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ left.appendSQL(stat, query);
+ stat.appendSQL(op.toString());
+ right.appendSQL(stat, query);
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/Or.java b/src/main/java/com/iciql/bytecode/Or.java
new file mode 100644
index 0000000..37da2a6
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/Or.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+import com.iciql.Token;
+
+/**
+ * An OR expression.
+ */
+public class Or implements Token {
+
+ private final Token left, right;
+
+ private Or(Token left, Token right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ static Or get(Token left, Token right) {
+ return new Or(left, right);
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ // untested
+ left.appendSQL(stat, query);
+ stat.appendSQL(" OR ");
+ right.appendSQL(stat, query);
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/Variable.java b/src/main/java/com/iciql/bytecode/Variable.java
new file mode 100644
index 0000000..f3dbc01
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/Variable.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.bytecode;
+
+import com.iciql.Query;
+import com.iciql.SQLStatement;
+import com.iciql.Token;
+
+/**
+ * A variable.
+ */
+public class Variable implements Token {
+
+ static final Variable THIS = new Variable("this", null);
+
+ private final String name;
+ private final Object obj;
+
+ private Variable(String name, Object obj) {
+ this.name = name;
+ this.obj = obj;
+ }
+
+ static Variable get(String name, Object obj) {
+ return new Variable(name, obj);
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ query.appendSQL(stat, null, obj);
+ }
+
+}
diff --git a/src/main/java/com/iciql/bytecode/package.html b/src/main/java/com/iciql/bytecode/package.html
new file mode 100644
index 0000000..5107481
--- /dev/null
+++ b/src/main/java/com/iciql/bytecode/package.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!--
+ Copyright 2004-2011 H2 Group.
+ Copyright 2011 James Moger.
+
+ 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<title>Javadoc package documentation</title>
+</head>
+<body>
+The class decompiler for natural syntax iciql clauses.
+</body>
+</html> \ No newline at end of file
diff --git a/src/main/java/com/iciql/package.html b/src/main/java/com/iciql/package.html
new file mode 100644
index 0000000..769837b
--- /dev/null
+++ b/src/main/java/com/iciql/package.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!--
+ Copyright 2004-2011 H2 Group.
+ Copyright 2011 James Moger.
+
+ 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<title>Javadoc package documentation</title>
+</head>
+<body>
+<i>iciql</i> (pronounced "icicle") is a Java JDBC SQL statement generator and simple object mapper
+</body>
+</html> \ No newline at end of file
diff --git a/src/main/java/com/iciql/util/GenerateModels.java b/src/main/java/com/iciql/util/GenerateModels.java
new file mode 100644
index 0000000..eac9f6c
--- /dev/null
+++ b/src/main/java/com/iciql/util/GenerateModels.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.util;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.iciql.Db;
+import com.iciql.DbInspector;
+
+/**
+ * Generates iciql models.
+ */
+public class GenerateModels {
+
+ /**
+ * The output stream where this tool writes to.
+ */
+ protected PrintStream out = System.out;
+
+ public static void main(String... args) {
+ GenerateModels tool = new GenerateModels();
+ try {
+ tool.runTool(args);
+ } catch (SQLException e) {
+ tool.out.print("Error: ");
+ tool.out.println(e.getMessage());
+ tool.out.println();
+ tool.showUsage();
+ }
+ }
+
+ public void runTool(String... args) throws SQLException {
+ String url = null;
+ String user = "sa";
+ String password = "";
+ String schema = null;
+ String table = null;
+ String packageName = "";
+ String folder = null;
+ boolean annotateSchema = true;
+ boolean trimStrings = false;
+ for (int i = 0; args != null && i < args.length; i++) {
+ String arg = args[i];
+ if (arg.equals("-url")) {
+ url = args[++i];
+ } else if (arg.equals("-user")) {
+ user = args[++i];
+ } else if (arg.equals("-password")) {
+ password = args[++i];
+ } else if (arg.equals("-schema")) {
+ schema = args[++i];
+ } else if (arg.equals("-table")) {
+ table = args[++i];
+ } else if (arg.equals("-package")) {
+ packageName = args[++i];
+ } else if (arg.equals("-folder")) {
+ folder = args[++i];
+ } else if (arg.equals("-annotateSchema")) {
+ try {
+ annotateSchema = Boolean.parseBoolean(args[++i]);
+ } catch (Throwable t) {
+ throw new SQLException("Can not parse -annotateSchema value");
+ }
+ } else if (arg.equals("-trimStrings")) {
+ try {
+ trimStrings = Boolean.parseBoolean(args[++i]);
+ } catch (Throwable t) {
+ throw new SQLException("Can not parse -trimStrings value");
+ }
+ } else {
+ throwUnsupportedOption(arg);
+ }
+ }
+ if (url == null) {
+ throw new SQLException("URL not set");
+ }
+ execute(url, user, password, schema, table, packageName, folder, annotateSchema, trimStrings);
+ }
+
+ /**
+ * Generates models from the database.
+ *
+ * @param url
+ * the database URL
+ * @param user
+ * the user name
+ * @param password
+ * the password
+ * @param schema
+ * the schema to read from. null for all schemas.
+ * @param table
+ * the table to model. null for all tables within schema.
+ * @param packageName
+ * the package name of the model classes.
+ * @param folder
+ * destination folder for model classes (package path not
+ * included)
+ * @param annotateSchema
+ * includes the schema in the table model annotations
+ * @param trimStrings
+ * automatically trim strings that exceed maxLength
+ */
+ public static void execute(String url, String user, String password, String schema, String table,
+ String packageName, String folder, boolean annotateSchema, boolean trimStrings)
+ throws SQLException {
+ try {
+ Db db;
+ if (password == null) {
+ db = Db.open(url, user, (String) null);
+ } else {
+ db = Db.open(url, user, password);
+ }
+ DbInspector inspector = new DbInspector(db);
+ List<String> models = inspector.generateModel(schema, table, packageName, annotateSchema,
+ trimStrings);
+ File parentFile;
+ if (StringUtils.isNullOrEmpty(folder)) {
+ parentFile = new File(System.getProperty("user.dir"));
+ } else {
+ parentFile = new File(folder);
+ }
+ parentFile.mkdirs();
+ Pattern p = Pattern.compile("class ([a-zA-Z0-9]+)");
+ for (String model : models) {
+ Matcher m = p.matcher(model);
+ if (m.find()) {
+ String className = m.group().substring("class".length()).trim();
+ File classFile = new File(parentFile, className + ".java");
+ Writer o = new FileWriter(classFile, false);
+ PrintWriter writer = new PrintWriter(new BufferedWriter(o));
+ writer.write(model);
+ writer.close();
+ System.out.println("Generated " + classFile.getAbsolutePath());
+ }
+ }
+ } catch (IOException io) {
+ throw new SQLException("could not generate model", io);
+ }
+ }
+
+ /**
+ * Throw a SQLException saying this command line option is not supported.
+ *
+ * @param option
+ * the unsupported option
+ * @return this method never returns normally
+ */
+ protected SQLException throwUnsupportedOption(String option) throws SQLException {
+ showUsage();
+ throw new SQLException("Unsupported option: " + option);
+ }
+
+ protected void showUsage() {
+ out.println("GenerateModels");
+ out.println("Usage:");
+ out.println();
+ out.println("(*) -url jdbc:h2:~test");
+ out.println(" -user <string>");
+ out.println(" -password <string>");
+ out.println(" -schema <string>");
+ out.println(" -table <string>");
+ out.println(" -package <string>");
+ out.println(" -folder <string>");
+ out.println(" -annotateSchema <boolean>");
+ out.println(" -trimStrings <boolean>");
+ }
+
+}
diff --git a/src/main/java/com/iciql/util/IciqlLogger.java b/src/main/java/com/iciql/util/IciqlLogger.java
new file mode 100644
index 0000000..d8005bb
--- /dev/null
+++ b/src/main/java/com/iciql/util/IciqlLogger.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.util;
+
+import java.text.DecimalFormat;
+import java.text.MessageFormat;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.iciql.IciqlException;
+
+/**
+ * Utility class to optionally log generated statements to IciqlListeners.<br>
+ * Statement logging is disabled by default.
+ * <p>
+ * This class also tracks the counts for generated statements by major type.
+ *
+ */
+public class IciqlLogger {
+
+ /**
+ * Enumeration of the different statement types that are logged.
+ */
+ public enum StatementType {
+ STAT, TOTAL, CREATE, INSERT, UPDATE, MERGE, DELETE, SELECT, DROP, WARN;
+ }
+
+ /**
+ * Interface that defines an iciql listener.
+ */
+ public interface IciqlListener {
+ void logIciql(StatementType type, String statement);
+ }
+
+ private static final ExecutorService EXEC = Executors.newSingleThreadExecutor();
+ private static final Set<IciqlListener> LISTENERS = Utils.newHashSet();
+ private static final IciqlListener CONSOLE = new IciqlListener() {
+
+ @Override
+ public void logIciql(StatementType type, String message) {
+ System.out.println(message);
+ }
+ };
+
+ private static final AtomicLong SELECT_COUNT = new AtomicLong();
+ private static final AtomicLong CREATE_COUNT = new AtomicLong();
+ private static final AtomicLong INSERT_COUNT = new AtomicLong();
+ private static final AtomicLong UPDATE_COUNT = new AtomicLong();
+ private static final AtomicLong MERGE_COUNT = new AtomicLong();
+ private static final AtomicLong DELETE_COUNT = new AtomicLong();
+ private static final AtomicLong DROP_COUNT = new AtomicLong();
+ private static final AtomicLong WARN_COUNT = new AtomicLong();
+
+ /**
+ * Activates the Console Logger.
+ */
+ public static void activateConsoleLogger() {
+ registerListener(CONSOLE);
+ }
+
+ /**
+ * Deactivates the Console Logger.
+ */
+ public static void deactivateConsoleLogger() {
+ unregisterListener(CONSOLE);
+ }
+
+ /**
+ * Registers a listener with the relay.
+ *
+ * @param listener
+ */
+ public static void registerListener(IciqlListener listener) {
+ LISTENERS.add(listener);
+ }
+
+ /**
+ * Unregisters a listener with the relay.
+ *
+ * @param listener
+ */
+ public static void unregisterListener(IciqlListener listener) {
+ if (!LISTENERS.remove(listener)) {
+ throw new IciqlException("Failed to remove iciql listener {0}", listener);
+ }
+ }
+
+ public static void create(String statement) {
+ CREATE_COUNT.incrementAndGet();
+ logStatement(StatementType.CREATE, statement);
+ }
+
+ public static void insert(String statement) {
+ INSERT_COUNT.incrementAndGet();
+ logStatement(StatementType.INSERT, statement);
+ }
+
+ public static void update(String statement) {
+ UPDATE_COUNT.incrementAndGet();
+ logStatement(StatementType.UPDATE, statement);
+ }
+
+ public static void merge(String statement) {
+ MERGE_COUNT.incrementAndGet();
+ logStatement(StatementType.MERGE, statement);
+ }
+
+ public static void delete(String statement) {
+ DELETE_COUNT.incrementAndGet();
+ logStatement(StatementType.DELETE, statement);
+ }
+
+ public static void select(String statement) {
+ SELECT_COUNT.incrementAndGet();
+ logStatement(StatementType.SELECT, statement);
+ }
+
+ public static void drop(String statement) {
+ DROP_COUNT.incrementAndGet();
+ logStatement(StatementType.DROP, statement);
+ }
+
+ public static void warn(String message, Object... args) {
+ WARN_COUNT.incrementAndGet();
+ logStatement(StatementType.WARN, args.length > 0 ? MessageFormat.format(message, args) : message);
+ }
+
+ private static void logStatement(final StatementType type, final String statement) {
+ for (final IciqlListener listener : LISTENERS) {
+ EXEC.execute(new Runnable() {
+ public void run() {
+ listener.logIciql(type, statement);
+ }
+ });
+ }
+ }
+
+ public static long getCreateCount() {
+ return CREATE_COUNT.longValue();
+ }
+
+ public static long getInsertCount() {
+ return INSERT_COUNT.longValue();
+ }
+
+ public static long getUpdateCount() {
+ return UPDATE_COUNT.longValue();
+ }
+
+ public static long getMergeCount() {
+ return MERGE_COUNT.longValue();
+ }
+
+ public static long getDeleteCount() {
+ return DELETE_COUNT.longValue();
+ }
+
+ public static long getSelectCount() {
+ return SELECT_COUNT.longValue();
+ }
+
+ public static long getDropCount() {
+ return DROP_COUNT.longValue();
+ }
+
+ public static long getWarnCount() {
+ return WARN_COUNT.longValue();
+ }
+
+ public static long getTotalCount() {
+ return getCreateCount() + getInsertCount() + getUpdateCount() + getDeleteCount() + getMergeCount()
+ + getSelectCount() + getDropCount();
+ }
+
+ public static void logStats() {
+ logStatement(StatementType.STAT, "iciql Runtime Statistics");
+ logStatement(StatementType.STAT, "========================");
+ logStat(StatementType.WARN, getWarnCount());
+ logStatement(StatementType.STAT, "========================");
+ logStat(StatementType.CREATE, getCreateCount());
+ logStat(StatementType.INSERT, getInsertCount());
+ logStat(StatementType.UPDATE, getUpdateCount());
+ logStat(StatementType.MERGE, getMergeCount());
+ logStat(StatementType.DELETE, getDeleteCount());
+ logStat(StatementType.SELECT, getSelectCount());
+ logStat(StatementType.DROP, getDropCount());
+ logStatement(StatementType.STAT, "========================");
+ logStat(StatementType.TOTAL, getTotalCount());
+ }
+
+ private static void logStat(StatementType type, long value) {
+ if (value > 0) {
+ DecimalFormat df = new DecimalFormat("###,###,###,###");
+ logStatement(StatementType.STAT,
+ StringUtils.pad(type.name(), 6, " ", true) + " = " + df.format(value));
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/iciql/util/JdbcUtils.java b/src/main/java/com/iciql/util/JdbcUtils.java
new file mode 100644
index 0000000..4a4a2b6
--- /dev/null
+++ b/src/main/java/com/iciql/util/JdbcUtils.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.util;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Properties;
+
+import javax.naming.Context;
+import javax.sql.DataSource;
+import javax.sql.XAConnection;
+
+/**
+ * This is a utility class with JDBC helper functions.
+ */
+public class JdbcUtils {
+
+ private static final String[] DRIVERS = { "h2:", "org.h2.Driver", "Cache:",
+ "com.intersys.jdbc.CacheDriver", "daffodilDB://", "in.co.daffodil.db.rmi.RmiDaffodilDBDriver",
+ "daffodil", "in.co.daffodil.db.jdbc.DaffodilDBDriver", "db2:", "COM.ibm.db2.jdbc.net.DB2Driver",
+ "derby:net:", "org.apache.derby.jdbc.ClientDriver", "derby://",
+ "org.apache.derby.jdbc.ClientDriver", "derby:", "org.apache.derby.jdbc.EmbeddedDriver",
+ "FrontBase:", "com.frontbase.jdbc.FBJDriver", "firebirdsql:", "org.firebirdsql.jdbc.FBDriver",
+ "hsqldb:", "org.hsqldb.jdbcDriver", "informix-sqli:", "com.informix.jdbc.IfxDriver", "jtds:",
+ "net.sourceforge.jtds.jdbc.Driver", "microsoft:", "com.microsoft.jdbc.sqlserver.SQLServerDriver",
+ "mimer:", "com.mimer.jdbc.Driver", "mysql:", "com.mysql.jdbc.Driver", "odbc:",
+ "sun.jdbc.odbc.JdbcOdbcDriver", "oracle:", "oracle.jdbc.driver.OracleDriver", "pervasive:",
+ "com.pervasive.jdbc.v2.Driver", "pointbase:micro:", "com.pointbase.me.jdbc.jdbcDriver",
+ "pointbase:", "com.pointbase.jdbc.jdbcUniversalDriver", "postgresql:", "org.postgresql.Driver",
+ "sybase:", "com.sybase.jdbc3.jdbc.SybDriver", "sqlserver:",
+ "com.microsoft.sqlserver.jdbc.SQLServerDriver", "teradata:", "com.ncr.teradata.TeraDriver", };
+
+ private JdbcUtils() {
+ // utility class
+ }
+
+ /**
+ * Close a statement without throwing an exception.
+ *
+ * @param stat
+ * the statement or null
+ */
+ public static void closeSilently(Statement stat) {
+ if (stat != null) {
+ try {
+ stat.close();
+ } catch (SQLException e) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Close a connection without throwing an exception.
+ *
+ * @param conn
+ * the connection or null
+ */
+ public static void closeSilently(Connection conn) {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Close a result set without throwing an exception.
+ *
+ * @param rs
+ * the result set or null
+ */
+ public static void closeSilently(ResultSet rs) {
+ closeSilently(rs, false);
+ }
+
+ /**
+ * Close a result set, and optionally its statement without throwing an
+ * exception.
+ *
+ * @param rs
+ * the result set or null
+ */
+ public static void closeSilently(ResultSet rs, boolean closeStatement) {
+ if (rs != null) {
+ Statement stat = null;
+ if (closeStatement) {
+ try {
+ stat = rs.getStatement();
+ } catch (SQLException e) {
+ // ignore
+ }
+ }
+ try {
+ rs.close();
+ } catch (SQLException e) {
+ // ignore
+ }
+ closeSilently(stat);
+ }
+ }
+
+ /**
+ * Close an XA connection set without throwing an exception.
+ *
+ * @param conn
+ * the XA connection or null
+ */
+ public static void closeSilently(XAConnection conn) {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Open a new database connection with the given settings.
+ *
+ * @param driver
+ * the driver class name
+ * @param url
+ * the database URL
+ * @param user
+ * the user name
+ * @param password
+ * the password
+ * @return the database connection
+ */
+ public static Connection getConnection(String driver, String url, String user, String password)
+ throws SQLException {
+ Properties prop = new Properties();
+ if (user != null) {
+ prop.setProperty("user", user);
+ }
+ if (password != null) {
+ prop.setProperty("password", password);
+ }
+ return getConnection(driver, url, prop);
+ }
+
+ /**
+ * Escape table or schema patterns used for DatabaseMetaData functions.
+ *
+ * @param pattern
+ * the pattern
+ * @return the escaped pattern
+ */
+ public static String escapeMetaDataPattern(String pattern) {
+ if (pattern == null || pattern.length() == 0) {
+ return pattern;
+ }
+ return StringUtils.replaceAll(pattern, "\\", "\\\\");
+ }
+
+ /**
+ * Open a new database connection with the given settings.
+ *
+ * @param driver
+ * the driver class name
+ * @param url
+ * the database URL
+ * @param prop
+ * the properties containing at least the user name and password
+ * @return the database connection
+ */
+ public static Connection getConnection(String driver, String url, Properties prop) throws SQLException {
+ if (StringUtils.isNullOrEmpty(driver)) {
+ JdbcUtils.load(url);
+ } else {
+ Class<?> d = Utils.loadClass(driver);
+ if (java.sql.Driver.class.isAssignableFrom(d)) {
+ return DriverManager.getConnection(url, prop);
+ } else if (javax.naming.Context.class.isAssignableFrom(d)) {
+ // JNDI context
+ try {
+ Context context = (Context) d.newInstance();
+ DataSource ds = (DataSource) context.lookup(url);
+ String user = prop.getProperty("user");
+ String password = prop.getProperty("password");
+ if (StringUtils.isNullOrEmpty(user) && StringUtils.isNullOrEmpty(password)) {
+ return ds.getConnection();
+ }
+ return ds.getConnection(user, password);
+ } catch (SQLException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new SQLException("Failed to get connection for " + url, e);
+ }
+ } else {
+ // Don't know, but maybe it loaded a JDBC Driver
+ return DriverManager.getConnection(url, prop);
+ }
+ }
+ return DriverManager.getConnection(url, prop);
+ }
+
+ /**
+ * Get the driver class name for the given URL, or null if the URL is
+ * unknown.
+ *
+ * @param url
+ * the database URL
+ * @return the driver class name
+ */
+ public static String getDriver(String url) {
+ if (url.startsWith("jdbc:")) {
+ url = url.substring("jdbc:".length());
+ for (int i = 0; i < DRIVERS.length; i += 2) {
+ String prefix = DRIVERS[i];
+ if (url.startsWith(prefix)) {
+ return DRIVERS[i + 1];
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Load the driver class for the given URL, if the database URL is known.
+ *
+ * @param url
+ * the database URL
+ */
+ public static void load(String url) {
+ String driver = getDriver(url);
+ if (driver != null) {
+ Utils.loadClass(driver);
+ }
+ }
+
+}
diff --git a/src/main/java/com/iciql/util/Slf4jIciqlListener.java b/src/main/java/com/iciql/util/Slf4jIciqlListener.java
new file mode 100644
index 0000000..ded393f
--- /dev/null
+++ b/src/main/java/com/iciql/util/Slf4jIciqlListener.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.iciql.Iciql;
+import com.iciql.util.IciqlLogger.IciqlListener;
+import com.iciql.util.IciqlLogger.StatementType;
+
+/**
+ * Slf4jIciqlListener interfaces the IciqlLogger to the SLF4J logging framework.
+ */
+public class Slf4jIciqlListener implements IciqlListener {
+
+ private Logger logger = LoggerFactory.getLogger(Iciql.class);
+
+ /**
+ * Enumeration representing the SLF4J log levels.
+ */
+ public enum Level {
+ ERROR, WARN, INFO, DEBUG, TRACE, OFF;
+ }
+
+ private final Level defaultLevel;
+
+ private final Map<StatementType, Level> levels;
+
+ public Slf4jIciqlListener() {
+ this(Level.TRACE);
+ }
+
+ public Slf4jIciqlListener(Level defaultLevel) {
+ this.defaultLevel = defaultLevel;
+ levels = new HashMap<StatementType, Level>();
+ for (StatementType type : StatementType.values()) {
+ levels.put(type, defaultLevel);
+ }
+ }
+
+ /**
+ * Sets the logging level for a particular statement type.
+ *
+ * @param type
+ * @param level
+ */
+ public void setLevel(StatementType type, Level level) {
+ levels.put(type, defaultLevel);
+ }
+
+ @Override
+ public void logIciql(StatementType type, String statement) {
+ Level level = levels.get(type);
+ switch (level) {
+ case ERROR:
+ logger.error(statement);
+ break;
+ case WARN:
+ logger.warn(statement);
+ break;
+ case INFO:
+ logger.info(statement);
+ break;
+ case DEBUG:
+ logger.debug(statement);
+ break;
+ case TRACE:
+ logger.trace(statement);
+ break;
+ case OFF:
+ break;
+ }
+ }
+}
diff --git a/src/main/java/com/iciql/util/StatementBuilder.java b/src/main/java/com/iciql/util/StatementBuilder.java
new file mode 100644
index 0000000..47e8054
--- /dev/null
+++ b/src/main/java/com/iciql/util/StatementBuilder.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.util;
+
+/**
+ * A utility class to build a statement. In addition to the methods supported by
+ * StringBuilder, it allows to add a text only in the second iteration. This
+ * simplified constructs such as:
+ *
+ * <pre>
+ * StringBuilder buff = new StringBuilder();
+ * for (int i = 0; i &lt; args.length; i++) {
+ * if (i &gt; 0) {
+ * buff.append(&quot;, &quot;);
+ * }
+ * buff.append(args[i]);
+ * }
+ * </pre>
+ *
+ * to
+ *
+ * <pre>
+ * StatementBuilder buff = new StatementBuilder();
+ * for (String s : args) {
+ * buff.appendExceptFirst(&quot;, &quot;);
+ * buff.append(a);
+ * }
+ * </pre>
+ */
+public class StatementBuilder {
+
+ private final StringBuilder builder = new StringBuilder();
+ private int index;
+
+ /**
+ * Create a new builder.
+ */
+ public StatementBuilder() {
+ // nothing to do
+ }
+
+ /**
+ * Create a new builder.
+ *
+ * @param string
+ * the initial string
+ */
+ public StatementBuilder(String string) {
+ builder.append(string);
+ }
+
+ /**
+ * Append a text.
+ *
+ * @param s
+ * the text to append
+ * @return itself
+ */
+ public StatementBuilder append(String s) {
+ builder.append(s);
+ return this;
+ }
+
+ /**
+ * Append a character.
+ *
+ * @param c
+ * the character to append
+ * @return itself
+ */
+ public StatementBuilder append(char c) {
+ builder.append(c);
+ return this;
+ }
+
+ /**
+ * Append a number.
+ *
+ * @param x
+ * the number to append
+ * @return itself
+ */
+ public StatementBuilder append(long x) {
+ builder.append(x);
+ return this;
+ }
+
+ /**
+ * Returns the current value of the loop counter.
+ *
+ * @return the loop counter
+ */
+ public int getCount() {
+ return index;
+ }
+
+ /**
+ * Reset the loop counter.
+ *
+ * @return itself
+ */
+ public StatementBuilder resetCount() {
+ index = 0;
+ return this;
+ }
+
+ /**
+ * Append a text, but only if appendExceptFirst was never called.
+ *
+ * @param s
+ * the text to append
+ */
+ public void appendOnlyFirst(String s) {
+ if (index == 0) {
+ builder.append(s);
+ }
+ }
+
+ /**
+ * Append a text, except when this method is called the first time.
+ *
+ * @param s
+ * the text to append
+ */
+ public void appendExceptFirst(String s) {
+ if (index++ > 0) {
+ builder.append(s);
+ }
+ }
+
+ public void append(StatementBuilder sb) {
+ builder.append(sb);
+ }
+
+ public void insert(int offset, char c) {
+ builder.insert(offset, c);
+ }
+
+ public String toString() {
+ return builder.toString();
+ }
+
+ /**
+ * Get the length.
+ *
+ * @return the length
+ */
+ public int length() {
+ return builder.length();
+ }
+}
diff --git a/src/main/java/com/iciql/util/StringUtils.java b/src/main/java/com/iciql/util/StringUtils.java
new file mode 100644
index 0000000..dd3f180
--- /dev/null
+++ b/src/main/java/com/iciql/util/StringUtils.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+
+/**
+ * Common string utilities.
+ *
+ */
+public class StringUtils {
+
+ /**
+ * Replace all occurrences of the before string with the after string.
+ *
+ * @param s
+ * the string
+ * @param before
+ * the old text
+ * @param after
+ * the new text
+ * @return the string with the before string replaced
+ */
+ public static String replaceAll(String s, String before, String after) {
+ int next = s.indexOf(before);
+ if (next < 0) {
+ return s;
+ }
+ StringBuilder buff = new StringBuilder(s.length() - before.length() + after.length());
+ int index = 0;
+ while (true) {
+ buff.append(s.substring(index, next)).append(after);
+ index = next + before.length();
+ next = s.indexOf(before, index);
+ if (next < 0) {
+ buff.append(s.substring(index));
+ break;
+ }
+ }
+ return buff.toString();
+ }
+
+ /**
+ * Check if a String is null or empty (the length is null).
+ *
+ * @param s
+ * the string to check
+ * @return true if it is null or empty
+ */
+ public static boolean isNullOrEmpty(String s) {
+ return s == null || s.length() == 0;
+ }
+
+ /**
+ * Convert a string to a Java literal using the correct escape sequences.
+ * The literal is not enclosed in double quotes. The result can be used in
+ * properties files or in Java source code.
+ *
+ * @param s
+ * the text to convert
+ * @return the Java representation
+ */
+ public static String javaEncode(String s) {
+ int length = s.length();
+ StringBuilder buff = new StringBuilder(length);
+ for (int i = 0; i < length; i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ // case '\b':
+ // // BS backspace
+ // // not supported in properties files
+ // buff.append("\\b");
+ // break;
+ case '\t':
+ // HT horizontal tab
+ buff.append("\\t");
+ break;
+ case '\n':
+ // LF linefeed
+ buff.append("\\n");
+ break;
+ case '\f':
+ // FF form feed
+ buff.append("\\f");
+ break;
+ case '\r':
+ // CR carriage return
+ buff.append("\\r");
+ break;
+ case '"':
+ // double quote
+ buff.append("\\\"");
+ break;
+ case '\\':
+ // backslash
+ buff.append("\\\\");
+ break;
+ default:
+ int ch = c & 0xffff;
+ if (ch >= ' ' && (ch < 0x80)) {
+ buff.append(c);
+ // not supported in properties files
+ // } else if(ch < 0xff) {
+ // buff.append("\\");
+ // // make sure it's three characters (0x200 is octal 1000)
+ // buff.append(Integer.toOctalString(0x200 |
+ // ch).substring(1));
+ } else {
+ buff.append("\\u");
+ // make sure it's four characters
+ buff.append(Integer.toHexString(0x10000 | ch).substring(1));
+ }
+ }
+ }
+ return buff.toString();
+ }
+
+ /**
+ * Pad a string. This method is used for the SQL function RPAD and LPAD.
+ *
+ * @param string
+ * the original string
+ * @param n
+ * the target length
+ * @param padding
+ * the padding string
+ * @param right
+ * true if the padding should be appended at the end
+ * @return the padded string
+ */
+ public static String pad(String string, int n, String padding, boolean right) {
+ if (n < 0) {
+ n = 0;
+ }
+ if (n < string.length()) {
+ return string.substring(0, n);
+ } else if (n == string.length()) {
+ return string;
+ }
+ char paddingChar;
+ if (padding == null || padding.length() == 0) {
+ paddingChar = ' ';
+ } else {
+ paddingChar = padding.charAt(0);
+ }
+ StringBuilder buff = new StringBuilder(n);
+ n -= string.length();
+ if (right) {
+ buff.append(string);
+ }
+ for (int i = 0; i < n; i++) {
+ buff.append(paddingChar);
+ }
+ if (!right) {
+ buff.append(string);
+ }
+ return buff.toString();
+ }
+
+ /**
+ * Convert a string to a SQL literal. Null is converted to NULL. The text is
+ * enclosed in single quotes. If there are any special characters, the
+ * method STRINGDECODE is used.
+ *
+ * @param s
+ * the text to convert.
+ * @return the SQL literal
+ */
+ public static String quoteStringSQL(String s) {
+ if (s == null) {
+ return "NULL";
+ }
+ int length = s.length();
+ StringBuilder buff = new StringBuilder(length + 2);
+ buff.append('\'');
+ for (int i = 0; i < length; i++) {
+ char c = s.charAt(i);
+ if (c == '\'') {
+ buff.append(c);
+ } else if (c < ' ' || c > 127) {
+ // need to start from the beginning because maybe there was a \
+ // that was not quoted
+ return "STRINGDECODE(" + quoteStringSQL(javaEncode(s)) + ")";
+ }
+ buff.append(c);
+ }
+ buff.append('\'');
+ return buff.toString();
+ }
+
+ /**
+ * Split a string into an array of strings using the given separator. A null
+ * string will result in a null array, and an empty string in a zero element
+ * array.
+ *
+ * @param s
+ * the string to split
+ * @param separatorChar
+ * the separator character
+ * @param trim
+ * whether each element should be trimmed
+ * @return the array list
+ */
+ public static String[] arraySplit(String s, char separatorChar, boolean trim) {
+ if (s == null) {
+ return null;
+ }
+ int length = s.length();
+ if (length == 0) {
+ return new String[0];
+ }
+ ArrayList<String> list = Utils.newArrayList();
+ StringBuilder buff = new StringBuilder(length);
+ for (int i = 0; i < length; i++) {
+ char c = s.charAt(i);
+ if (c == separatorChar) {
+ String e = buff.toString();
+ list.add(trim ? e.trim() : e);
+ buff.setLength(0);
+ } else if (c == '\\' && i < length - 1) {
+ buff.append(s.charAt(++i));
+ } else {
+ buff.append(c);
+ }
+ }
+ String e = buff.toString();
+ list.add(trim ? e.trim() : e);
+ String[] array = new String[list.size()];
+ list.toArray(array);
+ return array;
+ }
+
+ /**
+ * Calculates the SHA1 of the string.
+ *
+ * @param text
+ * @return sha1 of the string
+ */
+ public static String calculateSHA1(String text) {
+ try {
+ byte[] bytes = text.getBytes("iso-8859-1");
+ return calculateSHA1(bytes);
+ } catch (UnsupportedEncodingException u) {
+ throw new RuntimeException(u);
+ }
+ }
+
+ /**
+ * Calculates the SHA1 of the byte array.
+ *
+ * @param bytes
+ * @return sha1 of the byte array
+ */
+ public static String calculateSHA1(byte[] bytes) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ md.update(bytes, 0, bytes.length);
+ byte[] digest = md.digest();
+ StringBuilder sb = new StringBuilder(digest.length * 2);
+ for (int i = 0; i < digest.length; i++) {
+ if (((int) digest[i] & 0xff) < 0x10) {
+ sb.append('0');
+ }
+ sb.append(Integer.toHexString((int) digest[i] & 0xff));
+ }
+ return sb.toString();
+ } catch (NoSuchAlgorithmException t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ /**
+ * Counts the occurrences of char c in the given string.
+ *
+ * @param c
+ * the character to count
+ * @param value
+ * the source string
+ * @return the count of c in value
+ */
+ public static int count(char c, String value) {
+ int count = 0;
+ for (char cv : value.toCharArray()) {
+ if (cv == c) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Prepare text for html presentation. Replace sensitive characters with
+ * html entities.
+ *
+ * @param inStr
+ * @param changeSpace
+ * @return plain text escaped for html
+ */
+ public static String escapeForHtml(String inStr, boolean changeSpace) {
+ StringBuffer retStr = new StringBuffer();
+ int i = 0;
+ while (i < inStr.length()) {
+ if (inStr.charAt(i) == '&') {
+ retStr.append("&amp;");
+ } else if (inStr.charAt(i) == '<') {
+ retStr.append("&lt;");
+ } else if (inStr.charAt(i) == '>') {
+ retStr.append("&gt;");
+ } else if (inStr.charAt(i) == '\"') {
+ retStr.append("&quot;");
+ } else if (changeSpace && inStr.charAt(i) == ' ') {
+ retStr.append("&nbsp;");
+ } else if (changeSpace && inStr.charAt(i) == '\t') {
+ retStr.append(" &nbsp; &nbsp;");
+ } else {
+ retStr.append(inStr.charAt(i));
+ }
+ i++;
+ }
+ return retStr.toString();
+ }
+
+ /**
+ * Replaces carriage returns and line feeds with html line breaks.
+ *
+ * @param string
+ * @return plain text with html line breaks
+ */
+ public static String breakLinesForHtml(String string) {
+ return string.replace("\r\n", "<br/>").replace("\r", "<br/>").replace("\n", "<br/>");
+ }
+
+ /**
+ * Returns the string content of the specified file.
+ *
+ * @param file
+ * @param lineEnding
+ * @return the string content of the file
+ */
+ public static String readContent(File file, String lineEnding) {
+ StringBuilder sb = new StringBuilder();
+ try {
+ InputStreamReader is = new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"));
+ BufferedReader reader = new BufferedReader(is);
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ if (lineEnding != null) {
+ sb.append(lineEnding);
+ }
+ }
+ reader.close();
+ } catch (Throwable t) {
+ System.err.println("Failed to read content of " + file.getAbsolutePath());
+ t.printStackTrace();
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/iciql/util/Utils.java b/src/main/java/com/iciql/util/Utils.java
new file mode 100644
index 0000000..77110b8
--- /dev/null
+++ b/src/main/java/com/iciql/util/Utils.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.lang.reflect.Constructor;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.iciql.Iciql.EnumId;
+import com.iciql.Iciql.EnumType;
+import com.iciql.IciqlException;
+
+/**
+ * Generic utility methods.
+ */
+public class Utils {
+
+ public static final AtomicLong COUNTER = new AtomicLong(0);
+
+ public static final AtomicInteger AS_COUNTER = new AtomicInteger(0);
+
+ private static final boolean MAKE_ACCESSIBLE = true;
+
+ private static final int BUFFER_BLOCK_SIZE = 4 * 1024;
+
+ public static synchronized int nextAsCount() {
+ // prevent negative values and use a threadsafe counter
+ int count = AS_COUNTER.incrementAndGet();
+ if (count == Integer.MAX_VALUE) {
+ count = 0;
+ AS_COUNTER.set(count);
+ }
+ return count;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <X> Class<X> getClass(X x) {
+ return (Class<X>) x.getClass();
+ }
+
+ public static Class<?> loadClass(String className) {
+ try {
+ return Class.forName(className);
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public static <T> ArrayList<T> newArrayList() {
+ return new ArrayList<T>();
+ }
+
+ public static <T> ArrayList<T> newArrayList(Collection<T> c) {
+ return new ArrayList<T>(c);
+ }
+
+ public static <T> HashSet<T> newHashSet() {
+ return new HashSet<T>();
+ }
+
+ public static <T> HashSet<T> newHashSet(Collection<T> list) {
+ return new HashSet<T>(list);
+ }
+
+ public static <A, B> HashMap<A, B> newHashMap() {
+ return new HashMap<A, B>();
+ }
+
+ public static <A, B> Map<A, B> newSynchronizedHashMap() {
+ HashMap<A, B> map = newHashMap();
+ return Collections.synchronizedMap(map);
+ }
+
+ public static <A, B> IdentityHashMap<A, B> newIdentityHashMap() {
+ return new IdentityHashMap<A, B>();
+ }
+
+ public static <T> ThreadLocal<T> newThreadLocal(final Class<? extends T> clazz) {
+ return new ThreadLocal<T>() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected T initialValue() {
+ try {
+ return clazz.newInstance();
+ } catch (Exception e) {
+ if (MAKE_ACCESSIBLE) {
+ Constructor[] constructors = clazz.getDeclaredConstructors();
+ // try 0 length constructors
+ for (Constructor c : constructors) {
+ if (c.getParameterTypes().length == 0) {
+ c.setAccessible(true);
+ try {
+ return clazz.newInstance();
+ } catch (Exception e2) {
+ // ignore
+ }
+ }
+ }
+ }
+ throw new IciqlException(e,
+ "Missing default constructor? Exception trying to instantiate {0}: {1}",
+ clazz.getName(), e.getMessage());
+ }
+ }
+ };
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static <T> T newObject(Class<T> clazz) {
+ // must create new instances
+ if (clazz == int.class || clazz == Integer.class) {
+ return (T) new Integer((int) COUNTER.getAndIncrement());
+ } else if (clazz == String.class) {
+ return (T) ("" + COUNTER.getAndIncrement());
+ } else if (clazz == long.class || clazz == Long.class) {
+ return (T) new Long(COUNTER.getAndIncrement());
+ } else if (clazz == short.class || clazz == Short.class) {
+ return (T) new Short((short) COUNTER.getAndIncrement());
+ } else if (clazz == byte.class || clazz == Byte.class) {
+ return (T) new Byte((byte) COUNTER.getAndIncrement());
+ } else if (clazz == float.class || clazz == Float.class) {
+ return (T) new Float(COUNTER.getAndIncrement());
+ } else if (clazz == double.class || clazz == Double.class) {
+ return (T) new Double(COUNTER.getAndIncrement());
+ } else if (clazz == boolean.class || clazz == Boolean.class) {
+ COUNTER.getAndIncrement();
+ return (T) new Boolean(false);
+ } else if (clazz == BigDecimal.class) {
+ return (T) new BigDecimal(COUNTER.getAndIncrement());
+ } else if (clazz == BigInteger.class) {
+ return (T) new BigInteger("" + COUNTER.getAndIncrement());
+ } else if (clazz == java.sql.Date.class) {
+ return (T) new java.sql.Date(COUNTER.getAndIncrement());
+ } else if (clazz == java.sql.Time.class) {
+ return (T) new java.sql.Time(COUNTER.getAndIncrement());
+ } else if (clazz == java.sql.Timestamp.class) {
+ return (T) new java.sql.Timestamp(COUNTER.getAndIncrement());
+ } else if (clazz == java.util.Date.class) {
+ return (T) new java.util.Date(COUNTER.getAndIncrement());
+ } else if (clazz == byte[].class) {
+ COUNTER.getAndIncrement();
+ return (T) new byte[0];
+ } else if (clazz.isEnum()) {
+ COUNTER.getAndIncrement();
+ // enums can not be instantiated reflectively
+ // return first constant as reference
+ return clazz.getEnumConstants()[0];
+ } else if (clazz == java.util.UUID.class) {
+ COUNTER.getAndIncrement();
+ return (T) UUID.randomUUID();
+ }
+ try {
+ return clazz.newInstance();
+ } catch (Exception e) {
+ if (MAKE_ACCESSIBLE) {
+ Constructor[] constructors = clazz.getDeclaredConstructors();
+ // try 0 length constructors
+ for (Constructor c : constructors) {
+ if (c.getParameterTypes().length == 0) {
+ c.setAccessible(true);
+ try {
+ return clazz.newInstance();
+ } catch (Exception e2) {
+ // ignore
+ }
+ }
+ }
+ // try 1 length constructors
+ for (Constructor c : constructors) {
+ if (c.getParameterTypes().length == 1) {
+ c.setAccessible(true);
+ try {
+ return (T) c.newInstance(new Object[1]);
+ } catch (Exception e2) {
+ // ignore
+ }
+ }
+ }
+ }
+ throw new IciqlException(e,
+ "Missing default constructor?! Exception trying to instantiate {0}: {1}",
+ clazz.getName(), e.getMessage());
+ }
+ }
+
+ public static <T> boolean isSimpleType(Class<T> clazz) {
+ if (Number.class.isAssignableFrom(clazz)) {
+ return true;
+ } else if (clazz == String.class) {
+ return true;
+ }
+ return false;
+ }
+
+ public static Object convert(Object o, Class<?> targetType) {
+ if (o == null) {
+ return null;
+ }
+ Class<?> currentType = o.getClass();
+ if (targetType.isAssignableFrom(currentType)) {
+ return o;
+ }
+
+ // convert from CLOB/TEXT/VARCHAR to String
+ if (targetType == String.class) {
+ if (Clob.class.isAssignableFrom(currentType)) {
+ Clob c = (Clob) o;
+ try {
+ Reader r = c.getCharacterStream();
+ return readStringAndClose(r, -1);
+ } catch (Exception e) {
+ throw new IciqlException(e, "error converting CLOB to String: ", e.toString());
+ }
+ }
+ return o.toString();
+ }
+
+ if (Boolean.class.isAssignableFrom(targetType) || boolean.class.isAssignableFrom(targetType)) {
+ // convert from number to boolean
+ if (Number.class.isAssignableFrom(currentType)) {
+ Number n = (Number) o;
+ return n.intValue() > 0;
+ }
+ // convert from string to boolean
+ if (String.class.isAssignableFrom(currentType)) {
+ String s = o.toString().toLowerCase();
+ float f = 0f;
+ try {
+ f = Float.parseFloat(s);
+ } catch (Exception e) {
+ }
+ return f > 0 || s.equals("true") || s.equals("yes") || s.equals("y") || s.equals("on");
+ }
+ }
+
+ // convert from boolean to number
+ if (Boolean.class.isAssignableFrom(currentType)) {
+ Boolean b = (Boolean) o;
+ if (Number.class.isAssignableFrom(targetType)) {
+ return b ? 1 : 0;
+ }
+ if (boolean.class.isAssignableFrom(targetType)) {
+ return b.booleanValue();
+ }
+ }
+
+ // convert from number to number
+ if (Number.class.isAssignableFrom(currentType)) {
+ Number n = (Number) o;
+ if (targetType == byte.class || targetType == Byte.class) {
+ return n.byteValue();
+ } else if (targetType == short.class || targetType == Short.class) {
+ return n.shortValue();
+ } else if (targetType == int.class || targetType == Integer.class) {
+ return n.intValue();
+ } else if (targetType == long.class || targetType == Long.class) {
+ return n.longValue();
+ } else if (targetType == double.class || targetType == Double.class) {
+ return n.doubleValue();
+ } else if (targetType == float.class || targetType == Float.class) {
+ return n.floatValue();
+ }
+ }
+
+ // convert from BLOB
+ if (targetType == byte[].class) {
+ if (Blob.class.isAssignableFrom(currentType)) {
+ Blob b = (Blob) o;
+ try {
+ InputStream is = b.getBinaryStream();
+ return readBlobAndClose(is, -1);
+ } catch (Exception e) {
+ throw new IciqlException(e, "error converting BLOB to byte[]: ", e.toString());
+ }
+ }
+ }
+ throw new IciqlException("Can not convert the value {0} from {1} to {2}", o, currentType, targetType);
+ }
+
+ public static Object convertEnum(Enum<?> o, EnumType type) {
+ if (o == null) {
+ return null;
+ }
+ switch (type) {
+ case ORDINAL:
+ return o.ordinal();
+ case ENUMID:
+ if (!EnumId.class.isAssignableFrom(o.getClass())) {
+ throw new IciqlException("Can not convert the enum {0} using ENUMID", o);
+ }
+ EnumId enumid = (EnumId) o;
+ return enumid.enumId();
+ case NAME:
+ default:
+ return o.name();
+ }
+ }
+
+ public static Object convertEnum(Object o, Class<?> targetType, EnumType type) {
+ if (o == null) {
+ return null;
+ }
+ Class<?> currentType = o.getClass();
+ if (targetType.isAssignableFrom(currentType)) {
+ return o;
+ }
+ // convert from VARCHAR/TEXT/INT to Enum
+ Enum<?>[] values = (Enum[]) targetType.getEnumConstants();
+ if (Clob.class.isAssignableFrom(currentType)) {
+ // TEXT/CLOB field
+ Clob c = (Clob) o;
+ String name = null;
+ try {
+ Reader r = c.getCharacterStream();
+ name = readStringAndClose(r, -1);
+ } catch (Exception e) {
+ throw new IciqlException(e, "error converting CLOB to String: ", e.toString());
+ }
+
+ // find name match
+ for (Enum<?> value : values) {
+ if (value.name().equalsIgnoreCase(name)) {
+ return value;
+ }
+ }
+ } else if (String.class.isAssignableFrom(currentType)) {
+ // VARCHAR field
+ String name = (String) o;
+ for (Enum<?> value : values) {
+ if (value.name().equalsIgnoreCase(name)) {
+ return value;
+ }
+ }
+ } else if (Number.class.isAssignableFrom(currentType)) {
+ // INT field
+ int n = ((Number) o).intValue();
+ if (type.equals(EnumType.ORDINAL)) {
+ // ORDINAL mapping
+ for (Enum<?> value : values) {
+ if (value.ordinal() == n) {
+ return value;
+ }
+ }
+ } else if (type.equals(EnumType.ENUMID)) {
+ if (!EnumId.class.isAssignableFrom(targetType)) {
+ throw new IciqlException("Can not convert the value {0} from {1} to {2} using ENUMID", o,
+ currentType, targetType);
+ }
+ // ENUMID mapping
+ for (Enum<?> value : values) {
+ EnumId enumid = (EnumId) value;
+ if (enumid.enumId() == n) {
+ return value;
+ }
+ }
+ }
+ }
+ throw new IciqlException("Can not convert the value {0} from {1} to {2}", o, currentType, targetType);
+ }
+
+ /**
+ * Read a number of characters from a reader and close it.
+ *
+ * @param in
+ * the reader
+ * @param length
+ * the maximum number of characters to read, or -1 to read until
+ * the end of file
+ * @return the string read
+ */
+ public static String readStringAndClose(Reader in, int length) throws IOException {
+ try {
+ if (length <= 0) {
+ length = Integer.MAX_VALUE;
+ }
+ int block = Math.min(BUFFER_BLOCK_SIZE, length);
+ StringWriter out = new StringWriter(length == Integer.MAX_VALUE ? block : length);
+ char[] buff = new char[block];
+ while (length > 0) {
+ int len = Math.min(block, length);
+ len = in.read(buff, 0, len);
+ if (len < 0) {
+ break;
+ }
+ out.write(buff, 0, len);
+ length -= len;
+ }
+ return out.toString();
+ } finally {
+ in.close();
+ }
+ }
+
+ /**
+ * Read a number of bytes from a stream and close it.
+ *
+ * @param in
+ * the stream
+ * @param length
+ * the maximum number of bytes to read, or -1 to read until the
+ * end of file
+ * @return the string read
+ */
+ public static byte[] readBlobAndClose(InputStream in, int length) throws IOException {
+ try {
+ if (length <= 0) {
+ length = Integer.MAX_VALUE;
+ }
+ int block = Math.min(BUFFER_BLOCK_SIZE, length);
+ ByteArrayOutputStream out = new ByteArrayOutputStream(length == Integer.MAX_VALUE ? block
+ : length);
+ byte[] buff = new byte[block];
+ while (length > 0) {
+ int len = Math.min(block, length);
+ len = in.read(buff, 0, len);
+ if (len < 0) {
+ break;
+ }
+ out.write(buff, 0, len);
+ length -= len;
+ }
+ return out.toByteArray();
+ } finally {
+ in.close();
+ }
+ }
+}
diff --git a/src/main/java/com/iciql/util/WeakIdentityHashMap.java b/src/main/java/com/iciql/util/WeakIdentityHashMap.java
new file mode 100644
index 0000000..bc03cd0
--- /dev/null
+++ b/src/main/java/com/iciql/util/WeakIdentityHashMap.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.
+ */
+
+package com.iciql.util;
+
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import com.iciql.IciqlException;
+
+/**
+ * This hash map uses weak references, so that elements that are no longer
+ * referenced elsewhere can be garbage collected. It also uses object identity
+ * to compare keys. The garbage collection happens when trying to add new data,
+ * or when resizing.
+ *
+ * @param <K>
+ * the keys
+ * @param <V>
+ * the value
+ */
+
+public class WeakIdentityHashMap<K, V> implements Map<K, V> {
+
+ private static final int MAX_LOAD = 90;
+ private static final WeakReference<Object> DELETED_KEY = new WeakReference<Object>(null);
+ private int mask, len, size, deletedCount, level;
+ private int maxSize, minSize, maxDeleted;
+ private WeakReference<K>[] keys;
+ private V[] values;
+
+ public WeakIdentityHashMap() {
+ reset(2);
+ }
+
+ public int size() {
+ return size;
+ }
+
+ private void checkSizePut() {
+ if (deletedCount > size) {
+ rehash(level);
+ }
+ if (size + deletedCount >= maxSize) {
+ rehash(level + 1);
+ }
+ }
+
+ private void checkSizeRemove() {
+ if (size < minSize && level > 0) {
+ rehash(level - 1);
+ } else if (deletedCount > maxDeleted) {
+ rehash(level);
+ }
+ }
+
+ private int getIndex(Object key) {
+ return System.identityHashCode(key) & mask;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void reset(int newLevel) {
+ minSize = size * 3 / 4;
+ size = 0;
+ level = newLevel;
+ len = 2 << level;
+ mask = len - 1;
+ maxSize = (int) (len * MAX_LOAD / 100L);
+ deletedCount = 0;
+ maxDeleted = 20 + len / 2;
+ keys = new WeakReference[len];
+ values = (V[]) new Object[len];
+ }
+
+ public V put(K key, V value) {
+ checkSizePut();
+ int index = getIndex(key);
+ int plus = 1;
+ int deleted = -1;
+ do {
+ WeakReference<K> k = keys[index];
+ if (k == null) {
+ // found an empty record
+ if (deleted >= 0) {
+ index = deleted;
+ deletedCount--;
+ }
+ size++;
+ keys[index] = new WeakReference<K>(key);
+ values[index] = value;
+ return null;
+ } else if (k == DELETED_KEY) {
+ if (deleted < 0) {
+ // found the first deleted record
+ deleted = index;
+ }
+ } else {
+ Object r = k.get();
+ if (r == null) {
+ delete(index);
+ } else if (r == key) {
+ // update existing
+ V old = values[index];
+ values[index] = value;
+ return old;
+ }
+ }
+ index = (index + plus++) & mask;
+ } while (plus <= len);
+ throw new IciqlException("Hashmap is full");
+ }
+
+ public V remove(Object key) {
+ checkSizeRemove();
+ int index = getIndex(key);
+ int plus = 1;
+ do {
+ WeakReference<K> k = keys[index];
+ if (k == null) {
+ // found an empty record
+ return null;
+ } else if (k == DELETED_KEY) {
+ // continue
+ } else {
+ Object r = k.get();
+ if (r == null) {
+ delete(index);
+ } else if (r == key) {
+ // found the record
+ V old = values[index];
+ delete(index);
+ return old;
+ }
+ }
+ index = (index + plus++) & mask;
+ k = keys[index];
+ } while (plus <= len);
+ // not found
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void delete(int index) {
+ keys[index] = (WeakReference<K>) DELETED_KEY;
+ values[index] = null;
+ deletedCount++;
+ size--;
+ }
+
+ private void rehash(int newLevel) {
+ WeakReference<K>[] oldKeys = keys;
+ V[] oldValues = values;
+ reset(newLevel);
+ for (int i = 0; i < oldKeys.length; i++) {
+ WeakReference<K> k = oldKeys[i];
+ if (k != null && k != DELETED_KEY) {
+ K key = k.get();
+ if (key != null) {
+ put(key, oldValues[i]);
+ }
+ }
+ }
+ }
+
+ public V get(Object key) {
+ int index = getIndex(key);
+ int plus = 1;
+ do {
+ WeakReference<K> k = keys[index];
+ if (k == null) {
+ return null;
+ } else if (k == DELETED_KEY) {
+ // continue
+ } else {
+ Object r = k.get();
+ if (r == null) {
+ delete(index);
+ } else if (r == key) {
+ return values[index];
+ }
+ }
+ index = (index + plus++) & mask;
+ } while (plus <= len);
+ return null;
+ }
+
+ public void clear() {
+ reset(2);
+ }
+
+ public boolean containsKey(Object key) {
+ return get(key) != null;
+ }
+
+ public boolean containsValue(Object value) {
+ if (value == null) {
+ return false;
+ }
+ for (V item : values) {
+ if (value.equals(item)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Set<java.util.Map.Entry<K, V>> entrySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ public Set<K> keySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void putAll(Map<? extends K, ? extends V> m) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Collection<V> values() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/main/java/com/iciql/util/package.html b/src/main/java/com/iciql/util/package.html
new file mode 100644
index 0000000..3d24dee
--- /dev/null
+++ b/src/main/java/com/iciql/util/package.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!--
+ Copyright 2004-2011 H2 Group.
+ Copyright 2011 James Moger.
+
+ 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<title>Javadoc package documentation</title>
+</head>
+<body>
+Utility classes for iciql.
+</body>
+</html> \ No newline at end of file