diff options
Diffstat (limited to 'src/main/java')
86 files changed, 12905 insertions, 13162 deletions
diff --git a/src/main/java/com/iciql/CompareType.java b/src/main/java/com/iciql/CompareType.java index 87ce77e..c97c0c8 100644 --- a/src/main/java/com/iciql/CompareType.java +++ b/src/main/java/com/iciql/CompareType.java @@ -22,24 +22,24 @@ package com.iciql; */
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), IN("IN", true), NOT_IN("NOT IN", true);
+ 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), IN("IN", true), NOT_IN("NOT IN", true);
- private String text;
- private boolean hasRightExpression;
+ private String text;
+ private boolean hasRightExpression;
- CompareType(String text, boolean hasRightExpression) {
- this.text = text;
- this.hasRightExpression = hasRightExpression;
- }
+ CompareType(String text, boolean hasRightExpression) {
+ this.text = text;
+ this.hasRightExpression = hasRightExpression;
+ }
- String getString() {
- return text;
- }
+ String getString() {
+ return text;
+ }
- boolean hasRightExpression() {
- return hasRightExpression;
- }
+ boolean hasRightExpression() {
+ return hasRightExpression;
+ }
}
diff --git a/src/main/java/com/iciql/Condition.java b/src/main/java/com/iciql/Condition.java index 0ed1d06..ab5a5e3 100644 --- a/src/main/java/com/iciql/Condition.java +++ b/src/main/java/com/iciql/Condition.java @@ -19,56 +19,55 @@ package com.iciql; /**
* A condition contains one or two operands and a compare operation.
- *
- * @param <A>
- * the operand type
+ *
+ * @param <A> the operand type
*/
class Condition<A> implements Token {
- CompareType compareType;
- A x, y, z;
- Iterable<A> i;
+ CompareType compareType;
+ A x, y, z;
+ Iterable<A> i;
- Condition(A x, CompareType compareType) {
- this(x, null, null, null, compareType);
- }
+ Condition(A x, CompareType compareType) {
+ this(x, null, null, null, compareType);
+ }
- Condition(A x, A y, CompareType compareType) {
- this(x, y, null, null, compareType);
- }
+ Condition(A x, A y, CompareType compareType) {
+ this(x, y, null, null, compareType);
+ }
- Condition(A x, A y, A z, CompareType compareType) {
- this(x, y, z, null, compareType);
- }
+ Condition(A x, A y, A z, CompareType compareType) {
+ this(x, y, z, null, compareType);
+ }
- Condition(A x, Iterable<A> i, CompareType compareType) {
- this(x, null, null, i, compareType);
- }
+ Condition(A x, Iterable<A> i, CompareType compareType) {
+ this(x, null, null, i, compareType);
+ }
- Condition(A x, A y, A z, Iterable<A> i, CompareType compareType) {
- this.compareType = compareType;
- this.x = x;
- this.y = y;
- this.z = z;
- this.i = i;
- }
+ Condition(A x, A y, A z, Iterable<A> i, CompareType compareType) {
+ this.compareType = compareType;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.i = i;
+ }
- @SuppressWarnings("unchecked")
- public <T> void appendSQL(SQLStatement stat, Query<T> query) {
- query.appendSQL(stat, null, x);
- stat.appendSQL(" ");
- stat.appendSQL(compareType.getString());
- if (compareType.hasRightExpression()) {
- if (i == null) {
- stat.appendSQL(" ");
- if (z == null) {
- query.appendSQL(stat, x, y);
- } else {
- query.appendSQL(stat, x, y, z, compareType);
- }
- } else {
- query.appendSQL(stat, x, (Iterable<Object>)i, compareType);
- }
- }
- }
+ @SuppressWarnings("unchecked")
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ query.appendSQL(stat, null, x);
+ stat.appendSQL(" ");
+ stat.appendSQL(compareType.getString());
+ if (compareType.hasRightExpression()) {
+ if (i == null) {
+ stat.appendSQL(" ");
+ if (z == null) {
+ query.appendSQL(stat, x, y);
+ } else {
+ query.appendSQL(stat, x, y, z, compareType);
+ }
+ } else {
+ query.appendSQL(stat, x, (Iterable<Object>) i, compareType);
+ }
+ }
+ }
}
diff --git a/src/main/java/com/iciql/ConditionAndOr.java b/src/main/java/com/iciql/ConditionAndOr.java index 4d1cd0e..228fa9a 100644 --- a/src/main/java/com/iciql/ConditionAndOr.java +++ b/src/main/java/com/iciql/ConditionAndOr.java @@ -22,16 +22,16 @@ package com.iciql; */
enum ConditionAndOr implements Token {
- AND("AND"), OR("OR");
+ AND("AND"), OR("OR");
- private String text;
+ private String text;
- ConditionAndOr(String text) {
- this.text = text;
- }
+ ConditionAndOr(String text) {
+ this.text = text;
+ }
- public <T> void appendSQL(SQLStatement stat, Query<T> query) {
- stat.appendSQL(text);
- }
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {
+ stat.appendSQL(text);
+ }
}
diff --git a/src/main/java/com/iciql/ConditionOpenClose.java b/src/main/java/com/iciql/ConditionOpenClose.java index 5284abd..c0d6ea4 100644 --- a/src/main/java/com/iciql/ConditionOpenClose.java +++ b/src/main/java/com/iciql/ConditionOpenClose.java @@ -18,16 +18,16 @@ package com.iciql; enum ConditionOpenClose implements Token { - OPEN("("), CLOSE(")"); + OPEN("("), CLOSE(")"); - private String text; + private String text; - ConditionOpenClose(String text) { - this.text = text; - } + ConditionOpenClose(String text) { + this.text = text; + } - public <T> void appendSQL(SQLStatement stat, Query<T> query) { - stat.appendSQL(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 index e27e24a..8c8b898 100644 --- a/src/main/java/com/iciql/Constants.java +++ b/src/main/java/com/iciql/Constants.java @@ -25,38 +25,38 @@ import java.util.jar.Manifest; */
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 API_CURRENT = "15";
-
- public static String getVersion() {
- return getManifestValue("implementation-version", "0.0.0-SNAPSHOT");
- }
-
- public static String getBuildDate() {
- return getManifestValue("build-date", "PENDING");
- }
-
- private static String getManifestValue(String attrib, String defaultValue) {
- Class<?> clazz = Constants.class;
- String className = clazz.getSimpleName() + ".class";
- String classPath = clazz.getResource(className).toString();
- try {
- String manifestPath;
- if (classPath.indexOf('!') > -1) {
- manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF";
- } else {
- String pkgPath = "/" + clazz.getPackage().getName().replace('.', '/');
- manifestPath = classPath.substring(0, classPath.indexOf(pkgPath)) + "/META-INF/MANIFEST.MF";
- }
- Manifest manifest = new Manifest(new URL(manifestPath).openStream());
- Attributes attr = manifest.getMainAttributes();
- String value = attr.getValue(attrib);
- return value;
- } catch (Exception e) {
- }
- return defaultValue;
- }
+ 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 API_CURRENT = "15";
+
+ public static String getVersion() {
+ return getManifestValue("implementation-version", "0.0.0-SNAPSHOT");
+ }
+
+ public static String getBuildDate() {
+ return getManifestValue("build-date", "PENDING");
+ }
+
+ private static String getManifestValue(String attrib, String defaultValue) {
+ Class<?> clazz = Constants.class;
+ String className = clazz.getSimpleName() + ".class";
+ String classPath = clazz.getResource(className).toString();
+ try {
+ String manifestPath;
+ if (classPath.indexOf('!') > -1) {
+ manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF";
+ } else {
+ String pkgPath = "/" + clazz.getPackage().getName().replace('.', '/');
+ manifestPath = classPath.substring(0, classPath.indexOf(pkgPath)) + "/META-INF/MANIFEST.MF";
+ }
+ Manifest manifest = new Manifest(new URL(manifestPath).openStream());
+ Attributes attr = manifest.getMainAttributes();
+ String value = attr.getValue(attrib);
+ return value;
+ } catch (Exception e) {
+ }
+ return defaultValue;
+ }
}
diff --git a/src/main/java/com/iciql/Dao.java b/src/main/java/com/iciql/Dao.java index 29b42f0..8cb16ee 100644 --- a/src/main/java/com/iciql/Dao.java +++ b/src/main/java/com/iciql/Dao.java @@ -25,138 +25,137 @@ import java.util.List; * The Dao interface defines all CRUD methods for handling SQL object operations. * * @author James Moger - * */ public interface Dao extends AutoCloseable { - /** - * Insert an object into the database. - * - * @param t - * @return true if successful - */ - <T> boolean insert(T t); - - /** - * Insert an object into the database and return it's primary key. - * - * @param t - * @return - */ - <T> long insertAndGetKey(T t); - - /** - * Insert all objects into the database. - * - * @param list - */ - <T> void insertAll(List<T> list); - - /** - * Insert all objects into the database and return the list of primary keys. - * - * @param t - * @return a list of primary keys - */ - <T> List<Long> insertAllAndGetKeys(List<T> t); - - /** - * Updates an object in the database. - * - * @param t - * @return true if successful - */ - <T> boolean update(T t); - - /** - * Updates all objects in the database. - * - * @param list - */ - <T> void updateAll(List<T> list); - - /** - * Inserts or updates an object in the database. - * - * @param t - */ - <T> void merge(T t); - - /** - * Deletes an object from the database. - * - * @param t - * @return true if successful - */ - <T> boolean delete(T t); - - /** - * Deletes all objects from the database. - * - * @param list - */ - <T> void deleteAll(List<T> list); - - /** - * Returns the underlying Db instance for lower-level access to database methods - * or direct JDBC access. - * - * @return the db instance - */ - Db db(); - - /** - * Close the underlying Db instance. - */ - @Override - void close(); - - /** - * Used to specify custom names for method parameters to be used - * for the SqlQuery or SqlUpdate annotations. - * - * You don't need to explicitly bind the parameters as each parameter - * is accessible by the standard "argN" syntax (0-indexed). - * - * Additionally, if you are compiling with Java 8 AND specifying the - * -parameters flag for javac, then you may use the parameter's name. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ ElementType.PARAMETER }) - public @interface Bind { - String value(); - } - - /** - * - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ ElementType.PARAMETER }) - public @interface BindBean { - String value() default ""; - } - - /** - * Used to indicate that a method should execute a query. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ ElementType.METHOD }) - public @interface SqlQuery { - String value(); - } - - /** - * Used to indicate that a method should execute a statement. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ ElementType.METHOD }) - public @interface SqlStatement { - String value(); - } - - public class BeanBinder { - public void bind(BindBean bind, Object obj) { - - } - } + /** + * Insert an object into the database. + * + * @param t + * @return true if successful + */ + <T> boolean insert(T t); + + /** + * Insert an object into the database and return it's primary key. + * + * @param t + * @return + */ + <T> long insertAndGetKey(T t); + + /** + * Insert all objects into the database. + * + * @param list + */ + <T> void insertAll(List<T> list); + + /** + * Insert all objects into the database and return the list of primary keys. + * + * @param t + * @return a list of primary keys + */ + <T> List<Long> insertAllAndGetKeys(List<T> t); + + /** + * Updates an object in the database. + * + * @param t + * @return true if successful + */ + <T> boolean update(T t); + + /** + * Updates all objects in the database. + * + * @param list + */ + <T> void updateAll(List<T> list); + + /** + * Inserts or updates an object in the database. + * + * @param t + */ + <T> void merge(T t); + + /** + * Deletes an object from the database. + * + * @param t + * @return true if successful + */ + <T> boolean delete(T t); + + /** + * Deletes all objects from the database. + * + * @param list + */ + <T> void deleteAll(List<T> list); + + /** + * Returns the underlying Db instance for lower-level access to database methods + * or direct JDBC access. + * + * @return the db instance + */ + Db db(); + + /** + * Close the underlying Db instance. + */ + @Override + void close(); + + /** + * Used to specify custom names for method parameters to be used + * for the SqlQuery or SqlUpdate annotations. + * <p> + * You don't need to explicitly bind the parameters as each parameter + * is accessible by the standard "argN" syntax (0-indexed). + * <p> + * Additionally, if you are compiling with Java 8 AND specifying the + * -parameters flag for javac, then you may use the parameter's name. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + public @interface Bind { + String value(); + } + + /** + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + public @interface BindBean { + String value() default ""; + } + + /** + * Used to indicate that a method should execute a query. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + public @interface SqlQuery { + String value(); + } + + /** + * Used to indicate that a method should execute a statement. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + public @interface SqlStatement { + String value(); + } + + public class BeanBinder { + public void bind(BindBean bind, Object obj) { + + } + } } diff --git a/src/main/java/com/iciql/DaoClasspathStatementProvider.java b/src/main/java/com/iciql/DaoClasspathStatementProvider.java index b1bbe14..3bb0b9e 100644 --- a/src/main/java/com/iciql/DaoClasspathStatementProvider.java +++ b/src/main/java/com/iciql/DaoClasspathStatementProvider.java @@ -15,81 +15,80 @@ */ package com.iciql; +import com.iciql.Iciql.Mode; + import java.io.InputStream; import java.util.Properties; -import com.iciql.Iciql.Mode; - /** * Loads DAO statements from Properties resource files the classpath. * * @author James Moger - * */ public class DaoClasspathStatementProvider implements DaoStatementProvider { - private final Properties externalStatements; - - public DaoClasspathStatementProvider() { - externalStatements = load(); - } - - /** - * Returns the list of statement resources to try locating. - * - * @return - */ - protected String[] getStatementResources() { - return new String[] { "/iciql.properties", "/iciql.xml", "/conf/iciql.properties", "/conf/iciql.xml" }; - } - - /** - * Loads the first statement resource found on the classpath. - * - * @return the loaded statements - */ - private Properties load() { - - Properties props = new Properties(); - for (String resource : getStatementResources()) { - - InputStream is = null; - - try { - is = DaoProxy.class.getResourceAsStream(resource); - - if (is != null) { - - if (resource.toLowerCase().endsWith(".xml")) { - // load an .XML statements file - props.loadFromXML(is); - } else { - // load a .Properties statements file - props.load(is); - } - - break; - } - - } catch (Exception e) { - throw new IciqlException(e, "Failed to parse {0}", resource); - } finally { - try { - is.close(); - } catch (Exception e) { - } - } - - } - return props; - } - - @Override - public String getStatement(String idOrStatement, Mode mode) { - final String modePrefix = "%" + mode.name().toLowerCase() + "."; - String value = externalStatements.getProperty(idOrStatement, idOrStatement); - value = externalStatements.getProperty(modePrefix + idOrStatement, value); - return value; - } + private final Properties externalStatements; + + public DaoClasspathStatementProvider() { + externalStatements = load(); + } + + /** + * Returns the list of statement resources to try locating. + * + * @return + */ + protected String[] getStatementResources() { + return new String[]{"/iciql.properties", "/iciql.xml", "/conf/iciql.properties", "/conf/iciql.xml"}; + } + + /** + * Loads the first statement resource found on the classpath. + * + * @return the loaded statements + */ + private Properties load() { + + Properties props = new Properties(); + for (String resource : getStatementResources()) { + + InputStream is = null; + + try { + is = DaoProxy.class.getResourceAsStream(resource); + + if (is != null) { + + if (resource.toLowerCase().endsWith(".xml")) { + // load an .XML statements file + props.loadFromXML(is); + } else { + // load a .Properties statements file + props.load(is); + } + + break; + } + + } catch (Exception e) { + throw new IciqlException(e, "Failed to parse {0}", resource); + } finally { + try { + is.close(); + } catch (Exception e) { + } + } + + } + return props; + } + + @Override + public String getStatement(String idOrStatement, Mode mode) { + final String modePrefix = "%" + mode.name().toLowerCase() + "."; + String value = externalStatements.getProperty(idOrStatement, idOrStatement); + value = externalStatements.getProperty(modePrefix + idOrStatement, value); + return value; + } } diff --git a/src/main/java/com/iciql/DaoProxy.java b/src/main/java/com/iciql/DaoProxy.java index 34187c4..8851a0c 100644 --- a/src/main/java/com/iciql/DaoProxy.java +++ b/src/main/java/com/iciql/DaoProxy.java @@ -15,6 +15,11 @@ */ package com.iciql; +import com.iciql.Iciql.DataTypeAdapter; +import com.iciql.util.JdbcUtils; +import com.iciql.util.StringUtils; +import com.iciql.util.Utils; + import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Constructor; @@ -37,503 +42,497 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.iciql.Iciql.DataTypeAdapter; -import com.iciql.util.JdbcUtils; -import com.iciql.util.StringUtils; -import com.iciql.util.Utils; - /** * DaoProxy creates a dynamic instance of the provided Dao interface. * - * @author James Moger - * * @param <X> + * @author James Moger */ final class DaoProxy<X extends Dao> implements InvocationHandler, Dao { - private final Db db; + private final Db db; - private final Class<X> daoInterface; + private final Class<X> daoInterface; - private final char bindingDelimiter = ':'; + private final char bindingDelimiter = ':'; - private final Map<Method, IndexedSql> indexedSqlCache; + private final Map<Method, IndexedSql> indexedSqlCache; - DaoProxy(Db db, Class<X> daoInterface) { - this.db = db; - this.daoInterface = daoInterface; - this.indexedSqlCache = new ConcurrentHashMap<Method, IndexedSql>(); - } + DaoProxy(Db db, Class<X> daoInterface) { + this.db = db; + this.daoInterface = daoInterface; + this.indexedSqlCache = new ConcurrentHashMap<Method, IndexedSql>(); + } - /** - * Builds a proxy object for the DAO interface. - * - * @return a proxy object - */ - @SuppressWarnings("unchecked") - X build() { + /** + * Builds a proxy object for the DAO interface. + * + * @return a proxy object + */ + @SuppressWarnings("unchecked") + X build() { - if (!daoInterface.isInterface()) { - throw new IciqlException("Dao {0} must be an interface!", daoInterface.getName()); - } + if (!daoInterface.isInterface()) { + throw new IciqlException("Dao {0} must be an interface!", daoInterface.getName()); + } - ClassLoader classLoader = daoInterface.getClassLoader(); + ClassLoader classLoader = daoInterface.getClassLoader(); - Set<Class<?>> interfaces = new HashSet<Class<?>>(); - interfaces.add(Dao.class); - interfaces.add(daoInterface); - for (Class<?> clazz : daoInterface.getInterfaces()) { - interfaces.add(clazz); - } + Set<Class<?>> interfaces = new HashSet<Class<?>>(); + interfaces.add(Dao.class); + interfaces.add(daoInterface); + for (Class<?> clazz : daoInterface.getInterfaces()) { + interfaces.add(clazz); + } - Class<?>[] constructorParams = { InvocationHandler.class }; - Class<?>[] allInterfaces = interfaces.toArray(new Class<?>[interfaces.size()]); + Class<?>[] constructorParams = {InvocationHandler.class}; + Class<?>[] allInterfaces = interfaces.toArray(new Class<?>[interfaces.size()]); - try { + try { - Class<?> proxyClass = Proxy.getProxyClass(classLoader, allInterfaces); - Constructor<?> proxyConstructor = proxyClass.getConstructor(constructorParams); - return (X) proxyConstructor.newInstance(new Object[] { this }); + Class<?> proxyClass = Proxy.getProxyClass(classLoader, allInterfaces); + Constructor<?> proxyConstructor = proxyClass.getConstructor(constructorParams); + return (X) proxyConstructor.newInstance(new Object[]{this}); - } catch (Exception e) { - throw new IciqlException(e); - } - } + } catch (Exception e) { + throw new IciqlException(e); + } + } - /** - * Invoke intercepts method calls and delegates execution to the appropriate object. - */ - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - try { + /** + * Invoke intercepts method calls and delegates execution to the appropriate object. + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + try { - if (method.getDeclaringClass() == Dao.class) { + if (method.getDeclaringClass() == Dao.class) { - return method.invoke(this, args); + return method.invoke(this, args); - } else if (method.isAnnotationPresent(SqlQuery.class)) { + } else if (method.isAnnotationPresent(SqlQuery.class)) { - String sql = method.getAnnotation(SqlQuery.class).value(); - String statement = db.getDaoStatementProvider().getStatement(sql, db.getMode()); - return executeQuery(method, args, statement); + String sql = method.getAnnotation(SqlQuery.class).value(); + String statement = db.getDaoStatementProvider().getStatement(sql, db.getMode()); + return executeQuery(method, args, statement); - } else if (method.isAnnotationPresent(SqlStatement.class)) { + } else if (method.isAnnotationPresent(SqlStatement.class)) { - String sql = method.getAnnotation(SqlStatement.class).value(); - String statement = db.getDaoStatementProvider().getStatement(sql, db.getMode()); - return executeStatement(method, args, statement); + String sql = method.getAnnotation(SqlStatement.class).value(); + String statement = db.getDaoStatementProvider().getStatement(sql, db.getMode()); + return executeStatement(method, args, statement); - } else { + } else { - throw new IciqlException("Can not invoke non-dao method {0}.{1}", - method.getDeclaringClass().getSimpleName(), method.getName()); + throw new IciqlException("Can not invoke non-dao method {0}.{1}", + method.getDeclaringClass().getSimpleName(), method.getName()); - } + } - } catch (InvocationTargetException te) { - throw te.getCause(); - } - } + } catch (InvocationTargetException te) { + throw te.getCause(); + } + } - /** - * Execute a query. - * - * @param method - * @param methodArgs - * @param sql - * @return the result - */ - private Object executeQuery(Method method, Object[] methodArgs, String sql) { + /** + * Execute a query. + * + * @param method + * @param methodArgs + * @param sql + * @return the result + */ + private Object executeQuery(Method method, Object[] methodArgs, String sql) { /* - * Determine and validate the return type + * Determine and validate the return type */ - Class<?> returnType = method.getReturnType(); - - if (void.class == returnType) { - throw new IciqlException("You must specify a return type for @{0} {1}.{2}!", - SqlQuery.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName()); - } - - if (Collection.class.isAssignableFrom(returnType)) { - throw new IciqlException("You may not return a collection for an @{0} method, please change the return type of {1}.{2} to YourClass[]!", - SqlQuery.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName()); - } - - boolean isArray = false; - if (returnType.isArray()) { - isArray = true; - returnType = returnType.getComponentType(); - } - - boolean isJavaType = returnType.isEnum() - || returnType.isPrimitive() - || java.lang.Boolean.class.isAssignableFrom(returnType) - || java.lang.Number.class.isAssignableFrom(returnType) - || java.lang.String.class.isAssignableFrom(returnType) - || java.util.Date.class.isAssignableFrom(returnType) - || byte[].class.isAssignableFrom(returnType); - - Class<? extends DataTypeAdapter<?>> adapter = Utils.getDataTypeAdapter(method.getAnnotations()); - if (adapter == null) { - adapter = Utils.getDataTypeAdapter(returnType.getAnnotations()); - } + Class<?> returnType = method.getReturnType(); + + if (void.class == returnType) { + throw new IciqlException("You must specify a return type for @{0} {1}.{2}!", + SqlQuery.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName()); + } + + if (Collection.class.isAssignableFrom(returnType)) { + throw new IciqlException("You may not return a collection for an @{0} method, please change the return type of {1}.{2} to YourClass[]!", + SqlQuery.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName()); + } + + boolean isArray = false; + if (returnType.isArray()) { + isArray = true; + returnType = returnType.getComponentType(); + } + + boolean isJavaType = returnType.isEnum() + || returnType.isPrimitive() + || java.lang.Boolean.class.isAssignableFrom(returnType) + || java.lang.Number.class.isAssignableFrom(returnType) + || java.lang.String.class.isAssignableFrom(returnType) + || java.util.Date.class.isAssignableFrom(returnType) + || byte[].class.isAssignableFrom(returnType); + + Class<? extends DataTypeAdapter<?>> adapter = Utils.getDataTypeAdapter(method.getAnnotations()); + if (adapter == null) { + adapter = Utils.getDataTypeAdapter(returnType.getAnnotations()); + } /* * Prepare & execute sql */ - PreparedSql preparedSql = prepareSql(method, methodArgs, sql); + PreparedSql preparedSql = prepareSql(method, methodArgs, sql); - List<Object> objects; - if (!isJavaType && adapter == null) { + List<Object> objects; + if (!isJavaType && adapter == null) { - // query of an Iciql model - objects = db.executeQuery(returnType, preparedSql.sql, preparedSql.parameters); + // query of an Iciql model + objects = db.executeQuery(returnType, preparedSql.sql, preparedSql.parameters); - } else { + } else { - // query of (array of) standard Java type or a DataTypeAdapter type - objects = Utils.newArrayList(); - ResultSet rs = db.executeQuery(preparedSql.sql, preparedSql.parameters); - try { + // query of (array of) standard Java type or a DataTypeAdapter type + objects = Utils.newArrayList(); + ResultSet rs = db.executeQuery(preparedSql.sql, preparedSql.parameters); + try { - while (rs.next()) { + while (rs.next()) { - Object value = db.getDialect().deserialize(rs, 1, returnType, adapter); - objects.add(value); + Object value = db.getDialect().deserialize(rs, 1, returnType, adapter); + objects.add(value); - if (!isArray) { - // we are not returning an array so we break - // the loop and return the first result - break; - } - } + if (!isArray) { + // we are not returning an array so we break + // the loop and return the first result + break; + } + } - } catch (SQLException e) { - throw new IciqlException(e); - } finally { - JdbcUtils.closeSilently(rs); - } + } catch (SQLException e) { + throw new IciqlException(e); + } finally { + JdbcUtils.closeSilently(rs); + } - } + } /* * Return the results */ - if (objects == null || objects.isEmpty()) { + if (objects == null || objects.isEmpty()) { - // no results - if (isArray) { - // return an empty array - return Array.newInstance(returnType, 0); - } + // no results + if (isArray) { + // return an empty array + return Array.newInstance(returnType, 0); + } - // nothing to return! - return null; + // nothing to return! + return null; - } else if (isArray) { + } else if (isArray) { - // return an array of object results - Object array = Array.newInstance(returnType, objects.size()); - for (int i = 0; i < objects.size(); i++) { - Array.set(array, i, objects.get(i)); - } - return array; + // return an array of object results + Object array = Array.newInstance(returnType, objects.size()); + for (int i = 0; i < objects.size(); i++) { + Array.set(array, i, objects.get(i)); + } + return array; - } + } - // return first element - return objects.get(0); - } + // return first element + return objects.get(0); + } - /** - * Execute a statement. - * - * @param method - * @param methodArgs - * @param sql - * @return the result - */ - private Object executeStatement(Method method, Object[] methodArgs, String sql) { + /** + * Execute a statement. + * + * @param method + * @param methodArgs + * @param sql + * @return the result + */ + private Object executeStatement(Method method, Object[] methodArgs, String sql) { /* * Determine and validate the return type */ - Class<?> returnType = method.getReturnType(); + Class<?> returnType = method.getReturnType(); - if (void.class != returnType && boolean.class != returnType && int.class != returnType) { + if (void.class != returnType && boolean.class != returnType && int.class != returnType) { - throw new IciqlException("Invalid return type '{0}' for @{1} {2}.{3}!", - returnType.getSimpleName(), SqlQuery.class.getSimpleName(), - method.getDeclaringClass().getSimpleName(), method.getName()); - } + throw new IciqlException("Invalid return type '{0}' for @{1} {2}.{3}!", + returnType.getSimpleName(), SqlQuery.class.getSimpleName(), + method.getDeclaringClass().getSimpleName(), method.getName()); + } /* * Prepare & execute sql */ - PreparedSql preparedSql = prepareSql(method, methodArgs, sql); - int rows = db.executeUpdate(preparedSql.sql, preparedSql.parameters); + PreparedSql preparedSql = prepareSql(method, methodArgs, sql); + int rows = db.executeUpdate(preparedSql.sql, preparedSql.parameters); /* * Return the results */ - if (void.class == returnType) { - - // return nothing - return null; - - } else if (boolean.class == returnType) { - - // return true if any rows were affected - return rows > 0; - - } else { - - // return number of rows - return rows; - - } - } - - /** - * Prepares an sql statement and execution parameters based on the supplied - * method and it's arguments. - * - * @param method - * @param methodArgs - * @param sql - * @return a prepared sql statement and arguments - */ - private PreparedSql prepareSql(Method method, Object[] methodArgs, String sql) { - - if (methodArgs == null || methodArgs.length == 0) { - // no method arguments - return new PreparedSql(sql, null); - } - - IndexedSql indexedSql = indexedSqlCache.get(method); - - if (indexedSql == null) { - - // index the sql and method args - indexedSql = indexSql(method, sql); - - // cache the indexed sql for re-use - indexedSqlCache.put(method, indexedSql); - } - - final PreparedSql preparedSql = indexedSql.prepareSql(db, methodArgs); - return preparedSql; - } - - /** - * Indexes an sql statement and method args based on the supplied - * method and it's arguments. - * - * @param method - * @param sql - * @return an indexed sql statement and arguments - */ - private IndexedSql indexSql(Method method, String sql) { - - Map<String, IndexedArgument> parameterIndex = buildParameterIndex(method); - - // build a regex to extract parameter names from the sql statement - StringBuilder sb = new StringBuilder(); - sb.append(bindingDelimiter); - sb.append("{1}(\\?"); - for (String name : parameterIndex.keySet()) { - sb.append("|"); - // strip binding delimeter from name - sb.append(name); - } - sb.append(')'); - - // identify parameters, replace with the '?' PreparedStatement - // delimiter and build the PreparedStatement parameters array - final String regex = sb.toString(); - final Pattern p = Pattern.compile(regex); - final Matcher m = p.matcher(sql); - final StringBuffer buffer = new StringBuffer(); - - List<IndexedArgument> indexedArgs = Utils.newArrayList(); - int count = 0; - while (m.find()) { - String binding = m.group(1); - m.appendReplacement(buffer, "?"); - - IndexedArgument indexedArg; - if ("?".equals(binding)) { - // standard ? JDBC placeholder - indexedArg = parameterIndex.get("arg" + count); - } else { - // named placeholder - indexedArg = parameterIndex.get(binding); - } - - if (indexedArg == null) { - throw new IciqlException("Unbound SQL parameter '{0}' in {1}.{2}", - binding, method.getDeclaringClass().getSimpleName(), method.getName()); - } - indexedArgs.add(indexedArg); - - count++; - } - m.appendTail(buffer); - - final String statement = buffer.toString(); - - // create an IndexedSql container for the statement and indexes - return new IndexedSql(statement, Collections.unmodifiableList(indexedArgs)); - - } - - /** - * Builds an index of parameter name->(position,typeAdapter) from the method arguments - * array. This index is calculated once per method. - * - * @param method - * @return a bindings map of ("name", IndexedArgument) pairs - */ - private Map<String, IndexedArgument> buildParameterIndex(Method method) { - - Map<String, IndexedArgument> index = new TreeMap<String, IndexedArgument>(); - - Annotation [][] annotationsMatrix = method.getParameterAnnotations(); - for (int i = 0; i < annotationsMatrix.length; i++) { - - Annotation [] annotations = annotationsMatrix[i]; + if (void.class == returnType) { + + // return nothing + return null; + + } else if (boolean.class == returnType) { + + // return true if any rows were affected + return rows > 0; + + } else { + + // return number of rows + return rows; + + } + } + + /** + * Prepares an sql statement and execution parameters based on the supplied + * method and it's arguments. + * + * @param method + * @param methodArgs + * @param sql + * @return a prepared sql statement and arguments + */ + private PreparedSql prepareSql(Method method, Object[] methodArgs, String sql) { + + if (methodArgs == null || methodArgs.length == 0) { + // no method arguments + return new PreparedSql(sql, null); + } + + IndexedSql indexedSql = indexedSqlCache.get(method); + + if (indexedSql == null) { + + // index the sql and method args + indexedSql = indexSql(method, sql); + + // cache the indexed sql for re-use + indexedSqlCache.put(method, indexedSql); + } + + final PreparedSql preparedSql = indexedSql.prepareSql(db, methodArgs); + return preparedSql; + } + + /** + * Indexes an sql statement and method args based on the supplied + * method and it's arguments. + * + * @param method + * @param sql + * @return an indexed sql statement and arguments + */ + private IndexedSql indexSql(Method method, String sql) { + + Map<String, IndexedArgument> parameterIndex = buildParameterIndex(method); + + // build a regex to extract parameter names from the sql statement + StringBuilder sb = new StringBuilder(); + sb.append(bindingDelimiter); + sb.append("{1}(\\?"); + for (String name : parameterIndex.keySet()) { + sb.append("|"); + // strip binding delimeter from name + sb.append(name); + } + sb.append(')'); + + // identify parameters, replace with the '?' PreparedStatement + // delimiter and build the PreparedStatement parameters array + final String regex = sb.toString(); + final Pattern p = Pattern.compile(regex); + final Matcher m = p.matcher(sql); + final StringBuffer buffer = new StringBuffer(); + + List<IndexedArgument> indexedArgs = Utils.newArrayList(); + int count = 0; + while (m.find()) { + String binding = m.group(1); + m.appendReplacement(buffer, "?"); + + IndexedArgument indexedArg; + if ("?".equals(binding)) { + // standard ? JDBC placeholder + indexedArg = parameterIndex.get("arg" + count); + } else { + // named placeholder + indexedArg = parameterIndex.get(binding); + } + + if (indexedArg == null) { + throw new IciqlException("Unbound SQL parameter '{0}' in {1}.{2}", + binding, method.getDeclaringClass().getSimpleName(), method.getName()); + } + indexedArgs.add(indexedArg); + + count++; + } + m.appendTail(buffer); + + final String statement = buffer.toString(); + + // create an IndexedSql container for the statement and indexes + return new IndexedSql(statement, Collections.unmodifiableList(indexedArgs)); + + } + + /** + * Builds an index of parameter name->(position,typeAdapter) from the method arguments + * array. This index is calculated once per method. + * + * @param method + * @return a bindings map of ("name", IndexedArgument) pairs + */ + private Map<String, IndexedArgument> buildParameterIndex(Method method) { + + Map<String, IndexedArgument> index = new TreeMap<String, IndexedArgument>(); + + Annotation[][] annotationsMatrix = method.getParameterAnnotations(); + for (int i = 0; i < annotationsMatrix.length; i++) { + + Annotation[] annotations = annotationsMatrix[i]; /* * Conditionally map the bean properties of the method argument * class to Method and Field instances. */ - BindBean bean = getAnnotation(BindBean.class, annotations); - if (bean != null) { - final String prefix = bean.value(); - final Class<?> argumentClass = method.getParameterTypes()[i]; - Map<String, IndexedArgument> beanIndex = buildBeanIndex(i, prefix, argumentClass); - index.putAll(beanIndex); - } - - Class<? extends DataTypeAdapter<?>> typeAdapter = Utils.getDataTypeAdapter(annotations); - final IndexedArgument indexedArgument = new IndexedArgument(i, typeAdapter); - - // :N - 1-indexed, like JDBC ResultSet - index.put("" + (i + 1), indexedArgument); - - // argN - 0-indexed, like Reflection - index.put("arg" + i, indexedArgument); - - // Bound name - Bind binding = getAnnotation(Bind.class, annotations); - if (binding!= null && !binding.value().isEmpty()) { - index.put(binding.value(), indexedArgument); - } - - // try mapping Java 8 argument names, may overwrite argN - try { - Class<?> nullArgs = null; - Method getParameters = method.getClass().getMethod("getParameters", nullArgs); - if (getParameters != null) { - Object [] parameters = (Object []) getParameters.invoke(method, nullArgs); - if (parameters != null) { - Object o = parameters[i]; - Method getName = o.getClass().getMethod("getName", nullArgs); - String j8name = getName.invoke(o, nullArgs).toString(); - if (!j8name.isEmpty()) { - index.put(j8name, indexedArgument); - } - } - } - } catch (Throwable t) { - } - } - - return index; - } - - /** - * Builds an index of parameter name->(position,method) from the method arguments - * array. This index is calculated once per method. - * - * @param argumentIndex - * @param prefix - * @param beanClass - * @return a bindings map of ("prefix.property", IndexedArgument) pairs - */ - private Map<String, IndexedArgument> buildBeanIndex(int argumentIndex, String prefix, Class<?> beanClass) { - - final String beanPrefix = StringUtils.isNullOrEmpty(prefix) ? "" : (prefix + "."); - final Map<String, IndexedArgument> index = new TreeMap<String, IndexedArgument>(); - - // map JavaBean property getters - for (Method method : beanClass.getMethods()) { - - if (Modifier.isStatic(method.getModifiers()) - || method.getReturnType() == void.class - || method.getParameterTypes().length > 0 - || method.getDeclaringClass() == Object.class) { - - // not a JavaBean property - continue; - } - - final String propertyName; - final String name = method.getName(); - if (name.startsWith("get")) { - propertyName = method.getName().substring(3); - } else if (name.startsWith("is")) { - propertyName = method.getName().substring(2); - } else { - propertyName = null; - } - - if (propertyName == null) { - // not a conventional JavaBean property - continue; - } - - final String binding = beanPrefix + preparePropertyName(propertyName); - final IndexedArgument indexedArg = new IndexedArgument(argumentIndex, method); - - index.put(binding, indexedArg); - } - - // map public instance fields - for (Field field : beanClass.getFields()) { - - if (Modifier.isStatic(field.getModifiers())) { - // not a JavaBean property - continue; - } - - final String binding = beanPrefix + preparePropertyName(field.getName()); - final IndexedArgument indexedArg = new IndexedArgument(argumentIndex, field); - - index.put(binding, indexedArg); - - } - - return index; - } - - @SuppressWarnings("unchecked") - private <T> T getAnnotation(Class<T> annotationClass, Annotation [] annotations) { - if (annotations != null) { - for (Annotation annotation : annotations) { - if (annotation.annotationType() == annotationClass) { - return (T) annotation; - } - } - } - return null; - } - - private String preparePropertyName(String value) { - return Character.toLowerCase(value.charAt(0)) + value.substring(1); - } + BindBean bean = getAnnotation(BindBean.class, annotations); + if (bean != null) { + final String prefix = bean.value(); + final Class<?> argumentClass = method.getParameterTypes()[i]; + Map<String, IndexedArgument> beanIndex = buildBeanIndex(i, prefix, argumentClass); + index.putAll(beanIndex); + } + + Class<? extends DataTypeAdapter<?>> typeAdapter = Utils.getDataTypeAdapter(annotations); + final IndexedArgument indexedArgument = new IndexedArgument(i, typeAdapter); + + // :N - 1-indexed, like JDBC ResultSet + index.put("" + (i + 1), indexedArgument); + + // argN - 0-indexed, like Reflection + index.put("arg" + i, indexedArgument); + + // Bound name + Bind binding = getAnnotation(Bind.class, annotations); + if (binding != null && !binding.value().isEmpty()) { + index.put(binding.value(), indexedArgument); + } + + // try mapping Java 8 argument names, may overwrite argN + try { + Class<?> nullArgs = null; + Method getParameters = method.getClass().getMethod("getParameters", nullArgs); + if (getParameters != null) { + Object[] parameters = (Object[]) getParameters.invoke(method, nullArgs); + if (parameters != null) { + Object o = parameters[i]; + Method getName = o.getClass().getMethod("getName", nullArgs); + String j8name = getName.invoke(o, nullArgs).toString(); + if (!j8name.isEmpty()) { + index.put(j8name, indexedArgument); + } + } + } + } catch (Throwable t) { + } + } + + return index; + } + + /** + * Builds an index of parameter name->(position,method) from the method arguments + * array. This index is calculated once per method. + * + * @param argumentIndex + * @param prefix + * @param beanClass + * @return a bindings map of ("prefix.property", IndexedArgument) pairs + */ + private Map<String, IndexedArgument> buildBeanIndex(int argumentIndex, String prefix, Class<?> beanClass) { + + final String beanPrefix = StringUtils.isNullOrEmpty(prefix) ? "" : (prefix + "."); + final Map<String, IndexedArgument> index = new TreeMap<String, IndexedArgument>(); + + // map JavaBean property getters + for (Method method : beanClass.getMethods()) { + + if (Modifier.isStatic(method.getModifiers()) + || method.getReturnType() == void.class + || method.getParameterTypes().length > 0 + || method.getDeclaringClass() == Object.class) { + + // not a JavaBean property + continue; + } + + final String propertyName; + final String name = method.getName(); + if (name.startsWith("get")) { + propertyName = method.getName().substring(3); + } else if (name.startsWith("is")) { + propertyName = method.getName().substring(2); + } else { + propertyName = null; + } + + if (propertyName == null) { + // not a conventional JavaBean property + continue; + } + + final String binding = beanPrefix + preparePropertyName(propertyName); + final IndexedArgument indexedArg = new IndexedArgument(argumentIndex, method); + + index.put(binding, indexedArg); + } + + // map public instance fields + for (Field field : beanClass.getFields()) { + + if (Modifier.isStatic(field.getModifiers())) { + // not a JavaBean property + continue; + } + + final String binding = beanPrefix + preparePropertyName(field.getName()); + final IndexedArgument indexedArg = new IndexedArgument(argumentIndex, field); + + index.put(binding, indexedArg); + + } + + return index; + } + + @SuppressWarnings("unchecked") + private <T> T getAnnotation(Class<T> annotationClass, Annotation[] annotations) { + if (annotations != null) { + for (Annotation annotation : annotations) { + if (annotation.annotationType() == annotationClass) { + return (T) annotation; + } + } + } + return null; + } + + private String preparePropertyName(String value) { + return Character.toLowerCase(value.charAt(0)) + value.substring(1); + } /* * @@ -541,213 +540,213 @@ final class DaoProxy<X extends Dao> implements InvocationHandler, Dao { * */ - @Override - public final Db db() { - return db; - } - - @Override - public final <T> boolean insert(T t) { - return db.insert(t); - } - - @Override - public final <T> void insertAll(List<T> t) { - db.insertAll(t); - } - - @Override - public final <T> long insertAndGetKey(T t) { - return db.insertAndGetKey(t); - } - - @Override - public final <T> List<Long> insertAllAndGetKeys(List<T> t) { - return db.insertAllAndGetKeys(t); - } - - @Override - public final <T> boolean update(T t) { - return db.update(t); - } - - @Override - public final <T> void updateAll(List<T> t) { - db.updateAll(t); - } - - @Override - public final <T> void merge(T t) { - db.merge(t); - } - - @Override - public final <T> boolean delete(T t) { - return db.delete(t); - } - - @Override - public final <T> void deleteAll(List<T> t) { - db.deleteAll(t); - } - - @Override - public final void close() { - db.close(); - } - - /** - * Container class to hold the prepared JDBC SQL statement and execution - * parameters. - */ - private class PreparedSql { - final String sql; - final Object [] parameters; - - PreparedSql(String sql, Object [] parameters) { - this.sql = sql; - this.parameters = parameters; - } - - @Override - public String toString() { - return sql; - } - - } - - /** - * Container class to hold a parsed JDBC SQL statement and - * IndexedParameters. - * <p> - * Instances of this class are cached because they are functional processing - * containers as they contain Method and Field references for binding beans - * and matching to method arguments. - * </p> - */ - private class IndexedSql { - final String sql; - final List<IndexedArgument> indexedArgs; - - IndexedSql(String sql, List<IndexedArgument> indexedArgs) { - this.sql = sql; - this.indexedArgs = indexedArgs; - } - - /** - * Prepares the method arguments for statement execution. - * - * @param db - * @param methodArgs - * @return the prepared sql statement and parameters - */ - PreparedSql prepareSql(Db db, Object [] methodArgs) { - - Object [] parameters = new Object[indexedArgs.size()]; - - for (int i = 0; i < indexedArgs.size(); i++) { - - IndexedArgument indexedArg = indexedArgs.get(i); - Object methodArg = methodArgs[indexedArg.index]; + @Override + public final Db db() { + return db; + } + + @Override + public final <T> boolean insert(T t) { + return db.insert(t); + } + + @Override + public final <T> void insertAll(List<T> t) { + db.insertAll(t); + } + + @Override + public final <T> long insertAndGetKey(T t) { + return db.insertAndGetKey(t); + } + + @Override + public final <T> List<Long> insertAllAndGetKeys(List<T> t) { + return db.insertAllAndGetKeys(t); + } + + @Override + public final <T> boolean update(T t) { + return db.update(t); + } + + @Override + public final <T> void updateAll(List<T> t) { + db.updateAll(t); + } + + @Override + public final <T> void merge(T t) { + db.merge(t); + } + + @Override + public final <T> boolean delete(T t) { + return db.delete(t); + } + + @Override + public final <T> void deleteAll(List<T> t) { + db.deleteAll(t); + } + + @Override + public final void close() { + db.close(); + } + + /** + * Container class to hold the prepared JDBC SQL statement and execution + * parameters. + */ + private class PreparedSql { + final String sql; + final Object[] parameters; + + PreparedSql(String sql, Object[] parameters) { + this.sql = sql; + this.parameters = parameters; + } + + @Override + public String toString() { + return sql; + } + + } + + /** + * Container class to hold a parsed JDBC SQL statement and + * IndexedParameters. + * <p> + * Instances of this class are cached because they are functional processing + * containers as they contain Method and Field references for binding beans + * and matching to method arguments. + * </p> + */ + private class IndexedSql { + final String sql; + final List<IndexedArgument> indexedArgs; + + IndexedSql(String sql, List<IndexedArgument> indexedArgs) { + this.sql = sql; + this.indexedArgs = indexedArgs; + } + + /** + * Prepares the method arguments for statement execution. + * + * @param db + * @param methodArgs + * @return the prepared sql statement and parameters + */ + PreparedSql prepareSql(Db db, Object[] methodArgs) { + + Object[] parameters = new Object[indexedArgs.size()]; + + for (int i = 0; i < indexedArgs.size(); i++) { + + IndexedArgument indexedArg = indexedArgs.get(i); + Object methodArg = methodArgs[indexedArg.index]; + + Object value = methodArg; + Class<? extends DataTypeAdapter<?>> typeAdapter = indexedArg.typeAdapter; + + if (indexedArg.method != null) { + + // execute the bean method + try { + + value = indexedArg.method.invoke(methodArg); + typeAdapter = Utils.getDataTypeAdapter(indexedArg.method.getAnnotations()); + + } catch (Exception e) { + throw new IciqlException(e); + } + + } else if (indexedArg.field != null) { + + // extract the field value + try { + + value = indexedArg.field.get(methodArg); + typeAdapter = Utils.getDataTypeAdapter(indexedArg.field.getAnnotations()); + + } catch (Exception e) { + throw new IciqlException(e); + } + + } else if (typeAdapter == null) { + + // identify the type adapter for the argument class + typeAdapter = Utils.getDataTypeAdapter(methodArg.getClass().getAnnotations()); + } + + // prepare the parameter + parameters[i] = db.getDialect().serialize(value, typeAdapter); + + } + + return new PreparedSql(sql, parameters); + + } + + @Override + public String toString() { + return sql; + } + } + + /** + * IndexedArgument holds cached information about how to process an method + * argument by it's index in the method arguments array. + * <p> + * An argument may be passed-through, might be bound to a bean property, + * might be transformed with a type adapter, or a combination of these. + * </p> + */ + private class IndexedArgument { + final int index; + final Class<? extends DataTypeAdapter<?>> typeAdapter; + final Method method; + final Field field; + + IndexedArgument(int index, Class<? extends DataTypeAdapter<?>> typeAdapter) { + this.index = index; + this.typeAdapter = typeAdapter; + this.method = null; + this.field = null; + } + + IndexedArgument(int methodArgIndex, Method method) { + this.index = methodArgIndex; + this.typeAdapter = null; + this.method = method; + this.field = null; + } + + IndexedArgument(int methodArgIndex, Field field) { + this.index = methodArgIndex; + this.typeAdapter = null; + this.method = null; + this.field = field; + } + + @Override + public String toString() { + + String accessor; + if (method != null) { + accessor = "M:" + method.getDeclaringClass().getSimpleName() + "." + method.getName(); + } else if (field != null) { + accessor = "F:" + field.getDeclaringClass().getSimpleName() + "." + field.getName(); + } else { + accessor = "A:arg"; + } - Object value = methodArg; - Class<? extends DataTypeAdapter<?>> typeAdapter = indexedArg.typeAdapter; - - if (indexedArg.method != null) { - - // execute the bean method - try { - - value = indexedArg.method.invoke(methodArg); - typeAdapter = Utils.getDataTypeAdapter(indexedArg.method.getAnnotations()); - - } catch (Exception e) { - throw new IciqlException(e); - } - - } else if (indexedArg.field != null) { - - // extract the field value - try { - - value = indexedArg.field.get(methodArg); - typeAdapter = Utils.getDataTypeAdapter(indexedArg.field.getAnnotations()); - - } catch (Exception e) { - throw new IciqlException(e); - } - - } else if (typeAdapter == null) { - - // identify the type adapter for the argument class - typeAdapter = Utils.getDataTypeAdapter(methodArg.getClass().getAnnotations()); - } - - // prepare the parameter - parameters[i] = db.getDialect().serialize(value, typeAdapter); - - } - - return new PreparedSql(sql, parameters); - - } - - @Override - public String toString() { - return sql; - } - } - - /** - * IndexedArgument holds cached information about how to process an method - * argument by it's index in the method arguments array. - * <p> - * An argument may be passed-through, might be bound to a bean property, - * might be transformed with a type adapter, or a combination of these. - * </p> - */ - private class IndexedArgument { - final int index; - final Class<? extends DataTypeAdapter<?>> typeAdapter; - final Method method; - final Field field; - - IndexedArgument(int index, Class<? extends DataTypeAdapter<?>> typeAdapter) { - this.index = index; - this.typeAdapter = typeAdapter; - this.method = null; - this.field = null; - } - - IndexedArgument(int methodArgIndex, Method method) { - this.index = methodArgIndex; - this.typeAdapter = null; - this.method = method; - this.field = null; - } - - IndexedArgument(int methodArgIndex, Field field) { - this.index = methodArgIndex; - this.typeAdapter = null; - this.method = null; - this.field = field; - } - - @Override - public String toString() { - - String accessor; - if (method != null) { - accessor = "M:" + method.getDeclaringClass().getSimpleName() + "." + method.getName(); - } else if (field != null) { - accessor = "F:" + field.getDeclaringClass().getSimpleName() + "." + field.getName(); - } else { - accessor = "A:arg"; - } - - return index + ":" + accessor + (typeAdapter == null ? "" : (":" + typeAdapter.getSimpleName())); - } - - } + return index + ":" + accessor + (typeAdapter == null ? "" : (":" + typeAdapter.getSimpleName())); + } + + } } diff --git a/src/main/java/com/iciql/DaoStatementProvider.java b/src/main/java/com/iciql/DaoStatementProvider.java index 7e7522a..673638c 100644 --- a/src/main/java/com/iciql/DaoStatementProvider.java +++ b/src/main/java/com/iciql/DaoStatementProvider.java @@ -21,16 +21,15 @@ import com.iciql.Iciql.Mode; * Defines the interface for retrieving externalized DAO statements. * * @author James Moger - * */ public interface DaoStatementProvider { - /** - * Returns the statement associated with the id. - * - * @param idOrStatement - * @param mode - * @return the statement - */ - String getStatement(String idOrStatement, Mode mode); + /** + * Returns the statement associated with the id. + * + * @param idOrStatement + * @param mode + * @return the statement + */ + String getStatement(String idOrStatement, Mode mode); } diff --git a/src/main/java/com/iciql/Db.java b/src/main/java/com/iciql/Db.java index d0756bc..24396a4 100644 --- a/src/main/java/com/iciql/Db.java +++ b/src/main/java/com/iciql/Db.java @@ -19,6 +19,18 @@ package com.iciql; +import com.iciql.DbUpgrader.DefaultDbUpgrader; +import com.iciql.Iciql.IQTable; +import com.iciql.Iciql.IQVersion; +import com.iciql.Iciql.IQView; +import com.iciql.Iciql.Mode; +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; + +import javax.sql.DataSource; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; @@ -35,851 +47,827 @@ 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.Iciql.Mode; -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 implements AutoCloseable { - /** - * 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 Mode mode; - 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; - private DaoStatementProvider daoStatementProvider; - - 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); - DIALECTS.put("SQLite", SQLDialectSQLite.class); - } - - private Db(Connection conn, Mode mode) { - this.conn = conn; - this.mode = mode; - String databaseName = null; - try { - DatabaseMetaData 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(this); - daoStatementProvider = new NoExternalDaoStatements(); - } - - /** - * 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 <X> boolean isToken(X x) { - return TOKENS.containsKey(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) { - return open(url, Mode.PROD); - } - - public static Db open(String url, Mode mode) { - try { - Connection conn = JdbcUtils.getConnection(null, url, null, null); - return new Db(conn, mode); - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - public static Db open(String url, String user, String password) { - return open(url, user, password, Mode.PROD); - } - - public static Db open(String url, String user, String password, Mode mode) { - try { - Connection conn = JdbcUtils.getConnection(null, url, user, password); - return new Db(conn, mode); - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - public static Db open(String url, String user, char[] password) { - return open(url, user, password, Mode.PROD); - } - - public static Db open(String url, String user, char[] password, Mode mode) { - try { - Connection conn = JdbcUtils.getConnection(null, url, user, password == null ? null : new String(password)); - return new Db(conn, mode); - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - public static Db open(DataSource ds) { - return open(ds, Mode.PROD); - } - - /** - * 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 - * @param mode - * the runtime mode - * @return the database instance. - */ - public static Db open(DataSource ds, Mode mode) { - try { - return new Db(ds.getConnection(), mode); - } catch (SQLException e) { - throw new IciqlException(e); - } - } - - public static Db open(Connection conn) { - return open(conn, Mode.PROD); - } - - public static Db open(Connection conn, Mode mode) { - return new Db(conn, mode); - } - - /** - * Returns the Iciql runtime mode. - * - * @return the runtime mode - */ - public Mode getMode() { - return mode; - } - - /** - * Returns a new DAO instance for the specified class. - * - * @param daoClass - * @return - * @throws Exception - */ - @SuppressWarnings("resource") - public <X extends Dao> X open(Class<X> daoClass) { - return new DaoProxy<X>(this, daoClass).build(); - } - - /** - * Returns the DAO statement provider. - * - * @return the DAO statement provider - */ - public DaoStatementProvider getDaoStatementProvider() { - return daoStatementProvider; - } - - /** - * Sets the DAO statement provider. - * - * @param statementProvider - */ - public void setDaoStatementProvider(DaoStatementProvider statementProvider) { - if (statementProvider == null) { - throw new IciqlException("You must provide a valid {0} instance!", - DaoStatementProvider.class.getSimpleName()); - } - - this.daoStatementProvider = statementProvider; - } - - /** - * 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> boolean 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); - } - return rc == 1; - } - - public <T> long insertAndGetKey(T t) { - Class<?> clazz = t.getClass(); - return define(clazz).createIfRequired(this).insert(this, t, true); - } - - /** - * Upsert 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 or INSERT OR REPLACE INTO 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 upsert(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("upsert failed"); - } - } - - /** - * 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 or INSERT OR REPLACE INTO 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) { - upsert(t); - } - - public <T> boolean update(T t) { - Class<?> clazz = t.getClass(); - return define(clazz).createIfRequired(this).update(this, t) == 1; - } - - public <T> boolean delete(T t) { - Class<?> clazz = t.getClass(); - return define(clazz).createIfRequired(this).delete(this, t) == 1; - } - - 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> boolean 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 == 1; - } - - @SuppressWarnings("unchecked") - public <T> boolean 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 == 1; - } - - 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 { - // SQLite returns pre-closed ResultSets for query results with 0 rows - if (!rs.isClosed()) { - int[] columns = def.mapColumns(dialect, wildcardSelect, rs); - while (rs.next()) { - T item = Utils.newObject(modelClass); - def.readRow(dialect, 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(this); - 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; - } - - @Override - 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 || !dialect.supportsSavePoints()) { - 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 == null || 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 == null || 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().matches("select .*\\*.+"); - 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 == null || 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; - } - - /** - * Default DAO statement provider. - */ - class NoExternalDaoStatements implements DaoStatementProvider { - - @Override - public String getStatement(String idOrStatement, Mode mode) { - return idOrStatement; - } - - } + /** + * 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 Mode mode; + 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; + private DaoStatementProvider daoStatementProvider; + + 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); + DIALECTS.put("SQLite", SQLDialectSQLite.class); + } + + private Db(Connection conn, Mode mode) { + this.conn = conn; + this.mode = mode; + String databaseName = null; + try { + DatabaseMetaData 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(this); + daoStatementProvider = new NoExternalDaoStatements(); + } + + /** + * 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 <X> boolean isToken(X x) { + return TOKENS.containsKey(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) { + return open(url, Mode.PROD); + } + + public static Db open(String url, Mode mode) { + try { + Connection conn = JdbcUtils.getConnection(null, url, null, null); + return new Db(conn, mode); + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + public static Db open(String url, String user, String password) { + return open(url, user, password, Mode.PROD); + } + + public static Db open(String url, String user, String password, Mode mode) { + try { + Connection conn = JdbcUtils.getConnection(null, url, user, password); + return new Db(conn, mode); + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + public static Db open(String url, String user, char[] password) { + return open(url, user, password, Mode.PROD); + } + + public static Db open(String url, String user, char[] password, Mode mode) { + try { + Connection conn = JdbcUtils.getConnection(null, url, user, password == null ? null : new String(password)); + return new Db(conn, mode); + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + public static Db open(DataSource ds) { + return open(ds, Mode.PROD); + } + + /** + * 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 + * @param mode the runtime mode + * @return the database instance. + */ + public static Db open(DataSource ds, Mode mode) { + try { + return new Db(ds.getConnection(), mode); + } catch (SQLException e) { + throw new IciqlException(e); + } + } + + public static Db open(Connection conn) { + return open(conn, Mode.PROD); + } + + public static Db open(Connection conn, Mode mode) { + return new Db(conn, mode); + } + + /** + * Returns the Iciql runtime mode. + * + * @return the runtime mode + */ + public Mode getMode() { + return mode; + } + + /** + * Returns a new DAO instance for the specified class. + * + * @param daoClass + * @return + * @throws Exception + */ + @SuppressWarnings("resource") + public <X extends Dao> X open(Class<X> daoClass) { + return new DaoProxy<X>(this, daoClass).build(); + } + + /** + * Returns the DAO statement provider. + * + * @return the DAO statement provider + */ + public DaoStatementProvider getDaoStatementProvider() { + return daoStatementProvider; + } + + /** + * Sets the DAO statement provider. + * + * @param statementProvider + */ + public void setDaoStatementProvider(DaoStatementProvider statementProvider) { + if (statementProvider == null) { + throw new IciqlException("You must provide a valid {0} instance!", + DaoStatementProvider.class.getSimpleName()); + } + + this.daoStatementProvider = statementProvider; + } + + /** + * 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> boolean 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); + } + return rc == 1; + } + + public <T> long insertAndGetKey(T t) { + Class<?> clazz = t.getClass(); + return define(clazz).createIfRequired(this).insert(this, t, true); + } + + /** + * Upsert 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. + * <p> + * If the database does not support a MERGE or INSERT OR REPLACE INTO 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 upsert(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("upsert failed"); + } + } + + /** + * 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. + * <p> + * If the database does not support a MERGE or INSERT OR REPLACE INTO 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) { + upsert(t); + } + + public <T> boolean update(T t) { + Class<?> clazz = t.getClass(); + return define(clazz).createIfRequired(this).update(this, t) == 1; + } + + public <T> boolean delete(T t) { + Class<?> clazz = t.getClass(); + return define(clazz).createIfRequired(this).delete(this, t) == 1; + } + + 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> boolean 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 == 1; + } + + @SuppressWarnings("unchecked") + public <T> boolean 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 == 1; + } + + 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 { + // SQLite returns pre-closed ResultSets for query results with 0 rows + if (!rs.isClosed()) { + int[] columns = def.mapColumns(dialect, wildcardSelect, rs); + while (rs.next()) { + T item = Utils.newObject(modelClass); + def.readRow(dialect, 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(this); + 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; + } + + @Override + 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 || !dialect.supportsSavePoints()) { + 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. + * <p> + * Be sure to close the ResultSet with + * <p> + * <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. + * <p> + * Be sure to close the ResultSet with + * <p> + * <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 == null || 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 == null || 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().matches("select .*\\*.+"); + 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 == null || 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; + } + + /** + * Default DAO statement provider. + */ + class NoExternalDaoStatements implements DaoStatementProvider { + + @Override + public String getStatement(String idOrStatement, Mode mode) { + return idOrStatement; + } + + } } diff --git a/src/main/java/com/iciql/DbInspector.java b/src/main/java/com/iciql/DbInspector.java index acaceea..62576e3 100644 --- a/src/main/java/com/iciql/DbInspector.java +++ b/src/main/java/com/iciql/DbInspector.java @@ -17,6 +17,11 @@ package com.iciql; +import com.iciql.Iciql.IQTable; +import com.iciql.util.JdbcUtils; +import com.iciql.util.StringUtils; +import com.iciql.util.Utils; + import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; @@ -24,11 +29,6 @@ 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 @@ -36,169 +36,158 @@ import com.iciql.util.Utils; */ 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); - } - } + 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 index 1303f4e..1fd9284 100644 --- a/src/main/java/com/iciql/DbUpgrader.java +++ b/src/main/java/com/iciql/DbUpgrader.java @@ -27,55 +27,47 @@ import com.iciql.Iciql.IQVersion; */ 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 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); + /** + * 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 { + /** + * 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 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."); - } + 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 index 6270e14..26f1dd7 100644 --- a/src/main/java/com/iciql/DbVersion.java +++ b/src/main/java/com/iciql/DbVersion.java @@ -23,33 +23,32 @@ import com.iciql.Iciql.IQTable; /** * A system table to track database and table versions. */ -@IQTable(name = "iq_versions", primaryKey = { "schemaName", "tableName" }, memoryTable = true) +@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; - } + @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 index b16ee6e..54c9857 100644 --- a/src/main/java/com/iciql/Define.java +++ b/src/main/java/com/iciql/Define.java @@ -28,31 +28,31 @@ import com.iciql.Iciql.IndexType; public class Define {
- private static TableDefinition<?> currentTableDefinition;
- private static Iciql currentTable;
+ private static TableDefinition<?> currentTableDefinition;
+ private static Iciql currentTable;
- public static void skipCreate() {
- checkInDefine();
- currentTableDefinition.defineSkipCreate();
- }
+ 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(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 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);
- }
+ public static void constraintUnique(String name, Object... columns) {
+ checkInDefine();
+ currentTableDefinition.defineConstraintUnique(name, columns);
+ }
/*
- * The variable argument type Object can't be used twice :-)
+ * The variable argument type Object can't be used twice :-)
*/
// public static void constraintForeignKey(String name, String refTableName,
// ConstraintDeleteType deleteType, ConstraintUpdateType updateType,
@@ -61,91 +61,91 @@ public class Define { // 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);
- }
-
- public static void typeAdapter(Object column, Class<? extends DataTypeAdapter<?>> typeAdapter) {
- checkInDefine();
- currentTableDefinition.defineTypeAdapter(column, typeAdapter);
- }
-
- 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.");
- }
- }
+ 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);
+ }
+
+ public static void typeAdapter(Object column, Class<? extends DataTypeAdapter<?>> typeAdapter) {
+ checkInDefine();
+ currentTableDefinition.defineTypeAdapter(column, typeAdapter);
+ }
+
+ 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 index 99dbdc3..b4d4e2d 100644 --- a/src/main/java/com/iciql/Filter.java +++ b/src/main/java/com/iciql/Filter.java @@ -21,5 +21,5 @@ package com.iciql; * Represents the WHERE clause of a query. */ public interface Filter { - boolean where(); + boolean where(); } diff --git a/src/main/java/com/iciql/Function.java b/src/main/java/com/iciql/Function.java index 3faddb7..d0d3d2e 100644 --- a/src/main/java/com/iciql/Function.java +++ b/src/main/java/com/iciql/Function.java @@ -24,126 +24,126 @@ import com.iciql.util.Utils; */
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(")");
- }
- });
- }
+ // 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 index 302b34d..14d3ab6 100644 --- a/src/main/java/com/iciql/Iciql.java +++ b/src/main/java/com/iciql/Iciql.java @@ -171,7 +171,7 @@ import java.lang.annotation.Target; * <p>
* Automatic model generation: you may automatically generate model classes as
* strings with the Db and DbInspector objects:
- *
+ * <p>
* <pre>
* Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
* DbInspector inspector = new DbInspector(db);
@@ -179,10 +179,10 @@ import java.lang.annotation.Target; * inspector.generateModel(schema, table, packageName,
* annotateSchema, trimStrings)
* </pre>
- *
+ * <p>
* Or you may use the GenerateModels tool to generate and save your classes to
* the file system:
- *
+ * <p>
* <pre>
* java -jar iciql.jar
* -url "jdbc:h2:mem:"
@@ -190,10 +190,10 @@ import java.lang.annotation.Target; * -package packageName -folder destination
* -annotateSchema false -trimStrings true
* </pre>
- *
+ * <p>
* Model validation: you may validate your model class with DbInspector object.
* The DbInspector will report errors, warnings, and suggestions:
- *
+ * <p>
* <pre>
* Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
* DbInspector inspector = new DbInspector(db);
@@ -205,615 +205,613 @@ import java.lang.annotation.Target; */
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. IQTable and IQView annotations present on the super
- * class or above are honored. 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. IQTable and IQView annotations present on the super
- * class or above are honored. 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<X> {
- X enumId();
- Class<X> enumIdClass();
- }
-
-
-
- /**
- * 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() : X
- * </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{
- }
-
- /**
- * The runtime mode for Iciql.
- */
- public static enum Mode {
-
- DEV, TEST, PROD;
-
- public static Mode fromValue(String value) {
-
- for (Mode mode : values()) {
- if (mode.name().equalsIgnoreCase(value)) {
- return mode;
- }
- }
-
- return PROD;
- }
- }
-
- /**
- * This method is called to let the table define the primary key, indexes,
- * and the table name.
- */
- void defineIQ();
-
- /**
- * Specify a custom type adapter for a method return type, a class field, or a method
- * parameter. Type adapters allow you to transform content received from or inserted into
- * a database field.
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
- public @interface TypeAdapter {
- Class<? extends DataTypeAdapter<?>> value();
- }
-
- /**
- * Interface to allow implementations of custom data type adapters for supporting
- * database-specific data types, like the Postgres 'json' or 'xml' types,
- * or for supporting other object serialization schemes.
- * <p><b>NOTE:</b> Data type adapters are not thread-safe!</p>
- *
- * @param <T>
- */
- public interface DataTypeAdapter<T> {
-
- /**
- * The SQL data type for this adapter.
- *
- * @return the SQL data type
- */
- String getDataType();
-
- /**
- * The Java domain type for this adapter.
- *
- * @return the Java domain type
- */
- Class<T> getJavaType();
-
-
- /**
- * Set the runtime mode.
- * <p>
- * Allows type adapters to adapt type mappings based on the runtime
- * mode.
- * </p>
- *
- * @param mode
- */
- void setMode(Mode mode);
-
- /**
- * Serializes your Java object into a JDBC object.
- *
- * @param value
- * @return a JDBC object
- */
- Object serialize(T value);
-
- /**
- * Deserializes a JDBC object into your Java object.
- *
- * @param value
- * @return the Java object
- */
- T deserialize(Object value);
-
- }
+ /**
+ * 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>
+ * <p>
+ * 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. IQTable and IQView annotations present on the super
+ * class or above are honored. 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. IQTable and IQView annotations present on the super
+ * class or above are honored. 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<X> {
+ X enumId();
+
+ Class<X> enumIdClass();
+ }
+
+
+ /**
+ * 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() : X
+ * </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.
+ * <p>
+ * <pre>
+ * IQEnum(EnumType.NAME)
+ * </pre>
+ * <p>
+ * 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 {
+ }
+
+ /**
+ * The runtime mode for Iciql.
+ */
+ public static enum Mode {
+
+ DEV, TEST, PROD;
+
+ public static Mode fromValue(String value) {
+
+ for (Mode mode : values()) {
+ if (mode.name().equalsIgnoreCase(value)) {
+ return mode;
+ }
+ }
+
+ return PROD;
+ }
+ }
+
+ /**
+ * This method is called to let the table define the primary key, indexes,
+ * and the table name.
+ */
+ void defineIQ();
+
+ /**
+ * Specify a custom type adapter for a method return type, a class field, or a method
+ * parameter. Type adapters allow you to transform content received from or inserted into
+ * a database field.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
+ public @interface TypeAdapter {
+ Class<? extends DataTypeAdapter<?>> value();
+ }
+
+ /**
+ * Interface to allow implementations of custom data type adapters for supporting
+ * database-specific data types, like the Postgres 'json' or 'xml' types,
+ * or for supporting other object serialization schemes.
+ * <p><b>NOTE:</b> Data type adapters are not thread-safe!</p>
+ *
+ * @param <T>
+ */
+ public interface DataTypeAdapter<T> {
+
+ /**
+ * The SQL data type for this adapter.
+ *
+ * @return the SQL data type
+ */
+ String getDataType();
+
+ /**
+ * The Java domain type for this adapter.
+ *
+ * @return the Java domain type
+ */
+ Class<T> getJavaType();
+
+
+ /**
+ * Set the runtime mode.
+ * <p>
+ * Allows type adapters to adapt type mappings based on the runtime
+ * mode.
+ * </p>
+ *
+ * @param mode
+ */
+ void setMode(Mode mode);
+
+ /**
+ * Serializes your Java object into a JDBC object.
+ *
+ * @param value
+ * @return a JDBC object
+ */
+ Object serialize(T value);
+
+ /**
+ * Deserializes a JDBC object into your Java object.
+ *
+ * @param value
+ * @return the Java object
+ */
+ T deserialize(Object value);
+
+ }
}
diff --git a/src/main/java/com/iciql/IciqlException.java b/src/main/java/com/iciql/IciqlException.java index 3db62cf..b5e1ca6 100644 --- a/src/main/java/com/iciql/IciqlException.java +++ b/src/main/java/com/iciql/IciqlException.java @@ -26,170 +26,170 @@ import java.util.regex.Pattern; */
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 if (s.getMessage().startsWith("[SQLITE")) {
- // SQLite error codes
- final String msg = s.getMessage();
- switch (s.getErrorCode()) {
- case 1:
- iciqlCode = CODE_OBJECT_NOT_FOUND;
- break;
- case 19:
- if (msg.contains("UNIQUE")) {
- iciqlCode = CODE_DUPLICATE_KEY;
- } else {
- iciqlCode = CODE_CONSTRAINT_VIOLATION;
- }
- break;
- default:
- iciqlCode = s.getErrorCode();
- break;
- }
- } 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();
- }
+ 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 if (s.getMessage().startsWith("[SQLITE")) {
+ // SQLite error codes
+ final String msg = s.getMessage();
+ switch (s.getErrorCode()) {
+ case 1:
+ iciqlCode = CODE_OBJECT_NOT_FOUND;
+ break;
+ case 19:
+ if (msg.contains("UNIQUE")) {
+ iciqlCode = CODE_DUPLICATE_KEY;
+ } else {
+ iciqlCode = CODE_CONSTRAINT_VIOLATION;
+ }
+ break;
+ default:
+ iciqlCode = s.getErrorCode();
+ break;
+ }
+ } 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 index 7fa1de6..14dc615 100644 --- a/src/main/java/com/iciql/ModelUtils.java +++ b/src/main/java/com/iciql/ModelUtils.java @@ -17,7 +17,8 @@ package com.iciql; -import static com.iciql.util.StringUtils.isNullOrEmpty; +import com.iciql.TableDefinition.FieldDefinition; +import com.iciql.util.StringUtils; import java.lang.reflect.Method; import java.math.BigDecimal; @@ -32,8 +33,7 @@ import java.util.Map; import java.util.UUID; import java.util.regex.Pattern; -import com.iciql.TableDefinition.FieldDefinition; -import com.iciql.util.StringUtils; +import static com.iciql.util.StringUtils.isNullOrEmpty; /** * Utility methods for models related to type mapping, default value validation, @@ -41,469 +41,460 @@ import com.iciql.util.StringUtils; */ 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: - return "INT"; - case ENUMID: - String sqlType = SUPPORTED_TYPES.get(fieldDef.enumTypeClass); - if (sqlType == null) { - throw new IciqlException("Unsupported enum mapping type {0} for {1}", - fieldDef.enumTypeClass, fieldDef.columnName); - } - return sqlType; - 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 (sqlType.indexOf('(') > -1) { - // strip out length or precision - sqlType = sqlType.substring(0, sqlType.indexOf('(')); - } - - 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; - } + /** + * 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: + return "INT"; + case ENUMID: + String sqlType = SUPPORTED_TYPES.get(fieldDef.enumTypeClass); + if (sqlType == null) { + throw new IciqlException("Unsupported enum mapping type {0} for {1}", + fieldDef.enumTypeClass, fieldDef.columnName); + } + return sqlType; + 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 (sqlType.indexOf('(') > -1) { + // strip out length or precision + sqlType = sqlType.substring(0, sqlType.indexOf('(')); + } + + 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/NestedConditions.java b/src/main/java/com/iciql/NestedConditions.java index 5c92a86..c60645c 100644 --- a/src/main/java/com/iciql/NestedConditions.java +++ b/src/main/java/com/iciql/NestedConditions.java @@ -19,114 +19,114 @@ package com.iciql; public abstract class NestedConditions<T> { - public static class And<T> extends NestedConditions<T> { + public static class And<T> extends NestedConditions<T> { - public And(Db db, T alias) { - super(db, alias); - } + public And(Db db, T alias) { + super(db, alias); + } - protected QueryCondition<T, Boolean> and(boolean x) { - return where.and(x); - } + protected QueryCondition<T, Boolean> and(boolean x) { + return where.and(x); + } - protected QueryCondition<T, Byte> and(byte x) { - return where.and(x); - } + protected QueryCondition<T, Byte> and(byte x) { + return where.and(x); + } - protected QueryCondition<T, Short> and(short x) { - return where.and(x); - } + protected QueryCondition<T, Short> and(short x) { + return where.and(x); + } - protected QueryCondition<T, Integer> and(int x) { - return where.and(x); - } + protected QueryCondition<T, Integer> and(int x) { + return where.and(x); + } - protected QueryCondition<T, Long> and(long x) { - return where.and(x); - } + protected QueryCondition<T, Long> and(long x) { + return where.and(x); + } - protected QueryCondition<T, Float> and(float x) { - return where.and(x); - } + protected QueryCondition<T, Float> and(float x) { + return where.and(x); + } - protected QueryCondition<T, Double> and(double x) { - return where.and(x); - } + protected QueryCondition<T, Double> and(double x) { + return where.and(x); + } - protected <A> QueryCondition<T, A> and(A x) { - return where.and(x); - } + protected <A> QueryCondition<T, A> and(A x) { + return where.and(x); + } - protected QueryWhere<T> and(And<T> conditions) { - where.andOpen(); - where.query.addConditionToken(conditions.where.query); - return where.close(); - } + protected QueryWhere<T> and(And<T> conditions) { + where.andOpen(); + where.query.addConditionToken(conditions.where.query); + return where.close(); + } - protected QueryWhere<T> and(Or<T> conditions) { - where.andOpen(); - where.query.addConditionToken(conditions.where.query); - return where.close(); - } + protected QueryWhere<T> and(Or<T> conditions) { + where.andOpen(); + where.query.addConditionToken(conditions.where.query); + return where.close(); + } - } + } - public static class Or<T> extends NestedConditions<T> { + public static class Or<T> extends NestedConditions<T> { - public Or(Db db, T alias) { - super(db, alias); - } + public Or(Db db, T alias) { + super(db, alias); + } - protected QueryCondition<T, Boolean> or(boolean x) { - return where.or(x); - } + protected QueryCondition<T, Boolean> or(boolean x) { + return where.or(x); + } - protected QueryCondition<T, Byte> or(byte x) { - return where.or(x); - } + protected QueryCondition<T, Byte> or(byte x) { + return where.or(x); + } - protected QueryCondition<T, Short> or(short x) { - return where.or(x); - } + protected QueryCondition<T, Short> or(short x) { + return where.or(x); + } - protected QueryCondition<T, Integer> or(int x) { - return where.or(x); - } + protected QueryCondition<T, Integer> or(int x) { + return where.or(x); + } - protected QueryCondition<T, Long> or(long x) { - return where.or(x); - } + protected QueryCondition<T, Long> or(long x) { + return where.or(x); + } - protected QueryCondition<T, Float> or(float x) { - return where.or(x); - } + protected QueryCondition<T, Float> or(float x) { + return where.or(x); + } - protected QueryCondition<T, Double> or(double x) { - return where.or(x); - } + protected QueryCondition<T, Double> or(double x) { + return where.or(x); + } - protected <A> QueryCondition<T, A> or(A x) { - return where.or(x); - } + protected <A> QueryCondition<T, A> or(A x) { + return where.or(x); + } - protected QueryWhere<T> or(And<T> conditions) { - where.orOpen(); - where.query.addConditionToken(conditions.where.query); - return where.close(); - } + protected QueryWhere<T> or(And<T> conditions) { + where.orOpen(); + where.query.addConditionToken(conditions.where.query); + return where.close(); + } - protected QueryWhere<T> or(Or<T> conditions) { - where.orOpen(); - where.query.addConditionToken(conditions.where.query); - return where.close(); - } + protected QueryWhere<T> or(Or<T> conditions) { + where.orOpen(); + where.query.addConditionToken(conditions.where.query); + return where.close(); + } - } + } - QueryWhere<T> where; + QueryWhere<T> where; - private NestedConditions(Db db, T alias) { - where = new QueryWhere<T>(Query.rebuild(db, alias)); - } + private NestedConditions(Db db, T alias) { + where = new QueryWhere<T>(Query.rebuild(db, alias)); + } } diff --git a/src/main/java/com/iciql/OrderExpression.java b/src/main/java/com/iciql/OrderExpression.java index f450bfb..9694da1 100644 --- a/src/main/java/com/iciql/OrderExpression.java +++ b/src/main/java/com/iciql/OrderExpression.java @@ -19,37 +19,36 @@ package com.iciql; /**
* An expression to order by in a query.
- *
- * @param <T>
- * the query data type
+ *
+ * @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;
+ 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;
- }
+ 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");
- }
- }
+ 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 index 7a8a1ff..d26e7b7 100644 --- a/src/main/java/com/iciql/Query.java +++ b/src/main/java/com/iciql/Query.java @@ -17,15 +17,6 @@ package com.iciql; -import java.lang.reflect.Field; -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.DataTypeAdapter; import com.iciql.Iciql.EnumType; import com.iciql.NestedConditions.And; @@ -35,1015 +26,1002 @@ import com.iciql.util.IciqlLogger; import com.iciql.util.JdbcUtils; import com.iciql.util.Utils; +import java.lang.reflect.Field; +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; + /** * This class represents a query. * - * @param <T> - * the return type + * @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 int conditionDepth = 0; - 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, false); - return query; - } - - @SuppressWarnings("unchecked") - static <T> Query<T> rebuild(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, true); - 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() { - List<T> list = limit(1).select(false); - return list.isEmpty() ? null : list.get(0); - } - - public List<T> selectDistinct() { - return select(true); - } - - public <X, Z> X selectFirst(Z x) { - List<X> list = limit(1).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 { - // SQLite returns pre-closed ResultSets for query results with 0 rows - if (!rs.isClosed()) { - int[] columns = def.mapColumns(db.getDialect(), false, rs); - while (rs.next()) { - T item = from.newObject(); - def.readRow(db.getDialect(), 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> Query<T> setNull(A field) { - return set(field).to(null); - } - - 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 (Db.isToken(x)) { - // selecting a function - return selectFunction((X) x, distinct); - } else { - // selecting a column - SelectColumn<T> col = getColumnByReference(x); - if (col == null) { - col = getColumnByReference(getPrimitiveAliasByValue(x)); - } - if (col != null) { - return (List<X>) selectColumn(col, clazz, distinct); - } - } - - // selecting into a new object type - 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 { - // SQLite returns pre-closed ResultSets for query results with 0 rows - if (!rs.isClosed()) { - int[] columns = def.mapColumns(db.getDialect(), false, rs); - while (rs.next()) { - X row = Utils.newObject(clazz); - def.readRow(db.getDialect(), 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> selectFunction(X x, boolean distinct) { - SQLStatement stat = getSelectStatement(distinct); - appendSQL(stat, null, x); - appendFromWhere(stat); - ResultSet rs = stat.executeQuery(); - List<X> result = Utils.newArrayList(); - try { - // SQLite returns pre-closed ResultSets for query results with 0 rows - if (!rs.isClosed()) { - while (rs.next()) { - X value = (X) rs.getObject(1); - result.add(value); - } - } - } catch (Exception e) { - throw IciqlException.fromSQL(stat.getSQL(), e); - } finally { - JdbcUtils.closeSilently(rs, true); - } - return result; - } - - @SuppressWarnings("unchecked") - private <X> List<X> selectColumn(SelectColumn<T> col, Class<X> clazz, boolean distinct) { - SQLStatement stat = getSelectStatement(distinct); - col.appendSQL(stat); - appendFromWhere(stat); - ResultSet rs = stat.executeQuery(); - List<X> result = Utils.newArrayList(); - Class<? extends DataTypeAdapter<?>> typeAdapter = col.getFieldDefinition().typeAdapter; - try { - // SQLite returns pre-closed ResultSets for query results with 0 rows - if (!rs.isClosed()) { - while (rs.next()) { - X value = (X) db.getDialect().deserialize(rs, 1, clazz, typeAdapter); - 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 Query<T> where(And<T> conditions) { - whereTrue(); - addConditionToken(conditions.where.query); - return this; - } - - public Query<T> where(Or<T> conditions) { - whereFalse(); - addConditionToken(conditions.where.query); - return this; - } - - public QueryWhere<T> whereTrue() { - return whereTrue(true); - } - - public QueryWhere<T> whereFalse() { - return whereTrue(false); - } - - 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(byte field) { - return orderByDescPrimitive(field); - } - - public Query<T> orderByDesc(short field) { - return orderByDescPrimitive(field); - } - - public Query<T> orderByDesc(int field) { - return orderByDescPrimitive(field); - } - - public Query<T> orderByDesc(long field) { - return orderByDescPrimitive(field); - } - - public Query<T> orderByDesc(float field) { - return orderByDescPrimitive(field); - } - - public Query<T> orderByDesc(double field) { - return orderByDescPrimitive(field); - } - - Query<T> orderByDescPrimitive(Object field) { - Object alias = getPrimitiveAliasByValue(field); - if (alias == null) { - return orderByDesc(field); - } - return orderByDesc(alias); - } - - 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 != 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); - } - - public void appendSQL(SQLStatement stat, Object alias, Iterable<Object> values, - CompareType compareType) { - boolean first = true; - stat.appendSQL("("); - for (Object value : values) { - if (first) { - first = false; - } else { - stat.appendSQL(", "); - } - stat.appendSQL("?"); - addParameter(stat, alias, value); - } - stat.appendSQL(")"); - } - - private void addParameter(SQLStatement stat, Object alias, Object value) { - SelectColumn<T> col = getColumnByReference(alias); - if (col != null && value != null && value.getClass().isEnum()) { - // enum - TableDefinition.FieldDefinition field = col.getFieldDefinition(); - EnumType type = field.enumType; - Enum<?> anEnum = (Enum<?>) value; - Object y = Utils.convertEnum(anEnum, type); - stat.addParameter(y); - } else if (col != null) { - // object - TableDefinition.FieldDefinition field = col.getFieldDefinition(); - Class<? extends DataTypeAdapter<?>> typeAdapter = field.typeAdapter; - if (value != null && value instanceof String) { - if (field.trim && field.length > 0) { - // clip strings (issue-15) - String s = (String) value; - if (s.length() > field.length) { - value = s.substring(0, field.length); - } - } - } - Object parameter = db.getDialect().serialize(value, typeAdapter); - stat.addParameter(parameter); - } else { - // primitive - stat.addParameter(value); - } - } - - void addConditionToken(Token condition) { - if (condition == ConditionOpenClose.OPEN) { - conditionDepth ++; - } else if (condition == ConditionOpenClose.CLOSE) { - conditionDepth --; - if (conditionDepth < 0) { - throw new IciqlException("unmatch condition open-close count"); - } - } - conditions.add(condition); - } - - void addConditionToken(Query<T> other) { - for (Token condition : other.conditions) { - addConditionToken(condition); - } - } - - void addUpdateColumnDeclaration(UpdateColumn declaration) { - updateColumnDeclarations.add(declaration); - } - - void appendWhere(SQLStatement stat) { - if (conditionDepth != 0) { - throw new IciqlException("unmatch condition open-close count"); - } - if (!conditions.isEmpty()) { - stat.appendSQL(" WHERE "); - - boolean skipNextConjunction = false; - - for (Token token : conditions) { - - if (skipNextConjunction && token instanceof ConditionAndOr) { - skipNextConjunction = false; - continue; - } - - token.appendSQL(stat, this); - stat.appendSQL(" "); - - if (ConditionOpenClose.OPEN == token) { - skipNextConjunction = true; - } - } - } - } - - 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) { + private Db db; + private SelectTable<T> from; + private ArrayList<Token> conditions = Utils.newArrayList(); + private ArrayList<UpdateColumn> updateColumnDeclarations = Utils.newArrayList(); + private int conditionDepth = 0; + 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, false); + return query; + } + + @SuppressWarnings("unchecked") + static <T> Query<T> rebuild(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, true); + 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() { + List<T> list = limit(1).select(false); + return list.isEmpty() ? null : list.get(0); + } + + public List<T> selectDistinct() { + return select(true); + } + + public <X, Z> X selectFirst(Z x) { + List<X> list = limit(1).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 { + // SQLite returns pre-closed ResultSets for query results with 0 rows + if (!rs.isClosed()) { + int[] columns = def.mapColumns(db.getDialect(), false, rs); + while (rs.next()) { + T item = from.newObject(); + def.readRow(db.getDialect(), 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> Query<T> setNull(A field) { + return set(field).to(null); + } + + 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 (Db.isToken(x)) { + // selecting a function + return selectFunction((X) x, distinct); + } else { + // selecting a column + SelectColumn<T> col = getColumnByReference(x); + if (col == null) { + col = getColumnByReference(getPrimitiveAliasByValue(x)); + } + if (col != null) { + return (List<X>) selectColumn(col, clazz, distinct); + } + } + + // selecting into a new object type + 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 { + // SQLite returns pre-closed ResultSets for query results with 0 rows + if (!rs.isClosed()) { + int[] columns = def.mapColumns(db.getDialect(), false, rs); + while (rs.next()) { + X row = Utils.newObject(clazz); + def.readRow(db.getDialect(), 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> selectFunction(X x, boolean distinct) { + SQLStatement stat = getSelectStatement(distinct); + appendSQL(stat, null, x); + appendFromWhere(stat); + ResultSet rs = stat.executeQuery(); + List<X> result = Utils.newArrayList(); + try { + // SQLite returns pre-closed ResultSets for query results with 0 rows + if (!rs.isClosed()) { + while (rs.next()) { + X value = (X) rs.getObject(1); + result.add(value); + } + } + } catch (Exception e) { + throw IciqlException.fromSQL(stat.getSQL(), e); + } finally { + JdbcUtils.closeSilently(rs, true); + } + return result; + } + + @SuppressWarnings("unchecked") + private <X> List<X> selectColumn(SelectColumn<T> col, Class<X> clazz, boolean distinct) { + SQLStatement stat = getSelectStatement(distinct); + col.appendSQL(stat); + appendFromWhere(stat); + ResultSet rs = stat.executeQuery(); + List<X> result = Utils.newArrayList(); + Class<? extends DataTypeAdapter<?>> typeAdapter = col.getFieldDefinition().typeAdapter; + try { + // SQLite returns pre-closed ResultSets for query results with 0 rows + if (!rs.isClosed()) { + while (rs.next()) { + X value = (X) db.getDialect().deserialize(rs, 1, clazz, typeAdapter); + 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 Query<T> where(And<T> conditions) { + whereTrue(); + addConditionToken(conditions.where.query); + return this; + } + + public Query<T> where(Or<T> conditions) { + whereFalse(); + addConditionToken(conditions.where.query); + return this; + } + + public QueryWhere<T> whereTrue() { + return whereTrue(true); + } + + public QueryWhere<T> whereFalse() { + return whereTrue(false); + } + + 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(byte field) { + return orderByDescPrimitive(field); + } + + public Query<T> orderByDesc(short field) { + return orderByDescPrimitive(field); + } + + public Query<T> orderByDesc(int field) { + return orderByDescPrimitive(field); + } + + public Query<T> orderByDesc(long field) { + return orderByDescPrimitive(field); + } + + public Query<T> orderByDesc(float field) { + return orderByDescPrimitive(field); + } + + public Query<T> orderByDesc(double field) { + return orderByDescPrimitive(field); + } + + Query<T> orderByDescPrimitive(Object field) { + Object alias = getPrimitiveAliasByValue(field); + if (alias == null) { + return orderByDesc(field); + } + return orderByDesc(alias); + } + + 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 != 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); + } + + public void appendSQL(SQLStatement stat, Object alias, Iterable<Object> values, + CompareType compareType) { + boolean first = true; + stat.appendSQL("("); + for (Object value : values) { + if (first) { + first = false; + } else { + stat.appendSQL(", "); + } + stat.appendSQL("?"); + addParameter(stat, alias, value); + } + stat.appendSQL(")"); + } + + private void addParameter(SQLStatement stat, Object alias, Object value) { + SelectColumn<T> col = getColumnByReference(alias); + if (col != null && value != null && value.getClass().isEnum()) { + // enum + TableDefinition.FieldDefinition field = col.getFieldDefinition(); + EnumType type = field.enumType; + Enum<?> anEnum = (Enum<?>) value; + Object y = Utils.convertEnum(anEnum, type); + stat.addParameter(y); + } else if (col != null) { + // object + TableDefinition.FieldDefinition field = col.getFieldDefinition(); + Class<? extends DataTypeAdapter<?>> typeAdapter = field.typeAdapter; + if (value != null && value instanceof String) { + if (field.trim && field.length > 0) { + // clip strings (issue-15) + String s = (String) value; + if (s.length() > field.length) { + value = s.substring(0, field.length); + } + } + } + Object parameter = db.getDialect().serialize(value, typeAdapter); + stat.addParameter(parameter); + } else { + // primitive + stat.addParameter(value); + } + } + + void addConditionToken(Token condition) { + if (condition == ConditionOpenClose.OPEN) { + conditionDepth++; + } else if (condition == ConditionOpenClose.CLOSE) { + conditionDepth--; + if (conditionDepth < 0) { + throw new IciqlException("unmatch condition open-close count"); + } + } + conditions.add(condition); + } + + void addConditionToken(Query<T> other) { + for (Token condition : other.conditions) { + addConditionToken(condition); + } + } + + void addUpdateColumnDeclaration(UpdateColumn declaration) { + updateColumnDeclarations.add(declaration); + } + + void appendWhere(SQLStatement stat) { + if (conditionDepth != 0) { + throw new IciqlException("unmatch condition open-close count"); + } + if (!conditions.isEmpty()) { + stat.appendSQL(" WHERE "); + + boolean skipNextConjunction = false; + + for (Token token : conditions) { + + if (skipNextConjunction && token instanceof ConditionAndOr) { + skipNextConjunction = false; + continue; + } + + token.appendSQL(stat, this); + stat.appendSQL(" "); + + if (ConditionOpenClose.OPEN == token) { + skipNextConjunction = true; + } + } + } + } + + 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" }) + @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); @@ -1052,63 +1030,63 @@ public class Query<T> { 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); - } + 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 index 72d19dc..f3a5561 100644 --- a/src/main/java/com/iciql/QueryBetween.java +++ b/src/main/java/com/iciql/QueryBetween.java @@ -18,43 +18,37 @@ 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
+ *
+ * @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;
+ 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;
- }
+ /**
+ * 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);
- }
+ /**
+ * 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 index cef95c1..fce66af 100644 --- a/src/main/java/com/iciql/QueryCondition.java +++ b/src/main/java/com/iciql/QueryCondition.java @@ -21,128 +21,126 @@ import com.iciql.util.Utils; /** * This class represents a query with an incomplete condition. - * - * @param <T> - * the return type of the query - * @param <A> - * the incomplete condition data type + * + * @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> oneOf(A... a) { - return oneOf(Utils.newArrayIterable(a)); - } - - public QueryWhere<T> oneOf(Iterable<A> i) { - query.addConditionToken(new Condition<A>(x, i, CompareType.IN)); - return new QueryWhere<T>(query); - } - - public QueryWhere<T> noneOf(A... a) { - return noneOf(Utils.newArrayIterable(a)); - } - - public QueryWhere<T> noneOf(Iterable<A> i) { - query.addConditionToken(new Condition<A>(x, i, CompareType.NOT_IN)); - 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, CompareType.IS_NULL)); - return new QueryWhere<T>(query); - } - - public QueryWhere<T> isNotNull() { - query.addConditionToken(new Condition<A>(x, 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); - } + 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> oneOf(A... a) { + return oneOf(Utils.newArrayIterable(a)); + } + + public QueryWhere<T> oneOf(Iterable<A> i) { + query.addConditionToken(new Condition<A>(x, i, CompareType.IN)); + return new QueryWhere<T>(query); + } + + public QueryWhere<T> noneOf(A... a) { + return noneOf(Utils.newArrayIterable(a)); + } + + public QueryWhere<T> noneOf(Iterable<A> i) { + query.addConditionToken(new Condition<A>(x, i, CompareType.NOT_IN)); + 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, CompareType.IS_NULL)); + return new QueryWhere<T>(query); + } + + public QueryWhere<T> isNotNull() { + query.addConditionToken(new Condition<A>(x, 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 index 6d0484e..325ae50 100644 --- a/src/main/java/com/iciql/QueryJoin.java +++ b/src/main/java/com/iciql/QueryJoin.java @@ -23,53 +23,53 @@ package com.iciql; public class QueryJoin<T> {
- private Query<T> query;
- private SelectTable<T> join;
+ private Query<T> query;
+ private SelectTable<T> join;
- QueryJoin(Query<T> query, SelectTable<T> join) {
- this.query = query;
- this.join = 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, Boolean> on(boolean x) {
+ query.getFrom().getAliasDefinition().checkMultipleBooleans();
+ return addPrimitive(x);
+ }
- public QueryJoinCondition<T, Byte> on(byte x) {
- 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, Short> on(short x) {
+ return addPrimitive(x);
+ }
- public QueryJoinCondition<T, Integer> on(int 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, Long> on(long x) {
+ return addPrimitive(x);
+ }
- public QueryJoinCondition<T, Float> on(float x) {
- return addPrimitive(x);
- }
+ public QueryJoinCondition<T, Float> on(float x) {
+ return addPrimitive(x);
+ }
- public QueryJoinCondition<T, Double> on(double 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);
- }
+ 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);
- }
+ 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 index 6dfd218..d1a82d9 100644 --- a/src/main/java/com/iciql/QueryJoinCondition.java +++ b/src/main/java/com/iciql/QueryJoinCondition.java @@ -20,64 +20,63 @@ package com.iciql; /**
* This class represents a query with join and an incomplete condition.
- *
- * @param <A>
- * the incomplete condition data type
+ *
+ * @param <A> the incomplete condition data type
*/
public class QueryJoinCondition<T, A> {
- private Query<T> query;
- private SelectTable<T> join;
- private A x;
+ 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;
+ }
- 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(boolean y) {
- return addPrimitive(y);
- }
+ public Query<T> is(byte 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(short y) {
- return addPrimitive(y);
- }
+ public Query<T> is(int 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(long y) {
+ return addPrimitive(y);
+ }
- public Query<T> is(float y) {
- return addPrimitive(y);
- }
+ public Query<T> is(float y) {
+ return addPrimitive(y);
+ }
- public Query<T> is(double 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;
- }
+ @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;
- }
+ 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 index fdce1cb..16f1397 100644 --- a/src/main/java/com/iciql/QueryWhere.java +++ b/src/main/java/com/iciql/QueryWhere.java @@ -17,626 +17,592 @@ package com.iciql;
-import java.util.List;
-
import com.iciql.NestedConditions.And;
import com.iciql.NestedConditions.Or;
+import java.util.List;
+
/**
* This class represents a query with a condition.
*
- * @param <T>
- * the return type
+ * @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);
- }
-
- public QueryWhere<T> and(And<T> conditions) {
- andOpen();
- query.addConditionToken(conditions.where.query);
- return close();
- }
-
- public QueryWhere<T> and(Or<T> conditions) {
- andOpen();
- query.addConditionToken(conditions.where.query);
- return close();
- }
-
- public QueryWhere<T> andOpen() {
- return open(ConditionAndOr.AND);
- }
-
- /**
- * 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> or(And<T> conditions) {
- orOpen();
- query.addConditionToken(conditions.where.query);
- return close();
- }
-
- public QueryWhere<T> or(Or<T> conditions) {
- orOpen();
- query.addConditionToken(conditions.where.query);
- return close();
- }
-
- public QueryWhere<T> orOpen() {
- return open(ConditionAndOr.OR);
- }
-
- private QueryWhere<T> open(ConditionAndOr andOr) {
- query.addConditionToken(andOr);
- query.addConditionToken(ConditionOpenClose.OPEN);
- return this;
- }
-
- public QueryWhere<T> close() {
- query.addConditionToken(ConditionOpenClose.CLOSE);
- return this;
- }
-
- 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;
- }
-
- /**
- * Group by primitive boolean field
- *
- * @param field
- * a primitive boolean field
- * @return the query
- */
- public QueryWhere<T> groupBy(boolean field) {
- query.getFrom().getAliasDefinition().checkMultipleBooleans();
- return groupByPrimitive(field);
- }
-
- /**
- * Group by primitive byte field
- *
- * @param field
- * a primitive byte field
- * @return the query
- */
- public QueryWhere<T> groupBy(byte field) {
- return groupByPrimitive(field);
- }
-
- /**
- * Group by primitive short field
- *
- * @param field
- * a primitive short field
- * @return the query
- */
- public QueryWhere<T> groupBy(short field) {
- return groupByPrimitive(field);
- }
-
- public QueryWhere<T> groupBy(int field) {
- return groupByPrimitive(field);
- }
-
- /**
- * Group by primitive long field
- *
- * @param field
- * a primitive long field
- * @return the query
- */
- public QueryWhere<T> groupBy(long field) {
- return groupByPrimitive(field);
- }
-
- /**
- * Group by primitive float field
- *
- * @param field
- * a primitive float field
- * @return the query
- */
- public QueryWhere<T> groupBy(float field) {
- return groupByPrimitive(field);
- }
-
- /**
- * Group by primitive double field
- *
- * @param field
- * a primitive double field
- * @return the query
- */
- public QueryWhere<T> groupBy(double field) {
- return groupByPrimitive(field);
- }
-
- private QueryWhere<T> groupByPrimitive(Object field) {
- query.groupByPrimitive(field);
- return this;
- }
-
- public QueryWhere<T> groupBy(Object field) {
- query.getFrom().getAliasDefinition().checkMultipleEnums(field);
- query.groupBy(field);
- return this;
- }
-
- /**
- * Group by a number of Object columns.
- *
- * @param expressions
- * the group by expressions
- * @return the query
- */
-
- public QueryWhere<T> groupBy(Object... expressions) {
- query.groupBy(expressions);
- return this;
- }
-
- public int delete() {
- return query.delete();
- }
-
- public int update() {
- return query.update();
- }
-
- public long selectCount() {
- return query.selectCount();
- }
+ 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);
+ }
+
+ public QueryWhere<T> and(And<T> conditions) {
+ andOpen();
+ query.addConditionToken(conditions.where.query);
+ return close();
+ }
+
+ public QueryWhere<T> and(Or<T> conditions) {
+ andOpen();
+ query.addConditionToken(conditions.where.query);
+ return close();
+ }
+
+ public QueryWhere<T> andOpen() {
+ return open(ConditionAndOr.AND);
+ }
+
+ /**
+ * 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> or(And<T> conditions) {
+ orOpen();
+ query.addConditionToken(conditions.where.query);
+ return close();
+ }
+
+ public QueryWhere<T> or(Or<T> conditions) {
+ orOpen();
+ query.addConditionToken(conditions.where.query);
+ return close();
+ }
+
+ public QueryWhere<T> orOpen() {
+ return open(ConditionAndOr.OR);
+ }
+
+ private QueryWhere<T> open(ConditionAndOr andOr) {
+ query.addConditionToken(andOr);
+ query.addConditionToken(ConditionOpenClose.OPEN);
+ return this;
+ }
+
+ public QueryWhere<T> close() {
+ query.addConditionToken(ConditionOpenClose.CLOSE);
+ return this;
+ }
+
+ 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;
+ }
+
+ /**
+ * Group by primitive boolean field
+ *
+ * @param field a primitive boolean field
+ * @return the query
+ */
+ public QueryWhere<T> groupBy(boolean field) {
+ query.getFrom().getAliasDefinition().checkMultipleBooleans();
+ return groupByPrimitive(field);
+ }
+
+ /**
+ * Group by primitive byte field
+ *
+ * @param field a primitive byte field
+ * @return the query
+ */
+ public QueryWhere<T> groupBy(byte field) {
+ return groupByPrimitive(field);
+ }
+
+ /**
+ * Group by primitive short field
+ *
+ * @param field a primitive short field
+ * @return the query
+ */
+ public QueryWhere<T> groupBy(short field) {
+ return groupByPrimitive(field);
+ }
+
+ public QueryWhere<T> groupBy(int field) {
+ return groupByPrimitive(field);
+ }
+
+ /**
+ * Group by primitive long field
+ *
+ * @param field a primitive long field
+ * @return the query
+ */
+ public QueryWhere<T> groupBy(long field) {
+ return groupByPrimitive(field);
+ }
+
+ /**
+ * Group by primitive float field
+ *
+ * @param field a primitive float field
+ * @return the query
+ */
+ public QueryWhere<T> groupBy(float field) {
+ return groupByPrimitive(field);
+ }
+
+ /**
+ * Group by primitive double field
+ *
+ * @param field a primitive double field
+ * @return the query
+ */
+ public QueryWhere<T> groupBy(double field) {
+ return groupByPrimitive(field);
+ }
+
+ private QueryWhere<T> groupByPrimitive(Object field) {
+ query.groupByPrimitive(field);
+ return this;
+ }
+
+ public QueryWhere<T> groupBy(Object field) {
+ query.getFrom().getAliasDefinition().checkMultipleEnums(field);
+ query.groupBy(field);
+ return this;
+ }
+
+ /**
+ * Group by a number of Object columns.
+ *
+ * @param expressions the group by expressions
+ * @return the query
+ */
+
+ public QueryWhere<T> groupBy(Object... expressions) {
+ query.groupBy(expressions);
+ 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 index 0fbedba..f60007e 100644 --- a/src/main/java/com/iciql/RuntimeParameter.java +++ b/src/main/java/com/iciql/RuntimeParameter.java @@ -20,30 +20,29 @@ 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
+ *
+ * @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 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);
- }
- }
+ 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 index cbfd882..cc747a4 100644 --- a/src/main/java/com/iciql/RuntimeToken.java +++ b/src/main/java/com/iciql/RuntimeToken.java @@ -15,43 +15,40 @@ */
package com.iciql;
-import java.text.MessageFormat;
-
import com.iciql.util.StringUtils;
+import java.text.MessageFormat;
+
/**
* Represents a traditional PreparedStatment fragment like "id=?, name=?".
- *
*/
public class RuntimeToken implements Token {
- final String fragment;
- final Object[] args;
+ final String fragment;
+ final Object[] args;
- public RuntimeToken(String fragment, Object... args) {
- this.fragment = fragment;
- this.args = args == null ? new Object[0] : 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);
- }
- }
+ /**
+ * 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 index 8e6361a..1cb4931 100644 --- a/src/main/java/com/iciql/SQLDialect.java +++ b/src/main/java/com/iciql/SQLDialect.java @@ -18,210 +18,188 @@ package com.iciql; -import java.sql.ResultSet; - import com.iciql.Iciql.DataTypeAdapter; import com.iciql.TableDefinition.IndexDefinition; +import java.sql.ResultSet; + /** * This interface defines points where iciql can build different statements * depending on the database used. */ public interface SQLDialect { - /** - * Registers the type adapter instance. - * - * @param typeAdapter - */ - void registerAdapter(DataTypeAdapter<?> typeAdapter); - - /** - * Returns the registered instance of the type adapter. - * - * @param typeAdapter - * @return the type adapter instance - */ - DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter); - - /** - * Serialize the Java object into a type or format that the database will accept. - * - * @param value - * @param typeAdapter - * @return the serialized object - */ - <T> Object serialize(T value, Class<? extends DataTypeAdapter<?>> typeAdapter); - - /** - * Deserialize the object received from the database into a Java type. - * - * @param rs - * @param columnIndex - * @param targetType - * @param typeAdapter - * @return the deserialized object - */ - Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter); - - /** - * Configure the dialect. - * - * @param db - */ - void configureDialect(Db db); - - /** - * Returns true if savepoints are supported. - * - * @return true if savepoints may be used. - */ - boolean supportsSavePoints(); - - /** - * 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 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 prepareStringParameter(Object o); - - /** - * Returns the name of a formatted column identifier for the dialect. - * - * @param name - * the column name - * @return the column name without formatting syntax - */ - String extractColumnName(String name); + /** + * Registers the type adapter instance. + * + * @param typeAdapter + */ + void registerAdapter(DataTypeAdapter<?> typeAdapter); + + /** + * Returns the registered instance of the type adapter. + * + * @param typeAdapter + * @return the type adapter instance + */ + DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter); + + /** + * Serialize the Java object into a type or format that the database will accept. + * + * @param value + * @param typeAdapter + * @return the serialized object + */ + <T> Object serialize(T value, Class<? extends DataTypeAdapter<?>> typeAdapter); + + /** + * Deserialize the object received from the database into a Java type. + * + * @param rs + * @param columnIndex + * @param targetType + * @param typeAdapter + * @return the deserialized object + */ + Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter); + + /** + * Configure the dialect. + * + * @param db + */ + void configureDialect(Db db); + + /** + * Returns true if savepoints are supported. + * + * @return true if savepoints may be used. + */ + boolean supportsSavePoints(); + + /** + * 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 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 prepareStringParameter(Object o); + + /** + * Returns the name of a formatted column identifier for the dialect. + * + * @param name the column name + * @return the column name without formatting syntax + */ + String extractColumnName(String name); } diff --git a/src/main/java/com/iciql/SQLDialectDefault.java b/src/main/java/com/iciql/SQLDialectDefault.java index ec3f605..4f635a9 100644 --- a/src/main/java/com/iciql/SQLDialectDefault.java +++ b/src/main/java/com/iciql/SQLDialectDefault.java @@ -18,17 +18,6 @@ package com.iciql; -import java.sql.Blob; -import java.sql.Clob; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.text.SimpleDateFormat; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import com.iciql.Iciql.ConstraintDeleteType; import com.iciql.Iciql.ConstraintUpdateType; import com.iciql.Iciql.DataTypeAdapter; @@ -42,491 +31,502 @@ import com.iciql.util.StatementBuilder; import com.iciql.util.StringUtils; import com.iciql.util.Utils; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** * Default implementation of an SQL dialect. */ public class SQLDialectDefault implements SQLDialect { - final String LITERAL = "'"; - - protected float databaseVersion; - protected int databaseMajorVersion; - protected int databaseMinorVersion; - protected String databaseName; - protected String productVersion; - protected Mode mode; - protected Map<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>> typeAdapters; - - public SQLDialectDefault() { - typeAdapters = new ConcurrentHashMap<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>>(); - } - - @Override - public String toString() { - return getClass().getName() + ": " + databaseName + " " + productVersion; - } - - @Override - public void configureDialect(Db db) { - Connection conn = db.getConnection(); - DatabaseMetaData data = null; - try { - data = conn.getMetaData(); - databaseName = data.getDatabaseProductName(); - databaseMajorVersion = data.getDatabaseMajorVersion(); - databaseMinorVersion = data.getDatabaseMinorVersion(); - databaseVersion = Float.parseFloat(databaseMajorVersion + "." - + databaseMinorVersion); - productVersion = data.getDatabaseProductVersion(); - } catch (SQLException e) { - throw new IciqlException(e, "failed to retrieve database metadata!"); - } - - mode = db.getMode(); - } - - @Override - public boolean supportsSavePoints() { - return true; - } - - /** - * 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 String extractColumnName(String name) { - return name.replace('\"', ' ').replace('\'', ' ').trim(); - } - - @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(')'); - } - } - - // create unique constraints - if (def.constraintsUnique.size() > 0) { - buff.append(", "); - buff.resetCount(); - for (ConstraintUniqueDefinition constraint : def.constraintsUnique) { - buff.append("CONSTRAINT "); - buff.append(constraint.constraintName); - buff.append(" UNIQUE "); - buff.append(" ("); - for (String col : constraint.uniqueColumns) { - buff.appendExceptFirst(", "); - buff.append(prepareColumnName(col)); - } - buff.append(") "); - } - } - - // create foreign key constraints - if (def.constraintsForeignKey.size() > 0) { - buff.append(", "); - buff.resetCount(); - for (ConstraintForeignKeyDefinition constraint : def.constraintsForeignKey) { - buff.appendExceptFirst(", "); - buff.append(String.format("CONSTRAINT %s FOREIGN KEY(%s) REFERENCES %s(%s)", - constraint.constraintName, - constraint.foreignColumns.get(0), - constraint.referenceTable, - constraint.referenceColumns.get(0))); - - 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; - } - } - } - - 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 ("INTEGER".equals(dataType)) { - return true; - } else if ("TINYINT".equals(dataType)) { - return true; - } else if ("SMALLINT".equals(dataType)) { - return true; - } else if ("MEDIUMINT".equals(dataType)) { - return true; - } else if ("BIGINT".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); - Object parameter = serialize(value, field.typeAdapter); - stat.addParameter(parameter); - } - 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); - Object parameter = serialize(value, field.typeAdapter); - stat.addParameter(parameter); - } - } - 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 void registerAdapter(DataTypeAdapter<?> typeAdapter) { - typeAdapters.put((Class<? extends DataTypeAdapter<?>>) typeAdapter.getClass(), typeAdapter); - } - - @Override - public DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter) { - DataTypeAdapter<?> dta = typeAdapters.get(typeAdapter); - if (dta == null) { - dta = Utils.newObject(typeAdapter); - typeAdapters.put(typeAdapter, dta); - } - dta.setMode(mode); - return dta; - } - - @SuppressWarnings("unchecked") - @Override - public <T> Object serialize(T value, Class<? extends DataTypeAdapter<?>> typeAdapter) { - if (typeAdapter == null) { - // pass-through - return value; - } - - DataTypeAdapter<T> dta = (DataTypeAdapter<T>) getAdapter(typeAdapter); - return dta.serialize(value); - } - - @Override - public Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter) { - Object value = null; - try { - if (typeAdapter == null) { - // standard object deserialization - Object o = rs.getObject(columnIndex); - if (o == null) { - // no-op - value = null; - } else if (Clob.class.isAssignableFrom(o.getClass())) { - value = Utils.convert(o, String.class); - } else if (Blob.class.isAssignableFrom(o.getClass())) { - value = Utils.convert(o, byte[].class); - } else { - value = Utils.convert(o, targetType); - } - } else { - // custom object deserialization with a DataTypeAdapter - DataTypeAdapter<?> dta = getAdapter(typeAdapter); - Object object = rs.getObject(columnIndex); - value = dta.deserialize(object); - } - } catch (SQLException e) { - throw new IciqlException(e, "Can not convert the value at column {0} to {1}", - columnIndex, targetType.getName()); - } - return value; - } - - @Override - public String prepareStringParameter(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(); - } + final String LITERAL = "'"; + + protected float databaseVersion; + protected int databaseMajorVersion; + protected int databaseMinorVersion; + protected String databaseName; + protected String productVersion; + protected Mode mode; + protected Map<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>> typeAdapters; + + public SQLDialectDefault() { + typeAdapters = new ConcurrentHashMap<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>>(); + } + + @Override + public String toString() { + return getClass().getName() + ": " + databaseName + " " + productVersion; + } + + @Override + public void configureDialect(Db db) { + Connection conn = db.getConnection(); + DatabaseMetaData data = null; + try { + data = conn.getMetaData(); + databaseName = data.getDatabaseProductName(); + databaseMajorVersion = data.getDatabaseMajorVersion(); + databaseMinorVersion = data.getDatabaseMinorVersion(); + databaseVersion = Float.parseFloat(databaseMajorVersion + "." + + databaseMinorVersion); + productVersion = data.getDatabaseProductVersion(); + } catch (SQLException e) { + throw new IciqlException(e, "failed to retrieve database metadata!"); + } + + mode = db.getMode(); + } + + @Override + public boolean supportsSavePoints() { + return true; + } + + /** + * 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 String extractColumnName(String name) { + return name.replace('\"', ' ').replace('\'', ' ').trim(); + } + + @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(')'); + } + } + + // create unique constraints + if (def.constraintsUnique.size() > 0) { + buff.append(", "); + buff.resetCount(); + for (ConstraintUniqueDefinition constraint : def.constraintsUnique) { + buff.append("CONSTRAINT "); + buff.append(constraint.constraintName); + buff.append(" UNIQUE "); + buff.append(" ("); + for (String col : constraint.uniqueColumns) { + buff.appendExceptFirst(", "); + buff.append(prepareColumnName(col)); + } + buff.append(") "); + } + } + + // create foreign key constraints + if (def.constraintsForeignKey.size() > 0) { + buff.append(", "); + buff.resetCount(); + for (ConstraintForeignKeyDefinition constraint : def.constraintsForeignKey) { + buff.appendExceptFirst(", "); + buff.append(String.format("CONSTRAINT %s FOREIGN KEY(%s) REFERENCES %s(%s)", + constraint.constraintName, + constraint.foreignColumns.get(0), + constraint.referenceTable, + constraint.referenceColumns.get(0))); + + 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; + } + } + } + + 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 ("INTEGER".equals(dataType)) { + return true; + } else if ("TINYINT".equals(dataType)) { + return true; + } else if ("SMALLINT".equals(dataType)) { + return true; + } else if ("MEDIUMINT".equals(dataType)) { + return true; + } else if ("BIGINT".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); + Object parameter = serialize(value, field.typeAdapter); + stat.addParameter(parameter); + } + 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); + Object parameter = serialize(value, field.typeAdapter); + stat.addParameter(parameter); + } + } + 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 void registerAdapter(DataTypeAdapter<?> typeAdapter) { + typeAdapters.put((Class<? extends DataTypeAdapter<?>>) typeAdapter.getClass(), typeAdapter); + } + + @Override + public DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter) { + DataTypeAdapter<?> dta = typeAdapters.get(typeAdapter); + if (dta == null) { + dta = Utils.newObject(typeAdapter); + typeAdapters.put(typeAdapter, dta); + } + dta.setMode(mode); + return dta; + } + + @SuppressWarnings("unchecked") + @Override + public <T> Object serialize(T value, Class<? extends DataTypeAdapter<?>> typeAdapter) { + if (typeAdapter == null) { + // pass-through + return value; + } + + DataTypeAdapter<T> dta = (DataTypeAdapter<T>) getAdapter(typeAdapter); + return dta.serialize(value); + } + + @Override + public Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter) { + Object value = null; + try { + if (typeAdapter == null) { + // standard object deserialization + Object o = rs.getObject(columnIndex); + if (o == null) { + // no-op + value = null; + } else if (Clob.class.isAssignableFrom(o.getClass())) { + value = Utils.convert(o, String.class); + } else if (Blob.class.isAssignableFrom(o.getClass())) { + value = Utils.convert(o, byte[].class); + } else { + value = Utils.convert(o, targetType); + } + } else { + // custom object deserialization with a DataTypeAdapter + DataTypeAdapter<?> dta = getAdapter(typeAdapter); + Object object = rs.getObject(columnIndex); + value = dta.deserialize(object); + } + } catch (SQLException e) { + throw new IciqlException(e, "Can not convert the value at column {0} to {1}", + columnIndex, targetType.getName()); + } + return value; + } + + @Override + public String prepareStringParameter(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(); + } } diff --git a/src/main/java/com/iciql/SQLDialectDerby.java b/src/main/java/com/iciql/SQLDialectDerby.java index 99405b7..77fc73e 100644 --- a/src/main/java/com/iciql/SQLDialectDerby.java +++ b/src/main/java/com/iciql/SQLDialectDerby.java @@ -23,49 +23,49 @@ import com.iciql.util.StatementBuilder; */
public class SQLDialectDerby extends SQLDialectDefault {
- @Override
- public Class<? extends java.util.Date> getDateTimeClass() {
- return java.sql.Timestamp.class;
- }
+ @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 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 (databaseMajorVersion >= 10 && databaseMinorVersion >= 5) {
- 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
+ public void appendLimitOffset(SQLStatement stat, long limit, long offset) {
+ // FETCH/OFFSET added in 10.5
+ if (databaseMajorVersion >= 10 && databaseMinorVersion >= 5) {
+ 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
+ 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;
- }
+ @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 index 2d7d0fd..12cc161 100644 --- a/src/main/java/com/iciql/SQLDialectH2.java +++ b/src/main/java/com/iciql/SQLDialectH2.java @@ -25,112 +25,112 @@ import com.iciql.util.StatementBuilder; */
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";
- }
- }
+ /**
+ * 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
+ 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
+ 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
+ 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 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);
- Object parameter = serialize(value, field.typeAdapter);
- stat.addParameter(parameter);
- }
- 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);
+ Object parameter = serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
+ 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 index 8b05ca4..74a3dcc 100644 --- a/src/main/java/com/iciql/SQLDialectHSQL.java +++ b/src/main/java/com/iciql/SQLDialectHSQL.java @@ -16,135 +16,135 @@ package com.iciql;
-import java.text.MessageFormat;
-
import com.iciql.TableDefinition.FieldDefinition;
import com.iciql.util.StatementBuilder;
+import java.text.MessageFormat;
+
/**
* 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";
- }
- }
+ /**
+ * 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
+ 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
+ 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);
- Object parameter = serialize(value, field.typeAdapter);
- stat.addParameter(parameter);
- }
+ @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);
+ Object parameter = serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
- // map to temporary table
- buff.resetCount();
- buff.append(")) AS vals (");
- for (FieldDefinition field : def.fields) {
- buff.appendExceptFirst(", ");
- buff.append(prepareColumnName(valuePrefix + field.columnName));
- }
+ // 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 ");
+ 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('=');
- }
- }
+ // 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));
- }
+ // 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());
- }
+ // 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 index e591d4a..7f765fa 100644 --- a/src/main/java/com/iciql/SQLDialectMSSQL.java +++ b/src/main/java/com/iciql/SQLDialectMSSQL.java @@ -22,37 +22,37 @@ package com.iciql; */ public class SQLDialectMSSQL extends SQLDialectDefault { - @Override - public String extractColumnName(String name) { - return super.extractColumnName(name).replace('[', ' ').replace(']', ' ').trim(); - } - - /** - * 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()); - } + @Override + public String extractColumnName(String name) { + return super.extractColumnName(name).replace('[', ' ').replace(']', ' ').trim(); + } + + /** + * 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 index ec5923f..e78b8a0 100644 --- a/src/main/java/com/iciql/SQLDialectMySQL.java +++ b/src/main/java/com/iciql/SQLDialectMySQL.java @@ -24,71 +24,71 @@ import com.iciql.util.StatementBuilder; */
public class SQLDialectMySQL extends SQLDialectDefault {
- @Override
- public String convertSqlType(String sqlType) {
- if (sqlType.equals("CLOB")) {
- return "TEXT";
- }
- return sqlType;
- }
+ @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
+ 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 <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
+ 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
+ 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);
- Object parameter = serialize(value, field.typeAdapter);
- stat.addParameter(parameter);
- }
- 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());
- }
+ @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);
+ Object parameter = serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
+ 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 index 54e47b0..97b92f8 100644 --- a/src/main/java/com/iciql/SQLDialectPostgreSQL.java +++ b/src/main/java/com/iciql/SQLDialectPostgreSQL.java @@ -25,137 +25,137 @@ import com.iciql.util.StatementBuilder; */
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());
- }
-
- @Override
- public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,
- TableDefinition<T> def, Object obj) {
-
- FieldDefinition primaryKey = null;
- for (FieldDefinition field : def.fields) {
- if (field.isPrimaryKey) {
- primaryKey = field;
- }
- }
-
- if (primaryKey == null || databaseVersion < 9.5f) {
- // simulated UPSERT for <= 9.4 release
- super.prepareMerge(stat, schemaName, tableName, def, obj);
- return;
- }
-
- // official UPSERT added in 9.5 release
- 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);
- Object parameter = serialize(value, field.typeAdapter);
- stat.addParameter(parameter);
- }
-
- buff.append(") ON CONFLICT (");
- buff.resetCount();
- for (FieldDefinition field : def.fields) {
- if (field.isPrimaryKey) {
- buff.appendExceptFirst(", ");
- buff.append(field.columnName);
- }
- }
- buff.append(") DO UPDATE SET ");
- buff.resetCount();
- for (FieldDefinition field : def.fields) {
- buff.appendExceptFirst(", ");
- buff.append(field.columnName);
- buff.append("=?");
- Object value = def.getValue(obj, field);
- Object parameter = serialize(value, field.typeAdapter);
- stat.addParameter(parameter);
- }
- stat.setSQL(buff.toString());
- }
+ @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());
+ }
+
+ @Override
+ public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,
+ TableDefinition<T> def, Object obj) {
+
+ FieldDefinition primaryKey = null;
+ for (FieldDefinition field : def.fields) {
+ if (field.isPrimaryKey) {
+ primaryKey = field;
+ }
+ }
+
+ if (primaryKey == null || databaseVersion < 9.5f) {
+ // simulated UPSERT for <= 9.4 release
+ super.prepareMerge(stat, schemaName, tableName, def, obj);
+ return;
+ }
+
+ // official UPSERT added in 9.5 release
+ 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);
+ Object parameter = serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
+
+ buff.append(") ON CONFLICT (");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ if (field.isPrimaryKey) {
+ buff.appendExceptFirst(", ");
+ buff.append(field.columnName);
+ }
+ }
+ buff.append(") DO UPDATE SET ");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(field.columnName);
+ buff.append("=?");
+ Object value = def.getValue(obj, field);
+ Object parameter = serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
+ stat.setSQL(buff.toString());
+ }
}
\ No newline at end of file diff --git a/src/main/java/com/iciql/SQLDialectSQLite.java b/src/main/java/com/iciql/SQLDialectSQLite.java index 436af54..b99bf96 100644 --- a/src/main/java/com/iciql/SQLDialectSQLite.java +++ b/src/main/java/com/iciql/SQLDialectSQLite.java @@ -16,165 +16,165 @@ package com.iciql;
-import java.sql.Date;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Time;
-import java.sql.Timestamp;
-
import com.iciql.Iciql.DataTypeAdapter;
import com.iciql.TableDefinition.FieldDefinition;
import com.iciql.TableDefinition.IndexDefinition;
import com.iciql.util.IciqlLogger;
import com.iciql.util.StatementBuilder;
+import java.sql.Date;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+
/**
* SQLite database dialect.
*/
public class SQLDialectSQLite extends SQLDialectDefault {
- @Override
- public boolean supportsSavePoints() {
- // SAVEPOINT support was added after the 3.8.7 release
- String [] chunks = productVersion.split("\\.");
- if (Integer.parseInt(chunks[0]) > 3) {
- return true;
- }
- float f = Float.parseFloat(chunks[1] + "." + chunks[2]);
- return (f > 8.7);
- }
-
- @Override
- protected <T> String prepareCreateTable(TableDefinition<T> def) {
- return "CREATE TABLE IF NOT EXISTS";
- }
-
- @Override
- protected <T> String prepareCreateView(TableDefinition<T> def) {
- return "CREATE VIEW IF NOT EXISTS";
- }
-
- @Override
- public String convertSqlType(String sqlType) {
- if (isIntegerType(sqlType)) {
- return "INTEGER";
- }
- return sqlType;
- }
-
- @Override
- protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,
- boolean isAutoIncrement, boolean isPrimaryKey) {
- String convertedType = convertSqlType(dataType);
- buff.append(convertedType);
- if (isPrimaryKey) {
- buff.append(" PRIMARY KEY");
- if (isAutoIncrement) {
- buff.append(" AUTOINCREMENT");
- }
- return true;
- }
- return false;
- }
-
- @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 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 IF NOT EXISTS ");
- buff.append(index.indexName);
- buff.append(" ON ");
- buff.append(tableName);
- buff.append("(");
- for (String col : index.columnNames) {
- buff.appendExceptFirst(", ");
- buff.append(prepareColumnName(col));
- }
- buff.append(") ");
-
- stat.setSQL(buff.toString().trim());
- }
-
- @Override
- public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,
- TableDefinition<T> def, Object obj) {
- StatementBuilder buff = new StatementBuilder("INSERT OR REPLACE INTO ");
- buff.append(prepareTableName(schemaName, tableName)).append(" (");
- buff.resetCount();
- for (FieldDefinition field : def.fields) {
- 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);
- Object parameter = serialize(value, field.typeAdapter);
- stat.addParameter(parameter);
- }
- buff.append(')');
- stat.setSQL(buff.toString());
- }
-
- @Override
- public Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter) {
- try {
- return super.deserialize(rs, columnIndex, targetType, typeAdapter);
- } catch (IciqlException e) {
- if (typeAdapter == null && e.getMessage().startsWith("Can not convert")) {
- try {
- // give the SQLite JDBC driver an opportunity to deserialize DateTime objects
- if (Timestamp.class.equals(targetType)) {
- return rs.getTimestamp(columnIndex);
- } else if (Time.class.equals(targetType)) {
- return rs.getTime(columnIndex);
- } else if (Date.class.equals(targetType)) {
- return rs.getDate(columnIndex);
- } else if (java.util.Date.class.equals(targetType)) {
- Timestamp timestamp = rs.getTimestamp(columnIndex);
- return new java.util.Date(timestamp.getTime());
- }
- } catch (SQLException x) {
- throw new IciqlException(x, "Can not convert the value at column {0} to {1}",
- columnIndex, targetType.getName());
- }
- }
-
- // rethrow e
- throw e;
- }
- }
-
- @Override
- public String prepareStringParameter(Object o) {
- if (o instanceof Boolean) {
- // SQLite does not have an explicit BOOLEAN type
- Boolean bool = (Boolean) o;
- return bool ? "1" : "0";
- }
- return super.prepareStringParameter(o);
- }
-} + @Override
+ public boolean supportsSavePoints() {
+ // SAVEPOINT support was added after the 3.8.7 release
+ String[] chunks = productVersion.split("\\.");
+ if (Integer.parseInt(chunks[0]) > 3) {
+ return true;
+ }
+ float f = Float.parseFloat(chunks[1] + "." + chunks[2]);
+ return (f > 8.7);
+ }
+
+ @Override
+ protected <T> String prepareCreateTable(TableDefinition<T> def) {
+ return "CREATE TABLE IF NOT EXISTS";
+ }
+
+ @Override
+ protected <T> String prepareCreateView(TableDefinition<T> def) {
+ return "CREATE VIEW IF NOT EXISTS";
+ }
+
+ @Override
+ public String convertSqlType(String sqlType) {
+ if (isIntegerType(sqlType)) {
+ return "INTEGER";
+ }
+ return sqlType;
+ }
+
+ @Override
+ protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,
+ boolean isAutoIncrement, boolean isPrimaryKey) {
+ String convertedType = convertSqlType(dataType);
+ buff.append(convertedType);
+ if (isPrimaryKey) {
+ buff.append(" PRIMARY KEY");
+ if (isAutoIncrement) {
+ buff.append(" AUTOINCREMENT");
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @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 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 IF NOT EXISTS ");
+ buff.append(index.indexName);
+ buff.append(" ON ");
+ buff.append(tableName);
+ buff.append("(");
+ for (String col : index.columnNames) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+
+ stat.setSQL(buff.toString().trim());
+ }
+
+ @Override
+ public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,
+ TableDefinition<T> def, Object obj) {
+ StatementBuilder buff = new StatementBuilder("INSERT OR REPLACE INTO ");
+ buff.append(prepareTableName(schemaName, tableName)).append(" (");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ 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);
+ Object parameter = serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
+ buff.append(')');
+ stat.setSQL(buff.toString());
+ }
+
+ @Override
+ public Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter) {
+ try {
+ return super.deserialize(rs, columnIndex, targetType, typeAdapter);
+ } catch (IciqlException e) {
+ if (typeAdapter == null && e.getMessage().startsWith("Can not convert")) {
+ try {
+ // give the SQLite JDBC driver an opportunity to deserialize DateTime objects
+ if (Timestamp.class.equals(targetType)) {
+ return rs.getTimestamp(columnIndex);
+ } else if (Time.class.equals(targetType)) {
+ return rs.getTime(columnIndex);
+ } else if (Date.class.equals(targetType)) {
+ return rs.getDate(columnIndex);
+ } else if (java.util.Date.class.equals(targetType)) {
+ Timestamp timestamp = rs.getTimestamp(columnIndex);
+ return new java.util.Date(timestamp.getTime());
+ }
+ } catch (SQLException x) {
+ throw new IciqlException(x, "Can not convert the value at column {0} to {1}",
+ columnIndex, targetType.getName());
+ }
+ }
+
+ // rethrow e
+ throw e;
+ }
+ }
+
+ @Override
+ public String prepareStringParameter(Object o) {
+ if (o instanceof Boolean) {
+ // SQLite does not have an explicit BOOLEAN type
+ Boolean bool = (Boolean) o;
+ return bool ? "1" : "0";
+ }
+ return super.prepareStringParameter(o);
+ }
+}
diff --git a/src/main/java/com/iciql/SQLStatement.java b/src/main/java/com/iciql/SQLStatement.java index 7eb0b04..d49e722 100644 --- a/src/main/java/com/iciql/SQLStatement.java +++ b/src/main/java/com/iciql/SQLStatement.java @@ -17,174 +17,174 @@ package com.iciql;
+import com.iciql.util.JdbcUtils;
+
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().prepareStringParameter(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;
- }
+ 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().prepareStringParameter(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 index 43a1a93..ef39703 100644 --- a/src/main/java/com/iciql/SelectColumn.java +++ b/src/main/java/com/iciql/SelectColumn.java @@ -21,37 +21,36 @@ import com.iciql.TableDefinition.FieldDefinition; /**
* This class represents a column of a table in a query.
- *
- * @param <T>
- * the table data type
+ *
+ * @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());
- }
+ 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 index 37b42c4..acb5e17 100644 --- a/src/main/java/com/iciql/SelectTable.java +++ b/src/main/java/com/iciql/SelectTable.java @@ -17,96 +17,95 @@ package com.iciql;
-import java.util.ArrayList;
-
import com.iciql.util.Utils;
+import java.util.ArrayList;
+
/**
* This class represents a table in a query.
- *
- * @param <T>
- * the table class
+ *
+ * @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;
- }
+ 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 index 398d214..410a42b 100644 --- a/src/main/java/com/iciql/SubQuery.java +++ b/src/main/java/com/iciql/SubQuery.java @@ -17,16 +17,16 @@ 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));
- }
+ 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 index effea3b..c6ca051 100644 --- a/src/main/java/com/iciql/SubQueryCondition.java +++ b/src/main/java/com/iciql/SubQueryCondition.java @@ -18,24 +18,23 @@ package com.iciql; /**
* A condition that contains a subquery.
- *
- * @param <A>
- * the operand type
+ *
+ * @param <A> the operand type
*/
class SubQueryCondition<A, Y, Z> implements Token {
- A x;
- SubQuery<Y, Z> subquery;
+ A x;
+ SubQuery<Y, Z> subquery;
- SubQueryCondition(A x, SubQuery<Y, Z> subquery) {
- this.x = x;
- this.subquery = 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(")");
- }
+ 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 index 53ef895..fe273c0 100644 --- a/src/main/java/com/iciql/TableDefinition.java +++ b/src/main/java/com/iciql/TableDefinition.java @@ -18,6 +18,12 @@ package com.iciql; +import com.iciql.Iciql.*; +import com.iciql.util.IciqlLogger; +import com.iciql.util.StatementBuilder; +import com.iciql.util.StringUtils; +import com.iciql.util.Utils; + import java.lang.reflect.Field; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -30,1193 +36,1149 @@ 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.DataTypeAdapter; -import com.iciql.Iciql.EnumId; -import com.iciql.Iciql.EnumType; -import com.iciql.Iciql.IQColumn; -import com.iciql.Iciql.IQConstraint; -import com.iciql.Iciql.IQContraintForeignKey; -import com.iciql.Iciql.IQContraintUnique; -import com.iciql.Iciql.IQContraintsForeignKey; -import com.iciql.Iciql.IQContraintsUnique; -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 + * @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; - Class<?> enumTypeClass; - boolean isPrimitive; - String constraint; - Class<? extends DataTypeAdapter<?>> typeAdapter; - - 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); - } - - if (field.getType().isPrimitive() && o == null) { - // do not attempt to set a primitive to null - return; - } - - field.set(obj, o); - } catch (IciqlException e) { - throw e; - } catch (Exception 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(); - ArrayList<ConstraintForeignKeyDefinition> constraintsForeignKey = Utils.newArrayList(); - 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 defineTypeAdapter(Object column, Class<? extends DataTypeAdapter<?>> typeAdapter) { - FieldDefinition def = fieldMap.get(column); - if (def != null) { - def.typeAdapter = typeAdapter; - } - } - - void mapFields(Db db) { - 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 = classFields(inheritColumns); - - 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(); - String defaultValue = ""; - String constraint = ""; - String dataType = null; - Class<? extends DataTypeAdapter<?>> typeAdapter = null; - - // configure Java -> SQL enum mapping - EnumType enumType = Utils.getEnumType(f); - Class<?> enumTypeClass = Utils.getEnumTypeClass(f); - - // 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); - } - - // identify the type adapter - typeAdapter = Utils.getDataTypeAdapter(f.getAnnotations()); - if (typeAdapter == null) { - typeAdapter = Utils.getDataTypeAdapter(f.getType().getAnnotations()); - } - - if (typeAdapter != null) { - DataTypeAdapter<?> dtt = db.getDialect().getAdapter(typeAdapter); - dataType = dtt.getDataType(); - } - - 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.enumTypeClass = enumTypeClass; - fieldDef.dataType = StringUtils.isNullOrEmpty(dataType) ? ModelUtils.getDataType(fieldDef) : dataType; - fieldDef.typeAdapter = typeAdapter; - 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!", tableName); - } - if (primaryKey.size() > 0) { - setPrimaryKey(primaryKey); - } - } - - private List<Field> classFields(boolean inheritColumns) { - List<Field> classFields = Utils.newArrayList(); - classFields.addAll(Arrays.asList(clazz.getDeclaredFields())); - Class<?> superClass = clazz; - while (inheritColumns) { - superClass = superClass.getSuperclass(); - classFields.addAll(Arrays.asList(superClass.getDeclaredFields())); - - if (superClass.isAnnotationPresent(IQView.class)) { - IQView superView = superClass.getAnnotation(IQView.class); - inheritColumns = superView.inheritColumns(); - } else if (superClass.isAnnotationPresent(IQTable.class)) { - IQTable superTable = superClass.getAnnotation(IQTable.class); - inheritColumns = superTable.inheritColumns(); - } else { - inheritColumns = false; - } - } - return classFields; - } - - 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) { - if (!field.nullable) { - // try to interpret and instantiate a default value - value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass()); - } - } - Object parameter = db.getDialect().serialize(value, field.typeAdapter); - stat.addParameter(parameter); - } - 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()); - } - Object parameter = db.getDialect().serialize(value, field.typeAdapter); - stat.addParameter(parameter); - } - 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) { - return !StringUtils.isNullOrEmpty(field.defaultValue); - } - } - 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(" = ?"); - Object parameter = db.getDialect().serialize(value, field.typeAdapter); - stat.addParameter(parameter); - } - } - 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; - } - } - } - - // 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, boolean reuse) { - for (FieldDefinition def : fields) { - Object value; - if (!reuse) { - value = def.initWithNewObject(obj); - } else { - value = def.getValue(obj); - } - SelectColumn<T> column = new SelectColumn<T>(table, def); - map.put(value, 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(SQLDialect dialect, 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(dialect.extractColumnName(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(SQLDialect dialect, Object item, ResultSet rs, int[] columns) { - for (int i = 0; i < fields.size(); i++) { - FieldDefinition def = fields.get(i); - Class<?> targetType = def.field.getType(); - Object o; - if (targetType.isEnum()) { - Object obj; - try { - obj = rs.getObject(columns[i]); - } catch (SQLException e) { - throw new IciqlException(e); - } - o = Utils.convertEnum(obj, targetType, def.enumType); - } else { - o = dialect.deserialize(rs, columns[i], targetType, def.typeAdapter); - } - 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); - } - } - } + /** + * 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; + Class<?> enumTypeClass; + boolean isPrimitive; + String constraint; + Class<? extends DataTypeAdapter<?>> typeAdapter; + + 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); + } + + if (field.getType().isPrimitive() && o == null) { + // do not attempt to set a primitive to null + return; + } + + field.set(obj, o); + } catch (IciqlException e) { + throw e; + } catch (Exception 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(); + ArrayList<ConstraintForeignKeyDefinition> constraintsForeignKey = Utils.newArrayList(); + 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 defineTypeAdapter(Object column, Class<? extends DataTypeAdapter<?>> typeAdapter) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.typeAdapter = typeAdapter; + } + } + + void mapFields(Db db) { + 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 = classFields(inheritColumns); + + 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(); + String defaultValue = ""; + String constraint = ""; + String dataType = null; + Class<? extends DataTypeAdapter<?>> typeAdapter = null; + + // configure Java -> SQL enum mapping + EnumType enumType = Utils.getEnumType(f); + Class<?> enumTypeClass = Utils.getEnumTypeClass(f); + + // 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); + } + + // identify the type adapter + typeAdapter = Utils.getDataTypeAdapter(f.getAnnotations()); + if (typeAdapter == null) { + typeAdapter = Utils.getDataTypeAdapter(f.getType().getAnnotations()); + } + + if (typeAdapter != null) { + DataTypeAdapter<?> dtt = db.getDialect().getAdapter(typeAdapter); + dataType = dtt.getDataType(); + } + + 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.enumTypeClass = enumTypeClass; + fieldDef.dataType = StringUtils.isNullOrEmpty(dataType) ? ModelUtils.getDataType(fieldDef) : dataType; + fieldDef.typeAdapter = typeAdapter; + 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!", tableName); + } + if (primaryKey.size() > 0) { + setPrimaryKey(primaryKey); + } + } + + private List<Field> classFields(boolean inheritColumns) { + List<Field> classFields = Utils.newArrayList(); + classFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + Class<?> superClass = clazz; + while (inheritColumns) { + superClass = superClass.getSuperclass(); + classFields.addAll(Arrays.asList(superClass.getDeclaredFields())); + + if (superClass.isAnnotationPresent(IQView.class)) { + IQView superView = superClass.getAnnotation(IQView.class); + inheritColumns = superView.inheritColumns(); + } else if (superClass.isAnnotationPresent(IQTable.class)) { + IQTable superTable = superClass.getAnnotation(IQTable.class); + inheritColumns = superTable.inheritColumns(); + } else { + inheritColumns = false; + } + } + return classFields; + } + + 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) { + if (!field.nullable) { + // try to interpret and instantiate a default value + value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass()); + } + } + Object parameter = db.getDialect().serialize(value, field.typeAdapter); + stat.addParameter(parameter); + } + 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()); + } + Object parameter = db.getDialect().serialize(value, field.typeAdapter); + stat.addParameter(parameter); + } + 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) { + return !StringUtils.isNullOrEmpty(field.defaultValue); + } + } + 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(" = ?"); + Object parameter = db.getDialect().serialize(value, field.typeAdapter); + stat.addParameter(parameter); + } + } + 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; + } + } + } + + // 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, boolean reuse) { + for (FieldDefinition def : fields) { + Object value; + if (!reuse) { + value = def.initWithNewObject(obj); + } else { + value = def.getValue(obj); + } + SelectColumn<T> column = new SelectColumn<T>(table, def); + map.put(value, 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. + * <p> + * This is not always true. + * <p> + * 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(SQLDialect dialect, 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(dialect.extractColumnName(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(SQLDialect dialect, Object item, ResultSet rs, int[] columns) { + for (int i = 0; i < fields.size(); i++) { + FieldDefinition def = fields.get(i); + Class<?> targetType = def.field.getType(); + Object o; + if (targetType.isEnum()) { + Object obj; + try { + obj = rs.getObject(columns[i]); + } catch (SQLException e) { + throw new IciqlException(e); + } + o = Utils.convertEnum(obj, targetType, def.enumType); + } else { + o = dialect.deserialize(rs, columns[i], targetType, def.typeAdapter); + } + 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 index b717203..d775f0f 100644 --- a/src/main/java/com/iciql/TableInspector.java +++ b/src/main/java/com/iciql/TableInspector.java @@ -18,25 +18,6 @@ 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; @@ -51,6 +32,23 @@ import com.iciql.util.StatementBuilder; import com.iciql.util.StringUtils; import com.iciql.util.Utils; +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 static com.iciql.ValidationRemark.*; +import static com.iciql.util.JdbcUtils.closeSilently; +import static com.iciql.util.StringUtils.isNullOrEmpty; +import static java.text.MessageFormat.format; + /** * 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 @@ -58,666 +56,661 @@ import com.iciql.util.Utils; */ 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) { + 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()); - } - } + } + 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 index 010f5a1..3c08eec 100644 --- a/src/main/java/com/iciql/TestCondition.java +++ b/src/main/java/com/iciql/TestCondition.java @@ -21,95 +21,94 @@ import com.iciql.util.Utils; /**
* This class represents an incomplete condition.
- *
- * @param <A>
- * the incomplete condition data type
+ *
+ * @param <A> the incomplete condition data type
*/
public class TestCondition<A> {
- private A x;
+ private A x;
- public TestCondition(A x) {
- this.x = 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 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 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 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 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 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(")");
- }
- });
- }
+ 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 index cc2203c..acc25c4 100644 --- a/src/main/java/com/iciql/Token.java +++ b/src/main/java/com/iciql/Token.java @@ -21,15 +21,13 @@ 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
- */
+ /**
+ * 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);
+ <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 index 1eaf14c..970ec61 100644 --- a/src/main/java/com/iciql/UpdateColumn.java +++ b/src/main/java/com/iciql/UpdateColumn.java @@ -23,13 +23,12 @@ package com.iciql; */ public interface UpdateColumn { - /** - * Append the SQL to the given statement using the given query. - * - * @param stat - * the statement to append the SQL to - */ + /** + * Append the SQL to the given statement using the given query. + * + * @param stat the statement to append the SQL to + */ - void appendSQL(SQLStatement stat); + void appendSQL(SQLStatement stat); } diff --git a/src/main/java/com/iciql/UpdateColumnIncrement.java b/src/main/java/com/iciql/UpdateColumnIncrement.java index 143ce48..e8f96e3 100644 --- a/src/main/java/com/iciql/UpdateColumnIncrement.java +++ b/src/main/java/com/iciql/UpdateColumnIncrement.java @@ -19,37 +19,35 @@ 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 + * + * @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(")"); - } + 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 index a961480..7ae74a0 100644 --- a/src/main/java/com/iciql/UpdateColumnSet.java +++ b/src/main/java/com/iciql/UpdateColumnSet.java @@ -19,45 +19,43 @@ 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 + * + * @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); - } - } + 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 index 33320ab..74b334e 100644 --- a/src/main/java/com/iciql/ValidationRemark.java +++ b/src/main/java/com/iciql/ValidationRemark.java @@ -28,100 +28,100 @@ import com.iciql.util.StringUtils; */ 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(); - } + /** + * 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/adapter/GsonTypeAdapter.java b/src/main/java/com/iciql/adapter/GsonTypeAdapter.java index 8d23cfd..83cbe51 100644 --- a/src/main/java/com/iciql/adapter/GsonTypeAdapter.java +++ b/src/main/java/com/iciql/adapter/GsonTypeAdapter.java @@ -26,13 +26,13 @@ import com.iciql.Iciql.Mode; * <p> * You use this by creating a subclass which defines your object class. * </p> - * + * <p> * <pre> * public class CustomObjectAdapter extends GsonTypeAdapter<CustomObject> { * * public Class<CustomObject> getJavaType() { * return CustomObject.class; - * } + * } * } * </pre> * @@ -40,32 +40,32 @@ import com.iciql.Iciql.Mode; */ public abstract class GsonTypeAdapter<T> implements DataTypeAdapter<T> { - protected Mode mode; + protected Mode mode; - protected Gson gson() { - return new GsonBuilder().create(); - } + protected Gson gson() { + return new GsonBuilder().create(); + } - @Override - public void setMode(Mode mode) { - this.mode = mode; - } + @Override + public void setMode(Mode mode) { + this.mode = mode; + } - @Override - public String getDataType() { - return "TEXT"; - } + @Override + public String getDataType() { + return "TEXT"; + } - @Override - public Object serialize(T value) { - return gson().toJson(value); - } + @Override + public Object serialize(T value) { + return gson().toJson(value); + } - @Override - public T deserialize(Object value) { - String json = value.toString(); - Gson gson = gson(); - T t = gson.fromJson(json, getJavaType()); - return t; - } + @Override + public T deserialize(Object value) { + String json = value.toString(); + Gson gson = gson(); + T t = gson.fromJson(json, getJavaType()); + return t; + } } diff --git a/src/main/java/com/iciql/adapter/JavaSerializationTypeAdapter.java b/src/main/java/com/iciql/adapter/JavaSerializationTypeAdapter.java index df24495..bebb8fa 100644 --- a/src/main/java/com/iciql/adapter/JavaSerializationTypeAdapter.java +++ b/src/main/java/com/iciql/adapter/JavaSerializationTypeAdapter.java @@ -16,6 +16,10 @@ package com.iciql.adapter; +import com.iciql.Iciql.DataTypeAdapter; +import com.iciql.Iciql.Mode; +import com.iciql.IciqlException; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -25,10 +29,6 @@ import java.io.ObjectOutputStream; import java.sql.Blob; import java.sql.SQLException; -import com.iciql.Iciql.DataTypeAdapter; -import com.iciql.Iciql.Mode; -import com.iciql.IciqlException; - /** * Base class for inserting/retrieving a Java Object as a BLOB field using Java Serialization. * <p>You use this by creating a subclass which defines your object class.</p> @@ -40,68 +40,69 @@ import com.iciql.IciqlException; * } * } * </pre> + * * @param <T> */ public abstract class JavaSerializationTypeAdapter<T> implements DataTypeAdapter<T> { - protected Mode mode; + protected Mode mode; - @Override - public void setMode(Mode mode) { - this.mode = mode; - } + @Override + public void setMode(Mode mode) { + this.mode = mode; + } - @Override - public final String getDataType() { - return "BLOB"; - } + @Override + public final String getDataType() { + return "BLOB"; + } - @Override - public final Object serialize(T value) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try { - new ObjectOutputStream(os).writeObject(value); - return os.toByteArray(); - } catch (IOException e) { - throw new IciqlException(e); - } finally { - try { - os.close(); - } catch (IOException e) { - throw new IciqlException (e); - } - } - } + @Override + public final Object serialize(T value) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + new ObjectOutputStream(os).writeObject(value); + return os.toByteArray(); + } catch (IOException e) { + throw new IciqlException(e); + } finally { + try { + os.close(); + } catch (IOException e) { + throw new IciqlException(e); + } + } + } - @SuppressWarnings("unchecked") - @Override - public final T deserialize(Object value) { - InputStream is = null; - if (value instanceof Blob) { - Blob blob = (Blob) value; - try { - is = blob.getBinaryStream(); - } catch (SQLException e) { - throw new IciqlException(e); - } - } else if (value instanceof byte[]) { - byte [] bytes = (byte []) value; - is = new ByteArrayInputStream(bytes); - } + @SuppressWarnings("unchecked") + @Override + public final T deserialize(Object value) { + InputStream is = null; + if (value instanceof Blob) { + Blob blob = (Blob) value; + try { + is = blob.getBinaryStream(); + } catch (SQLException e) { + throw new IciqlException(e); + } + } else if (value instanceof byte[]) { + byte[] bytes = (byte[]) value; + is = new ByteArrayInputStream(bytes); + } - try { - T object = (T) new ObjectInputStream(is).readObject(); - return object; - } catch (Exception e) { - throw new IciqlException(e); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - throw new IciqlException (e); - } - } - } - } + try { + T object = (T) new ObjectInputStream(is).readObject(); + return object; + } catch (Exception e) { + throw new IciqlException(e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + throw new IciqlException(e); + } + } + } + } } diff --git a/src/main/java/com/iciql/adapter/SnakeYamlTypeAdapter.java b/src/main/java/com/iciql/adapter/SnakeYamlTypeAdapter.java index da3b20b..f96afcb 100644 --- a/src/main/java/com/iciql/adapter/SnakeYamlTypeAdapter.java +++ b/src/main/java/com/iciql/adapter/SnakeYamlTypeAdapter.java @@ -16,48 +16,47 @@ package com.iciql.adapter; +import com.iciql.Iciql.DataTypeAdapter; +import com.iciql.Iciql.Mode; import org.yaml.snakeyaml.DumperOptions.FlowStyle; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.nodes.Tag; -import com.iciql.Iciql.DataTypeAdapter; -import com.iciql.Iciql.Mode; - /** * Base class for inserting/retrieving a Java Object (de)serialized as YAML using SnakeYaml. */ public abstract class SnakeYamlTypeAdapter<T> implements DataTypeAdapter<T> { - protected Mode mode; - - protected Yaml yaml() { - return new Yaml(); - } - - @Override - public void setMode(Mode mode) { - this.mode = mode; - } - - @Override - public String getDataType() { - return "TEXT"; - } - - @Override - public abstract Class<T> getJavaType(); - - @Override - public Object serialize(Object value) { - return yaml().dumpAs(value, Tag.MAP, FlowStyle.BLOCK); - } - - @Override - public T deserialize(Object value) { - String yaml = value.toString(); - Yaml processor = yaml(); - T t = processor.loadAs(yaml, getJavaType()); - return t; - } + protected Mode mode; + + protected Yaml yaml() { + return new Yaml(); + } + + @Override + public void setMode(Mode mode) { + this.mode = mode; + } + + @Override + public String getDataType() { + return "TEXT"; + } + + @Override + public abstract Class<T> getJavaType(); + + @Override + public Object serialize(Object value) { + return yaml().dumpAs(value, Tag.MAP, FlowStyle.BLOCK); + } + + @Override + public T deserialize(Object value) { + String yaml = value.toString(); + Yaml processor = yaml(); + T t = processor.loadAs(yaml, getJavaType()); + return t; + } } diff --git a/src/main/java/com/iciql/adapter/XStreamTypeAdapter.java b/src/main/java/com/iciql/adapter/XStreamTypeAdapter.java index a554724..224e7bb 100644 --- a/src/main/java/com/iciql/adapter/XStreamTypeAdapter.java +++ b/src/main/java/com/iciql/adapter/XStreamTypeAdapter.java @@ -25,38 +25,38 @@ import com.thoughtworks.xstream.XStream; */ public class XStreamTypeAdapter implements DataTypeAdapter<Object> { - protected Mode mode; - - protected XStream xstream() { - return new XStream(); - } - - @Override - public void setMode(Mode mode) { - this.mode = mode; - } - - @Override - public String getDataType() { - return "TEXT"; - } - - @Override - public Class<Object> getJavaType() { - return Object.class; - } - - @Override - public Object serialize(Object value) { - return xstream().toXML(value); - } - - @Override - public Object deserialize(Object value) { - String xml = value.toString(); - XStream xstream = xstream(); - Object t = xstream.fromXML(xml); - return t; - } + protected Mode mode; + + protected XStream xstream() { + return new XStream(); + } + + @Override + public void setMode(Mode mode) { + this.mode = mode; + } + + @Override + public String getDataType() { + return "TEXT"; + } + + @Override + public Class<Object> getJavaType() { + return Object.class; + } + + @Override + public Object serialize(Object value) { + return xstream().toXML(value); + } + + @Override + public Object deserialize(Object value) { + String xml = value.toString(); + XStream xstream = xstream(); + Object t = xstream.fromXML(xml); + return t; + } } diff --git a/src/main/java/com/iciql/adapter/postgresql/JsonObjectAdapter.java b/src/main/java/com/iciql/adapter/postgresql/JsonObjectAdapter.java index 118cfc1..91f989a 100644 --- a/src/main/java/com/iciql/adapter/postgresql/JsonObjectAdapter.java +++ b/src/main/java/com/iciql/adapter/postgresql/JsonObjectAdapter.java @@ -15,38 +15,36 @@ */ package com.iciql.adapter.postgresql; -import java.sql.SQLException; - +import com.iciql.adapter.GsonTypeAdapter; import org.postgresql.util.PGobject; -import com.iciql.adapter.GsonTypeAdapter; +import java.sql.SQLException; /** * Postgres JSON data type adapter maps a JSON column to a domain object using * Google GSON. * - * @author James Moger - * * @param <T> + * @author James Moger */ public abstract class JsonObjectAdapter<T> extends GsonTypeAdapter<T> { - @Override - public String getDataType() { - return "json"; - } + @Override + public String getDataType() { + return "json"; + } - @Override - public Object serialize(T value) { + @Override + public Object serialize(T value) { - String json = gson().toJson(value); - PGobject pg = new PGobject(); - pg.setType(getDataType()); - try { - pg.setValue(json); - } catch (SQLException e) { - // not thrown on base PGobject - } - return pg; - } + String json = gson().toJson(value); + PGobject pg = new PGobject(); + pg.setType(getDataType()); + try { + pg.setValue(json); + } catch (SQLException e) { + // not thrown on base PGobject + } + return pg; + } } diff --git a/src/main/java/com/iciql/adapter/postgresql/JsonStringAdapter.java b/src/main/java/com/iciql/adapter/postgresql/JsonStringAdapter.java index 01d2834..5295d52 100644 --- a/src/main/java/com/iciql/adapter/postgresql/JsonStringAdapter.java +++ b/src/main/java/com/iciql/adapter/postgresql/JsonStringAdapter.java @@ -15,49 +15,48 @@ */ package com.iciql.adapter.postgresql; -import java.sql.SQLException; - -import org.postgresql.util.PGobject; - import com.iciql.Iciql.DataTypeAdapter; import com.iciql.Iciql.Mode; +import org.postgresql.util.PGobject; + +import java.sql.SQLException; /** * Handles transforming raw strings to/from the Postgres JSON data type. */ public class JsonStringAdapter implements DataTypeAdapter<String> { - protected Mode mode; - - @Override - public void setMode(Mode mode) { - this.mode = mode; - } - - @Override - public String getDataType() { - return "json"; - } - - @Override - public Class<String> getJavaType() { - return String.class; - } - - @Override - public Object serialize(String value) { - PGobject pg = new PGobject(); - pg.setType(getDataType()); - try { - pg.setValue(value); - } catch (SQLException e) { - // not thrown on base PGobject - } - return pg; - } - - @Override - public String deserialize(Object value) { - return value.toString(); - } + protected Mode mode; + + @Override + public void setMode(Mode mode) { + this.mode = mode; + } + + @Override + public String getDataType() { + return "json"; + } + + @Override + public Class<String> getJavaType() { + return String.class; + } + + @Override + public Object serialize(String value) { + PGobject pg = new PGobject(); + pg.setType(getDataType()); + try { + pg.setValue(value); + } catch (SQLException e) { + // not thrown on base PGobject + } + return pg; + } + + @Override + public String deserialize(Object value) { + return value.toString(); + } }
\ No newline at end of file diff --git a/src/main/java/com/iciql/adapter/postgresql/JsonbObjectAdapter.java b/src/main/java/com/iciql/adapter/postgresql/JsonbObjectAdapter.java index 6d8ad2c..947778b 100644 --- a/src/main/java/com/iciql/adapter/postgresql/JsonbObjectAdapter.java +++ b/src/main/java/com/iciql/adapter/postgresql/JsonbObjectAdapter.java @@ -15,38 +15,36 @@ */ package com.iciql.adapter.postgresql; -import java.sql.SQLException; - +import com.iciql.adapter.GsonTypeAdapter; import org.postgresql.util.PGobject; -import com.iciql.adapter.GsonTypeAdapter; +import java.sql.SQLException; /** * Postgres JSONB data type adapter maps a JSONB column to a domain object using * Google GSON. * - * @author James Moger - * * @param <T> + * @author James Moger */ public abstract class JsonbObjectAdapter<T> extends GsonTypeAdapter<T> { - @Override - public String getDataType() { - return "jsonb"; - } + @Override + public String getDataType() { + return "jsonb"; + } - @Override - public Object serialize(T value) { + @Override + public Object serialize(T value) { - String json = gson().toJson(value); - PGobject pg = new PGobject(); - pg.setType(getDataType()); - try { - pg.setValue(json); - } catch (SQLException e) { - // not thrown on base PGobject - } - return pg; - } + String json = gson().toJson(value); + PGobject pg = new PGobject(); + pg.setType(getDataType()); + try { + pg.setValue(json); + } catch (SQLException e) { + // not thrown on base PGobject + } + return pg; + } } diff --git a/src/main/java/com/iciql/adapter/postgresql/JsonbStringAdapter.java b/src/main/java/com/iciql/adapter/postgresql/JsonbStringAdapter.java index 9d7388b..88d69c6 100644 --- a/src/main/java/com/iciql/adapter/postgresql/JsonbStringAdapter.java +++ b/src/main/java/com/iciql/adapter/postgresql/JsonbStringAdapter.java @@ -15,49 +15,48 @@ */ package com.iciql.adapter.postgresql; -import java.sql.SQLException; - -import org.postgresql.util.PGobject; - import com.iciql.Iciql.DataTypeAdapter; import com.iciql.Iciql.Mode; +import org.postgresql.util.PGobject; + +import java.sql.SQLException; /** * Handles transforming raw strings to/from the Postgres JSONB data type. */ public class JsonbStringAdapter implements DataTypeAdapter<String> { - protected Mode mode; - - @Override - public void setMode(Mode mode) { - this.mode = mode; - } - - @Override - public String getDataType() { - return "jsonb"; - } - - @Override - public Class<String> getJavaType() { - return String.class; - } - - @Override - public Object serialize(String value) { - PGobject pg = new PGobject(); - pg.setType(getDataType()); - try { - pg.setValue(value); - } catch (SQLException e) { - // not thrown on base PGobject - } - return pg; - } - - @Override - public String deserialize(Object value) { - return value.toString(); - } + protected Mode mode; + + @Override + public void setMode(Mode mode) { + this.mode = mode; + } + + @Override + public String getDataType() { + return "jsonb"; + } + + @Override + public Class<String> getJavaType() { + return String.class; + } + + @Override + public Object serialize(String value) { + PGobject pg = new PGobject(); + pg.setType(getDataType()); + try { + pg.setValue(value); + } catch (SQLException e) { + // not thrown on base PGobject + } + return pg; + } + + @Override + public String deserialize(Object value) { + return value.toString(); + } }
\ No newline at end of file diff --git a/src/main/java/com/iciql/adapter/postgresql/XmlObjectAdapter.java b/src/main/java/com/iciql/adapter/postgresql/XmlObjectAdapter.java index eba7c06..f5160d3 100644 --- a/src/main/java/com/iciql/adapter/postgresql/XmlObjectAdapter.java +++ b/src/main/java/com/iciql/adapter/postgresql/XmlObjectAdapter.java @@ -23,13 +23,12 @@ import com.iciql.adapter.XStreamTypeAdapter; * XStream. * * @author James Moger - * */ public abstract class XmlObjectAdapter extends XStreamTypeAdapter { - @Override - public String getDataType() { - return "xml"; - } + @Override + public String getDataType() { + return "xml"; + } } diff --git a/src/main/java/com/iciql/adapter/postgresql/XmlStringAdapter.java b/src/main/java/com/iciql/adapter/postgresql/XmlStringAdapter.java index 0aea77e..a795e4f 100644 --- a/src/main/java/com/iciql/adapter/postgresql/XmlStringAdapter.java +++ b/src/main/java/com/iciql/adapter/postgresql/XmlStringAdapter.java @@ -15,49 +15,48 @@ */ package com.iciql.adapter.postgresql; -import java.sql.SQLException; - -import org.postgresql.util.PGobject; - import com.iciql.Iciql.DataTypeAdapter; import com.iciql.Iciql.Mode; +import org.postgresql.util.PGobject; + +import java.sql.SQLException; /** * Handles transforming raw strings to/from the Postgres XML data type. */ public class XmlStringAdapter implements DataTypeAdapter<String> { - protected Mode mode; - - @Override - public void setMode(Mode mode) { - this.mode = mode; - } - - @Override - public String getDataType() { - return "xml"; - } - - @Override - public Class<String> getJavaType() { - return String.class; - } - - @Override - public Object serialize(String value) { - PGobject pg = new PGobject(); - pg.setType(getDataType()); - try { - pg.setValue(value); - } catch (SQLException e) { - // not thrown on base PGobject - } - return pg; - } - - @Override - public String deserialize(Object value) { - return value.toString(); - } + protected Mode mode; + + @Override + public void setMode(Mode mode) { + this.mode = mode; + } + + @Override + public String getDataType() { + return "xml"; + } + + @Override + public Class<String> getJavaType() { + return String.class; + } + + @Override + public Object serialize(String value) { + PGobject pg = new PGobject(); + pg.setType(getDataType()); + try { + pg.setValue(value); + } catch (SQLException e) { + // not thrown on base PGobject + } + return pg; + } + + @Override + public String deserialize(Object value) { + return value.toString(); + } }
\ No newline at end of file diff --git a/src/main/java/com/iciql/bytecode/And.java b/src/main/java/com/iciql/bytecode/And.java index 808a9c1..0b04525 100644 --- a/src/main/java/com/iciql/bytecode/And.java +++ b/src/main/java/com/iciql/bytecode/And.java @@ -26,21 +26,21 @@ import com.iciql.Token; */ 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); - } + 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 index 29516c2..5ea2b74 100644 --- a/src/main/java/com/iciql/bytecode/ArrayGet.java +++ b/src/main/java/com/iciql/bytecode/ArrayGet.java @@ -26,24 +26,24 @@ import com.iciql.Token; */ 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("]"); - } + 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 index 2a1d69e..bdf7fc5 100644 --- a/src/main/java/com/iciql/bytecode/CaseWhen.java +++ b/src/main/java/com/iciql/bytecode/CaseWhen.java @@ -26,37 +26,37 @@ import com.iciql.Token; */ 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"); - } + 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 index 38fd2f5..52c71b4 100644 --- a/src/main/java/com/iciql/bytecode/ClassReader.java +++ b/src/main/java/com/iciql/bytecode/ClassReader.java @@ -17,6 +17,9 @@ package com.iciql.bytecode; +import com.iciql.IciqlException; +import com.iciql.Token; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -25,1433 +28,1430 @@ 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 static final boolean DEBUG = false; - private byte[] data; - private int pos; - private Constant[] constantPool; - private int startByteCode; - private String methodName; + 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 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); - } - } + 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; - } + 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 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 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; - } - } + 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(); - } + 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 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 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 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 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 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 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 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 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 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 getAbsolutePos(int start, int offset) { + return start - startByteCode - 1 + (short) offset; + } - private int readByte() { - return data[pos++] & 0xff; - } + private int readByte() { + return data[pos++] & 0xff; + } - private int readShort() { - byte[] buff = data; - return ((buff[pos++] & 0xff) << 8) + (buff[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 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); - } + 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 index 65cd66b..7adf8a9 100644 --- a/src/main/java/com/iciql/bytecode/Constant.java +++ b/src/main/java/com/iciql/bytecode/Constant.java @@ -24,15 +24,15 @@ import com.iciql.Token; */ 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 - } + /** + * 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(); + Constant.Type getType(); - int intValue(); + int intValue(); } diff --git a/src/main/java/com/iciql/bytecode/ConstantNumber.java b/src/main/java/com/iciql/bytecode/ConstantNumber.java index 934de3d..b0c25b8 100644 --- a/src/main/java/com/iciql/bytecode/ConstantNumber.java +++ b/src/main/java/com/iciql/bytecode/ConstantNumber.java @@ -25,46 +25,46 @@ import com.iciql.SQLStatement; */ public class ConstantNumber implements Constant { - private final String value; - private final Type type; - private final long longValue; + 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; - } + 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(String v) { + return new ConstantNumber(v, 0, Type.STRING); + } - static ConstantNumber get(int v) { - return new ConstantNumber("" + v, v, Type.INT); - } + 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(long v) { + return new ConstantNumber("" + v, v, Type.LONG); + } - static ConstantNumber get(String s, long x, Type type) { - return new ConstantNumber(s, x, type); - } + static ConstantNumber get(String s, long x, Type type) { + return new ConstantNumber(s, x, type); + } - public int intValue() { - return (int) longValue; - } + public int intValue() { + return (int) longValue; + } - public String toString() { - return value; - } + public String toString() { + return value; + } - public <T> void appendSQL(SQLStatement stat, Query<T> query) { - stat.appendSQL(toString()); - } + public <T> void appendSQL(SQLStatement stat, Query<T> query) { + stat.appendSQL(toString()); + } - public Constant.Type getType() { - return type; - } + 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 index 985f97d..937082b 100644 --- a/src/main/java/com/iciql/bytecode/ConstantString.java +++ b/src/main/java/com/iciql/bytecode/ConstantString.java @@ -26,30 +26,30 @@ import com.iciql.util.StringUtils; */ public class ConstantString implements Constant { - private final String value; + private final String value; - private ConstantString(String value) { - this.value = value; - } + private ConstantString(String value) { + this.value = value; + } - static ConstantString get(String v) { - return new ConstantString(v); - } + static ConstantString get(String v) { + return new ConstantString(v); + } - public String toString() { - return value; - } + public String toString() { + return value; + } - public int intValue() { - return 0; - } + public int intValue() { + return 0; + } - public <T> void appendSQL(SQLStatement stat, Query<T> query) { - stat.appendSQL(StringUtils.quoteStringSQL(value)); - } + public <T> void appendSQL(SQLStatement stat, Query<T> query) { + stat.appendSQL(StringUtils.quoteStringSQL(value)); + } - public Constant.Type getType() { - return Constant.Type.STRING; - } + 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 index 56a55ea..31506c8 100644 --- a/src/main/java/com/iciql/bytecode/Function.java +++ b/src/main/java/com/iciql/bytecode/Function.java @@ -26,22 +26,22 @@ import com.iciql.Token; */ class Function implements Token { - private final String name; - private final Token expr; + private final String name; + private final Token expr; - Function(String name, Token expr) { - this.name = name; - this.expr = expr; - } + Function(String name, Token expr) { + this.name = name; + this.expr = expr; + } - public String toString() { - return name + "(" + 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(")"); - } + 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 index ab5ab84..d6d5333 100644 --- a/src/main/java/com/iciql/bytecode/Not.java +++ b/src/main/java/com/iciql/bytecode/Not.java @@ -26,30 +26,30 @@ import com.iciql.Token; */ 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(")"); - } + 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 index a28de56..a036b6e 100644 --- a/src/main/java/com/iciql/bytecode/Null.java +++ b/src/main/java/com/iciql/bytecode/Null.java @@ -26,19 +26,19 @@ import com.iciql.Token; */ public class Null implements Token { - static final Null INSTANCE = new Null(); + static final Null INSTANCE = new Null(); - private Null() { - // don't allow to create new instances - } + private Null() { + // don't allow to create new instances + } - public String toString() { - return "null"; - } + public String toString() { + return "null"; + } - public <T> void appendSQL(SQLStatement stat, Query<T> query) { - // untested - stat.appendSQL("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 index 7cd42d9..5b385b6 100644 --- a/src/main/java/com/iciql/bytecode/Operation.java +++ b/src/main/java/com/iciql/bytecode/Operation.java @@ -26,86 +26,86 @@ import com.iciql.Token; */ 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); - } + /** + * 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 index 37da2a6..00e326e 100644 --- a/src/main/java/com/iciql/bytecode/Or.java +++ b/src/main/java/com/iciql/bytecode/Or.java @@ -26,22 +26,22 @@ import com.iciql.Token; */ 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); - } + 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 index f3dbc01..da2a634 100644 --- a/src/main/java/com/iciql/bytecode/Variable.java +++ b/src/main/java/com/iciql/bytecode/Variable.java @@ -26,26 +26,26 @@ import com.iciql.Token; */ public class Variable implements Token { - static final Variable THIS = new Variable("this", null); + static final Variable THIS = new Variable("this", null); - private final String name; - private final Object obj; + private final String name; + private final Object obj; - private Variable(String name, Object obj) { - this.name = name; - this.obj = 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); - } + static Variable get(String name, Object obj) { + return new Variable(name, obj); + } - public String toString() { - return name; - } + public String toString() { + return name; + } - public <T> void appendSQL(SQLStatement stat, Query<T> query) { - query.appendSQL(stat, null, obj); - } + 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 index 5107481..b5a73a1 100644 --- a/src/main/java/com/iciql/bytecode/package.html +++ b/src/main/java/com/iciql/bytecode/package.html @@ -16,8 +16,9 @@ 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> + <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. diff --git a/src/main/java/com/iciql/package.html b/src/main/java/com/iciql/package.html index 769837b..4afe927 100644 --- a/src/main/java/com/iciql/package.html +++ b/src/main/java/com/iciql/package.html @@ -16,8 +16,9 @@ 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>
+ <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
diff --git a/src/main/java/com/iciql/util/GenerateModels.java b/src/main/java/com/iciql/util/GenerateModels.java index eac9f6c..b8dcaa4 100644 --- a/src/main/java/com/iciql/util/GenerateModels.java +++ b/src/main/java/com/iciql/util/GenerateModels.java @@ -17,6 +17,9 @@ package com.iciql.util; +import com.iciql.Db; +import com.iciql.DbInspector; + import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -29,165 +32,152 @@ 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; + /** + * 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 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); - } + 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); - } - } + /** + * 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); - } + /** + * 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>"); - } + 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 index d8005bb..2a2caf6 100644 --- a/src/main/java/com/iciql/util/IciqlLogger.java +++ b/src/main/java/com/iciql/util/IciqlLogger.java @@ -16,6 +16,8 @@ package com.iciql.util; +import com.iciql.IciqlException; + import java.text.DecimalFormat; import java.text.MessageFormat; import java.util.Set; @@ -23,192 +25,189 @@ 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)); - } - } + /** + * 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 index 4a4a2b6..0f15eb6 100644 --- a/src/main/java/com/iciql/util/JdbcUtils.java +++ b/src/main/java/com/iciql/util/JdbcUtils.java @@ -17,6 +17,9 @@ package com.iciql.util; +import javax.naming.Context; +import javax.sql.DataSource; +import javax.sql.XAConnection; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; @@ -24,231 +27,212 @@ 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); - } - } + 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 index ded393f..383d80f 100644 --- a/src/main/java/com/iciql/util/Slf4jIciqlListener.java +++ b/src/main/java/com/iciql/util/Slf4jIciqlListener.java @@ -16,77 +16,76 @@ 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;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
/**
* Slf4jIciqlListener interfaces the IciqlLogger to the SLF4J logging framework.
*/
public class Slf4jIciqlListener implements IciqlListener {
- private Logger logger = LoggerFactory.getLogger(Iciql.class);
+ private Logger logger = LoggerFactory.getLogger(Iciql.class);
- /**
- * Enumeration representing the SLF4J log levels.
- */
- public enum Level {
- ERROR, WARN, INFO, DEBUG, TRACE, OFF;
- }
+ /**
+ * Enumeration representing the SLF4J log levels.
+ */
+ public enum Level {
+ ERROR, WARN, INFO, DEBUG, TRACE, OFF;
+ }
- private final Level defaultLevel;
+ private final Level defaultLevel;
- private final Map<StatementType, Level> levels;
+ private final Map<StatementType, Level> levels;
- public Slf4jIciqlListener() {
- this(Level.TRACE);
- }
+ 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);
- }
- }
+ 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);
- }
+ /**
+ * 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;
- }
- }
+ @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 index 47e8054..469d1d9 100644 --- a/src/main/java/com/iciql/util/StatementBuilder.java +++ b/src/main/java/com/iciql/util/StatementBuilder.java @@ -21,19 +21,19 @@ 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: - * + * <p> * <pre> * StringBuilder buff = new StringBuilder(); * for (int i = 0; i < args.length; i++) { * if (i > 0) { * buff.append(", "); - * } + * } * buff.append(args[i]); * } * </pre> - * + * <p> * to - * + * <p> * <pre> * StatementBuilder buff = new StatementBuilder(); * for (String s : args) { @@ -44,123 +44,117 @@ package com.iciql.util; */ 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(); - } + 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 index dd3f180..3cf8f83 100644 --- a/src/main/java/com/iciql/util/StringUtils.java +++ b/src/main/java/com/iciql/util/StringUtils.java @@ -29,354 +29,338 @@ 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(); - } + /** + * 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; - } + /** + * 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(); - } + /** + * 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(); - } + /** + * 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(); - } + /** + * 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; - } + /** + * 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 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); - } - } + /** + * 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; - } + /** + * 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("&"); - } else if (inStr.charAt(i) == '<') { - retStr.append("<"); - } else if (inStr.charAt(i) == '>') { - retStr.append(">"); - } else if (inStr.charAt(i) == '\"') { - retStr.append("""); - } else if (changeSpace && inStr.charAt(i) == ' ') { - retStr.append(" "); - } else if (changeSpace && inStr.charAt(i) == '\t') { - retStr.append(" "); - } else { - retStr.append(inStr.charAt(i)); - } - i++; - } - return retStr.toString(); - } + /** + * 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("&"); + } else if (inStr.charAt(i) == '<') { + retStr.append("<"); + } else if (inStr.charAt(i) == '>') { + retStr.append(">"); + } else if (inStr.charAt(i) == '\"') { + retStr.append("""); + } else if (changeSpace && inStr.charAt(i) == ' ') { + retStr.append(" "); + } else if (changeSpace && inStr.charAt(i) == '\t') { + retStr.append(" "); + } 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/>"); - } + /** + * 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(); - } + /** + * 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 index ada7c34..16528d8 100644 --- a/src/main/java/com/iciql/util/Utils.java +++ b/src/main/java/com/iciql/util/Utils.java @@ -17,6 +17,13 @@ package com.iciql.util;
+import com.iciql.Iciql.DataTypeAdapter;
+import com.iciql.Iciql.EnumId;
+import com.iciql.Iciql.EnumType;
+import com.iciql.Iciql.IQEnum;
+import com.iciql.Iciql.TypeAdapter;
+import com.iciql.IciqlException;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -44,581 +51,570 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
-import com.iciql.Iciql.DataTypeAdapter;
-import com.iciql.Iciql.EnumId;
-import com.iciql.Iciql.EnumType;
-import com.iciql.Iciql.IQEnum;
-import com.iciql.Iciql.TypeAdapter;
-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> Iterable<T> newArrayIterable(final T[] a) {
- return Arrays.asList(a);
- }
-
- 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() % Integer.MAX_VALUE));
- } 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() % Short.MAX_VALUE));
- } else if (clazz == byte.class || clazz == Byte.class) {
- return (T) new Byte((byte) (COUNTER.getAndIncrement() % Byte.MAX_VALUE));
- } 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();
- } else if (Set.class == clazz) {
- COUNTER.getAndIncrement();
- return (T) new HashSet();
- } else if (List.class == clazz) {
- COUNTER.getAndIncrement();
- return (T) new ArrayList();
- } else if (Map.class == clazz) {
- COUNTER.getAndIncrement();
- return (T) new HashMap();
- }
- 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;
- Integer n = b ? 1 : 0;
- if (Number.class.isAssignableFrom(targetType)) {
- return n.intValue();
- } else if (byte.class.isAssignableFrom(targetType)) {
- return n.byteValue();
- } else if (short.class.isAssignableFrom(targetType)) {
- return n.shortValue();
- } else if (int.class.isAssignableFrom(targetType)) {
- return n.intValue();
- } else if (long.class.isAssignableFrom(targetType)) {
- return n.longValue();
- } else if (float.class.isAssignableFrom(targetType)) {
- return n.floatValue();
- } else if (double.class.isAssignableFrom(targetType)) {
- return n.doubleValue();
- } else 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();
- } else if (targetType == BigDecimal.class) {
- return new BigDecimal(n.doubleValue());
- } else if (targetType == java.util.Date.class) {
- return new java.util.Date(n.longValue());
- } else if (targetType == java.sql.Date.class) {
- return new java.sql.Date(n.longValue());
- } else if (targetType == java.sql.Time.class) {
- return new java.sql.Time(n.longValue());
- } else if (targetType == java.sql.Timestamp.class) {
- return new java.sql.Timestamp(n.longValue());
- }
- }
-
- if (Date.class.isAssignableFrom(currentType)) {
- Date d = (Date) o;
- if (targetType == Date.class) {
- return o;
- } else if (targetType == java.sql.Date.class) {
- return new java.sql.Date(d.getTime());
- } else if (targetType == java.sql.Time.class) {
- return new java.sql.Time(d.getTime());
- } else if (targetType == java.sql.Timestamp.class) {
- return new java.sql.Timestamp(d.getTime());
- }
- }
-
- // 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);
- }
-
- /**
- * Identify the EnumType for the field.
- *
- * @param f
- * @return null or the EnumType
- */
- public static EnumType getEnumType(Field f) {
- EnumType enumType = null;
- 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();
- }
- }
- return enumType;
- }
-
- /**
- * Identify the EnumType from the annotations.
- *
- * @param annotations
- * @return null or the EnumType
- */
- public static EnumType getEnumType(Annotation [] annotations) {
- EnumType enumType = null;
- if (annotations != null) {
- for (Annotation annotation : annotations) {
- if (annotation instanceof IQEnum) {
- enumType = ((IQEnum) annotation).value();
- break;
- }
- }
- }
- return enumType;
- }
-
- public static Class<?> getEnumTypeClass(Field f) {
- if (f.getType().isEnum()) {
- if (EnumId.class.isAssignableFrom(f.getType())) {
- // custom enumid mapping
- return ((EnumId<?>) f.getType().getEnumConstants()[0]).enumIdClass();
- }
- }
- return null;
- }
-
- 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
- if (type.equals(EnumType.ENUMID)) {
- // ENUMID mapping
- for (Enum<?> value : values) {
- EnumId<?> enumid = (EnumId<?>) value;
- if (enumid.enumId().equals(name)) {
- return value;
- }
- }
- } else if (type.equals(EnumType.NAME)) {
- // standard Enum.name() mapping
- for (Enum<?> value : values) {
- if (value.name().equalsIgnoreCase(name)) {
- return value;
- }
- }
- }
- } else if (String.class.isAssignableFrom(currentType)) {
- // VARCHAR field
- String name = (String) o;
- if (type.equals(EnumType.ENUMID)) {
- // ENUMID mapping
- for (Enum<?> value : values) {
- EnumId<?> enumid = (EnumId<?>) value;
- if (enumid.enumId().equals(name)) {
- return value;
- }
- }
- } else if (type.equals(EnumType.NAME)) {
- // standard Enum.name() mapping
- 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().equals(n)) {
- return value;
- }
- }
- }
- } else {
- // custom object mapping
- 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().equals(o)) {
- 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();
- }
- }
-
- /**
- * Identify the data type adapter class in the annotations.
- *
- * @param annotations
- * @return null or the data type adapter class
- */
- public static Class<? extends DataTypeAdapter<?>> getDataTypeAdapter(Annotation [] annotations) {
- Class<? extends DataTypeAdapter<?>> typeAdapter = null;
- if (annotations != null) {
- for (Annotation annotation : annotations) {
- if (annotation instanceof TypeAdapter) {
- typeAdapter = ((TypeAdapter) annotation).value();
- } else if (annotation.annotationType().isAnnotationPresent(TypeAdapter.class)) {
- typeAdapter = annotation.annotationType().getAnnotation(TypeAdapter.class).value();
- }
- }
- }
- return typeAdapter;
- }
+ 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> Iterable<T> newArrayIterable(final T[] a) {
+ return Arrays.asList(a);
+ }
+
+ 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() % Integer.MAX_VALUE));
+ } 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() % Short.MAX_VALUE));
+ } else if (clazz == byte.class || clazz == Byte.class) {
+ return (T) new Byte((byte) (COUNTER.getAndIncrement() % Byte.MAX_VALUE));
+ } 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();
+ } else if (Set.class == clazz) {
+ COUNTER.getAndIncrement();
+ return (T) new HashSet();
+ } else if (List.class == clazz) {
+ COUNTER.getAndIncrement();
+ return (T) new ArrayList();
+ } else if (Map.class == clazz) {
+ COUNTER.getAndIncrement();
+ return (T) new HashMap();
+ }
+ 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;
+ Integer n = b ? 1 : 0;
+ if (Number.class.isAssignableFrom(targetType)) {
+ return n.intValue();
+ } else if (byte.class.isAssignableFrom(targetType)) {
+ return n.byteValue();
+ } else if (short.class.isAssignableFrom(targetType)) {
+ return n.shortValue();
+ } else if (int.class.isAssignableFrom(targetType)) {
+ return n.intValue();
+ } else if (long.class.isAssignableFrom(targetType)) {
+ return n.longValue();
+ } else if (float.class.isAssignableFrom(targetType)) {
+ return n.floatValue();
+ } else if (double.class.isAssignableFrom(targetType)) {
+ return n.doubleValue();
+ } else 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();
+ } else if (targetType == BigDecimal.class) {
+ return new BigDecimal(n.doubleValue());
+ } else if (targetType == java.util.Date.class) {
+ return new java.util.Date(n.longValue());
+ } else if (targetType == java.sql.Date.class) {
+ return new java.sql.Date(n.longValue());
+ } else if (targetType == java.sql.Time.class) {
+ return new java.sql.Time(n.longValue());
+ } else if (targetType == java.sql.Timestamp.class) {
+ return new java.sql.Timestamp(n.longValue());
+ }
+ }
+
+ if (Date.class.isAssignableFrom(currentType)) {
+ Date d = (Date) o;
+ if (targetType == Date.class) {
+ return o;
+ } else if (targetType == java.sql.Date.class) {
+ return new java.sql.Date(d.getTime());
+ } else if (targetType == java.sql.Time.class) {
+ return new java.sql.Time(d.getTime());
+ } else if (targetType == java.sql.Timestamp.class) {
+ return new java.sql.Timestamp(d.getTime());
+ }
+ }
+
+ // 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);
+ }
+
+ /**
+ * Identify the EnumType for the field.
+ *
+ * @param f
+ * @return null or the EnumType
+ */
+ public static EnumType getEnumType(Field f) {
+ EnumType enumType = null;
+ 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();
+ }
+ }
+ return enumType;
+ }
+
+ /**
+ * Identify the EnumType from the annotations.
+ *
+ * @param annotations
+ * @return null or the EnumType
+ */
+ public static EnumType getEnumType(Annotation[] annotations) {
+ EnumType enumType = null;
+ if (annotations != null) {
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof IQEnum) {
+ enumType = ((IQEnum) annotation).value();
+ break;
+ }
+ }
+ }
+ return enumType;
+ }
+
+ public static Class<?> getEnumTypeClass(Field f) {
+ if (f.getType().isEnum()) {
+ if (EnumId.class.isAssignableFrom(f.getType())) {
+ // custom enumid mapping
+ return ((EnumId<?>) f.getType().getEnumConstants()[0]).enumIdClass();
+ }
+ }
+ return null;
+ }
+
+ 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
+ if (type.equals(EnumType.ENUMID)) {
+ // ENUMID mapping
+ for (Enum<?> value : values) {
+ EnumId<?> enumid = (EnumId<?>) value;
+ if (enumid.enumId().equals(name)) {
+ return value;
+ }
+ }
+ } else if (type.equals(EnumType.NAME)) {
+ // standard Enum.name() mapping
+ for (Enum<?> value : values) {
+ if (value.name().equalsIgnoreCase(name)) {
+ return value;
+ }
+ }
+ }
+ } else if (String.class.isAssignableFrom(currentType)) {
+ // VARCHAR field
+ String name = (String) o;
+ if (type.equals(EnumType.ENUMID)) {
+ // ENUMID mapping
+ for (Enum<?> value : values) {
+ EnumId<?> enumid = (EnumId<?>) value;
+ if (enumid.enumId().equals(name)) {
+ return value;
+ }
+ }
+ } else if (type.equals(EnumType.NAME)) {
+ // standard Enum.name() mapping
+ 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().equals(n)) {
+ return value;
+ }
+ }
+ }
+ } else {
+ // custom object mapping
+ 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().equals(o)) {
+ 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();
+ }
+ }
+
+ /**
+ * Identify the data type adapter class in the annotations.
+ *
+ * @param annotations
+ * @return null or the data type adapter class
+ */
+ public static Class<? extends DataTypeAdapter<?>> getDataTypeAdapter(Annotation[] annotations) {
+ Class<? extends DataTypeAdapter<?>> typeAdapter = null;
+ if (annotations != null) {
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof TypeAdapter) {
+ typeAdapter = ((TypeAdapter) annotation).value();
+ } else if (annotation.annotationType().isAnnotationPresent(TypeAdapter.class)) {
+ typeAdapter = annotation.annotationType().getAnnotation(TypeAdapter.class).value();
+ }
+ }
+ }
+ return typeAdapter;
+ }
}
diff --git a/src/main/java/com/iciql/util/WeakIdentityHashMap.java b/src/main/java/com/iciql/util/WeakIdentityHashMap.java index bc03cd0..0a2ad1e 100644 --- a/src/main/java/com/iciql/util/WeakIdentityHashMap.java +++ b/src/main/java/com/iciql/util/WeakIdentityHashMap.java @@ -17,227 +17,225 @@ package com.iciql.util;
+import com.iciql.IciqlException;
+
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
+ *
+ * @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();
- }
+ 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 index 3d24dee..62adcb5 100644 --- a/src/main/java/com/iciql/util/package.html +++ b/src/main/java/com/iciql/util/package.html @@ -16,8 +16,9 @@ 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>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
+ <title>Javadoc package documentation</title>
</head>
<body>
Utility classes for iciql.
diff --git a/src/main/java/java/lang/AutoCloseable.java b/src/main/java/java/lang/AutoCloseable.java index 2141fbc..4484403 100644 --- a/src/main/java/java/lang/AutoCloseable.java +++ b/src/main/java/java/lang/AutoCloseable.java @@ -36,18 +36,18 @@ public interface AutoCloseable { * Closes this resource, relinquishing any underlying resources. * This method is invoked automatically by the {@code * try}-with-resources statement. - * + * <p> * <p>Classes implementing this method are strongly encouraged to * be declared to throw more specific exceptions (or no exception * at all, if the close cannot fail). - * + * <p> * <p>Note that unlike the {@link java.io.Closeable#close close} * method of {@link java.io.Closeable}, this {@code close} method * is <em>not</em> required to be idempotent. In other words, * calling this {@code close} method more than once may have some * visible side effect, unlike {@code Closeable.close} which is * required to have no effect if called more than once. - * + * <p> * However, while not required to be idempotent, implementers of * this interface are strongly encouraged to make their {@code * close} methods idempotent. |