提交 113996ea authored 作者: Thomas Mueller's avatar Thomas Mueller

Cleanup:

- Javadocs
- Work in progress
上级 daa2b0bf
......@@ -19,7 +19,6 @@ import org.h2.jaqu.bytecode.ClassReader;
import org.h2.jaqu.util.StatementLogger;
import org.h2.jaqu.util.JdbcUtils;
import org.h2.jaqu.util.Utils;
//import org.h2.util.JdbcUtils;
//## Java 1.5 end ##
/**
......@@ -38,8 +37,8 @@ public class Query<T> {
private final IdentityHashMap<Object, SelectColumn<T>> aliasMap = Utils.newIdentityHashMap();
private ArrayList<OrderExpression<T>> orderByList = Utils.newArrayList();
private Object[] groupByExpressions;
private long limit = 0;
private long offset = 0;
private long limit;
private long offset;
Query(Db db) {
this.db = db;
......@@ -65,6 +64,7 @@ public class Query<T> {
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
int whyTrue;
JdbcUtils.closeSilently(rs, true);
}
}
......@@ -120,24 +120,26 @@ public class Query<T> {
StatementLogger.delete(stat.getSQL());
return stat.executeUpdate();
}
public <A> SetColumn<T, A> set(A field) {
int renameSetColumnClassToUpdateSetColumn;
return new SetColumn<T, A>(this, field);
}
public <A> IncrementColumn<T, A> increment(A field) {
int renameIncrementColumnClassToUpdateIncrementColumn;
return new IncrementColumn<T, A>(this, field);
}
public int update() {
if (declarations.size() == 0)
throw new RuntimeException("Please specify SET or INCREMENT before calling Update!");
SQLStatement stat = new SQLStatement(db);
SQLStatement stat = new SQLStatement(db);
stat.appendSQL("UPDATE ");
from.appendSQL(stat);
stat.appendSQL(" SET ");
int i = 0;
for (Declaration declaration:declarations) {
for (Declaration declaration : declarations) {
if (i++ > 0) {
stat.appendSQL(", ");
}
......@@ -145,7 +147,7 @@ public class Query<T> {
}
appendWhere(stat);
StatementLogger.update(stat.getSQL());
return stat.executeUpdate();
return stat.executeUpdate();
}
public <X, Z> List<X> selectDistinct(Z x) {
......@@ -196,6 +198,7 @@ public class Query<T> {
try {
X value;
Object o = rs.getObject(1);
int convertHereIsProbablyWrong;
if (Clob.class.isAssignableFrom(o.getClass())) {
value = (X) Utils.convert(o, String.class);
} else
......@@ -266,7 +269,7 @@ public class Query<T> {
return this;
}
//## Java 1.5 end ##
/**
* Order by a number of columns.
*
......@@ -323,8 +326,9 @@ public class Query<T> {
void addConditionToken(Token condition) {
conditions.add(condition);
}
void addDeclarationToken(Declaration declaration) {
int todoWhatIsDeclaration;
declarations.add(declaration);
}
......
......@@ -33,12 +33,12 @@ public class QueryWhere<T> {
query.addConditionToken(ConditionAndOr.OR);
return new QueryCondition<T, A>(query, x);
}
public QueryWhere<T> limit(long limit) {
query.limit(limit);
return this;
}
public QueryWhere<T> offset(long offset) {
query.offset(offset);
return this;
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
import org.h2.jaqu.TableDefinition.IndexDefinition;
import org.h2.jaqu.util.StatementBuilder;
import org.h2.jaqu.util.StringUtils;
/**
* This interface defines points where JaQu can build different statements
* depending on the database used.
*/
public interface SQLDialect {
/**
* Get the SQL snippet for the table name.
*
* @param schema the schema name, or null for no schema
* @param table the table name
* @return the SQL snippet
*/
String tableName(String schema, String table);
/**
* Get the CREATE INDEX statement.
*
* @param schema the schema name
* @param table the table name
* @param index the index definition
* @return the SQL statement
*/
String createIndex(String schema, String table, IndexDefinition index);
/**
* Append "LIMIT limit" to the SQL statement.
*
* @param stat the statement
* @param limit the limit
*/
void appendLimit(SQLStatement stat, long limit);
/**
* Append "OFFSET offset" to the SQL statement.
*
* @param stat the statement
* @param offset the offset
*/
void appendOffset(SQLStatement stat, long offset);
/**
* Default implementation of an SQL dialect.
* Designed for an H2 database. May be suitable for others.
*/
public static class DefaultSQLDialect implements SQLDialect {
public String tableName(String schema, String table) {
if (StringUtils.isNullOrEmpty(schema)) {
return table;
}
return schema + "." + table;
}
public String createIndex(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(")");
return buff.toString();
}
public void appendLimit(SQLStatement stat, long limit) {
stat.appendSQL(" LIMIT " + limit);
}
public void appendOffset(SQLStatement stat, long offset) {
stat.appendSQL(" OFFSET " + offset);
}
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
import org.h2.jaqu.TableDefinition.IndexDefinition;
import org.h2.jaqu.util.StatementBuilder;
import org.h2.jaqu.util.StringUtils;
/**
* This interface defines points where JaQu can build different statements
* depending on the database used.
*/
public interface SQLDialect {
/**
* Get the SQL snippet for the table name.
*
* @param schema the schema name, or null for no schema
* @param table the table name
* @return the SQL snippet
*/
String tableName(String schema, String table);
/**
* Get the CREATE INDEX statement.
*
* @param schema the schema name
* @param table the table name
* @param index the index definition
* @return the SQL statement
*/
String createIndex(String schema, String table, IndexDefinition index);
/**
* Append "LIMIT limit" to the SQL statement.
*
* @param stat the statement
* @param limit the limit
*/
void appendLimit(SQLStatement stat, long limit);
/**
* Append "OFFSET offset" to the SQL statement.
*
* @param stat the statement
* @param offset the offset
*/
void appendOffset(SQLStatement stat, long offset);
/**
* Default implementation of an SQL dialect.
* Designed for an H2 database. May be suitable for others.
*/
public static class DefaultSQLDialect implements SQLDialect {
public String tableName(String schema, String table) {
if (StringUtils.isNullOrEmpty(schema)) {
return table;
}
return schema + "." + table;
}
public String createIndex(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(")");
return buff.toString();
}
public void appendLimit(SQLStatement stat, long limit) {
stat.appendSQL(" LIMIT " + limit);
}
public void appendOffset(SQLStatement stat, long offset) {
stat.appendSQL(" OFFSET " + offset);
}
}
}
......@@ -38,7 +38,7 @@ public class SQLStatement {
sql = null;
return this;
}
public SQLStatement appendTable(String schema, String table) {
return appendSQL(db.getDialect().tableName(schema, table));
}
......@@ -54,7 +54,7 @@ public class SQLStatement {
params.add(o);
return this;
}
ResultSet executeQuery() {
try {
return prepare(false).executeQuery();
......@@ -74,7 +74,7 @@ public class SQLStatement {
JdbcUtils.closeSilently(ps);
}
}
long executeInsert() {
PreparedStatement ps = null;
try {
......@@ -82,9 +82,10 @@ public class SQLStatement {
ps.executeUpdate();
long identity = -1;
ResultSet rs = ps.getGeneratedKeys();
if (rs != null && rs.next())
if (rs != null && rs.next()) {
identity = rs.getLong(1);
JdbcUtils.closeSilently(rs);
}
JdbcUtils.closeSilently(rs);
return identity;
} catch (SQLException e) {
throw new RuntimeException(e);
......@@ -101,8 +102,8 @@ public class SQLStatement {
}
}
private PreparedStatement prepare(boolean returnIdentity) {
PreparedStatement prep = db.prepare(getSQL(), returnIdentity);
private 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);
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
/**
* This class represents "SET column = value" in an UPDATE statement.
*
* @param <T> the query type
* @param <A> the new value data type
*/
//## Java 1.5 begin ##
public class SetColumn<T, A> implements Declaration {
private Query<T> query;
private A x;
private A y;
SetColumn(Query<T> query, A x) {
this.query = query;
this.x = x;
}
public Query<T> to(A y) {
query.addDeclarationToken(this);
this.y = y;
return query;
}
public void appendSQL(SQLStatement stat) {
query.appendSQL(stat, x);
stat.appendSQL("=?");
stat.addParameter(y);
}
}
//## Java 1.5 end ##
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
/**
* This class represents "SET column = value" in an UPDATE statement.
*
* @param <T> the query type
* @param <A> the new value data type
*/
//## Java 1.5 begin ##
public class SetColumn<T, A> implements Declaration {
private Query<T> query;
private A x;
private A y;
SetColumn(Query<T> query, A x) {
this.query = query;
this.x = x;
}
public Query<T> to(A y) {
query.addDeclarationToken(this);
this.y = y;
return query;
}
public void appendSQL(SQLStatement stat) {
query.appendSQL(stat, x);
stat.appendSQL("=?");
stat.addParameter(y);
}
}
//## Java 1.5 end ##
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version
* 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html). Initial Developer: H2 Group
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jaqu;
......@@ -10,6 +11,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* A class that implements this interface can be used as a database table.
*/
/**
* A class that implements the JaQu model mapping options.
* <p>
......@@ -112,95 +116,93 @@ import java.lang.annotation.Target;
* <p>
* You may automatically generate model classes as strings with the <i>Db</i>
* and <i>DbInspector</i> objects.
*
*
* <pre>
* Db db = Db.open(&quot;jdbc:h2:mem:&quot;, &quot;sa&quot;, &quot;sa&quot;);
* DbInspector inspector = new DbInspector(db);
* List&lt;String&gt; models = inspector.generateModel(schema, table,
* packageName, annotateSchema, trimStrings);
* List&lt;String&gt; models =
* inspector.generateModel(schema, table, packageName,
* annotateSchema, trimStrings)
* </pre>
*
*
* OR you may use the <i>GenerateModels</i> tool to generate and save your
* classes to the filesystem.
*
*
* <pre>
* java -cp h2jaqu.jar org.h2.jaqu.util.GenerateModels
* java -cp h2jaqu.jar org.h2.jaqu.util.GenerateModels
* -url &quot;jdbc:h2:mem:&quot;
* -user sa -password sa -schema schemaName -table tableName
* -package packageName -folder destination
* -annotateSchema false -trimStrings true
* </pre>
*
*
* <b>Model Validation</b>
* <p>
* You may validate your model class with <i>DbInspector</i> object.<br>
* The DbInspector will report ERRORS, WARNINGS, and SUGGESTIONS to help you.
*
* The DbInspector will report ERRORS, WARNINGS, and
* SUGGESTIONS to help you.
*
* <pre>
* Db db = Db.open(&quot;jdbc:h2:mem:&quot;, &quot;sa&quot;, &quot;sa&quot;);
* Db db = Db.open(&quot;jdbc:h2:mem:&quot;,
* &quot;sa&quot;, &quot;sa&quot;);
* DbInspector inspector = new DbInspector(db);
* MyModel model = new MyModel();
* List&lt;Validation&gt; remarks = inspector.validateModel(model, throwOnError);
* for (Validation remark : remarks)
* List&lt;Validation&gt; remarks =
* inspector.validateModel(new MyModel(), throwOnError);
* for (Validation remark : remarks) {
* System.out.println(remark);
* }
* </pre>
*/
public interface Table {
static final int reviewWholeClassAndJavadocs = 0;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JQDatabase {
/**
* If <b>version</b> is set to a <i>non-zero</i> value, JaQu will
* If version is set to a non-zero value, JaQu will
* maintain a "_jq_versions" table within your database. The
* <i>version</i> number will be used to call to a registered
* <i>DbUpgrader</i> implementation to perform relevant ALTERs or
* whatever.
* <p>
* <b>Default: <i>0</i></b>
* <p>
* <b>NOTE:</b><br>
* You must specify a <i>DbUpgrader</i> on your <i>Db</i> object to
* version number will be 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 version() default 0;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JQSchema {
/**
* <b>schema</b> may be optionally specified. If it is not specified the
* schema will be ignored.
* <p>
* <b>Default: <i>Unspecified</i></b>
* The schema may be optionally specified. If it is not specified the
* schema will be ignored. Default: Unspecified.
*/
String name() default "";
}
/**
* Enumeration defining the 4 index types.
*
*/
public static enum IndexType {
STANDARD, UNIQUE, HASH, UNIQUE_HASH;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JQIndex {
/**
* <b>standard</b> indexes may be optionally specified. If not specified,
* Standard indexes may be optionally specified. If not specified,
* these values will be ignored.
* <ul>
* <li>standard = "id, name"
* <li>standard = "id name"
* <li>standard = { "id name", "date" }
* </ul>
* Standard indexes may still be added in the <i>define()</i> method if
* Standard indexes may still be added in the define() method if
* the model class is not annotated with JQTable.
* <p>
* <b>Default: <i>Unspecified</i></b>
* Default: Unspecified.
*/
String[] standard() default {};
......@@ -218,7 +220,7 @@ public interface Table {
* <b>Default: <i>Unspecified</i></b>
*/
String[] unique() default {};
/**
* <b>hash</b> indexes may be optionally specified. If not specified,
* these values will be ignored.
......@@ -232,7 +234,7 @@ public interface Table {
* <b>Default: <i>Unspecified</i></b>
*/
String[] hash() default {};
/**
* <b>uniqueHash</b> indexes may be optionally specified. If not specified,
* these values will be ignored.
......@@ -337,8 +339,8 @@ public interface Table {
* If <b>version</b> is set to a <i>non-zero</i> value, JaQu will
* maintain a "_jq_versions" table within your database. The
* <i>version</i> number will be used to call to a registered
* <i>DbUpgrader</i> implementation to perform relevant ALTERs or
* whatever.
* <i>DbUpgrader</i> implementation to perform relevant ALTER
* statements.
* <p>
* <b>Default: <i>0</i></b>
* <p>
......
......@@ -227,7 +227,7 @@ class TableDefinition<T> {
for (Field f : classFields) {
// default to field name
String columnName = f.getName();
String columnName = f.getName();
boolean isAutoIncrement = false;
boolean isPrimaryKey = false;
int maxLength = 0;
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
import static java.text.MessageFormat.format;
import static org.h2.jaqu.Validation.CONSIDER;
import static org.h2.jaqu.Validation.ERROR;
import static org.h2.jaqu.Validation.WARN;
import static org.h2.jaqu.util.JdbcUtils.closeSilently;
import static org.h2.jaqu.util.StringUtils.isNullOrEmpty;
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 org.h2.jaqu.Table.IndexType;
import org.h2.jaqu.Table.JQColumn;
import org.h2.jaqu.Table.JQIndex;
import org.h2.jaqu.Table.JQSchema;
import org.h2.jaqu.Table.JQTable;
import org.h2.jaqu.TableDefinition.FieldDefinition;
import org.h2.jaqu.TableDefinition.IndexDefinition;
import org.h2.jaqu.util.StatementBuilder;
import org.h2.jaqu.util.Utils;
/**
* Class to inspect the contents of a particular table including its indexes.
* This class does the bulk of the work in terms of model generation and model
* validation.
*/
public class TableInspector {
private String schema;
private String table;
private boolean forceUpperCase;
private Class<? extends java.util.Date> dateClazz;
private List<String> primaryKeys = Utils.newArrayList();
private Map<String, IndexInspector> indexes;
private Map<String, ColumnInspector> columns;
private final String eol = "\n";
private int todoReviewWholeClass;
TableInspector(String schema, String table, boolean forceUpperCase,
Class<? extends java.util.Date> dateClazz) {
this.schema = schema;
this.table = table;
this.forceUpperCase = forceUpperCase;
this.dateClazz = dateClazz;
}
/**
* 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)
&& info.name.toLowerCase().startsWith("primary")) {
// Skip PrimaryKey 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.getClassType(col.type, dateClazz);
col.size = rs.getInt("COLUMN_SIZE");
// Allow Null
try {
col.allowNull = rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable;
} catch (SQLException x) {
}
// AutoIncrement
try {
col.isAutoIncrement = rs.getBoolean("IS_AUTOINCREMENT");
} catch (SQLException x) {
}
// Primary Key
if (primaryKeys.size() == 1) {
if (col.name.equalsIgnoreCase(primaryKeys.get(0)))
col.isPrimaryKey = true;
}
// Default Value
if (!col.isAutoIncrement) {
try {
col.defaultValue = rs.getString("COLUMN_DEFAULT");
} catch (SQLException t) {
try {
col.defaultValue = rs.getString("COLUMN_DEF");
} catch (SQLException x) {
}
}
}
columns.put(col.name, col);
}
} finally {
closeSilently(rs);
}
}
/**
* Generates a model (class definition) from this table.
* The model includes indexes, primary keys, default values, maxLengths,
* and allowNull information.
* <p>
* The caller may optionally set a destination package name, whether or not
* ot 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) {
// Set of imports
Set<String> imports = Utils.newHashSet();
imports.add(JQSchema.class.getCanonicalName());
imports.add(JQTable.class.getCanonicalName());
imports.add(JQIndex.class.getCanonicalName());
imports.add(JQColumn.class.getCanonicalName());
// Table 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);
// @JQSchema
if (annotateSchema && !isNullOrEmpty(schema)) {
model.append('@').append(JQSchema.class.getSimpleName());
model.append('(');
AnnotationBuilder ap = new AnnotationBuilder();
ap.addParameter("name", schema);
model.append(ap);
model.append(')').append(eol);
}
// @JQTable
model.append('@').append(JQTable.class.getSimpleName());
model.append('(');
// JQTable Annotation Parameters
AnnotationBuilder ap = new AnnotationBuilder();
ap.addParameter("name", table);
if (primaryKeys.size() > 1) {
StringBuilder pk = new StringBuilder();
for (String key : primaryKeys) {
pk.append(key).append(' ');
}
pk.trimToSize();
ap.addParameter("primaryKey", pk.toString());
}
// Finish @JQTable annotation
model.append(ap);
model.append(')').append(eol);
// @JQIndex
ap = new AnnotationBuilder();
generateIndexAnnotations(ap, "standard", IndexType.STANDARD);
generateIndexAnnotations(ap, "unique", IndexType.UNIQUE);
generateIndexAnnotations(ap, "hash", IndexType.HASH);
generateIndexAnnotations(ap, "uniqueHash", IndexType.UNIQUE_HASH);
if (ap.length() > 0) {
model.append('@').append(JQIndex.class.getSimpleName());
model.append('(');
model.append(ap);
model.append(')').append(eol);
}
// Class Declaration
String clazzName = ModelUtils.createClassName(table);
model.append(format("public class {0} '{'", clazzName)).append(eol);
model.append(eol);
// Field Declarations
model.append(fields);
// Default Constructor
model.append("\tpublic ").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
*/
void generateIndexAnnotations(AnnotationBuilder ap, String parameter, IndexType type) {
List<IndexInspector> list = getIndexes(type);
if (list.size() == 0) {
// No matching indexes
return;
}
if (list.size() == 1) {
ap.addParameter(parameter, list.get(0).getColumnsString());
} else {
List<String> parameters = Utils.newArrayList();
for (IndexInspector index:list) {
parameters.add(index.getColumnsString());
}
ap.addParameter(parameter, parameters);
}
}
/**
* Returns indexes of a specific type from the map.
* <p>
* @param type
* @return
*/
List<IndexInspector> getIndexes(IndexType type) {
List<IndexInspector> list = Utils.newArrayList();
for (IndexInspector index:indexes.values()) {
if (index.type.equals(type)) {
list.add(index);
}
}
return list;
}
/**
* Generates a column field definition with annotations.
* <p>
* @param imports
* @param col
* @param trimStrings
* @return
*/
StatementBuilder generateColumn(Set<String> imports, ColumnInspector col,
boolean trimStrings) {
StatementBuilder sb = new StatementBuilder();
Class<?> clazz = col.clazz;
String cname = ModelUtils.createFieldName(col.name.toLowerCase());
sb.append('\t');
if (clazz == null) {
// Unsupported Type
clazz = Object.class;
sb.append("// Unsupported type " + col.type);
} else {
// @JQColumn
imports.add(clazz.getCanonicalName());
sb.append('@').append(JQColumn.class.getSimpleName());
// JQColumn Annotation Parameters
AnnotationBuilder ap = new AnnotationBuilder();
// JQColumn.name
if (!col.name.equalsIgnoreCase(cname)) {
ap.addParameter("name", col.name);
}
// JQColumn.primaryKey
// Composite Primary Keys are annotated on the Table
if (col.isPrimaryKey && primaryKeys.size() == 1) {
ap.addParameter("primaryKey=true");
}
// JQColumn.maxLength
if ((clazz == String.class) && (col.size > 0)
&& (col.size < Integer.MAX_VALUE)) {
ap.addParameter("maxLength", col.size);
// JQColumn.trimStrings
if (trimStrings) {
ap.addParameter("trimString=true");
}
} else {
// JQColumn.AutoIncrement
if (col.isAutoIncrement) {
ap.addParameter("autoIncrement=true");
}
}
// JQColumn.allowNull
if (!col.allowNull) {
ap.addParameter("allowNull=false");
}
// JQColumn.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("\tpublic ");
sb.append(clazz.getSimpleName());
sb.append(' ');
sb.append(cname);
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.
* <p>
* Results are returned as a List&lt;Validation&gt; which includes
* recommendations, warnings, and errors about the model.
* <p>
* The caller may choose to have validate throw an exception on any validation
* ERROR.
* <p>
* @param <T>
* @param def
* @param throwError
* @return List&lt;Validation&gt;
*/
<T> List<Validation> validate(TableDefinition<T> def,
boolean throwError) {
List<Validation> 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}(name={1})",
JQSchema.class.getSimpleName(), schema)));
} else if (!schema.equalsIgnoreCase(def.schemaName)) {
remarks.add(ERROR(table, "SCHEMA",
format("@{0}(name={1}) != {2}",
JQSchema.class.getSimpleName(), def.schemaName,
schema)).throwError(throwError));
}
}
// Index Validation
for (IndexInspector index:indexes.values()) {
validate(remarks, def, index, throwError);
}
// Field Column Validation
List<FieldDefinition> fieldDefs = def.getFields();
for (FieldDefinition fieldDef : fieldDefs) {
validate(remarks, fieldDef, throwError);
}
return remarks;
}
/**
* Validates an inspected index from the database against the IndexDefinition
* within the TableDefinition.
* <p>
* <b>TODO</b>: Complete index validation
* <p>
* @param <T>
* @param remarks
* @param def
* @param index
* @param throwError
*/
<T> void validate(List<Validation> remarks, TableDefinition<T> def,
IndexInspector index, boolean throwError) {
List<IndexDefinition> defIndexes = def.getIndexes(IndexType.STANDARD);
List<IndexInspector> dbIndexes = getIndexes(IndexType.STANDARD);
if (defIndexes.size() > dbIndexes.size()) {
remarks.add(WARN(table, IndexType.STANDARD.name(), "# of Model Indexes > DB Indexes!"));
} else if (defIndexes.size() < dbIndexes.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.
// At this point my head was starting to hurt.
}
/**
* Validates a column against the model's field definition. Checks for
* existence, supported type, type mapping, default value, defined lengths,
* primary key, autoincrement.
* <p>
* @param remarks
* @param fieldDef
* @param throwError
*/
void validate(List<Validation> remarks, FieldDefinition fieldDef,
boolean throwError) {
// Unknown Field
String fname = forceUpperCase ?
fieldDef.columnName.toUpperCase() : fieldDef.columnName;
if (!columns.containsKey(fname)) {
// Unknown column mapping!
remarks.add(ERROR(table, fieldDef,
"Does not exist in database!").throwError(throwError));
return;
}
ColumnInspector col = columns.get(fname);
Class<?> fieldClazz = fieldDef.field.getType();
Class<?> jdbcClazz = ModelUtils.getClassType(col.type, dateClazz);
// Supported Type Check
// JaQu maps to VARCHAR for unsupported types.
if (fieldDef.dataType.equals("VARCHAR")
&& (fieldClazz != String.class)) {
remarks.add(ERROR(table, fieldDef,
"JaQu does not currently implement support for "
+ fieldClazz.getName()).throwError(throwError));
}
// Number Types
if (!fieldClazz.equals(jdbcClazz)) {
if (Number.class.isAssignableFrom(fieldClazz)) {
remarks.add(WARN(table, col,
format("Precision Mismatch: ModelObject={0}, ColumnObject={1}",
fieldClazz.getSimpleName(), jdbcClazz.getSimpleName())));
} else {
if (!Date.class.isAssignableFrom(jdbcClazz)) {
remarks.add(WARN(table, col,
format("Object Mismatch: ModelObject={0}, ColumnObject={1}",
fieldClazz.getSimpleName(), jdbcClazz.getSimpleName())));
}
}
}
// String Types
if (fieldClazz == String.class) {
if ((fieldDef.maxLength != col.size)
&& (col.size < Integer.MAX_VALUE)) {
remarks.add(WARN(table, col,
format("{0}.maxLength={1}, ColumnMaxLength={2}",
JQColumn.class.getSimpleName(),
fieldDef.maxLength, col.size)));
}
if (fieldDef.maxLength > 0 && !fieldDef.trimString) {
remarks.add(CONSIDER(table, col,
format("{0}.truncateToMaxLength=true"
+ " will prevent RuntimeExceptions on"
+ " INSERTs or UPDATEs, but will clip data!",
JQColumn.class.getSimpleName())));
}
}
// Numeric AutoIncrement
if (fieldDef.isAutoIncrement != col.isAutoIncrement) {
remarks.add(WARN(table, col, format("{0}.isAutoIncrement={1}"
+ " while Column autoIncrement={2}",
JQColumn.class.getSimpleName(), fieldDef.isAutoIncrement,
col.isAutoIncrement)));
}
// Last Check
// Default Value...
if (!col.isAutoIncrement && !col.isPrimaryKey) {
// Check Model.defaultValue Format
if (!ModelUtils.isProperlyFormattedDefaultValue(fieldDef.defaultValue)) {
remarks.add(ERROR(table, col, format("{0}.defaultValue=\"{1}\""
+ " is improperly formatted!",
JQColumn.class.getSimpleName(),
fieldDef.defaultValue)).throwError(throwError));
// Next field
return;
}
// Compare Model.defaultValue to Column.defaultValue
if (isNullOrEmpty(fieldDef.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}\"",
JQColumn.class.getSimpleName(), col.defaultValue)));
} else if (!isNullOrEmpty(fieldDef.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=\"\"",
JQColumn.class.getSimpleName(), fieldDef.defaultValue)));
} else if (!isNullOrEmpty(fieldDef.defaultValue)
&& !isNullOrEmpty(col.defaultValue)) {
if (!fieldDef.defaultValue.equals(col.defaultValue)) {
// Model.defaultValue != Column.defaultValue
remarks.add(WARN(table, col, format("{0}.defaultValue=\"{1}\""
+ " while Column default=\"{2}\"",
JQColumn.class.getSimpleName(), fieldDef.defaultValue,
col.defaultValue)));
}
}
// Sanity Check Model.defaultValue Literal Value
if (!ModelUtils.isValidDefaultValue(fieldDef.field.getType(),
fieldDef.defaultValue)) {
remarks.add(ERROR(table, col,
format("{0}.defaultValue=\"{1}\" is invalid!!",
JQColumn.class.getSimpleName(),
fieldDef.defaultValue)));
}
}
}
/**
* Represents an index as it exists in the database.
*
*/
public 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"));
}
public String getColumnsString() {
StatementBuilder sb = new StatementBuilder();
for (String col : columns) {
sb.appendExceptFirst(", ");
sb.append(col);
}
return sb.toString().trim();
}
}
/**
* Represents a column as it exists in the database.
*
*/
public static class ColumnInspector implements Comparable<ColumnInspector> {
String name;
String type;
int size;
boolean allowNull;
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(", ");
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('\"');
}
int todoEscape;
flat.append(o.toString().trim());
if (o instanceof String) {
flat.append('\"');
}
}
append(flat);
append(" }");
} else {
if (value instanceof String) {
append('\"');
}
int todoEscape;
append(value.toString().trim());
if (value instanceof String) {
append('\"');
}
}
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
import static java.text.MessageFormat.format;
import static org.h2.jaqu.Validation.CONSIDER;
import static org.h2.jaqu.Validation.ERROR;
import static org.h2.jaqu.Validation.WARN;
import static org.h2.jaqu.util.JdbcUtils.closeSilently;
import static org.h2.jaqu.util.StringUtils.isNullOrEmpty;
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 org.h2.jaqu.Table.IndexType;
import org.h2.jaqu.Table.JQColumn;
import org.h2.jaqu.Table.JQIndex;
import org.h2.jaqu.Table.JQSchema;
import org.h2.jaqu.Table.JQTable;
import org.h2.jaqu.TableDefinition.FieldDefinition;
import org.h2.jaqu.TableDefinition.IndexDefinition;
import org.h2.jaqu.util.StatementBuilder;
import org.h2.jaqu.util.Utils;
/**
* Class to inspect the contents of a particular table including its indexes.
* This class does the bulk of the work in terms of model generation and model
* validation.
*/
public class TableInspector {
private final static int todoReviewClass = 0;
private String schema;
private String table;
private boolean forceUpperCase;
private Class<? extends java.util.Date> dateClazz;
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, boolean forceUpperCase,
Class<? extends java.util.Date> dateClazz) {
this.schema = schema;
this.table = table;
this.forceUpperCase = forceUpperCase;
this.dateClazz = dateClazz;
}
/**
* 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)
&& info.name.toLowerCase().startsWith("primary")) {
// 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.getClassType(col.type, dateClazz);
col.size = rs.getInt("COLUMN_SIZE");
col.allowNull = rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable;
col.isAutoIncrement = rs.getBoolean("IS_AUTOINCREMENT");
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, col);
}
} finally {
closeSilently(rs);
}
}
/**
* Generates a model (class definition) from this table.
* The model includes indexes, primary keys, default values, maxLengths,
* and allowNull information.
* <p>
* The caller may optionally set a destination package name, whether or not
* ot 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(JQSchema.class.getCanonicalName());
imports.add(JQTable.class.getCanonicalName());
imports.add(JQIndex.class.getCanonicalName());
imports.add(JQColumn.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);
// @JQSchema
if (annotateSchema && !isNullOrEmpty(schema)) {
model.append('@').append(JQSchema.class.getSimpleName());
model.append('(');
AnnotationBuilder ap = new AnnotationBuilder();
ap.addParameter("name", schema);
model.append(ap);
model.append(')').append(eol);
}
// @JQTable
model.append('@').append(JQTable.class.getSimpleName());
model.append('(');
// JQTable annotation parameters
AnnotationBuilder ap = new AnnotationBuilder();
ap.addParameter("name", table);
if (primaryKeys.size() > 1) {
StringBuilder pk = new StringBuilder();
for (String key : primaryKeys) {
pk.append(key).append(' ');
}
pk.trimToSize();
ap.addParameter("primaryKey", pk.toString());
}
// finish @JQTable annotation
model.append(ap);
model.append(')').append(eol);
// @JQIndex
ap = new AnnotationBuilder();
generateIndexAnnotations(ap, "standard", IndexType.STANDARD);
generateIndexAnnotations(ap, "unique", IndexType.UNIQUE);
generateIndexAnnotations(ap, "hash", IndexType.HASH);
generateIndexAnnotations(ap, "uniqueHash", IndexType.UNIQUE_HASH);
if (ap.length() > 0) {
model.append('@').append(JQIndex.class.getSimpleName());
model.append('(');
model.append(ap);
model.append(')').append(eol);
}
// class declaration
String clazzName = ModelUtils.createClassName(table);
model.append(format("public class {0} '{'", clazzName)).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
*/
void generateIndexAnnotations(AnnotationBuilder ap, String parameter, IndexType type) {
List<IndexInspector> list = getIndexes(type);
if (list.size() == 0) {
// no matching indexes
return;
}
if (list.size() == 1) {
ap.addParameter(parameter, list.get(0).getColumnsString());
} else {
List<String> parameters = Utils.newArrayList();
for (IndexInspector index:list) {
parameters.add(index.getColumnsString());
}
ap.addParameter(parameter, parameters);
}
}
/**
* Returns indexes of a specific type from the map.
* <p>
* @param type
* @return
*/
List<IndexInspector> getIndexes(IndexType type) {
List<IndexInspector> list = Utils.newArrayList();
for (IndexInspector index:indexes.values()) {
if (index.type.equals(type)) {
list.add(index);
}
}
return list;
}
/**
* Generates a column field definition with annotations.
* <p>
* @param imports
* @param col
* @param trimStrings
* @return
*/
StatementBuilder generateColumn(Set<String> imports, ColumnInspector col,
boolean trimStrings) {
StatementBuilder sb = new StatementBuilder();
Class<?> clazz = col.clazz;
String column = ModelUtils.createFieldName(col.name.toLowerCase());
sb.append('\t');
if (clazz == null) {
// unsupported type
clazz = Object.class;
sb.append("// unsupported type " + col.type);
} else {
// @JQColumn
imports.add(clazz.getCanonicalName());
sb.append('@').append(JQColumn.class.getSimpleName());
// JQColumn annotation parameters
AnnotationBuilder ap = new AnnotationBuilder();
// JQColumn.name
if (!col.name.equalsIgnoreCase(column)) {
ap.addParameter("name", col.name);
}
// JQColumn.primaryKey
// composite primary keys are annotated on the table
if (col.isPrimaryKey && primaryKeys.size() == 1) {
ap.addParameter("primaryKey=true");
}
// JQColumn.maxLength
if ((clazz == String.class) && (col.size > 0)
&& (col.size < Integer.MAX_VALUE)) {
ap.addParameter("maxLength", col.size);
// JQColumn.trimStrings
if (trimStrings) {
ap.addParameter("trimString=true");
}
} else {
// JQColumn.AutoIncrement
if (col.isAutoIncrement) {
ap.addParameter("autoIncrement=true");
}
}
// JQColumn.allowNull
if (!col.allowNull) {
ap.addParameter("allowNull=false");
}
// JQColumn.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("\tpublic ");
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.
* <p>
* Results are returned as a List&lt;Validation&gt; which includes
* recommendations, warnings, and errors about the model.
* <p>
* The caller may choose to have validate throw an exception on any
* validation ERROR.
* <p>
*
* @param <T>
* @param def
* @param throwError
* @return List&lt;Validation&gt;
*/
<T> List<Validation> validate(TableDefinition<T> def,
boolean throwError) {
List<Validation> 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}(name={1})",
JQSchema.class.getSimpleName(), schema)));
} else if (!schema.equalsIgnoreCase(def.schemaName)) {
remarks.add(ERROR(table, "SCHEMA",
format("@{0}(name={1}) != {2}",
JQSchema.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.
* <p>
* <b>TODO</b>: Complete index validation
* <p>
* @param <T>
* @param remarks
* @param def
* @param index
* @param throwError
*/
<T> void validate(List<Validation> remarks, TableDefinition<T> def,
IndexInspector index, boolean throwError) {
List<IndexDefinition> defIndexes = def.getIndexes(IndexType.STANDARD);
List<IndexInspector> dbIndexes = getIndexes(IndexType.STANDARD);
if (defIndexes.size() > dbIndexes.size()) {
remarks.add(WARN(table, IndexType.STANDARD.name(), "# of Model Indexes > DB Indexes!"));
} else if (defIndexes.size() < dbIndexes.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.
// At this point my head was starting to hurt.
}
/**
* Validates a column against the model's field definition. Checks for
* existence, supported type, type mapping, default value, defined lengths,
* primary key, autoincrement.
* <p>
* @param remarks
* @param fieldDef
* @param throwError
*/
void validate(List<Validation> remarks, FieldDefinition fieldDef,
boolean throwError) {
// unknown field
String field = forceUpperCase ?
fieldDef.columnName.toUpperCase() : fieldDef.columnName;
if (!columns.containsKey(field)) {
// unknown column mapping
remarks.add(ERROR(table, fieldDef,
"Does not exist in database!").throwError(throwError));
return;
}
ColumnInspector col = columns.get(field);
Class<?> fieldClazz = fieldDef.field.getType();
Class<?> jdbcClazz = ModelUtils.getClassType(col.type, dateClazz);
// supported type check
// JaQu maps to VARCHAR for unsupported types.
if (fieldDef.dataType.equals("VARCHAR")
&& (fieldClazz != String.class)) {
remarks.add(ERROR(table, fieldDef,
"JaQu does not currently implement support for "
+ fieldClazz.getName()).throwError(throwError));
}
// number types
if (!fieldClazz.equals(jdbcClazz)) {
if (Number.class.isAssignableFrom(fieldClazz)) {
remarks.add(WARN(table, col,
format("Precision Mismatch: ModelObject={0}, ColumnObject={1}",
fieldClazz.getSimpleName(), jdbcClazz.getSimpleName())));
} else {
if (!Date.class.isAssignableFrom(jdbcClazz)) {
remarks.add(WARN(table, col,
format("Object Mismatch: ModelObject={0}, ColumnObject={1}",
fieldClazz.getSimpleName(), jdbcClazz.getSimpleName())));
}
}
}
// string types
if (fieldClazz == String.class) {
if ((fieldDef.maxLength != col.size)
&& (col.size < Integer.MAX_VALUE)) {
remarks.add(WARN(table, col,
format("{0}.maxLength={1}, ColumnMaxLength={2}",
JQColumn.class.getSimpleName(),
fieldDef.maxLength, col.size)));
}
if (fieldDef.maxLength > 0 && !fieldDef.trimString) {
remarks.add(CONSIDER(table, col,
format("{0}.truncateToMaxLength=true"
+ " will prevent RuntimeExceptions on"
+ " INSERT or UPDATE, but will clip data!",
JQColumn.class.getSimpleName())));
}
}
// numeric autoIncrement
if (fieldDef.isAutoIncrement != col.isAutoIncrement) {
remarks.add(WARN(table, col, format("{0}.isAutoIncrement={1}"
+ " while Column autoIncrement={2}",
JQColumn.class.getSimpleName(), fieldDef.isAutoIncrement,
col.isAutoIncrement)));
}
// last check
// default value...
if (!col.isAutoIncrement && !col.isPrimaryKey) {
// check Model.defaultValue format
if (!ModelUtils.isProperlyFormattedDefaultValue(fieldDef.defaultValue)) {
remarks.add(ERROR(table, col, format("{0}.defaultValue=\"{1}\""
+ " is improperly formatted!",
JQColumn.class.getSimpleName(),
fieldDef.defaultValue)).throwError(throwError));
// next field
return;
}
// compare Model.defaultValue to Column.defaultValue
if (isNullOrEmpty(fieldDef.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}\"",
JQColumn.class.getSimpleName(), col.defaultValue)));
} else if (!isNullOrEmpty(fieldDef.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=\"\"",
JQColumn.class.getSimpleName(), fieldDef.defaultValue)));
} else if (!isNullOrEmpty(fieldDef.defaultValue)
&& !isNullOrEmpty(col.defaultValue)) {
if (!fieldDef.defaultValue.equals(col.defaultValue)) {
// Model.defaultValue != Column.defaultValue
remarks.add(WARN(table, col, format("{0}.defaultValue=\"{1}\""
+ " while Column default=\"{2}\"",
JQColumn.class.getSimpleName(), fieldDef.defaultValue,
col.defaultValue)));
}
}
// sanity check Model.defaultValue literal value
if (!ModelUtils.isValidDefaultValue(fieldDef.field.getType(),
fieldDef.defaultValue)) {
remarks.add(ERROR(table, col,
format("{0}.defaultValue=\"{1}\" is invalid!!",
JQColumn.class.getSimpleName(),
fieldDef.defaultValue)));
}
}
}
/**
* Represents an index as it exists in the database.
*/
public 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"));
}
public String getColumnsString() {
StatementBuilder sb = new StatementBuilder();
for (String col : columns) {
sb.appendExceptFirst(", ");
sb.append(col);
}
return sb.toString().trim();
}
}
/**
* Represents a column as it exists in the database.
*
*/
public static class ColumnInspector implements Comparable<ColumnInspector> {
String name;
String type;
int size;
boolean allowNull;
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(", ");
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('\"');
}
int todoEscape;
flat.append(o.toString().trim());
if (o instanceof String) {
flat.append('\"');
}
}
append(flat);
append(" }");
} else {
if (value instanceof String) {
append('\"');
}
int todoEscape;
append(value.toString().trim());
if (value instanceof String) {
append('\"');
}
}
}
}
}
\ No newline at end of file
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
import org.h2.jaqu.TableDefinition.FieldDefinition;
import org.h2.jaqu.TableInspector.ColumnInspector;
import org.h2.jaqu.util.StringUtils;
/**
* A Validation Remark is a result of running a model validation.
* <p>
* Each remark has a level, associated component (schema, table, column, index),
* and a message.
*
*/
public class Validation {
public static Validation CONSIDER(String table, String type, String message) {
return new Validation(Level.CONSIDER, table, type, message);
}
public static Validation CONSIDER(String table, ColumnInspector col, String message) {
return new Validation(Level.CONSIDER, table, col, message);
}
public static Validation WARN(String table, ColumnInspector col, String message) {
return new Validation(Level.WARN, table, col, message);
}
public static Validation WARN(String table, String type, String message) {
return new Validation(Level.WARN, table, type, message);
}
public static Validation ERROR(String table, ColumnInspector col, String message) {
return new Validation(Level.ERROR, table, col, message);
}
public static Validation ERROR(String table, String type, String message) {
return new Validation(Level.ERROR, table, type, message);
}
public static Validation ERROR(String table, FieldDefinition field, String message) {
return new Validation(Level.ERROR, table, field, message);
}
public static enum Level {
CONSIDER, WARN, ERROR;
}
Level level;
String table;
String fieldType;
String fieldName;
String message;
private Validation(Level level, String table, String type, String message) {
this.level = level;
this.table = table;
this.fieldType = type;
this.fieldName = "";
this.message = message;
}
private Validation(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 Validation(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 Validation throwError(boolean throwOnError) {
if (throwOnError && isError())
throw new RuntimeException(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();
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
import org.h2.jaqu.TableDefinition.FieldDefinition;
import org.h2.jaqu.TableInspector.ColumnInspector;
import org.h2.jaqu.util.StringUtils;
/**
* A Validation Remark is a result of running a model validation.
* <p>
* Each remark has a level, associated component (schema, table, column, index),
* and a message.
*
*/
public class Validation {
private int todoReviewWholeClass;
public static Validation CONSIDER(String table, String type, String message) {
return new Validation(Level.CONSIDER, table, type, message);
}
public static Validation CONSIDER(String table, ColumnInspector col, String message) {
return new Validation(Level.CONSIDER, table, col, message);
}
public static Validation WARN(String table, ColumnInspector col, String message) {
return new Validation(Level.WARN, table, col, message);
}
public static Validation WARN(String table, String type, String message) {
return new Validation(Level.WARN, table, type, message);
}
public static Validation ERROR(String table, ColumnInspector col, String message) {
return new Validation(Level.ERROR, table, col, message);
}
public static Validation ERROR(String table, String type, String message) {
return new Validation(Level.ERROR, table, type, message);
}
public static Validation ERROR(String table, FieldDefinition field, String message) {
return new Validation(Level.ERROR, table, field, message);
}
public static enum Level {
CONSIDER, WARN, ERROR;
}
Level level;
String table;
String fieldType;
String fieldName;
String message;
private Validation(Level level, String table, String type, String message) {
this.level = level;
this.table = table;
this.fieldType = type;
this.fieldName = "";
this.message = message;
}
private Validation(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 Validation(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 Validation throwError(boolean throwOnError) {
if (throwOnError && isError())
throw new RuntimeException(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();
}
}
......@@ -13,6 +13,8 @@ package org.h2.jaqu.util;
*/
public class ClassUtils {
int todoDelete;
private ClassUtils() {
// utility class
}
......@@ -31,5 +33,7 @@ public class ClassUtils {
throw new RuntimeException(e);
}
}
//## Java 1.5 end ##
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu.util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.h2.jaqu.Db;
import org.h2.jaqu.DbInspector;
import org.h2.message.DbException;
/**
* Generates JaQu models.
*/
public class GenerateModels {
/**
* The output stream where this tool writes to.
*/
protected PrintStream out = System.out;
public static void main(String... args) throws SQLException {
new GenerateModels().runTool(args);
}
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 {
Connection conn = null;
try {
org.h2.Driver.load();
conn = DriverManager.getConnection(url, user, password);
Db db = Db.open(url, user, password.toCharArray());
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 DbException.convertIOException(io, "could not generate model").getSQLException();
} finally {
JdbcUtils.closeSilently(conn);
}
}
/**
* 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: java "+getClass().getName());
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>");
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu.util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.h2.jaqu.Db;
import org.h2.jaqu.DbInspector;
import org.h2.message.DbException;
/**
* Generates JaQu models.
*/
public class GenerateModels {
private static final int todoReview = 0;
/**
* The output stream where this tool writes to.
*/
protected PrintStream out = System.out;
public static void main(String... args) throws SQLException {
new GenerateModels().runTool(args);
}
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 {
Connection conn = null;
try {
org.h2.Driver.load();
conn = DriverManager.getConnection(url, user, password);
Db db = Db.open(url, user, password.toCharArray());
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 DbException.convertIOException(io, "could not generate model").getSQLException();
} finally {
JdbcUtils.closeSilently(conn);
}
}
/**
* 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: java "+getClass().getName());
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>");
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jaqu.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.naming.Context;
import javax.sql.DataSource;
import javax.sql.XAConnection;
/**
* This is a utility class with JDBC helper functions.
*/
public class JdbcUtils {
private static final String[] DRIVERS = {
"h2:", "org.h2.Driver",
"Cache:", "com.intersys.jdbc.CacheDriver",
"daffodilDB://", "in.co.daffodil.db.rmi.RmiDaffodilDBDriver",
"daffodil", "in.co.daffodil.db.jdbc.DaffodilDBDriver",
"db2:", "COM.ibm.db2.jdbc.net.DB2Driver",
"derby:net:", "org.apache.derby.jdbc.ClientDriver",
"derby://", "org.apache.derby.jdbc.ClientDriver",
"derby:", "org.apache.derby.jdbc.EmbeddedDriver",
"FrontBase:", "com.frontbase.jdbc.FBJDriver",
"firebirdsql:", "org.firebirdsql.jdbc.FBDriver",
"hsqldb:", "org.hsqldb.jdbcDriver",
"informix-sqli:", "com.informix.jdbc.IfxDriver",
"jtds:", "net.sourceforge.jtds.jdbc.Driver",
"microsoft:", "com.microsoft.jdbc.sqlserver.SQLServerDriver",
"mimer:", "com.mimer.jdbc.Driver",
"mysql:", "com.mysql.jdbc.Driver",
"odbc:", "sun.jdbc.odbc.JdbcOdbcDriver",
"oracle:", "oracle.jdbc.driver.OracleDriver",
"pervasive:", "com.pervasive.jdbc.v2.Driver",
"pointbase:micro:", "com.pointbase.me.jdbc.jdbcDriver",
"pointbase:", "com.pointbase.jdbc.jdbcUniversalDriver",
"postgresql:", "org.postgresql.Driver",
"sybase:", "com.sybase.jdbc3.jdbc.SybDriver",
"sqlserver:", "com.microsoft.sqlserver.jdbc.SQLServerDriver",
"teradata:", "com.ncr.teradata.TeraDriver",
};
private JdbcUtils() {
// utility class
}
/**
* Close a statement without throwing an exception.
*
* @param stat the statement or null
*/
public static void closeSilently(Statement stat) {
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
// ignore
}
}
}
/**
* Close a connection without throwing an exception.
*
* @param conn the connection or null
*/
public static void closeSilently(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
/**
* Close a result set without throwing an exception.
*
* @param rs the result set or null
*/
public static void closeSilently(ResultSet rs) {
closeSilently(rs, false);
}
/**
* Close a result set, and optionally its statement without throwing an
* exception.
*
* @param rs the result set or null
*/
public static void closeSilently(ResultSet rs, boolean closeStatement) {
if (rs != null) {
Statement stat = null;
if (closeStatement) {
try {
stat = rs.getStatement();
} catch (SQLException e) {
//ignore
}
}
try {
rs.close();
} catch (SQLException e) {
// ignore
}
closeSilently(stat);
}
}
/**
* Close an XA connection set without throwing an exception.
*
* @param conn the XA connection or null
*/
//## Java 1.4 begin ##
public static void closeSilently(XAConnection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
//## Java 1.4 end ##
/**
* 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 = ClassUtils.loadClass(driver);
if (java.sql.Driver.class.isAssignableFrom(d)) {
return DriverManager.getConnection(url, prop);
//## Java 1.4 begin ##
} 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 (Exception e) {
throw Message.convert(e);
}
//## Java 1.4 end ##
} 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) {
ClassUtils.loadClass(driver);
}
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jaqu.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.naming.Context;
import javax.sql.DataSource;
import javax.sql.XAConnection;
/**
* This is a utility class with JDBC helper functions.
*/
public class JdbcUtils {
private static final int todoDeleteClass = 0;
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
*/
//## Java 1.4 begin ##
public static void closeSilently(XAConnection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
//## Java 1.4 end ##
/**
* 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 = ClassUtils.loadClass(driver);
if (java.sql.Driver.class.isAssignableFrom(d)) {
return DriverManager.getConnection(url, prop);
//## Java 1.4 begin ##
} 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 (Exception e) {
throw Message.convert(e);
}
//## Java 1.4 end ##
} 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) {
ClassUtils.loadClass(driver);
}
}
}
......@@ -18,6 +18,8 @@ import java.sql.SQLException;
*/
public class Message {
private int todoDelete;
private Message() {
// utility class
}
......
......@@ -30,6 +30,8 @@ package org.h2.jaqu.util;
*/
public class StatementBuilder {
private int todoDelete;
private final StringBuilder builder = new StringBuilder();
private int index;
......@@ -113,11 +115,11 @@ public class StatementBuilder {
builder.append(s);
}
}
public void append(StatementBuilder sb) {
builder.append(sb);
}
public void insert(int offset, char c) {
builder.insert(offset, c);
}
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu.util;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.concurrent.atomic.AtomicLong;
/**
* Utility class to optionally log generated statements to an output stream.<br>
* Default output stream is System.out.<br>
* Statement logging is disabled by default.
* <p>
* This class also tracks the counts for generated statements by major type.
*
*/
public class StatementLogger {
public static boolean logStatements = false;
public static PrintWriter out = new PrintWriter(System.out);
public final static AtomicLong selectCount = new AtomicLong(0);
public final static AtomicLong createCount = new AtomicLong(0);
public final static AtomicLong insertCount = new AtomicLong(0);
public final static AtomicLong updateCount = new AtomicLong(0);
public final static AtomicLong mergeCount = new AtomicLong(0);
public final static AtomicLong deleteCount = new AtomicLong(0);
public static void create(String statement) {
createCount.incrementAndGet();
log(statement);
}
public static void insert(String statement) {
insertCount.incrementAndGet();
log(statement);
}
public static void update(String statement) {
updateCount.incrementAndGet();
log(statement);
}
public static void merge(String statement) {
mergeCount.incrementAndGet();
log(statement);
}
public static void delete(String statement) {
deleteCount.incrementAndGet();
log(statement);
}
public static void select(String statement) {
selectCount.incrementAndGet();
log(statement);
}
private static void log(String statement) {
if (logStatements)
out.println(statement);
}
public static void printStats() {
out.println("JaQu Runtime Stats");
out.println("=======================");
printStat("CREATE", createCount);
printStat("INSERT", insertCount);
printStat("UPDATE", updateCount);
printStat("MERGE", mergeCount);
printStat("DELETE", deleteCount);
printStat("SELECT", selectCount);
}
private static void printStat(String name, AtomicLong value) {
if (value.get() > 0) {
DecimalFormat df = new DecimalFormat("###,###,###,###");
out.println(name + "=" + df.format(createCount.get()));
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu.util;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.concurrent.atomic.AtomicLong;
/**
* Utility class to optionally log generated statements to an output stream.<br>
* Default output stream is System.out.<br>
* Statement logging is disabled by default.
* <p>
* This class also tracks the counts for generated statements by major type.
*
*/
public class StatementLogger {
public static boolean logStatements;
private static PrintWriter out = new PrintWriter(System.out);
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();
public static void create(String statement) {
CREATE_COUNT.incrementAndGet();
log(statement);
}
public static void insert(String statement) {
INSERT_COUNT.incrementAndGet();
log(statement);
}
public static void update(String statement) {
UPDATE_COUNT.incrementAndGet();
log(statement);
}
public static void merge(String statement) {
MERGE_COUNT.incrementAndGet();
log(statement);
}
public static void delete(String statement) {
DELETE_COUNT.incrementAndGet();
log(statement);
}
public static void select(String statement) {
SELECT_COUNT.incrementAndGet();
log(statement);
}
private static void log(String statement) {
if (logStatements) {
out.println(statement);
}
}
public static void printStats() {
out.println("JaQu Runtime Statistics");
out.println("=======================");
printStat("CREATE", CREATE_COUNT);
printStat("INSERT", INSERT_COUNT);
printStat("UPDATE", UPDATE_COUNT);
printStat("MERGE", MERGE_COUNT);
printStat("DELETE", DELETE_COUNT);
printStat("SELECT", SELECT_COUNT);
}
private static void printStat(String name, AtomicLong value) {
if (value.get() > 0) {
DecimalFormat df = new DecimalFormat("###,###,###,###");
out.println(name + "=" + df.format(CREATE_COUNT.get()));
}
}
}
\ No newline at end of file
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jaqu.util;
/**
* Common string utilities. I expect that this class will be removed.
*
*/
public class StringUtils {
/**
* Replace all occurrences of the before string with the after string.
*
* @param s the string
* @param before the old text
* @param after the new text
* @return the string with the before string replaced
*/
public static String replaceAll(String s, String before, String after) {
int next = s.indexOf(before);
if (next < 0) {
return s;
}
StringBuilder buff = new StringBuilder(s.length() - before.length() + after.length());
int index = 0;
while (true) {
buff.append(s.substring(index, next)).append(after);
index = next + before.length();
next = s.indexOf(before, index);
if (next < 0) {
buff.append(s.substring(index));
break;
}
}
return buff.toString();
}
/**
* Check if a String is null or empty (the length is null).
*
* @param s the string to check
* @return true if it is null or empty
*/
public static boolean isNullOrEmpty(String s) {
return s == null || s.length() == 0;
}
/**
* Convert a string to a Java literal using the correct escape sequences.
* The literal is not enclosed in double quotes. The result can be used in
* properties files or in Java source code.
*
* @param s the text to convert
* @return the Java representation
*/
public static String javaEncode(String s) {
int length = s.length();
StringBuilder buff = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
switch (c) {
// case '\b':
// // BS backspace
// // not supported in properties files
// buff.append("\\b");
// break;
case '\t':
// HT horizontal tab
buff.append("\\t");
break;
case '\n':
// LF linefeed
buff.append("\\n");
break;
case '\f':
// FF form feed
buff.append("\\f");
break;
case '\r':
// CR carriage return
buff.append("\\r");
break;
case '"':
// double quote
buff.append("\\\"");
break;
case '\\':
// backslash
buff.append("\\\\");
break;
default:
int ch = c & 0xffff;
if (ch >= ' ' && (ch < 0x80)) {
buff.append(c);
// not supported in properties files
// } else if(ch < 0xff) {
// buff.append("\\");
// // make sure it's three characters (0x200 is octal 1000)
// buff.append(Integer.toOctalString(0x200 |
// ch).substring(1));
} else {
buff.append("\\u");
// make sure it's four characters
buff.append(Integer.toHexString(0x10000 | ch).substring(1));
}
}
}
return buff.toString();
}
/**
* Pad a string. This method is used for the SQL function RPAD and LPAD.
*
* @param string the original string
* @param n the target length
* @param padding the padding string
* @param right true if the padding should be appended at the end
* @return the padded string
*/
public static String pad(String string, int n, String padding, boolean right) {
if (n < 0) {
n = 0;
}
if (n < string.length()) {
return string.substring(0, n);
} else if (n == string.length()) {
return string;
}
char paddingChar;
if (padding == null || padding.length() == 0) {
paddingChar = ' ';
} else {
paddingChar = padding.charAt(0);
}
StringBuilder buff = new StringBuilder(n);
n -= string.length();
if (right) {
buff.append(string);
}
for (int i = 0; i < n; i++) {
buff.append(paddingChar);
}
if (!right) {
buff.append(string);
}
return buff.toString();
}
/**
* Convert a string to a SQL literal. Null is converted to NULL. The text is
* enclosed in single quotes. If there are any special characters, the method
* STRINGDECODE is used.
*
* @param s the text to convert.
* @return the SQL literal
*/
public static String quoteStringSQL(String s) {
if (s == null) {
return "NULL";
}
int length = s.length();
StringBuilder buff = new StringBuilder(length + 2);
buff.append('\'');
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == '\'') {
buff.append(c);
} else if (c < ' ' || c > 127) {
// need to start from the beginning because maybe there was a \
// that was not quoted
return "STRINGDECODE(" + quoteStringSQL(javaEncode(s)) + ")";
}
buff.append(c);
}
buff.append('\'');
return buff.toString();
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jaqu.util;
/**
* Common string utilities. I expect that this class will be removed.
*
*/
public class StringUtils {
private int todoDelete;
/**
* Replace all occurrences of the before string with the after string.
*
* @param s the string
* @param before the old text
* @param after the new text
* @return the string with the before string replaced
*/
public static String replaceAll(String s, String before, String after) {
int next = s.indexOf(before);
if (next < 0) {
return s;
}
StringBuilder buff = new StringBuilder(s.length() - before.length() + after.length());
int index = 0;
while (true) {
buff.append(s.substring(index, next)).append(after);
index = next + before.length();
next = s.indexOf(before, index);
if (next < 0) {
buff.append(s.substring(index));
break;
}
}
return buff.toString();
}
/**
* Check if a String is null or empty (the length is null).
*
* @param s the string to check
* @return true if it is null or empty
*/
public static boolean isNullOrEmpty(String s) {
return s == null || s.length() == 0;
}
/**
* Convert a string to a Java literal using the correct escape sequences.
* The literal is not enclosed in double quotes. The result can be used in
* properties files or in Java source code.
*
* @param s the text to convert
* @return the Java representation
*/
public static String javaEncode(String s) {
int length = s.length();
StringBuilder buff = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
switch (c) {
// case '\b':
// // BS backspace
// // not supported in properties files
// buff.append("\\b");
// break;
case '\t':
// HT horizontal tab
buff.append("\\t");
break;
case '\n':
// LF linefeed
buff.append("\\n");
break;
case '\f':
// FF form feed
buff.append("\\f");
break;
case '\r':
// CR carriage return
buff.append("\\r");
break;
case '"':
// double quote
buff.append("\\\"");
break;
case '\\':
// backslash
buff.append("\\\\");
break;
default:
int ch = c & 0xffff;
if (ch >= ' ' && (ch < 0x80)) {
buff.append(c);
// not supported in properties files
// } else if(ch < 0xff) {
// buff.append("\\");
// // make sure it's three characters (0x200 is octal 1000)
// buff.append(Integer.toOctalString(0x200 |
// ch).substring(1));
} else {
buff.append("\\u");
// make sure it's four characters
buff.append(Integer.toHexString(0x10000 | ch).substring(1));
}
}
}
return buff.toString();
}
/**
* Pad a string. This method is used for the SQL function RPAD and LPAD.
*
* @param string the original string
* @param n the target length
* @param padding the padding string
* @param right true if the padding should be appended at the end
* @return the padded string
*/
public static String pad(String string, int n, String padding, boolean right) {
if (n < 0) {
n = 0;
}
if (n < string.length()) {
return string.substring(0, n);
} else if (n == string.length()) {
return string;
}
char paddingChar;
if (padding == null || padding.length() == 0) {
paddingChar = ' ';
} else {
paddingChar = padding.charAt(0);
}
StringBuilder buff = new StringBuilder(n);
n -= string.length();
if (right) {
buff.append(string);
}
for (int i = 0; i < n; i++) {
buff.append(paddingChar);
}
if (!right) {
buff.append(string);
}
return buff.toString();
}
/**
* Convert a string to a SQL literal. Null is converted to NULL. The text is
* enclosed in single quotes. If there are any special characters, the method
* STRINGDECODE is used.
*
* @param s the text to convert.
* @return the SQL literal
*/
public static String quoteStringSQL(String s) {
if (s == null) {
return "NULL";
}
int length = s.length();
StringBuilder buff = new StringBuilder(length + 2);
buff.append('\'');
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == '\'') {
buff.append(c);
} else if (c < ' ' || c > 127) {
// need to start from the beginning because maybe there was a \
// that was not quoted
return "STRINGDECODE(" + quoteStringSQL(javaEncode(s)) + ")";
}
buff.append(c);
}
buff.append('\'');
return buff.toString();
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论