\r
<arg value="--substitute" />\r
<arg value="%ENDCODE%=</pre>" />\r
+ \r
+ <arg value="--regex" />\r
+ <arg value=""\b(issue)(\s*[#]?|-){0,1}(\d+)\b!!!<a href='http://code.google.com/p/iciql/issues/detail?id=$3'>issue $3</a>"" />\r
+\r
</java> \r
</target>\r
\r
<tr><td>java.util.Date</td>\r
<td>TIMESTAMP</td></tr>\r
\r
+<tr><td>byte []</td>\r
+<td>BLOB</td></tr>\r
+\r
+<tr><td>java.lang.Enum</td>\r
+<td>VARCHAR/TEXT *@IQEnum(STRING)* or INT *@IQEnum(ORDINAL)*</td></tr>\r
+\r
</table>\r
\r
**NOTE:**<br/>\r
\r
### Unsupported Types\r
- Java primitives (use their object counterparts instead)\r
-- binary types (BLOB, etc)\r
- array types\r
- custom types\r
\r
\r
### Example Annotated Model\r
%BEGINCODE%\r
+import com.iciql.Iciql.EnumType;\r
import com.iciql.Iciql.IQColumn;\r
+import com.iciql.Iciql.IQEnum;\r
import com.iciql.Iciql.IQIndex;\r
import com.iciql.Iciql.IQTable;\r
\r
})\r
public class Product {\r
\r
+ @IQEnum(EnumType.ORDINAL)\r
+ public enum Availability {\r
+ ACTIVE, DISCONTINUED;\r
+ }\r
+\r
@IQColumn(primaryKey = true)\r
public Integer productId;\r
\r
\r
@IQColumn\r
private Integer reorderQuantity;\r
+ \r
+ @IQColumn\r
+ private Availability availability;\r
\r
public Product() {\r
// default constructor\r
### Current Release\r
**%VERSION%** ([zip](http://code.google.com/p/iciql/downloads/detail?name=%ZIP%)|[jar](http://code.google.com/p/iciql/downloads/detail?name=%JAR%)) *released %BUILDDATE%*\r
\r
+- added BLOB support (issue 1)\r
+- added java.lang.Enum support (issue 2)\r
- api change release (API v2)\r
- annotations overhaul to reduce verbosity\r
- @IQSchema(name="public") -> @IQSchema("public")\r
* <td>java.util.Date</td>\r
* <td>TIMESTAMP</td>\r
* </tr>\r
+ * <tr>\r
+ * <td>byte []</td>\r
+ * <td>BLOB</td>\r
+ * </tr>\r
* </table>\r
* <p>\r
- * Unsupported data types: binary types (BLOB, etc), and custom types.\r
+ * Unsupported data types: java.lang.Enum, Array Types, and custom types.\r
* <p>\r
* Table and field mapping: by default, the mapped table name is the class name\r
* and the public fields are reflectively mapped, by their name, to columns. As\r
/**\r
* An annotation for an iciql version.\r
* <p>\r
+ * \r
* @IQVersion(1)\r
*/\r
@Retention(RetentionPolicy.RUNTIME)\r
/**\r
* An annotation for a schema.\r
* <p>\r
+ * \r
* @IQSchema("PUBLIC")\r
*/\r
@Retention(RetentionPolicy.RUNTIME)\r
* <li>@IQIndex("name")\r
* <li>@IQIndex({"street", "city"})\r
* <li>@IQIndex(name="streetidx", value={"street", "city"})\r
- * <li>@IQIndex(name="addressidx", type=IndexType.UNIQUE, value={"house_number", "street", "city"})\r
+ * <li>@IQIndex(name="addressidx", type=IndexType.UNIQUE,\r
+ * value={"house_number", "street", "city"})\r
* </ul>\r
*/\r
@Retention(RetentionPolicy.RUNTIME)\r
\r
}\r
\r
+ /**\r
+ * Enumeration representing now to map a java.lang.Enum to a column.\r
+ * <p>\r
+ * <ul>\r
+ * <li>STRING\r
+ * <li>ORDINAL\r
+ * </ul>\r
+ */\r
+ public enum EnumType {\r
+ STRING, ORDINAL;\r
+ }\r
+\r
+ /**\r
+ * Annotation to define how a java.lang.Enum is mapped to a column.\r
+ * <p>\r
+ * This annotation can be used on:\r
+ * <ul>\r
+ * <li>a field instance of an enumeration type\r
+ * <li>on the enumeration class declaration\r
+ * </ul>\r
+ * If you choose to annotate the class declaration, that will be the default\r
+ * mapping strategy for all @IQColumn instances of the enum. This can still\r
+ * be overridden for an individual field by specifying the IQEnum\r
+ * annotation.\r
+ * <p>\r
+ * The default mapping is by STRING.\r
+ * \r
+ * <pre>\r
+ * IQEnum(EnumType.STRING)\r
+ * </pre>\r
+ * \r
+ * A string mapping will generate either a VARCHAR, if IQColumn.maxLength >\r
+ * 0 or a TEXT column if IQColumn.maxLength == 0\r
+ * \r
+ */\r
+ @Retention(RetentionPolicy.RUNTIME)\r
+ @Target({ ElementType.FIELD, ElementType.TYPE })\r
+ public @interface IQEnum {\r
+ EnumType value() default EnumType.STRING;\r
+ }\r
+\r
/**\r
* This method is called to let the table define the primary key, indexes,\r
* and the table name.\r
m.put(java.util.Date.class, "TIMESTAMP");
m.put(java.sql.Date.class, "DATE");
m.put(java.sql.Time.class, "TIME");
- // TODO add blobs, binary types, custom types?
+ m.put(byte[].class, "BLOB");
}
/**
// date
m.put("DATETIME", "TIMESTAMP");
m.put("SMALLDATETIME", "TIMESTAMP");
+
+ // binary types
+ m.put("TINYBLOB", "BLOB");
+ m.put("MEDIUMBLOB", "BLOB");
+ m.put("LONGBLOB", "BLOB");
+ m.put("IMAGE", "BLOB");
+ m.put("OID", "BLOB");
}
- private static final List<String> KEYWORDS = Arrays.asList("abstract", "assert", "boolean", "break", "byte",
- "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum",
- "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int",
- "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short",
- "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try",
- "void", "volatile", "while", "false", "null", "true");
+ private static final List<String> KEYWORDS = Arrays.asList("abstract", "assert", "boolean", "break",
+ "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else",
+ "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import",
+ "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected",
+ "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this",
+ "throw", "throws", "transient", "try", "void", "volatile", "while", "false", "null", "true");
/**
* Returns a SQL type mapping for a Java class.
*/
static String getDataType(FieldDefinition fieldDef, boolean strictTypeMapping) {
Class<?> fieldClass = fieldDef.field.getType();
+ if (fieldClass.isEnum()) {
+ if (fieldDef.enumType == null) {
+ throw new IciqlException(fieldDef.field.getName() + " enum field does not specify @IQEnum!");
+ }
+ switch (fieldDef.enumType) {
+ case STRING:
+ if (fieldDef.maxLength <= 0) {
+ return "TEXT";
+ }
+ return "VARCHAR";
+ case ORDINAL:
+ return "INT";
+ }
+ }
if (SUPPORTED_TYPES.containsKey(fieldClass)) {
String type = SUPPORTED_TYPES.get(fieldClass);
if (type.equals("VARCHAR") && fieldDef.maxLength <= 0) {
}
Pattern literalDefault = Pattern.compile("'.*'");
Pattern functionDefault = Pattern.compile("[^'].*[^']");
- return literalDefault.matcher(defaultValue).matches() || functionDefault.matcher(defaultValue).matches();
+ return literalDefault.matcher(defaultValue).matches()
+ || functionDefault.matcher(defaultValue).matches();
}
/**
package com.iciql;\r
\r
import java.lang.reflect.Field;\r
+import java.sql.Blob;\r
import java.sql.Clob;\r
import java.sql.ResultSet;\r
import java.sql.SQLException;\r
Object o = rs.getObject(1);\r
if (Clob.class.isAssignableFrom(o.getClass())) {\r
value = (X) Utils.convert(o, String.class);\r
+ } else if (Blob.class.isAssignableFrom(o.getClass())) {\r
+ value = (X) Utils.convert(o, byte[].class);\r
} else {\r
value = (X) o;\r
}\r
import java.util.List;\r
import java.util.Map;\r
\r
+import com.iciql.Iciql.EnumType;\r
import com.iciql.Iciql.IQColumn;\r
+import com.iciql.Iciql.IQEnum;\r
import com.iciql.Iciql.IQIndex;\r
import com.iciql.Iciql.IQIndexes;\r
import com.iciql.Iciql.IQSchema;\r
boolean trimString;\r
boolean allowNull;\r
String defaultValue;\r
+ EnumType enumType;\r
\r
Object getValue(Object obj) {\r
try {\r
int maxLength = 0;\r
boolean trimString = false;\r
boolean allowNull = true;\r
+ EnumType enumType = null;\r
String defaultValue = "";\r
boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class);\r
if (hasAnnotation) {\r
allowNull = col.allowNull();\r
defaultValue = col.defaultValue();\r
}\r
+\r
+ // configure Java -> SQL enum mapping\r
+ if (f.getType().isEnum()) {\r
+ if (f.getType().isAnnotationPresent(IQEnum.class)) {\r
+ // enum definition is annotated for all instances\r
+ IQEnum iqenum = f.getType().getAnnotation(IQEnum.class);\r
+ enumType = iqenum.value();\r
+ }\r
+ if (f.isAnnotationPresent(IQEnum.class)) {\r
+ // this instance of the enum is annotated\r
+ IQEnum iqenum = f.getAnnotation(IQEnum.class);\r
+ enumType = iqenum.value();\r
+ }\r
+ }\r
+\r
boolean isPublic = Modifier.isPublic(f.getModifiers());\r
boolean reflectiveMatch = isPublic && !byAnnotationsOnly;\r
if (reflectiveMatch || hasAnnotation) {\r
fieldDef.trimString = trimString;\r
fieldDef.allowNull = allowNull;\r
fieldDef.defaultValue = defaultValue;\r
+ fieldDef.enumType = enumType;\r
fieldDef.dataType = ModelUtils.getDataType(fieldDef, strictTypeMapping);\r
fields.add(fieldDef);\r
}\r
}\r
\r
/**\r
- * Optionally truncates strings to the maximum length\r
+ * Optionally truncates strings to the maximum length and converts\r
+ * java.lang.Enum types to Strings or Integers.\r
*/\r
private Object getValue(Object obj, FieldDefinition field) {\r
Object value = field.getValue(obj);\r
+ if (field.enumType != null) {\r
+ // convert enumeration to INT or STRING\r
+ Enum<?> iqenum = (Enum<?>) value;\r
+ switch (field.enumType) {\r
+ case STRING:\r
+ if (field.trimString && field.maxLength > 0) {\r
+ if (iqenum.name().length() > field.maxLength) {\r
+ return iqenum.name().substring(0, field.maxLength);\r
+ }\r
+ }\r
+ return iqenum.name();\r
+ case ORDINAL:\r
+ return iqenum.ordinal(); \r
+ }\r
+ }\r
if (field.trimString && field.maxLength > 0) {\r
if (value instanceof String) {\r
// clip strings\r
clazz = Object.class;
sb.append("// unsupported type " + col.type);
} else {
+ // Imports
+ // don't import byte []
+ if (!clazz.equals(byte[].class)) {
+ imports.add(clazz.getCanonicalName());
+ }
// @IQColumn
- imports.add(clazz.getCanonicalName());
sb.append('@').append(IQColumn.class.getSimpleName());
// IQColumn annotation parameters
String[] kv = token.split("=", 2);\r
content = content.replace(kv[0], kv[1]);\r
}\r
+ for (String token : params.regex) {\r
+ String[] kv = token.split("!!!", 2);\r
+ content = content.replaceAll(kv[0], kv[1]);\r
+ }\r
\r
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File(destinationFolder,\r
fileName)), Charset.forName("UTF-8"));\r
\r
@Parameter(names = { "--nomarkdown" }, description = "%STARTTOKEN%:%ENDTOKEN%", required = false)\r
public List<String> nomarkdown = new ArrayList<String>();\r
+ \r
+ @Parameter(names = { "--regex" }, description = "searchPattern!!!replacePattern", required = false)\r
+ public List<String> regex = new ArrayList<String>();\r
\r
}\r
}\r
\r
package com.iciql.util;\r
\r
+import java.io.ByteArrayOutputStream;\r
import java.io.IOException;\r
+import java.io.InputStream;\r
import java.io.Reader;\r
import java.io.StringWriter;\r
import java.lang.reflect.Constructor;\r
import java.math.BigDecimal;\r
import java.math.BigInteger;\r
+import java.sql.Blob;\r
import java.sql.Clob;\r
import java.util.ArrayList;\r
import java.util.Collection;\r
return (T) new java.sql.Timestamp(COUNTER.getAndIncrement());\r
} else if (clazz == java.util.Date.class) {\r
return (T) new java.util.Date(COUNTER.getAndIncrement());\r
+ } else if (clazz == byte[].class) {\r
+ return (T) new byte[0];\r
+ } else if (clazz.isEnum()) {\r
+ // enums can not be instantiated reflectively\r
+ // return first constant as reference\r
+ return clazz.getEnumConstants()[0];\r
} else if (clazz == List.class) {\r
return (T) new ArrayList();\r
}\r
if (targetType.isAssignableFrom(currentType)) {\r
return o;\r
}\r
+ // convert enum\r
+ if (targetType.isEnum()) {\r
+ return convertEnum(o, targetType);\r
+ }\r
+ // convert from CLOB/TEXT/VARCHAR to String\r
if (targetType == String.class) {\r
if (Clob.class.isAssignableFrom(currentType)) {\r
Clob c = (Clob) o;\r
}\r
return o.toString();\r
}\r
+\r
+ // convert from number to number\r
if (Number.class.isAssignableFrom(currentType)) {\r
Number n = (Number) o;\r
if (targetType == Byte.class) {\r
return n.floatValue();\r
}\r
}\r
+\r
+ // convert from BLOB\r
+ if (targetType == byte[].class) {\r
+ if (Blob.class.isAssignableFrom(currentType)) {\r
+ Blob b = (Blob) o;\r
+ try {\r
+ InputStream is = b.getBinaryStream();\r
+ return readBlobAndClose(is, -1);\r
+ } catch (Exception e) {\r
+ throw new IciqlException("Error converting BLOB to byte[]: " + e.toString(), e);\r
+ }\r
+ }\r
+ }\r
+\r
+ throw new IciqlException("Can not convert the value " + o + " from " + currentType + " to "\r
+ + targetType);\r
+ }\r
+ \r
+ private static Object convertEnum(Object o, Class<?> targetType) {\r
+ if (o == null) {\r
+ return null;\r
+ }\r
+ Class<?> currentType = o.getClass();\r
+ // convert from VARCHAR/TEXT/INT to Enum\r
+ Enum<?>[] values = (Enum[]) targetType.getEnumConstants();\r
+ if (Clob.class.isAssignableFrom(currentType)) {\r
+ // TEXT/CLOB field\r
+ Clob c = (Clob) o;\r
+ String name = null;\r
+ try {\r
+ Reader r = c.getCharacterStream();\r
+ name = readStringAndClose(r, -1);\r
+ } catch (Exception e) {\r
+ throw new IciqlException("Error converting CLOB to String: " + e.toString(), e);\r
+ }\r
+\r
+ // find name match\r
+ for (Enum<?> value : values) {\r
+ if (value.name().equalsIgnoreCase(name)) {\r
+ return value;\r
+ }\r
+ }\r
+ } else if (String.class.isAssignableFrom(currentType)) {\r
+ // VARCHAR field\r
+ String name = (String) o;\r
+ for (Enum<?> value : values) {\r
+ if (value.name().equalsIgnoreCase(name)) {\r
+ return value;\r
+ }\r
+ }\r
+ } else if (Number.class.isAssignableFrom(currentType)) {\r
+ // INT field\r
+ int n = ((Number) o).intValue();\r
+\r
+ // ORDINAL mapping\r
+ for (Enum<?> value : values) {\r
+ if (value.ordinal() == n) {\r
+ return value;\r
+ }\r
+ }\r
+ }\r
throw new IciqlException("Can not convert the value " + o + " from " + currentType + " to "\r
+ targetType);\r
}\r
in.close();\r
}\r
}\r
+\r
+ /**\r
+ * Read a number of bytes from a stream and close it.\r
+ * \r
+ * @param in\r
+ * the stream\r
+ * @param length\r
+ * the maximum number of bytes to read, or -1 to read until the\r
+ * end of file\r
+ * @return the string read\r
+ */\r
+ public static byte[] readBlobAndClose(InputStream in, int length) throws IOException {\r
+ try {\r
+ if (length <= 0) {\r
+ length = Integer.MAX_VALUE;\r
+ }\r
+ int block = Math.min(BUFFER_BLOCK_SIZE, length);\r
+ ByteArrayOutputStream out = new ByteArrayOutputStream(length == Integer.MAX_VALUE ? block\r
+ : length);\r
+ byte[] buff = new byte[block];\r
+ while (length > 0) {\r
+ int len = Math.min(block, length);\r
+ len = in.read(buff, 0, len);\r
+ if (len < 0) {\r
+ break;\r
+ }\r
+ out.write(buff, 0, len);\r
+ length -= len;\r
+ }\r
+ return out.toByteArray();\r
+ } finally {\r
+ in.close();\r
+ }\r
+ }\r
}\r
true);
assertEquals(1, models.size());
// a poor test, but a start
- assertEquals(1564, models.get(0).length());
+ assertEquals(1838, models.get(0).length());
}
@Test
*/
@IQTable(name = "AnnotatedProduct", primaryKey = "id")
-@IQIndexes({ @IQIndex({ "name", "cat" }),
- @IQIndex(name = "nameidx", type = IndexType.HASH, value = "name") })
+@IQIndexes({ @IQIndex({ "name", "cat" }), @IQIndex(name = "nameidx", type = IndexType.HASH, value = "name") })
public class ProductAnnotationOnly {
@IQColumn(autoIncrement = true)
import java.util.List;
import java.util.Random;
+import com.iciql.Iciql.EnumType;
import com.iciql.Iciql.IQColumn;
+import com.iciql.Iciql.IQEnum;
import com.iciql.Iciql.IQIndex;
import com.iciql.Iciql.IQIndexes;
import com.iciql.Iciql.IQTable;
public static final SupportedTypes SAMPLE = new SupportedTypes();
+ /**
+ * Test of plain enumeration.
+ *
+ * Each field declaraton of this enum must specify a mapping strategy.
+ */
+ public enum Flower {
+ ROSE, TULIP, MUM, PETUNIA, MARIGOLD, DAFFODIL;
+ }
+
+ /**
+ * Test of @IQEnum annotated enumeration.
+ * This strategy is the default strategy for all fields of the Tree enum.
+ *
+ * Individual Tree field declarations can override this strategy by
+ * specifying a different @IQEnum annotation.
+ *
+ * Here ORDINAL specifies that this enum will be mapped to an INT column.
+ */
+ @IQEnum(EnumType.ORDINAL)
+ public enum Tree {
+ PINE, OAK, BIRCH, WALNUT, MAPLE;
+ }
+
@IQColumn(primaryKey = true, autoIncrement = true)
public Integer id;
@IQColumn
private java.sql.Timestamp mySqlTimestamp;
+ @IQColumn
+ private byte[] myBlob;
+
+ @IQEnum(EnumType.STRING)
+ @IQColumn(trimString = true, maxLength = 25)
+ private Flower myFavoriteFlower;
+
+ @IQEnum(EnumType.ORDINAL)
+ @IQColumn
+ private Flower myOtherFavoriteFlower;
+
+ @IQColumn(maxLength = 25)
+ // @IQEnum is set on the enumeration definition and is shared
+ // by all uses of Tree as an @IQColumn
+ private Tree myFavoriteTree;
+
public static List<SupportedTypes> createList() {
List<SupportedTypes> list = Utils.newArrayList();
for (int i = 0; i < 10; i++) {
s.mySqlDate = new java.sql.Date(rand.nextLong());
s.mySqlTime = new java.sql.Time(rand.nextLong());
s.mySqlTimestamp = new java.sql.Timestamp(rand.nextLong());
+ s.myBlob = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ s.myFavoriteFlower = Flower.MUM;
+ s.myOtherFavoriteFlower = Flower.MARIGOLD;
+ s.myFavoriteTree = Tree.BIRCH;
return s;
}
same &= mySqlDate.toString().equals(s.mySqlDate.toString());
same &= mySqlTime.toString().equals(s.mySqlTime.toString());
same &= myString.equals(s.myString);
+ same &= compare(myBlob, s.myBlob);
+ same &= myFavoriteFlower.equals(s.myFavoriteFlower);
+ same &= myOtherFavoriteFlower.equals(s.myOtherFavoriteFlower);
+ same &= myFavoriteTree.equals(s.myFavoriteTree);
return same;
}
+ private boolean compare(byte[] a, byte[] b) {
+ if (b == null) {
+ return false;
+ }
+ if (a.length != b.length) {
+ return false;
+ }
+ for (int i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* This class demonstrates the table upgrade.
*/