package ome.tools.hibernate; import java.sql.Array; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import java.util.Map; /** * Hibernate type to store a java array using SQL ARRAY. * * @author Sylvain * * References : http://forum.hibernate.org/viewtopic.php?t=946973 * http://archives.postgresql.org/pgsql-jdbc/2003-02/msg00141.php */ public class SqlArray<T> implements Array { public final static ListAsSQLArrayUserType.ArrayFactory FACTORY = new ListAsSQLArrayUserType.ArrayFactory() { public Array BOOLEAN(Connection conn, List<Boolean> value) { return new BOOLEAN(value); } public Array DATE(Connection conn, List<Date> value) { return new DATE(value); } public Array DOUBLE(Connection conn, List<Double> value) { return new DOUBLE(value); } public Array FLOAT(Connection conn, List<Float> value) { return new FLOAT(value); } public Array INTEGER(Connection conn, List<Integer> value) { return new INTEGER(value); } public Array STRING(Connection conn, List<String> value) throws SQLException { return new STRING(value); } public Array STRING2(Connection conn, List<String[]> value) throws SQLException { return new STRING2(value); } }; private List<T> data; private int baseType; private String baseTypeName = null; protected SqlArray(List<T> data, int baseType) { this.data = data; this.baseType = baseType; } protected SqlArray(List<T> data, int baseType, String baseTypeName) { this(data, baseType); this.baseTypeName = baseTypeName; } public static class BOOLEAN extends SqlArray<Boolean> { public BOOLEAN(List<Boolean> data) { super(data, Types.BIT); } } public static class INTEGER extends SqlArray<Integer> { public INTEGER(List<Integer> data) { super(data, Types.INTEGER); } } public static class FLOAT extends SqlArray<Float> { public FLOAT(List<Float> data) { super(data, Types.FLOAT); } } public static class DOUBLE extends SqlArray<Double> { public DOUBLE(List<Double> data) { super(data, Types.DOUBLE); } } public static class STRING extends SqlArray<String> { public STRING(List<String> data) { super(data, Types.VARCHAR, "text"); } } public static class STRING2 extends SqlArray<String[]> { public STRING2(List<String[]> data) { super(data, Types.VARCHAR, "text"); } } public static class DATE extends SqlArray<Date> { public DATE(List<Date> data) { super(data, Types.TIMESTAMP); } } public String getBaseTypeName() { if (baseTypeName != null) { return baseTypeName; } else { throw new RuntimeException("No baseTypeName"); // return SessionsManager.getSettings().getDialect().getTypeName( // baseType ); } } public int getBaseType() { return baseType; } public Object getArray() { return data.toArray(); } public Object getArray(long index, int count) { int lastIndex = count - (int) index; if (lastIndex > data.size()) lastIndex = data.size(); return data.subList((int) (index - 1), lastIndex).toArray(); } @SuppressWarnings("unused") public Object getArray(Map<String, Class<?>> arg0) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public Object getArray(long arg0, int arg1, Map<String, Class<?>> arg2) { throw new UnsupportedOperationException(); } public ResultSet getResultSet() { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public ResultSet getResultSet(Map<String, Class<?>> arg0) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public ResultSet getResultSet(long index, int count) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public ResultSet getResultSet(long arg0, int arg1, Map<String, Class<?>> arg2) { throw new UnsupportedOperationException(); } @Override public void free() { // do nothing. Required by Java 6 } @Override public String toString() { StringBuilder result = new StringBuilder(); result.append('{'); boolean first = true; for (T t : data) { if (first) first = false; else result.append(','); if (t == null) { result.append("null"); continue; } switch (baseType) { case Types.BIT: case Types.BOOLEAN: result.append(((Boolean) t).booleanValue() ? "true" : "false"); break; case Types.INTEGER: case Types.FLOAT: case Types.DOUBLE: case Types.REAL: case Types.NUMERIC: case Types.DECIMAL: result.append(t); break; case Types.VARCHAR: if (t instanceof String[]) { String[] arr = (String[])t; result.append('{'); for (int i = 0; i < arr.length; i++) { if (i>0) { result.append(","); } appendString(result, arr[i]); } result.append('}'); } else { String s = (String) t; appendString(result, s); } break; case Types.TIMESTAMP: Date d = (Date) t; result.append('\''); appendDate(result, d); result.append(d); result.append('\''); break; default: throw new UnsupportedOperationException("Unsupported type " + baseType + " / " + getBaseTypeName()); } } result.append('}'); return result.toString(); } /** * Refactored out by Josh for text[][] support */ private void appendString(StringBuilder result, String s) { // Escape the string result.append('\"'); for (int p = 0; p < s.length(); ++p) { char ch = s.charAt(p); if (ch == '\0') throw new IllegalArgumentException( "Zero bytes may not occur in string parameters."); if (ch == '\\' || ch == '"') result.append('\\'); result.append(ch); } result.append('\"'); } private static GregorianCalendar calendar = null; protected void appendDate(StringBuilder sb, Date date) { if (calendar == null) calendar = new GregorianCalendar(); calendar.setTime(date); // Append Date { int l_year = calendar.get(Calendar.YEAR); // always use at least four digits for the year so very // early years, like 2, don't get misinterpreted // int l_yearlen = String.valueOf(l_year).length(); for (int i = 4; i > l_yearlen; i--) sb.append("0"); sb.append(l_year); sb.append('-'); int l_month = calendar.get(Calendar.MONTH) + 1; if (l_month < 10) sb.append('0'); sb.append(l_month); sb.append('-'); int l_day = calendar.get(Calendar.DAY_OF_MONTH); if (l_day < 10) sb.append('0'); sb.append(l_day); } sb.append(' '); // Append Time { int hours = calendar.get(Calendar.HOUR_OF_DAY); if (hours < 10) sb.append('0'); sb.append(hours); sb.append(':'); int minutes = calendar.get(Calendar.MINUTE); if (minutes < 10) sb.append('0'); sb.append(minutes); sb.append(':'); int seconds = calendar.get(Calendar.SECOND); if (seconds < 10) sb.append('0'); sb.append(seconds); if (date instanceof Timestamp) { // Add nanoseconds. // This won't work for postgresql versions < 7.2 which only want // a two digit fractional second. Timestamp t = (Timestamp) date; char[] decimalStr = { '0', '0', '0', '0', '0', '0', '0', '0', '0' }; char[] nanoStr = Integer.toString(t.getNanos()).toCharArray(); System.arraycopy(nanoStr, 0, decimalStr, decimalStr.length - nanoStr.length, nanoStr.length); sb.append('.'); sb.append(decimalStr, 0, 6); } } // Append Time Zone offset { // int offset = -(date.getTimezoneOffset()); int offset = (calendar.get(Calendar.ZONE_OFFSET) + calendar .get(Calendar.DST_OFFSET)) / (60 * 1000); int absoff = Math.abs(offset); int hours = absoff / 60; int mins = absoff - hours * 60; sb.append((offset >= 0) ? "+" : "-"); if (hours < 10) sb.append('0'); sb.append(hours); if (mins < 10) sb.append('0'); sb.append(mins); } // Append Era if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) sb.append(" BC"); } }