You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ModelUtils.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. /*
  2. * Copyright 2004-2011 H2 Group.
  3. * Copyright 2011 James Moger.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package com.iciql;
  18. import com.iciql.TableDefinition.FieldDefinition;
  19. import com.iciql.util.StringUtils;
  20. import java.lang.reflect.Method;
  21. import java.math.BigDecimal;
  22. import java.text.DateFormat;
  23. import java.text.MessageFormat;
  24. import java.text.SimpleDateFormat;
  25. import java.util.Arrays;
  26. import java.util.Date;
  27. import java.util.HashMap;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.UUID;
  31. import java.util.regex.Pattern;
  32. import static com.iciql.util.StringUtils.isNullOrEmpty;
  33. /**
  34. * Utility methods for models related to type mapping, default value validation,
  35. * and class or field name creation.
  36. */
  37. class ModelUtils {
  38. /**
  39. * The list of supported data types. It is used by the runtime mapping for
  40. * CREATE statements.
  41. */
  42. private static final Map<Class<?>, String> SUPPORTED_TYPES = new HashMap<Class<?>, String>();
  43. static {
  44. Map<Class<?>, String> m = SUPPORTED_TYPES;
  45. m.put(String.class, "VARCHAR");
  46. m.put(Boolean.class, "BOOLEAN");
  47. m.put(Byte.class, "TINYINT");
  48. m.put(Short.class, "SMALLINT");
  49. m.put(Integer.class, "INT");
  50. m.put(Long.class, "BIGINT");
  51. m.put(Float.class, "REAL");
  52. m.put(Double.class, "DOUBLE");
  53. m.put(BigDecimal.class, "DECIMAL");
  54. m.put(java.sql.Timestamp.class, "TIMESTAMP");
  55. m.put(java.util.Date.class, "TIMESTAMP");
  56. m.put(java.sql.Date.class, "DATE");
  57. m.put(java.sql.Time.class, "TIME");
  58. m.put(byte[].class, "BLOB");
  59. m.put(UUID.class, "UUID");
  60. // map primitives
  61. m.put(boolean.class, m.get(Boolean.class));
  62. m.put(byte.class, m.get(Byte.class));
  63. m.put(short.class, m.get(Short.class));
  64. m.put(int.class, m.get(Integer.class));
  65. m.put(long.class, m.get(Long.class));
  66. m.put(float.class, m.get(Float.class));
  67. m.put(double.class, m.get(Double.class));
  68. }
  69. /**
  70. * Convert SQL type aliases to the list of supported types. This map is used
  71. * by generation and validation.
  72. */
  73. private static final Map<String, String> SQL_TYPES = new HashMap<String, String>();
  74. static {
  75. Map<String, String> m = SQL_TYPES;
  76. m.put("CHAR", "VARCHAR");
  77. m.put("CHARACTER", "VARCHAR");
  78. m.put("NCHAR", "VARCHAR");
  79. m.put("VARCHAR_CASESENSITIVE", "VARCHAR");
  80. m.put("VARCHAR_IGNORECASE", "VARCHAR");
  81. m.put("LONGVARCHAR", "VARCHAR");
  82. m.put("VARCHAR2", "VARCHAR");
  83. m.put("NVARCHAR", "VARCHAR");
  84. m.put("NVARCHAR2", "VARCHAR");
  85. m.put("TEXT", "VARCHAR");
  86. m.put("NTEXT", "VARCHAR");
  87. m.put("TINYTEXT", "VARCHAR");
  88. m.put("MEDIUMTEXT", "VARCHAR");
  89. m.put("LONGTEXT", "VARCHAR");
  90. m.put("CLOB", "VARCHAR");
  91. m.put("NCLOB", "VARCHAR");
  92. // logic
  93. m.put("BIT", "BOOLEAN");
  94. m.put("BOOL", "BOOLEAN");
  95. // numeric
  96. m.put("BYTE", "TINYINT");
  97. m.put("INT2", "SMALLINT");
  98. m.put("YEAR", "SMALLINT");
  99. m.put("INTEGER", "INT");
  100. m.put("MEDIUMINT", "INT");
  101. m.put("INT4", "INT");
  102. m.put("SIGNED", "INT");
  103. m.put("INT8", "BIGINT");
  104. m.put("IDENTITY", "BIGINT");
  105. m.put("SERIAL", "INT");
  106. m.put("BIGSERIAL", "BIGINT");
  107. // decimal
  108. m.put("NUMBER", "DECIMAL");
  109. m.put("DEC", "DECIMAL");
  110. m.put("NUMERIC", "DECIMAL");
  111. m.put("FLOAT", "DOUBLE");
  112. m.put("FLOAT4", "DOUBLE");
  113. m.put("FLOAT8", "DOUBLE");
  114. m.put("DOUBLE PRECISION", "DOUBLE");
  115. // date
  116. m.put("DATETIME", "TIMESTAMP");
  117. m.put("SMALLDATETIME", "TIMESTAMP");
  118. // binary types
  119. m.put("TINYBLOB", "BLOB");
  120. m.put("MEDIUMBLOB", "BLOB");
  121. m.put("LONGBLOB", "BLOB");
  122. m.put("IMAGE", "BLOB");
  123. m.put("OID", "BLOB");
  124. }
  125. private static final List<String> KEYWORDS = Arrays.asList("abstract", "assert", "boolean", "break",
  126. "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else",
  127. "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import",
  128. "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected",
  129. "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this",
  130. "throw", "throws", "transient", "try", "void", "volatile", "while", "false", "null", "true");
  131. /**
  132. * Returns a SQL type mapping for a Java class.
  133. *
  134. * @param fieldDef the field to map
  135. * @return
  136. */
  137. static String getDataType(FieldDefinition fieldDef) {
  138. Class<?> fieldClass = fieldDef.field.getType();
  139. if (fieldClass.isEnum()) {
  140. switch (fieldDef.enumType) {
  141. case ORDINAL:
  142. return "INT";
  143. case ENUMID:
  144. String sqlType = SUPPORTED_TYPES.get(fieldDef.enumTypeClass);
  145. if (sqlType == null) {
  146. throw new IciqlException("Unsupported enum mapping type {0} for {1}",
  147. fieldDef.enumTypeClass, fieldDef.columnName);
  148. }
  149. return sqlType;
  150. case NAME:
  151. default:
  152. return "VARCHAR";
  153. }
  154. }
  155. if (SUPPORTED_TYPES.containsKey(fieldClass)) {
  156. return SUPPORTED_TYPES.get(fieldClass);
  157. }
  158. throw new IciqlException("Unsupported type " + fieldClass.getName());
  159. }
  160. /**
  161. * Returns the Java class for a given SQL type.
  162. *
  163. * @param sqlType
  164. * @param dateTimeClass the preferred date class (java.util.Date or
  165. * java.sql.Timestamp)
  166. * @return
  167. */
  168. static Class<?> getClassForSqlType(String sqlType, Class<? extends java.util.Date> dateTimeClass) {
  169. sqlType = sqlType.toUpperCase();
  170. // XXX dropping "UNSIGNED" or parts like that could be trouble
  171. sqlType = sqlType.split(" ")[0].trim();
  172. if (sqlType.indexOf('(') > -1) {
  173. // strip out length or precision
  174. sqlType = sqlType.substring(0, sqlType.indexOf('('));
  175. }
  176. if (SQL_TYPES.containsKey(sqlType)) {
  177. // convert the sqlType to a standard type
  178. sqlType = SQL_TYPES.get(sqlType);
  179. }
  180. Class<?> mappedClass = null;
  181. for (Class<?> clazz : SUPPORTED_TYPES.keySet()) {
  182. if (clazz.isPrimitive()) {
  183. // do not map from SQL TYPE to primitive type
  184. continue;
  185. }
  186. if (SUPPORTED_TYPES.get(clazz).equalsIgnoreCase(sqlType)) {
  187. mappedClass = clazz;
  188. break;
  189. }
  190. }
  191. if (mappedClass != null) {
  192. if (mappedClass.equals(java.util.Date.class) || mappedClass.equals(java.sql.Timestamp.class)) {
  193. return dateTimeClass;
  194. }
  195. return mappedClass;
  196. }
  197. return null;
  198. }
  199. /**
  200. * Tries to create a convert a SQL table name to a camel case class name.
  201. *
  202. * @param tableName the SQL table name
  203. * @return the class name
  204. */
  205. static String convertTableToClassName(String tableName) {
  206. String[] chunks = StringUtils.arraySplit(tableName, '_', false);
  207. StringBuilder className = new StringBuilder();
  208. for (String chunk : chunks) {
  209. if (chunk.length() == 0) {
  210. // leading or trailing _
  211. continue;
  212. }
  213. String[] subchunks = StringUtils.arraySplit(chunk, ' ', false);
  214. for (String subchunk : subchunks) {
  215. if (subchunk.length() == 0) {
  216. // leading or trailing space
  217. continue;
  218. }
  219. className.append(Character.toUpperCase(subchunk.charAt(0)));
  220. className.append(subchunk.substring(1).toLowerCase());
  221. }
  222. }
  223. return className.toString();
  224. }
  225. /**
  226. * Ensures that SQL column names don't collide with Java keywords.
  227. *
  228. * @param columnName the column name
  229. * @return the Java field name
  230. */
  231. static String convertColumnToFieldName(String columnName) {
  232. String lower = columnName.toLowerCase();
  233. if (KEYWORDS.contains(lower)) {
  234. lower += "Value";
  235. }
  236. return lower;
  237. }
  238. /**
  239. * Converts a DEFAULT clause value into an object.
  240. *
  241. * @param field definition
  242. * @return object
  243. */
  244. static Object getDefaultValue(FieldDefinition def, Class<? extends Date> dateTimeClass) {
  245. Class<?> valueType = getClassForSqlType(def.dataType, dateTimeClass);
  246. if (String.class.isAssignableFrom(valueType)) {
  247. if (StringUtils.isNullOrEmpty(def.defaultValue)) {
  248. // literal default must be specified within single quotes
  249. return null;
  250. }
  251. if (def.defaultValue.charAt(0) == '\''
  252. && def.defaultValue.charAt(def.defaultValue.length() - 1) == '\'') {
  253. // strip leading and trailing single quotes
  254. return def.defaultValue.substring(1, def.defaultValue.length() - 1).trim();
  255. }
  256. return def.defaultValue;
  257. }
  258. if (StringUtils.isNullOrEmpty(def.defaultValue)) {
  259. // can not create object from empty string
  260. return null;
  261. }
  262. // strip leading and trailing single quotes
  263. String content = def.defaultValue;
  264. if (content.charAt(0) == '\'') {
  265. content = content.substring(1);
  266. }
  267. if (content.charAt(content.length() - 1) == '\'') {
  268. content = content.substring(0, content.length() - 2);
  269. }
  270. if (StringUtils.isNullOrEmpty(content)) {
  271. // can not create object from empty string
  272. return null;
  273. }
  274. if (Boolean.class.isAssignableFrom(valueType) || boolean.class.isAssignableFrom(valueType)) {
  275. return Boolean.parseBoolean(content);
  276. }
  277. if (Number.class.isAssignableFrom(valueType)) {
  278. try {
  279. // delegate to static valueOf() method to parse string
  280. Method m = valueType.getMethod("valueOf", String.class);
  281. return m.invoke(null, content);
  282. } catch (NumberFormatException e) {
  283. throw new IciqlException(e, "Failed to parse {0} as a number!", def.defaultValue);
  284. } catch (Throwable t) {
  285. }
  286. }
  287. String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}";
  288. String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}";
  289. if (java.sql.Date.class.isAssignableFrom(valueType)) {
  290. // this may be a little loose....
  291. // 00-00-00
  292. // 00/00/00
  293. // 00.00.00
  294. Pattern pattern = Pattern.compile(dateRegex);
  295. if (pattern.matcher(content).matches()) {
  296. DateFormat df = DateFormat.getDateInstance();
  297. try {
  298. return df.parse(content);
  299. } catch (Exception e) {
  300. throw new IciqlException(e, "Failed to parse {0} as a date!", def.defaultValue);
  301. }
  302. }
  303. }
  304. if (java.sql.Time.class.isAssignableFrom(valueType)) {
  305. // 00:00:00
  306. Pattern pattern = Pattern.compile(timeRegex);
  307. if (pattern.matcher(content).matches()) {
  308. DateFormat df = DateFormat.getTimeInstance();
  309. try {
  310. return df.parse(content);
  311. } catch (Exception e) {
  312. throw new IciqlException(e, "Failed to parse {0} as a time!", def.defaultValue);
  313. }
  314. }
  315. }
  316. if (java.util.Date.class.isAssignableFrom(valueType)) {
  317. // this may be a little loose....
  318. // 00-00-00 00:00:00
  319. // 00/00/00T00:00:00
  320. // 00.00.00T00:00:00
  321. Pattern pattern = Pattern.compile(dateRegex + "." + timeRegex);
  322. if (pattern.matcher(content).matches()) {
  323. DateFormat df = DateFormat.getDateTimeInstance();
  324. try {
  325. return df.parse(content);
  326. } catch (Exception e) {
  327. throw new IciqlException(e, "Failed to parse {0} as a datetimestamp!", def.defaultValue);
  328. }
  329. }
  330. }
  331. return content;
  332. }
  333. /**
  334. * Converts the object into a DEFAULT clause value.
  335. *
  336. * @param o the default object
  337. * @return the value formatted for a DEFAULT clause
  338. */
  339. static String formatDefaultValue(Object o) {
  340. Class<?> objectClass = o.getClass();
  341. String value = null;
  342. if (Number.class.isAssignableFrom(objectClass)) {
  343. // NUMBER
  344. return ((Number) o).toString();
  345. } else if (Boolean.class.isAssignableFrom(objectClass)) {
  346. // BOOLEAN
  347. return o.toString();
  348. } else if (java.sql.Date.class.isAssignableFrom(objectClass)) {
  349. // DATE
  350. value = new SimpleDateFormat("yyyy-MM-dd").format((Date) o);
  351. } else if (java.sql.Time.class.isAssignableFrom(objectClass)) {
  352. // TIME
  353. value = new SimpleDateFormat("HH:mm:ss").format((Date) o);
  354. } else if (Date.class.isAssignableFrom(objectClass)) {
  355. // DATETIME
  356. value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) o);
  357. } else if (String.class.isAssignableFrom(objectClass)) {
  358. // STRING
  359. value = o.toString();
  360. }
  361. if (value == null) {
  362. return "''";
  363. }
  364. return MessageFormat.format("''{0}''", value);
  365. }
  366. /**
  367. * Checks the formatting of IQColumn.defaultValue().
  368. *
  369. * @param defaultValue the default value
  370. * @return true if it is
  371. */
  372. static boolean isProperlyFormattedDefaultValue(String defaultValue) {
  373. if (isNullOrEmpty(defaultValue)) {
  374. return true;
  375. }
  376. Pattern literalDefault = Pattern.compile("'.*'");
  377. Pattern functionDefault = Pattern.compile("[^'].*[^']");
  378. return literalDefault.matcher(defaultValue).matches()
  379. || functionDefault.matcher(defaultValue).matches();
  380. }
  381. /**
  382. * Checks to see if the default value matches the class.
  383. *
  384. * @param modelClass the class
  385. * @param defaultValue the value
  386. * @return true if it does
  387. */
  388. static boolean isValidDefaultValue(Class<?> modelClass, String defaultValue) {
  389. if (defaultValue == null) {
  390. // NULL
  391. return true;
  392. }
  393. if (defaultValue.trim().length() == 0) {
  394. // NULL (effectively)
  395. return true;
  396. }
  397. // function / variable
  398. Pattern functionDefault = Pattern.compile("[^'].*[^']");
  399. if (functionDefault.matcher(defaultValue).matches()) {
  400. // hard to validate this since its in the database
  401. // assume it is good
  402. return true;
  403. }
  404. // STRING
  405. if (modelClass == String.class) {
  406. Pattern stringDefault = Pattern.compile("'(.|\\n)*'");
  407. return stringDefault.matcher(defaultValue).matches();
  408. }
  409. String dateRegex = "[0-9]{1,4}[-/\\.][0-9]{1,2}[-/\\.][0-9]{1,2}";
  410. String timeRegex = "[0-2]{1}[0-9]{1}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}";
  411. // TIMESTAMP
  412. if (modelClass == java.util.Date.class || modelClass == java.sql.Timestamp.class) {
  413. // this may be a little loose....
  414. // 00-00-00 00:00:00
  415. // 00/00/00T00:00:00
  416. // 00.00.00T00:00:00
  417. Pattern pattern = Pattern.compile("'" + dateRegex + "." + timeRegex + "'");
  418. return pattern.matcher(defaultValue).matches();
  419. }
  420. // DATE
  421. if (modelClass == java.sql.Date.class) {
  422. // this may be a little loose....
  423. // 00-00-00
  424. // 00/00/00
  425. // 00.00.00
  426. Pattern pattern = Pattern.compile("'" + dateRegex + "'");
  427. return pattern.matcher(defaultValue).matches();
  428. }
  429. // TIME
  430. if (modelClass == java.sql.Time.class) {
  431. // 00:00:00
  432. Pattern pattern = Pattern.compile("'" + timeRegex + "'");
  433. return pattern.matcher(defaultValue).matches();
  434. }
  435. // NUMBER
  436. if (Number.class.isAssignableFrom(modelClass)) {
  437. // strip single quotes
  438. String unquoted = defaultValue;
  439. if (unquoted.charAt(0) == '\'') {
  440. unquoted = unquoted.substring(1);
  441. }
  442. if (unquoted.charAt(unquoted.length() - 1) == '\'') {
  443. unquoted = unquoted.substring(0, unquoted.length() - 1);
  444. }
  445. try {
  446. // delegate to static valueOf() method to parse string
  447. Method m = modelClass.getMethod("valueOf", String.class);
  448. m.invoke(null, unquoted);
  449. } catch (NumberFormatException ex) {
  450. return false;
  451. } catch (Throwable t) {
  452. }
  453. }
  454. return true;
  455. }
  456. }