提交 daa2b0bf authored 作者: Thomas Mueller's avatar Thomas Mueller

Cleanup:

- Replace inner classes with static initializers
上级 c2b7d146
/* /*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0 * Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html). * (http://h2database.com/html/license.html).
* Initial Developer: James Moger * Initial Developer: James Moger
*/ */
package org.h2.jaqu; package org.h2.jaqu;
import static org.h2.jaqu.util.StringUtils.isNullOrEmpty; import static org.h2.jaqu.util.StringUtils.isNullOrEmpty;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.h2.jaqu.TableDefinition.FieldDefinition; import org.h2.jaqu.TableDefinition.FieldDefinition;
/** /**
* Utility methods for models related to type mapping, default value validation, * Utility methods for models related to type mapping, default value validation,
* and class or field name creation. * and class or field name creation.
*/ */
public class ModelUtils { public class ModelUtils {
/** /**
* Returns a SQL type mapping for a Java class. * The list of supported data types. It is used by the runtime mapping for
* * CREATE statements.
* @param field the field to map */
* @param strictTypeMapping throws a RuntimeException if type is unsupported private static final Map<Class<?>, String> SUPPORTED_TYPES = new HashMap<Class<?>, String>();
* @return
*/ static {
public static String getDataType(FieldDefinition fieldDef, boolean strictTypeMapping) { Map<Class<?>, String> m = SUPPORTED_TYPES;
Class<?> fieldClass = fieldDef.field.getType(); m.put(String.class, "VARCHAR");
if (supportedTypes.containsKey(fieldClass)) { m.put(Boolean.class, "BIT");
String sqltype = supportedTypes.get(fieldClass); m.put(Byte.class, "TINYINT");
if (sqltype.equals("VARCHAR") && fieldDef.maxLength <= 0) m.put(Short.class, "SMALLINT");
// Unspecified length strings are TEXT, not VARCHAR m.put(Integer.class, "INT");
return "TEXT"; m.put(Long.class, "BIGINT");
return sqltype; m.put(Float.class, "REAL");
} m.put(Double.class, "DOUBLE");
if (!strictTypeMapping) m.put(BigDecimal.class, "DECIMAL");
return "VARCHAR"; m.put(java.sql.Timestamp.class, "TIMESTAMP");
else m.put(java.util.Date.class, "TIMESTAMP");
throw new RuntimeException("Unsupported type " + fieldClass.getName()); m.put(java.sql.Date.class, "DATE");
} m.put(java.sql.Time.class, "TIME");
// TODO add blobs, binary types, custom types?
@SuppressWarnings("serial") }
// Used by Runtime Mapping for CREATE statements
static Map<Class<?>, String> /**
supportedTypes = new HashMap<Class<?>, String>() { * Marshall SQL type aliases to the list of supported types.
{ * This map is used by Generation and Validation.
put(String.class, "VARCHAR"); */
put(Boolean.class, "BIT"); private static final Map<String, String> SQL_TYPES = new HashMap<String, String>();
put(Byte.class, "TINYINT");
put(Short.class, "SMALLINT"); static {
put(Integer.class, "INT"); Map<String, String> m = SQL_TYPES;
put(Long.class, "BIGINT"); m.put("CHAR", "VARCHAR");
put(Float.class, "REAL"); m.put("CHARACTER", "VARCHAR");
put(Double.class, "DOUBLE"); m.put("NCHAR", "VARCHAR");
put(BigDecimal.class, "DECIMAL"); m.put("VARCHAR_CASESENSITIVE", "VARCHAR");
put(java.sql.Timestamp.class, "TIMESTAMP"); m.put("VARCHAR_IGNORECASE", "VARCHAR");
put(java.util.Date.class, "TIMESTAMP"); m.put("LONGVARCHAR", "VARCHAR");
put(java.sql.Date.class, "DATE"); m.put("VARCHAR2", "VARCHAR");
put(java.sql.Time.class, "TIME"); m.put("NVARCHAR", "VARCHAR");
// TODO add blobs, binary types, custom types? m.put("NVARCHAR2", "VARCHAR");
} m.put("TEXT", "VARCHAR");
}; m.put("NTEXT", "VARCHAR");
m.put("TINYTEXT", "VARCHAR");
m.put("MEDIUMTEXT", "VARCHAR");
/** m.put("LONGTEXT", "VARCHAR");
* Returns the Java class type for a given SQL type. m.put("CLOB", "VARCHAR");
* m.put("NCLOB", "VARCHAR");
* @param sqlType
* @param dateClass the preferred date class (java.util.Date or java.sql.Timestamp) // logic
* @return m.put("BOOL", "BIT");
*/ m.put("BOOLEAN", "BIT");
public static Class<?> getClassType(String sqlType,
Class<? extends java.util.Date> dateClass) { // numberic
sqlType = sqlType.toUpperCase(); m.put("BYTE", "TINYINT");
// FIXME dropping "UNSIGNED" or parts like that. could be trouble. m.put("INT2", "SMALLINT");
sqlType = sqlType.split(" ")[0].trim(); m.put("YEAR", "SMALLINT");
m.put("INTEGER", "INT");
if (sqlTypes.containsKey(sqlType)) m.put("MEDIUMINT", "INT");
// Marshall sqlType to a standard type m.put("INT4", "INT");
sqlType = sqlTypes.get(sqlType); m.put("SIGNED", "INT");
Class<?> mappedClass = null; m.put("INT8", "BIGINT");
for (Class<?> clazz : supportedTypes.keySet()) m.put("IDENTITY", "BIGINT");
if (supportedTypes.get(clazz).equalsIgnoreCase(sqlType)) {
mappedClass = clazz; // decimal
break; m.put("NUMBER", "DECIMAL");
} m.put("DEC", "DECIMAL");
if (mappedClass != null) { m.put("NUMERIC", "DECIMAL");
if (mappedClass.equals(java.util.Date.class) m.put("FLOAT", "DOUBLE");
|| mappedClass.equals(java.sql.Timestamp.class)) m.put("FLOAT4", "DOUBLE");
return dateClass; m.put("FLOAT8", "DOUBLE");
return mappedClass;
} // date
return null; m.put("DATETIME", "TIMESTAMP");
} m.put("SMALLDATETIME", "TIMESTAMP");
}
// Marshall SQL type aliases to the list of supported types. private static final List<String> KEYWORDS = Arrays.asList("abstract", "assert", "boolean", "break", "byte", "case",
// Used by Generation and Validation "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends",
static Map<String, String> sqlTypes = new HashMap<String, String>() { "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface",
{ "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static",
// Strings "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void",
put("CHAR", "VARCHAR"); "volatile", "while", "false", "null", "true");
put("CHARACTER", "VARCHAR");
put("NCHAR", "VARCHAR"); private int todoReviewWholeClass;
put("VARCHAR_CASESENSITIVE", "VARCHAR");
put("VARCHAR_IGNORECASE", "VARCHAR"); /**
put("LONGVARCHAR", "VARCHAR"); * Returns a SQL type mapping for a Java class.
put("VARCHAR2", "VARCHAR"); *
put("NVARCHAR", "VARCHAR"); * @param field the field to map
put("NVARCHAR2", "VARCHAR"); * @param strictTypeMapping throws a RuntimeException if type is unsupported
put("TEXT", "VARCHAR"); * @return
put("NTEXT", "VARCHAR"); */
put("TINYTEXT", "VARCHAR"); public static String getDataType(FieldDefinition fieldDef, boolean strictTypeMapping) {
put("MEDIUMTEXT", "VARCHAR"); Class<?> fieldClass = fieldDef.field.getType();
put("LONGTEXT", "VARCHAR"); if (SUPPORTED_TYPES.containsKey(fieldClass)) {
put("CLOB", "VARCHAR"); String type = SUPPORTED_TYPES.get(fieldClass);
put("NCLOB", "VARCHAR"); if (type.equals("VARCHAR") && fieldDef.maxLength <= 0) {
// Unspecified length strings are TEXT, not VARCHAR
// Logic return "TEXT";
put("BOOL", "BIT"); }
put("BOOLEAN", "BIT"); return type;
}
// Whole Numbers if (!strictTypeMapping) {
put("BYTE", "TINYINT"); return "VARCHAR";
put("INT2", "SMALLINT"); }
put("YEAR", "SMALLINT"); throw new RuntimeException("Unsupported type " + fieldClass.getName());
put("INTEGER", "INT"); }
put("MEDIUMINT", "INT");
put("INT4", "INT"); /**
put("SIGNED", "INT"); * Returns the Java class type for a given SQL type.
put("INT8", "BIGINT"); *
put("IDENTITY", "BIGINT"); * @param sqlType
* @param dateClass the preferred date class (java.util.Date or
// Decimals * java.sql.Timestamp)
put("NUMBER", "DECIMAL"); * @return
put("DEC", "DECIMAL"); */
put("NUMERIC", "DECIMAL"); public static Class<?> getClassType(String sqlType,
put("FLOAT", "DOUBLE"); Class<? extends java.util.Date> dateClass) {
put("FLOAT4", "DOUBLE"); sqlType = sqlType.toUpperCase();
put("FLOAT8", "DOUBLE"); // TODO dropping "UNSIGNED" or parts like that could be trouble
sqlType = sqlType.split(" ")[0].trim();
// Dates
put("DATETIME", "TIMESTAMP"); if (SQL_TYPES.containsKey(sqlType)) {
put("SMALLDATETIME", "TIMESTAMP"); // marshall sqlType to a standard type
} sqlType = SQL_TYPES.get(sqlType);
}; }
Class<?> mappedClazz = null;
for (Class<?> clazz : SUPPORTED_TYPES.keySet()) {
/** if (SUPPORTED_TYPES.get(clazz).equalsIgnoreCase(sqlType)) {
* Tries to create a CamelCase class name from a table. mappedClazz = clazz;
* break;
* @param name }
* @return }
*/ if (mappedClazz != null) {
public static String createClassName(String name) { if (mappedClazz.equals(java.util.Date.class)
String[] chunks = name.split("_"); || mappedClazz.equals(java.sql.Timestamp.class)) {
StringBuilder newName = new StringBuilder(); return dateClass;
for (String chunk : chunks) { }
if (chunk.length() == 0) return mappedClazz;
// leading or trailing _ }
continue; return null;
newName.append(Character.toUpperCase(chunk.charAt(0))); }
newName.append(chunk.substring(1).toLowerCase());
} /**
return newName.toString(); * Tries to create a CamelCase class name from a table.
} *
* @param name
/** * @return
* Ensures that table column names don't collide with Java keywords. */
* public static String createClassName(String name) {
* @param col String[] chunks = name.split("_");
* @return StringBuilder newName = new StringBuilder();
*/ for (String chunk : chunks) {
public static String createFieldName(String col) { if (chunk.length() == 0) {
String cn = col.toLowerCase(); // leading or trailing _
if (keywords.contains(cn)) continue;
cn += "_value"; }
return cn; newName.append(Character.toUpperCase(chunk.charAt(0)));
} newName.append(chunk.substring(1).toLowerCase());
}
@SuppressWarnings("serial") return newName.toString();
static List<String> keywords = new ArrayList<String>() { }
{
add("abstract"); /**
add("assert"); * Ensures that table column names don't collide with Java keywords.
add("boolean"); *
add("break"); * @param col
add("byte"); * @return
add("case"); */
add("catch"); public static String createFieldName(String col) {
add("char"); String cn = col.toLowerCase();
add("class"); if (KEYWORDS.contains(cn)) {
add("const"); cn += "_value";
add("continue"); }
add("default"); return cn;
add("do"); }
add("double");
add("else"); /**
add("enum"); * Checks the formatting of JQColumn.defaultValue()
add("extends"); *
add("final"); * @param defaultValue
add("finally"); * @return
add("float"); */
add("for"); public static boolean isProperlyFormattedDefaultValue(String defaultValue) {
add("goto"); if (isNullOrEmpty(defaultValue)) {
add("if"); return true;
add("implements"); }
add("import"); Pattern literalDefault = Pattern.compile("'.*'");
add("instanceof"); Pattern functionDefault = Pattern.compile("[^'].*[^']");
add("int"); return literalDefault.matcher(defaultValue).matches()
add("interface"); || functionDefault.matcher(defaultValue).matches();
add("long"); }
add("native");
add("new"); /**
add("package"); * Checks to see if the defaultValue matches the Class.
add("private"); *
add("protected"); * @param modelClazz
add("public"); * @param defaultValue
add("return"); * @return
add("short"); */
add("static"); public static boolean isValidDefaultValue(Class<?> modelClazz,
add("strictfp"); String defaultValue) {
add("super");
add("switch"); if (defaultValue == null) {
add("synchronized"); // NULL
add("this"); return true;
add("throw"); }
add("throws"); if (defaultValue.trim().length() == 0) {
add("transient"); // NULL (effectively)
add("try"); return true;
add("void"); }
add("volatile");
add("while"); // TODO H2 single-quotes literal values, which is useful.
add("false"); // MySQL does not single-quote literal values so its hard to
add("null"); // differentiate a FUNCTION/VARIABLE from a literal value.
add("true");
} // function / variable
}; Pattern functionDefault = Pattern.compile("[^'].*[^']");
if (functionDefault.matcher(defaultValue).matches()) {
/** // hard to validate this since its in the database
* Checks the formatting of JQColumn.defaultValue() // assume it is good
* @param defaultValue return true;
* @return }
*/
public static boolean isProperlyFormattedDefaultValue(String defaultValue) { // STRING
if (isNullOrEmpty(defaultValue)) if (modelClazz == String.class) {
return true; Pattern stringDefault = Pattern.compile("'(.|\\n)*'");
Pattern literalDefault = Pattern.compile("'.*'"); return stringDefault.matcher(defaultValue).matches();
Pattern functionDefault = Pattern.compile("[^'].*[^']"); }
return literalDefault.matcher(defaultValue).matches()
|| functionDefault.matcher(defaultValue).matches(); String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}";
} String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}";
/** // TIMESTAMP
* Checks to see if the defaultValue matches the Class. if (modelClazz == java.util.Date.class
* || modelClazz == java.sql.Timestamp.class) {
* @param modelClazz // this may be a little loose....
* @param defaultValue // 00-00-00 00:00:00
* @return // 00/00/00T00:00:00
*/ // 00.00.00T00:00:00
public static boolean isValidDefaultValue(Class<?> modelClazz, Pattern pattern = Pattern.compile("'" + dateRegex + "." + timeRegex + "'");
String defaultValue) { return pattern.matcher(defaultValue).matches();
}
if (defaultValue == null)
// NULL // DATE
return true; if (modelClazz == java.sql.Date.class) {
if (defaultValue.trim().length() == 0) // this may be a little loose....
// NULL (effectively) // 00-00-00
return true; // 00/00/00
// 00.00.00
// FIXME H2 single-quotes literal values. Very Useful. Pattern pattern = Pattern.compile("'" + dateRegex + "'");
// MySQL does not single-quote literal values so its hard to return pattern.matcher(defaultValue).matches();
// differentiate a FUNCTION/VARIABLE from a literal value. }
// Function/Variable // TIME
Pattern functionDefault = Pattern.compile("[^'].*[^']"); if (modelClazz == java.sql.Time.class) {
if (functionDefault.matcher(defaultValue).matches()) // 00:00:00
// Hard to validate this since its in the DB. Assume its good. Pattern pattern = Pattern.compile("'" + timeRegex + "'");
return true; return pattern.matcher(defaultValue).matches();
}
// STRING
if (modelClazz == String.class) { // NUMBER
Pattern stringDefault = Pattern.compile("'(.|\\n)*'"); if (Number.class.isAssignableFrom(modelClazz)) {
return stringDefault.matcher(defaultValue).matches(); // strip single quotes
} String unquoted = defaultValue;
if (unquoted.charAt(0) == '\'') {
String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}"; unquoted = unquoted.substring(1);
String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}"; }
if (unquoted.charAt(unquoted.length() - 1) == '\'') {
// TIMESTAMPs unquoted = unquoted.substring(0, unquoted.length() - 1);
if (modelClazz == java.util.Date.class }
|| modelClazz == java.sql.Timestamp.class) {
// This may be a little loose.... try {
// 00-00-00 00:00:00 // delegate to static valueOf() method to parse string
// 00/00/00T00:00:00 Method m = modelClazz.getMethod("valueOf", String.class);
// 00.00.00T00:00:00 Object o = m.invoke(null, unquoted);
Pattern pattern = Pattern.compile("'" + dateRegex + "." + timeRegex + "'"); } catch (NumberFormatException ex) {
return pattern.matcher(defaultValue).matches(); return false;
} } catch (Throwable t) {
}
// DATE }
if (modelClazz == java.sql.Date.class) { return true;
// This may be a little loose.... }
// 00-00-00 }
// 00/00/00
// 00.00.00
Pattern pattern = Pattern.compile("'" + dateRegex + "'");
return pattern.matcher(defaultValue).matches();
}
// TIME
if (modelClazz == java.sql.Time.class) {
// 00:00:00
Pattern pattern = Pattern.compile("'" + timeRegex + "'");
return pattern.matcher(defaultValue).matches();
}
// NUMBER
if (Number.class.isAssignableFrom(modelClazz)) {
// Strip single quotes
String unquoted = defaultValue;
if (unquoted.charAt(0) == '\'')
unquoted = unquoted.substring(1);
if (unquoted.charAt(unquoted.length() - 1) == '\'')
unquoted = unquoted.substring(0, unquoted.length() - 1);
try {
// Delegate to static valueOf() method to parse string
Method m = modelClazz.getMethod("valueOf", String.class);
Object o = m.invoke(null, unquoted);
} catch (NumberFormatException nex) {
return false;
} catch (Throwable t) {
}
}
return true;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论