From 1894f266a11a3efb2769dcd7620ce2506bafef20 Mon Sep 17 00:00:00 2001 From: ray Date: Fri, 6 Jul 2007 00:16:21 +0000 Subject: [PATCH] De-DOS'd these files, from Dave Hoover. git-svn-id: https://samskivert.googlecode.com/svn/trunk@2122 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- src/java/com/samskivert/jdbc/jora/Cursor.java | 468 ++--- .../samskivert/jdbc/jora/FieldDescriptor.java | 902 ++++---- src/java/com/samskivert/jdbc/jora/Table.java | 1836 ++++++++--------- 3 files changed, 1603 insertions(+), 1603 deletions(-) diff --git a/src/java/com/samskivert/jdbc/jora/Cursor.java b/src/java/com/samskivert/jdbc/jora/Cursor.java index 7161d6f8..f7b30773 100644 --- a/src/java/com/samskivert/jdbc/jora/Cursor.java +++ b/src/java/com/samskivert/jdbc/jora/Cursor.java @@ -1,234 +1,234 @@ -// -// $Id$ -// -// samskivert library - useful routines for java programs -// Copyright (C) 2001-6 Konstantin Knizhnik, Michael Bayne -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation; either version 2.1 of the License, or -// (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -package com.samskivert.jdbc.jora; - -import java.util.*; -import java.sql.*; - -import com.samskivert.Log; - -/** - * Cursor is used for successive access to records fetched by SELECT statement. - * As far as records can be retrived from several derived tables (polymorphic - * form of select), this class can issue several requests to database. Cursor - * also provides methods for updating/deleting current record. - */ -public class Cursor -{ - /** - * A cursor is initially positioned before its first row; the first call to - * next makes the first row the current row; the second call makes the - * second row the current row, etc. - * - *

If an input stream from the previous row is open, it is implicitly - * closed. The ResultSet's warning chain is cleared when a new row is read. - * - * @return object constructed from fetched record or null if there are no - * more rows - */ - public V next () - throws SQLException - { - // if we closed everything up after the last call to next(), - // table will be null here and we should bail immediately - if (_table == null) { - return null; - } - - if (_result == null) { - if (_qbeObject != null) { - PreparedStatement qbeStmt = _conn.prepareStatement(_query); - _table.bindQueryVariables(qbeStmt, _qbeObject, _qbeMask); - _result = qbeStmt.executeQuery(); - _stmt = qbeStmt; - } else { - if (_stmt == null) { - _stmt = _conn.createStatement(); - } - _result = _stmt.executeQuery(_query); - } - } - if (_result.next()) { - return _currObject = _table.load(_result); - } - - _result.close(); - _result = null; - _currObject = null; - _table = null; - - if (_stmt != null) { - _stmt.close(); - } - return null; - } - - /** - * Returns the first element matched by this cursor or null if no elements - * were matched. Checks to ensure that no subsequent elements were matched - * by the query, logs a warning if there were spurious additional matches. - */ - public V get () - throws SQLException - { - V result = next(); - if (result != null) { - int spurious = 0; - while (next() != null) { - spurious++; - } - if (spurious > 0) { - Log.warning("Cursor.get() quietly tossed " + spurious + - " spurious additional records. " + - "[query=" + _query + "]."); - } - } - return result; - } - - /** - * Update current record pointed by cursor. This method can be called only - * after next() method, which returns non-null object. This objects is used - * to update current record fields. - * - *

If you are going to update or delete selected records, you should - * add "for update" clause to select statement. So parameter of - * jora.Table.select() statement should contain "for update" - * clause: record.table.Select("where name='xyz' for - * update");

- * - * Attention! Not all database drivers support update - * operation with cursor. This method will not work with such database - * drivers. - */ - public void update () - throws SQLException - { - if (_currObject == null) { - throw new IllegalStateException("No current object"); - } - _table.updateVariables(_result, _currObject); - } - - /** - * Delete current record pointed by cursor. This method can be called only - * after next() method, which returns non-null object. - * - *

If you are going to update or delete selected records, you should - * add "for update" clause to select statement. So parameter of - * jora.Table.select() statement should contain "for update" - * clause: record.table.Select("where name='xyz' for - * update");

- * - * Attention! Not all database drivers support delete - * operation with cursor. This method will not work with such database - * drivers. - */ - public void delete () - throws SQLException - { - if (_currObject == null) { - throw new IllegalStateException("No current object"); - } - _result.deleteRow(); - } - - /** - * Close the Cursor, even if we haven't read all the possible objects. - */ - public void close () - throws SQLException - { - if (_result != null) { - _result.close(); - _result = null; - } - if (_stmt != null) { - _stmt.close(); - _stmt = null; - } - } - - /** - * Extracts no more than maxElements records from database and store - * them into array. It is possible to extract rest records by successive - * next() or toArray() calls. Selected objects should have now components - * of InputStream, Blob or Clob type, because their data will be not - * available after fetching next record. - * - * @param maxElements limitation for result array size (and also for number - * of fetched records) - * @return List with objects constructed from fetched records. - */ - public ArrayList toArrayList (int maxElements) - throws SQLException - { - ArrayList al = new ArrayList(Math.min(maxElements, 100)); - V o; - while (--maxElements >= 0 && (o = next()) != null) { - al.add(o); - } - return al; - } - - /** - * Store all objects returned by SELECT query into a list of Object. - * Selected objects should have now components of InputStream, Blob or Clob - * type, because their data will be not available after fetching next - * record. - * - * @return Array with objects constructed from fetched records. - */ - public ArrayList toArrayList () - throws SQLException - { - return toArrayList(Integer.MAX_VALUE); - } - - protected Cursor (Table table, Connection conn, String query) - { - _table = table; - _conn = conn; - _query = query; - } - - protected Cursor (Table table, Connection conn, V obj, - FieldMask mask, boolean like) - { - _table = table; - _conn = conn; - _like = like; - _qbeObject = obj; - _qbeMask = mask; - _query = table.buildQueryList(obj, mask, like); - _stmt = null; - } - - protected Table _table; - protected Connection _conn; - protected ResultSet _result; - protected String _query; - protected Statement _stmt; - protected V _currObject, _qbeObject; - protected FieldMask _qbeMask; - protected boolean _like; -} - +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001-6 Konstantin Knizhnik, Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc.jora; + +import java.util.*; +import java.sql.*; + +import com.samskivert.Log; + +/** + * Cursor is used for successive access to records fetched by SELECT statement. + * As far as records can be retrived from several derived tables (polymorphic + * form of select), this class can issue several requests to database. Cursor + * also provides methods for updating/deleting current record. + */ +public class Cursor +{ + /** + * A cursor is initially positioned before its first row; the first call to + * next makes the first row the current row; the second call makes the + * second row the current row, etc. + * + *

If an input stream from the previous row is open, it is implicitly + * closed. The ResultSet's warning chain is cleared when a new row is read. + * + * @return object constructed from fetched record or null if there are no + * more rows + */ + public V next () + throws SQLException + { + // if we closed everything up after the last call to next(), + // table will be null here and we should bail immediately + if (_table == null) { + return null; + } + + if (_result == null) { + if (_qbeObject != null) { + PreparedStatement qbeStmt = _conn.prepareStatement(_query); + _table.bindQueryVariables(qbeStmt, _qbeObject, _qbeMask); + _result = qbeStmt.executeQuery(); + _stmt = qbeStmt; + } else { + if (_stmt == null) { + _stmt = _conn.createStatement(); + } + _result = _stmt.executeQuery(_query); + } + } + if (_result.next()) { + return _currObject = _table.load(_result); + } + + _result.close(); + _result = null; + _currObject = null; + _table = null; + + if (_stmt != null) { + _stmt.close(); + } + return null; + } + + /** + * Returns the first element matched by this cursor or null if no elements + * were matched. Checks to ensure that no subsequent elements were matched + * by the query, logs a warning if there were spurious additional matches. + */ + public V get () + throws SQLException + { + V result = next(); + if (result != null) { + int spurious = 0; + while (next() != null) { + spurious++; + } + if (spurious > 0) { + Log.warning("Cursor.get() quietly tossed " + spurious + + " spurious additional records. " + + "[query=" + _query + "]."); + } + } + return result; + } + + /** + * Update current record pointed by cursor. This method can be called only + * after next() method, which returns non-null object. This objects is used + * to update current record fields. + * + *

If you are going to update or delete selected records, you should + * add "for update" clause to select statement. So parameter of + * jora.Table.select() statement should contain "for update" + * clause: record.table.Select("where name='xyz' for + * update");

+ * + * Attention! Not all database drivers support update + * operation with cursor. This method will not work with such database + * drivers. + */ + public void update () + throws SQLException + { + if (_currObject == null) { + throw new IllegalStateException("No current object"); + } + _table.updateVariables(_result, _currObject); + } + + /** + * Delete current record pointed by cursor. This method can be called only + * after next() method, which returns non-null object. + * + *

If you are going to update or delete selected records, you should + * add "for update" clause to select statement. So parameter of + * jora.Table.select() statement should contain "for update" + * clause: record.table.Select("where name='xyz' for + * update");

+ * + * Attention! Not all database drivers support delete + * operation with cursor. This method will not work with such database + * drivers. + */ + public void delete () + throws SQLException + { + if (_currObject == null) { + throw new IllegalStateException("No current object"); + } + _result.deleteRow(); + } + + /** + * Close the Cursor, even if we haven't read all the possible objects. + */ + public void close () + throws SQLException + { + if (_result != null) { + _result.close(); + _result = null; + } + if (_stmt != null) { + _stmt.close(); + _stmt = null; + } + } + + /** + * Extracts no more than maxElements records from database and store + * them into array. It is possible to extract rest records by successive + * next() or toArray() calls. Selected objects should have now components + * of InputStream, Blob or Clob type, because their data will be not + * available after fetching next record. + * + * @param maxElements limitation for result array size (and also for number + * of fetched records) + * @return List with objects constructed from fetched records. + */ + public ArrayList toArrayList (int maxElements) + throws SQLException + { + ArrayList al = new ArrayList(Math.min(maxElements, 100)); + V o; + while (--maxElements >= 0 && (o = next()) != null) { + al.add(o); + } + return al; + } + + /** + * Store all objects returned by SELECT query into a list of Object. + * Selected objects should have now components of InputStream, Blob or Clob + * type, because their data will be not available after fetching next + * record. + * + * @return Array with objects constructed from fetched records. + */ + public ArrayList toArrayList () + throws SQLException + { + return toArrayList(Integer.MAX_VALUE); + } + + protected Cursor (Table table, Connection conn, String query) + { + _table = table; + _conn = conn; + _query = query; + } + + protected Cursor (Table table, Connection conn, V obj, + FieldMask mask, boolean like) + { + _table = table; + _conn = conn; + _like = like; + _qbeObject = obj; + _qbeMask = mask; + _query = table.buildQueryList(obj, mask, like); + _stmt = null; + } + + protected Table _table; + protected Connection _conn; + protected ResultSet _result; + protected String _query; + protected Statement _stmt; + protected V _currObject, _qbeObject; + protected FieldMask _qbeMask; + protected boolean _like; +} + diff --git a/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java b/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java index 972059b9..d55b5259 100644 --- a/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java +++ b/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java @@ -1,451 +1,451 @@ -// -// $Id$ -// -// samskivert library - useful routines for java programs -// Copyright (C) 2001-6 Konstantin Knizhnik, Michael Bayne -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation; either version 2.1 of the License, or -// (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -package com.samskivert.jdbc.jora; - -import java.sql.*; -import java.math.*; -import java.lang.reflect.*; - -class FieldDescriptor -{ - protected FieldDescriptor (Field field, String name) - { - this.name = name; - this.field = field; - this.scale = -1; - } - - protected final boolean isAtomic () - { - return inType < tClosure; - } - - protected final boolean isCompound () - { - return inType >= tCompound; - } - - protected final boolean isBuiltin () - { - return inType <= t_boolean; - } - - protected final boolean bindVariable ( - PreparedStatement pstmt, Object obj, int column) - throws SQLException - { - try { - switch (outType) { - case t_byte: - pstmt.setByte(column, field.getByte(obj)); - break; - case t_short: - pstmt.setShort(column, field.getShort(obj)); - break; - case t_int: - pstmt.setInt(column, field.getInt(obj)); - break; - case t_long: - pstmt.setLong(column, field.getLong(obj)); - break; - case t_float: - pstmt.setFloat(column, field.getFloat(obj)); - break; - case t_double: - pstmt.setDouble(column, field.getDouble(obj)); - break; - case t_boolean: - pstmt.setBoolean(column, field.getBoolean(obj)); - break; - case tByte: - pstmt.setByte(column, ((Byte)field.get(obj)).byteValue()); - break; - - case tShort: - pstmt.setShort(column, ((Short)field.get(obj)).shortValue()); - break; - case tInteger: - pstmt.setInt(column, ((Integer)field.get(obj)).intValue()); - break; - case tLong: - pstmt.setLong(column, ((Long)field.get(obj)).longValue()); - break; - case tFloat: - pstmt.setFloat(column, ((Float)field.get(obj)).floatValue()); - break; - case tDouble: - pstmt.setDouble(column,((Double)field.get(obj)).doubleValue()); - break; - case tBoolean: - pstmt.setBoolean(column, - ((Boolean)field.get(obj)).booleanValue()); - break; - - case tDecimal: - pstmt.setBigDecimal(column, (BigDecimal)field.get(obj)); - break; - case tString: - pstmt.setString(column, (String)field.get(obj)); - break; - case tBytes: - pstmt.setBytes(column, (byte[])field.get(obj)); - break; - case tDate: - pstmt.setDate(column, (java.sql.Date)field.get(obj)); - break; - case tTime: - pstmt.setTime(column, (java.sql.Time)field.get(obj)); - break; - case tTimestamp: - pstmt.setTimestamp(column, (java.sql.Timestamp)field.get(obj)); - break; - case tStream: - java.io.InputStream in = (java.io.InputStream)field.get(obj); - pstmt.setBinaryStream(column, in, in.available()); - break; - case tBlob: - pstmt.setBlob(column, (Blob)field.get(obj)); - break; - case tClob: - pstmt.setClob(column, (Clob)field.get(obj)); - break; - case tAsString: - pstmt.setString(column, field.get(obj).toString()); - break; - case tClosure: - // There is no reason to use piped streams because - // we need to pass total number of bytes to JDBC driver - java.io.ByteArrayOutputStream out = - new java.io.ByteArrayOutputStream(); - java.io.ObjectOutputStream clu = - new java.io.ObjectOutputStream(out); - clu.writeObject(field.get(obj)); - clu.close(); - pstmt.setBytes(column, out.toByteArray()); - break; - default: - return false; - } - } catch(SQLException ex) { - if (outType != tClosure && outType != tAsString) { - outType = tAsString; - return bindVariable(pstmt, obj, column); - } else { - throw ex; - } - } catch(IllegalAccessException ex) { - ex.printStackTrace(); - throw new IllegalAccessError(); - } catch(java.io.IOException ex) { - throw new DataTransferError(ex); - } - return true; - } - - protected final boolean updateVariable ( - ResultSet result, Object obj, int column) - throws SQLException - { - try { - switch (outType) { - case t_byte: - result.updateByte(column, field.getByte(obj)); - break; - case t_short: - result.updateShort(column, field.getShort(obj)); - break; - case t_int: - result.updateInt(column, field.getInt(obj)); - break; - case t_long: - result.updateLong(column, field.getLong(obj)); - break; - case t_float: - result.updateFloat(column, field.getFloat(obj)); - break; - case t_double: - result.updateDouble(column, field.getDouble(obj)); - break; - case t_boolean: - result.updateBoolean(column, field.getBoolean(obj)); - break; - - case tByte: - result.updateByte(column, ((Byte)field.get(obj)).byteValue()); - break; - case tShort: - result.updateShort(column, - ((Short)field.get(obj)).shortValue()); - break; - case tInteger: - result.updateInt(column, ((Integer)field.get(obj)).intValue()); - break; - case tLong: - result.updateLong(column, ((Long)field.get(obj)).longValue()); - break; - case tFloat: - result.updateFloat(column, - ((Float)field.get(obj)).floatValue()); - break; - case tDouble: - result.updateDouble(column, - ((Double)field.get(obj)).doubleValue()); - break; - case tBoolean: - result.updateBoolean(column, - ((Boolean)field.get(obj)).booleanValue()); - break; - - case tDecimal: - result.updateBigDecimal(column, (BigDecimal)field.get(obj)); - break; - case tString: - result.updateString(column, (String)field.get(obj)); - break; - case tBytes: - result.updateBytes(column, (byte[])field.get(obj)); - break; - case tDate: - result.updateDate(column, (java.sql.Date)field.get(obj)); - break; - case tTime: - result.updateTime(column, (java.sql.Time)field.get(obj)); - break; - case tTimestamp: - result.updateTimestamp(column, - (java.sql.Timestamp)field.get(obj)); - break; - case tStream: - java.io.InputStream in = (java.io.InputStream)field.get(obj); - result.updateBinaryStream(column, in, in.available()); - break; - case tBlob: - Blob blob = (Blob)field.get(obj); - result.updateBinaryStream(column, - blob.getBinaryStream(), - (int)blob.length()); - break; - case tClob: - Clob clob = (Clob)field.get(obj); - result.updateCharacterStream(column, - clob.getCharacterStream(), - (int)clob.length()); - break; - case tAsString: - result.updateString(column, field.get(obj).toString()); - break; - case tClosure: - // There is no reason to use piped streams because - // we need to pass total number of bytes to JDBC driver - java.io.ByteArrayOutputStream out = - new java.io.ByteArrayOutputStream(); - java.io.ObjectOutputStream clu = - new java.io.ObjectOutputStream(out); - clu.writeObject(field.get(obj)); - clu.close(); - result.updateBytes(column, out.toByteArray()); - break; - default: - return false; - } - } catch(SQLException ex) { - if (outType != tClosure && outType != tAsString) { - outType = tAsString; - return updateVariable(result, obj, column); - } else { - throw ex; - } - } catch(IllegalAccessException ex) { - ex.printStackTrace(); - throw new IllegalAccessError(); - } catch(java.io.IOException ex) { - throw new DataTransferError(ex); - } - return true; - } - - protected final boolean loadVariable ( - ResultSet result, Object obj, int column) - throws SQLException, IllegalAccessException - { - switch (inType) { - case t_byte: - field.setByte(obj, result.getByte(column)); - break; - case t_short: - field.setShort(obj, result.getShort(column)); - break; - case t_int: - field.setInt(obj, result.getInt(column)); - break; - case t_long: - field.setLong(obj, result.getLong(column)); - break; - case t_float: - field.setFloat(obj, result.getFloat(column)); - break; - case t_double: - field.setDouble(obj, result.getDouble(column)); - break; - case t_boolean: - field.setBoolean(obj, result.getBoolean(column)); - break; - - case tByte: - byte b = result.getByte(column); - field.set(obj, result.wasNull() ? null : Byte.valueOf(b)); - break; - case tShort: - short s = result.getShort(column); - field.set(obj, result.wasNull() ? null : Short.valueOf(s)); - break; - case tInteger: - int i = result.getInt(column); - field.set(obj, result.wasNull() ? null : Integer.valueOf(i)); - break; - case tLong: - long l = result.getLong(column); - field.set(obj, result.wasNull() ? null : Long.valueOf(l)); - break; - case tFloat: - float f = result.getFloat(column); - field.set(obj, result.wasNull() ? null : Float.valueOf(f)); - field.setFloat(obj, result.getFloat(column)); - break; - case tDouble: - double d = result.getDouble(column); - field.set(obj, result.wasNull() ? null : Double.valueOf(d)); - break; - case tBoolean: - boolean bl = result.getBoolean(column); - field.set(obj, result.wasNull() ? null : Boolean.valueOf(bl)); - break; - - case tDecimal: - field.set(obj, result.getBigDecimal(column)); - break; - case tString: - field.set(obj, result.getString(column)); - break; - case tBytes: - field.set(obj, result.getBytes(column)); - break; - case tDate: - field.set(obj, result.getDate(column)); - break; - case tTime: - field.set(obj, result.getTime(column)); - break; - case tTimestamp: - field.set(obj, result.getTimestamp(column)); - break; - case tStream: - field.set(obj, result.getBinaryStream(column)); - break; - case tBlob: - field.set(obj, result.getBlob(column)); - break; - case tClob: - field.set(obj, result.getClob(column)); - break; - case tClosure: - try { - java.io.InputStream input = result.getBinaryStream(column); - java.io.ObjectInputStream in = - new java.io.ObjectInputStream(input); - field.set(obj, in.readObject()); - in.close(); - } catch(ClassNotFoundException ex) { - throw new DataTransferError(ex); - } catch(java.io.IOException ex) { - throw new DataTransferError(ex); - } - break; - default: - return false; - } - return true; - } - - protected int inType; // type tag for field input (see constants below) - protected int outType; // type tag for field output - protected int scale; // scale for tDecimal type, - protected String name; // full (compound) name of component - protected Field field; // field info from java.lang.reflect - - protected Constructor constructor; // constructor of object component - - protected static final int t_byte = 0; - protected static final int t_short = 1; - protected static final int t_int = 2; - protected static final int t_long = 3; - protected static final int t_float = 4; - protected static final int t_double = 5; - protected static final int t_boolean = 6; - protected static final int tByte = 7; - protected static final int tShort = 8; - protected static final int tInteger = 9; - protected static final int tLong = 10; - protected static final int tFloat = 11; - protected static final int tDouble = 12; - protected static final int tBoolean = 13; - protected static final int tDecimal = 14; - protected static final int tString = 15; - protected static final int tBytes = 16; - protected static final int tDate = 17; - protected static final int tTime = 18; - protected static final int tTimestamp = 19; - protected static final int tStream = 20; - protected static final int tBlob = 21; - protected static final int tClob = 22; - protected static final int tAsString = 23; - - protected static final int tClosure = 24; - protected static final int tCompound = 25; - - protected static final int[] sqlTypeMapping = { - Types.INTEGER, // t_byte - Types.INTEGER, // t_short - Types.INTEGER, // t_int - Types.BIGINT, // t_long - Types.FLOAT, // t_float - Types.DOUBLE, // t_double - Types.BIT, // t_boolean - Types.INTEGER, // tByte - Types.INTEGER, // tShort - Types.INTEGER, // tInteger - Types.BIGINT, // tLong - Types.FLOAT, // tFloat - Types.DOUBLE, // tDouble - Types.BIT, // tBoolean - Types.NUMERIC, // tDecimal - Types.VARCHAR, // tString - Types.VARBINARY,// tBytes - Types.DATE, // tDate - Types.TIME, // tTime - Types.TIMESTAMP, // tTimestamp - Types.LONGVARBINARY, // tStream - Types.LONGVARBINARY, // tBlob - Types.LONGVARCHAR, // tClob - Types.VARCHAR, // tAsString - Types.LONGVARBINARY // tClosure - }; -} +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001-6 Konstantin Knizhnik, Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc.jora; + +import java.sql.*; +import java.math.*; +import java.lang.reflect.*; + +class FieldDescriptor +{ + protected FieldDescriptor (Field field, String name) + { + this.name = name; + this.field = field; + this.scale = -1; + } + + protected final boolean isAtomic () + { + return inType < tClosure; + } + + protected final boolean isCompound () + { + return inType >= tCompound; + } + + protected final boolean isBuiltin () + { + return inType <= t_boolean; + } + + protected final boolean bindVariable ( + PreparedStatement pstmt, Object obj, int column) + throws SQLException + { + try { + switch (outType) { + case t_byte: + pstmt.setByte(column, field.getByte(obj)); + break; + case t_short: + pstmt.setShort(column, field.getShort(obj)); + break; + case t_int: + pstmt.setInt(column, field.getInt(obj)); + break; + case t_long: + pstmt.setLong(column, field.getLong(obj)); + break; + case t_float: + pstmt.setFloat(column, field.getFloat(obj)); + break; + case t_double: + pstmt.setDouble(column, field.getDouble(obj)); + break; + case t_boolean: + pstmt.setBoolean(column, field.getBoolean(obj)); + break; + case tByte: + pstmt.setByte(column, ((Byte)field.get(obj)).byteValue()); + break; + + case tShort: + pstmt.setShort(column, ((Short)field.get(obj)).shortValue()); + break; + case tInteger: + pstmt.setInt(column, ((Integer)field.get(obj)).intValue()); + break; + case tLong: + pstmt.setLong(column, ((Long)field.get(obj)).longValue()); + break; + case tFloat: + pstmt.setFloat(column, ((Float)field.get(obj)).floatValue()); + break; + case tDouble: + pstmt.setDouble(column,((Double)field.get(obj)).doubleValue()); + break; + case tBoolean: + pstmt.setBoolean(column, + ((Boolean)field.get(obj)).booleanValue()); + break; + + case tDecimal: + pstmt.setBigDecimal(column, (BigDecimal)field.get(obj)); + break; + case tString: + pstmt.setString(column, (String)field.get(obj)); + break; + case tBytes: + pstmt.setBytes(column, (byte[])field.get(obj)); + break; + case tDate: + pstmt.setDate(column, (java.sql.Date)field.get(obj)); + break; + case tTime: + pstmt.setTime(column, (java.sql.Time)field.get(obj)); + break; + case tTimestamp: + pstmt.setTimestamp(column, (java.sql.Timestamp)field.get(obj)); + break; + case tStream: + java.io.InputStream in = (java.io.InputStream)field.get(obj); + pstmt.setBinaryStream(column, in, in.available()); + break; + case tBlob: + pstmt.setBlob(column, (Blob)field.get(obj)); + break; + case tClob: + pstmt.setClob(column, (Clob)field.get(obj)); + break; + case tAsString: + pstmt.setString(column, field.get(obj).toString()); + break; + case tClosure: + // There is no reason to use piped streams because + // we need to pass total number of bytes to JDBC driver + java.io.ByteArrayOutputStream out = + new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream clu = + new java.io.ObjectOutputStream(out); + clu.writeObject(field.get(obj)); + clu.close(); + pstmt.setBytes(column, out.toByteArray()); + break; + default: + return false; + } + } catch(SQLException ex) { + if (outType != tClosure && outType != tAsString) { + outType = tAsString; + return bindVariable(pstmt, obj, column); + } else { + throw ex; + } + } catch(IllegalAccessException ex) { + ex.printStackTrace(); + throw new IllegalAccessError(); + } catch(java.io.IOException ex) { + throw new DataTransferError(ex); + } + return true; + } + + protected final boolean updateVariable ( + ResultSet result, Object obj, int column) + throws SQLException + { + try { + switch (outType) { + case t_byte: + result.updateByte(column, field.getByte(obj)); + break; + case t_short: + result.updateShort(column, field.getShort(obj)); + break; + case t_int: + result.updateInt(column, field.getInt(obj)); + break; + case t_long: + result.updateLong(column, field.getLong(obj)); + break; + case t_float: + result.updateFloat(column, field.getFloat(obj)); + break; + case t_double: + result.updateDouble(column, field.getDouble(obj)); + break; + case t_boolean: + result.updateBoolean(column, field.getBoolean(obj)); + break; + + case tByte: + result.updateByte(column, ((Byte)field.get(obj)).byteValue()); + break; + case tShort: + result.updateShort(column, + ((Short)field.get(obj)).shortValue()); + break; + case tInteger: + result.updateInt(column, ((Integer)field.get(obj)).intValue()); + break; + case tLong: + result.updateLong(column, ((Long)field.get(obj)).longValue()); + break; + case tFloat: + result.updateFloat(column, + ((Float)field.get(obj)).floatValue()); + break; + case tDouble: + result.updateDouble(column, + ((Double)field.get(obj)).doubleValue()); + break; + case tBoolean: + result.updateBoolean(column, + ((Boolean)field.get(obj)).booleanValue()); + break; + + case tDecimal: + result.updateBigDecimal(column, (BigDecimal)field.get(obj)); + break; + case tString: + result.updateString(column, (String)field.get(obj)); + break; + case tBytes: + result.updateBytes(column, (byte[])field.get(obj)); + break; + case tDate: + result.updateDate(column, (java.sql.Date)field.get(obj)); + break; + case tTime: + result.updateTime(column, (java.sql.Time)field.get(obj)); + break; + case tTimestamp: + result.updateTimestamp(column, + (java.sql.Timestamp)field.get(obj)); + break; + case tStream: + java.io.InputStream in = (java.io.InputStream)field.get(obj); + result.updateBinaryStream(column, in, in.available()); + break; + case tBlob: + Blob blob = (Blob)field.get(obj); + result.updateBinaryStream(column, + blob.getBinaryStream(), + (int)blob.length()); + break; + case tClob: + Clob clob = (Clob)field.get(obj); + result.updateCharacterStream(column, + clob.getCharacterStream(), + (int)clob.length()); + break; + case tAsString: + result.updateString(column, field.get(obj).toString()); + break; + case tClosure: + // There is no reason to use piped streams because + // we need to pass total number of bytes to JDBC driver + java.io.ByteArrayOutputStream out = + new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream clu = + new java.io.ObjectOutputStream(out); + clu.writeObject(field.get(obj)); + clu.close(); + result.updateBytes(column, out.toByteArray()); + break; + default: + return false; + } + } catch(SQLException ex) { + if (outType != tClosure && outType != tAsString) { + outType = tAsString; + return updateVariable(result, obj, column); + } else { + throw ex; + } + } catch(IllegalAccessException ex) { + ex.printStackTrace(); + throw new IllegalAccessError(); + } catch(java.io.IOException ex) { + throw new DataTransferError(ex); + } + return true; + } + + protected final boolean loadVariable ( + ResultSet result, Object obj, int column) + throws SQLException, IllegalAccessException + { + switch (inType) { + case t_byte: + field.setByte(obj, result.getByte(column)); + break; + case t_short: + field.setShort(obj, result.getShort(column)); + break; + case t_int: + field.setInt(obj, result.getInt(column)); + break; + case t_long: + field.setLong(obj, result.getLong(column)); + break; + case t_float: + field.setFloat(obj, result.getFloat(column)); + break; + case t_double: + field.setDouble(obj, result.getDouble(column)); + break; + case t_boolean: + field.setBoolean(obj, result.getBoolean(column)); + break; + + case tByte: + byte b = result.getByte(column); + field.set(obj, result.wasNull() ? null : Byte.valueOf(b)); + break; + case tShort: + short s = result.getShort(column); + field.set(obj, result.wasNull() ? null : Short.valueOf(s)); + break; + case tInteger: + int i = result.getInt(column); + field.set(obj, result.wasNull() ? null : Integer.valueOf(i)); + break; + case tLong: + long l = result.getLong(column); + field.set(obj, result.wasNull() ? null : Long.valueOf(l)); + break; + case tFloat: + float f = result.getFloat(column); + field.set(obj, result.wasNull() ? null : Float.valueOf(f)); + field.setFloat(obj, result.getFloat(column)); + break; + case tDouble: + double d = result.getDouble(column); + field.set(obj, result.wasNull() ? null : Double.valueOf(d)); + break; + case tBoolean: + boolean bl = result.getBoolean(column); + field.set(obj, result.wasNull() ? null : Boolean.valueOf(bl)); + break; + + case tDecimal: + field.set(obj, result.getBigDecimal(column)); + break; + case tString: + field.set(obj, result.getString(column)); + break; + case tBytes: + field.set(obj, result.getBytes(column)); + break; + case tDate: + field.set(obj, result.getDate(column)); + break; + case tTime: + field.set(obj, result.getTime(column)); + break; + case tTimestamp: + field.set(obj, result.getTimestamp(column)); + break; + case tStream: + field.set(obj, result.getBinaryStream(column)); + break; + case tBlob: + field.set(obj, result.getBlob(column)); + break; + case tClob: + field.set(obj, result.getClob(column)); + break; + case tClosure: + try { + java.io.InputStream input = result.getBinaryStream(column); + java.io.ObjectInputStream in = + new java.io.ObjectInputStream(input); + field.set(obj, in.readObject()); + in.close(); + } catch(ClassNotFoundException ex) { + throw new DataTransferError(ex); + } catch(java.io.IOException ex) { + throw new DataTransferError(ex); + } + break; + default: + return false; + } + return true; + } + + protected int inType; // type tag for field input (see constants below) + protected int outType; // type tag for field output + protected int scale; // scale for tDecimal type, + protected String name; // full (compound) name of component + protected Field field; // field info from java.lang.reflect + + protected Constructor constructor; // constructor of object component + + protected static final int t_byte = 0; + protected static final int t_short = 1; + protected static final int t_int = 2; + protected static final int t_long = 3; + protected static final int t_float = 4; + protected static final int t_double = 5; + protected static final int t_boolean = 6; + protected static final int tByte = 7; + protected static final int tShort = 8; + protected static final int tInteger = 9; + protected static final int tLong = 10; + protected static final int tFloat = 11; + protected static final int tDouble = 12; + protected static final int tBoolean = 13; + protected static final int tDecimal = 14; + protected static final int tString = 15; + protected static final int tBytes = 16; + protected static final int tDate = 17; + protected static final int tTime = 18; + protected static final int tTimestamp = 19; + protected static final int tStream = 20; + protected static final int tBlob = 21; + protected static final int tClob = 22; + protected static final int tAsString = 23; + + protected static final int tClosure = 24; + protected static final int tCompound = 25; + + protected static final int[] sqlTypeMapping = { + Types.INTEGER, // t_byte + Types.INTEGER, // t_short + Types.INTEGER, // t_int + Types.BIGINT, // t_long + Types.FLOAT, // t_float + Types.DOUBLE, // t_double + Types.BIT, // t_boolean + Types.INTEGER, // tByte + Types.INTEGER, // tShort + Types.INTEGER, // tInteger + Types.BIGINT, // tLong + Types.FLOAT, // tFloat + Types.DOUBLE, // tDouble + Types.BIT, // tBoolean + Types.NUMERIC, // tDecimal + Types.VARCHAR, // tString + Types.VARBINARY,// tBytes + Types.DATE, // tDate + Types.TIME, // tTime + Types.TIMESTAMP, // tTimestamp + Types.LONGVARBINARY, // tStream + Types.LONGVARBINARY, // tBlob + Types.LONGVARCHAR, // tClob + Types.VARCHAR, // tAsString + Types.LONGVARBINARY // tClosure + }; +} diff --git a/src/java/com/samskivert/jdbc/jora/Table.java b/src/java/com/samskivert/jdbc/jora/Table.java index 1dd35643..2b32128d 100644 --- a/src/java/com/samskivert/jdbc/jora/Table.java +++ b/src/java/com/samskivert/jdbc/jora/Table.java @@ -1,918 +1,918 @@ -// -// $Id$ -// -// samskivert library - useful routines for java programs -// Copyright (C) 2001-6 Konstantin Knizhnik, Michael Bayne -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation; either version 2.1 of the License, or -// (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -package com.samskivert.jdbc.jora; - -import java.io.Serializable; -import java.util.*; -import java.sql.*; -import java.lang.reflect.*; - -import com.samskivert.jdbc.JDBCUtil; -import com.samskivert.util.StringUtil; - -/** - * Used to establish mapping between corteges of database tables and Java - * classes. This class is responsible for constructing SQL statements for - * extracting, updating and deleting records of the database table. - */ -public class Table -{ - /** - * Constructor for table object. Make association between Java class and - * database table. - * - * @param clazz the class that represents a row entry. - * @param tableName name of database table mapped on this Java class - * @param key table's primary key. This parameter is used in UPDATE/DELETE - * operations to locate record in the table. - * @param mixedCaseConvert whether or not to convert mixed case field - * names into underscore separated uppercase column names. - */ - public Table (Class clazz, String tableName, String key, - boolean mixedCaseConvert) - { - String[] keys = {key}; - init(clazz, tableName, keys, mixedCaseConvert); - } - - /** - * Constructor for table object. Make association between Java class and - * database table. - * - * @param clazz the class that represents a row entry. - * @param tableName name of database table mapped on this Java class - * @param key table's primary key. This parameter is used in UPDATE/DELETE - * operations to locate record in the table. - */ - public Table (Class clazz, String tableName, String key) { - String[] keys = {key}; - init(clazz, tableName, keys, false); - } - - /** - * Constructor for table object. Make association between Java class and - * database table. - * - * @param clazz the class that represents a row entry. - * @param tableName name of database table mapped on this Java class - * @param keys table primary keys. This parameter is used in UPDATE/DELETE - * operations to locate record in the table. - */ - public Table (Class clazz, String tableName, String[] keys) - { - init(clazz, tableName, keys, false); - } - - /** - * Constructor for table object. Make association between Java class and - * database table. - * - * @param clazz the class that represents a row entry. - * @param tableName name of database table mapped on this Java class - * @param keys table primary keys. This parameter is used in UPDATE/DELETE - * operations to locate record in the table. - * @param mixedCaseConvert whether or not to convert mixed case field - * names into underscore separated uppercase column names. - */ - public Table (Class clazz, String tableName, String[] keys, - boolean mixedCaseConvert) - { - init(clazz, tableName, keys, mixedCaseConvert); - } - - /** - * Returns the SQL name of the table on which we operate. - */ - public String getName () - { - return name; - } - - /** - * Select records from database table according to search condition - * - * @param condition valid SQL condition expression started with WHERE or - * empty string if all records should be fetched. - */ - public final Cursor select (Connection conn, String condition) - { - String query = "select " + listOfFields + " from " + name + - " " + condition; - return new Cursor(this, conn, query); - } - - /** - * Select records from database table according to search condition - * including the specified (comma separated) extra tables into the SELECT - * clause to facilitate a join in determining the key. - * - * @param tables the (comma separated) names of extra tables to include in - * the SELECT clause. - * @param condition valid SQL condition expression started with WHERE. - */ - public final Cursor select (Connection conn, String tables, - String condition) - { - String query = "select " + qualifiedListOfFields + - " from " + name + "," + tables + " " + condition; - return new Cursor(this, conn, query); - } - - /** - * Select records from database table according to search condition - * including the specified (comma separated) extra tables into the SELECT - * clause to facilitate a join in determining the key. To facilitate - * situations where data from multiple tables is being combined into a - * single object, the fields will not be qualified with the primary table - * name. - * - * @param tables the (comma separated) names of extra tables to include in - * the SELECT clause. - * @param condition valid SQL condition expression started with WHERE. - */ - public final Cursor join (Connection conn, String tables, - String condition) - { - String query = "select " + listOfFields + - " from " + name + "," + tables + " " + condition; - return new Cursor(this, conn, query); - } - - /** - * Like {@link #join} but does a straight join with the specified table. - */ - public final Cursor straightJoin (Connection conn, String table, - String condition) - { - String query = "select " + listOfFields + - " from " + name + " straight_join " + table + " " + condition; - return new Cursor(this, conn, query); - } - - /** - * Select records from database table using obj object as template. - * - * @param obj example object for search: selected objects should match all - * non-null fields. - */ - public final Cursor queryByExample (Connection conn, T obj) - { - return new Cursor(this, conn, obj, null, false); - } - - /** - * Select records from database table using obj object as template - * for selection. - * - * @param obj example object for search. - * @param mask field mask indicating which fields in the example object - * should be used when building the query. - */ - public final Cursor queryByExample (Connection conn, T obj, - FieldMask mask) - { - return new Cursor(this, conn, obj, mask, false); - } - - /** - * The same as the queryByExample, but string fields for the obj are - * matched using 'like' instead of equals, which allows you to send % in to - * do matching. - */ - public final Cursor queryByLikeExample (Connection conn, T obj) - { - return new Cursor(this, conn, obj, null, true); - } - - /** - * The same as the queryByExample, but string fields for the obj are - * matched using 'like' instead of equals, which allows you to send % in to - * do matching. - */ - public final Cursor queryByLikeExample (Connection conn, T obj, - FieldMask mask) - { - return new Cursor(this, conn, obj, mask, true); - } - - /** - * Insert new record in the table. Values of inserted record fields are - * taken from specifed object. - * - * @param obj object specifing values of inserted record fields - */ - public synchronized void insert (Connection conn, T obj) - throws SQLException - { - String sql = "insert into " + name + - " (" + listOfFields + ") values (?"; - for (int i = 1; i < nColumns; i++) { - sql += ",?"; - } - sql += ")"; - PreparedStatement insertStmt = conn.prepareStatement(sql); - bindUpdateVariables(insertStmt, obj, null); - insertStmt.executeUpdate(); - insertStmt.close(); - } - - /** - * Insert several new records in the table. Values of inserted records - * fields are taken from objects of specified array. - * - * @param objects array with objects specifing values of inserted record - * fields - */ - public synchronized void insert (Connection conn, T[] objects) - throws SQLException - { - String sql = "insert into " + name + - " (" + listOfFields + ") values (?"; - for (int i = 1; i < nColumns; i++) { - sql += ",?"; - } - sql += ")"; - PreparedStatement insertStmt = conn.prepareStatement(sql); - for (int i = 0; i < objects.length; i++) { - bindUpdateVariables(insertStmt, objects[i], null); - insertStmt.addBatch(); - } - insertStmt.executeBatch(); - insertStmt.close(); - } - - /** - * Returns a field mask that can be configured and used to update subsets - * of entire objects via calls to {@link #update(Connection,Object,FieldMask)}. - */ - public FieldMask getFieldMask () - { - return (FieldMask)fMask.clone(); - } - - /** - * Update record in the table using table's primary key to locate record in - * the table and values of fields of specified object obj to alter - * record fields. - * - * @param obj object specifing value of primary key and new values of - * updated record fields - * - * @return number of objects actually updated - */ - public int update (Connection conn, T obj) - throws SQLException - { - return update(conn, obj, null); - } - - /** - * Update record in the table using table's primary key to locate record in - * the table and values of fields of specified object obj to alter - * record fields. Only the fields marked as modified in the supplied field - * mask will be updated in the database. - * - * @param obj object specifing value of primary key and new values of - * updated record fields - * @param mask a {@link FieldMask} instance configured to indicate which of - * the object's fields are modified and should be written to the database. - * - * @return number of objects actually updated - */ - public synchronized int update (Connection conn, T obj, FieldMask mask) - throws SQLException - { - int nUpdated = 0; - String sql = "update " + name + " set " + - (mask != null ? buildListOfAssignments(mask) : listOfAssignments) + - buildUpdateWhere(); - PreparedStatement ustmt = conn.prepareStatement(sql); - int column = bindUpdateVariables(ustmt, obj, mask); - for (int i = 0; i < primaryKeys.length; i++) { - int fidx = primaryKeyIndices[i]; - fields[fidx].bindVariable(ustmt, obj, column+i+1); - } - nUpdated = ustmt.executeUpdate(); - ustmt.close(); - return nUpdated; - } - - /** - * Update set of records in the table using table's primary key to locate - * record in the table and values of fields of objects from sepecifed array - * objects to alter record fields. - * - * @param objects array of objects specifing primiray keys and and new - * values of updated record fields - * - * @return number of objects actually updated - */ - public synchronized int update (Connection conn, T[] objects) - throws SQLException - { - if (primaryKeys == null) { - throw new IllegalStateException( - "No primary key for table " + name + "."); - } - - int nUpdated = 0; - String sql = "update " + name + " set " + listOfAssignments + - buildUpdateWhere(); - PreparedStatement updateStmt = conn.prepareStatement(sql); - for (int i = 0; i < objects.length; i++) { - int column = bindUpdateVariables(updateStmt, objects[i], null); - for (int j = 0; j < primaryKeys.length; j++) { - int fidx = primaryKeyIndices[j]; - fields[fidx].bindVariable( - updateStmt, objects[i], column+1+j); - } - updateStmt.addBatch(); - } - int rc[] = updateStmt.executeBatch(); - for (int k = 0; k < rc.length; k++) { - nUpdated += rc[k]; - } - updateStmt.close(); - return nUpdated; - } - - /** - * Delete record with specified value of primary key from the table. - * - * @param obj object containing value of primary key. - */ - public synchronized int delete (Connection conn, T obj) - throws SQLException - { - if (primaryKeys == null) { - throw new IllegalStateException( - "No primary key for table " + name + "."); - } - int nDeleted = 0; - String sql = "delete from " + name + - " where " + primaryKeys[0] + " = ?"; - for (int i = 1; i < primaryKeys.length; i++) { - sql += " and " + primaryKeys[i] + " = ?"; - } - PreparedStatement deleteStmt = conn.prepareStatement(sql); - for (int i = 0; i < primaryKeys.length; i++) { - fields[primaryKeyIndices[i]].bindVariable(deleteStmt, obj,i+1); - } - nDeleted = deleteStmt.executeUpdate(); - deleteStmt.close(); - return nDeleted; - } - - /** - * Delete records with specified primary keys from the table. - * - * @param objects array of objects containing values of primary key. - * - * @return number of objects actually deleted - */ - public synchronized int delete (Connection conn, T[] objects) - throws SQLException - { - if (primaryKeys == null) { - throw new IllegalStateException( - "No primary key for table " + name + "."); - } - int nDeleted = 0; - String sql = "delete from " + name + - " where " + primaryKeys[0] + " = ?"; - for (int i = 1; i < primaryKeys.length; i++) { - sql += " and " + primaryKeys[i] + " = ?"; - } - PreparedStatement deleteStmt = conn.prepareStatement(sql); - for (int i = 0; i < objects.length; i++) { - for (int j = 0; j < primaryKeys.length; j++) { - fields[primaryKeyIndices[j]].bindVariable( - deleteStmt, objects[i], j+1); - } - deleteStmt.addBatch(); - } - int rc[] = deleteStmt.executeBatch(); - for (int k = 0; k < rc.length; k++) { - nDeleted += rc[k]; - } - deleteStmt.close(); - return nDeleted; - } - - /** - * Generates a string representation of this table. - */ - public String toString () - { - return "[name=" + name + - ", primaryKeys=" + StringUtil.toString(primaryKeys) + "]"; - } - - /** - * Spearator of name components of compound field. For example, if Java - * class constains component "location" of Point class, which has two - * components "x" and "y", then database table should have columns - * "location_x" and "location_y" (if '_' is used as separator). - */ - public static String fieldSeparator = "_"; - - protected final void init (Class clazz, String tableName, String[] keys, - boolean mixedCaseConvert) - { - name = tableName; - this.mixedCaseConvert = mixedCaseConvert; - _rowClass = clazz; - primaryKeys = keys; - listOfFields = ""; - qualifiedListOfFields = ""; - listOfAssignments = ""; - ArrayList fieldsVector = - new ArrayList(); - nFields = buildFieldsList(fieldsVector, _rowClass, ""); - fields = fieldsVector.toArray(new FieldDescriptor[nFields]); - fMask = new FieldMask(fields); - - try { - constructor = _rowClass.getDeclaredConstructor(new Class[0]); - setBypass.invoke(constructor, bypassFlag); - } catch(Exception ex) {} - - if (keys != null && keys.length > 0) { - primaryKeyIndices = new int[keys.length]; - for (int j = keys.length; --j >= 0;) { - int i = nFields; - while (--i >= 0) { - if (fields[i].name.equals(keys[j])) { - if (!fields[i].isAtomic()) { - throw new IllegalArgumentException( - "Non-atomic primary key provided"); - } - primaryKeyIndices[j] = i; - break; - } - } - if (i < 0) { - throw new NoSuchFieldError("No such field '" + keys[j] - + "' in table " + name); - } - } - } - } - - protected final String convertName (String name) - { - if (mixedCaseConvert) { - return StringUtil.unStudlyName(name); - } else { - return name; - } - } - - protected final int buildFieldsList (ArrayList buf, - Class _rowClass, String prefix) - { - Field[] f = _rowClass.getDeclaredFields(); - - Class superclass = _rowClass; - while ((superclass = superclass.getSuperclass()) != null) { - Field[] inheritedFields = superclass.getDeclaredFields(); - Field[] allFields = new Field[inheritedFields.length + f.length]; - System.arraycopy(inheritedFields, 0, allFields, 0, - inheritedFields.length); - System.arraycopy(f,0, allFields, inheritedFields.length, f.length); - f = allFields; - } - - try { - for (int i = f.length; --i>= 0;) { - setBypass.invoke(f[i], bypassFlag); - } - } catch(Exception ex) { - System.err.println("Failed to set bypass attribute"); - } - - int n = 0; - for (int i = 0; i < f.length; i++) { - if ((f[i].getModifiers()&(Modifier.TRANSIENT|Modifier.STATIC))==0) - { - String name = f[i].getName(); - Class fieldClass = f[i].getType(); - String fullName = prefix + convertName(name); - FieldDescriptor fd = new FieldDescriptor(f[i], fullName); - int type; - - buf.add(fd); - n += 1; - - String c = fieldClass.getName(); - if (c.equals("byte")) type = FieldDescriptor.t_byte; - else if (c.equals("short")) type = FieldDescriptor.t_short; - else if (c.equals("int")) type = FieldDescriptor.t_int; - else if (c.equals("long")) type = FieldDescriptor.t_long; - else if (c.equals("float")) type = FieldDescriptor.t_float; - else if (c.equals("double")) type = FieldDescriptor.t_double; - else if (c.equals("boolean")) type = FieldDescriptor.t_boolean; - else if (c.equals("java.lang.Byte")) - type = FieldDescriptor.tByte; - else if (c.equals("java.lang.Short")) - type = FieldDescriptor.tShort; - else if (c.equals("java.lang.Integer")) - type = FieldDescriptor.tInteger; - else if (c.equals("java.lang.Long")) - type = FieldDescriptor.tLong; - else if (c.equals("java.lang.Float")) - type = FieldDescriptor.tFloat; - else if (c.equals("java.lang.Double")) - type = FieldDescriptor.tDouble; - else if (c.equals("java.lang.Boolean")) - type = FieldDescriptor.tBoolean; - else if (c.equals("java.math.BigDecimal")) - type = FieldDescriptor.tDecimal; - else if (c.equals("java.lang.String")) - type = FieldDescriptor.tString; - else if (fieldClass.equals(BYTE_PROTO.getClass())) - type = FieldDescriptor.tBytes; - else if (c.equals("java.sql.Date")) - type = FieldDescriptor.tDate; - else if (c.equals("java.sql.Time")) - type = FieldDescriptor.tTime; - else if (c.equals("java.sql.Timestamp")) - type = FieldDescriptor.tTimestamp; - else if (c.equals("java.lang.InputStream")) - type = FieldDescriptor.tStream; - else if (c.equals("java.sql.BlobLocator")) - type = FieldDescriptor.tBlob; - else if (c.equals("java.sql.ClobLocator")) - type = FieldDescriptor.tClob; - else if (serializableClass.isAssignableFrom(fieldClass)) - type = FieldDescriptor.tClosure; - else { - int nComponents = buildFieldsList(buf, fieldClass, - fd.name+fieldSeparator); - fd.inType = fd.outType = - FieldDescriptor.tCompound + nComponents; - - try { - fd.constructor = - fieldClass.getDeclaredConstructor(new Class[0]); - setBypass.invoke(fd.constructor, bypassFlag); - } catch(Exception ex) {} - - n += nComponents; - continue; - } - if (listOfFields.length() != 0) { - listOfFields += ","; - qualifiedListOfFields += ","; - listOfAssignments += ","; - } - listOfFields += fullName; - qualifiedListOfFields += this.name + "." + fullName; - listOfAssignments += fullName + "=?"; - - fd.inType = fd.outType = type; - nColumns += 1; - } - } - return n; - } - - protected final String buildListOfAssignments (FieldMask mask) - { - StringBuilder sql = new StringBuilder(); - int fcount = fields.length; - for (int i = 0; i < fcount; i++) { - // skip non-modified fields - if (!mask.isModified(i)) { - continue; - } - // separate fields by a comma - if (sql.length() > 0) { - sql.append(","); - } - // append the necessary SQL to update this column - sql.append(fields[i].name).append("=?"); - } - return sql.toString(); - } - - protected final T load (ResultSet result) throws SQLException - { - T obj; - try { - obj = constructor.newInstance(constructorArgs); - } - catch(IllegalAccessException ex) { throw new IllegalAccessError(); } - catch(InstantiationException ex) { throw new InstantiationError(); } - catch(Exception ex) { - throw new InstantiationError("Exception was thrown by constructor"); - } - load(obj, 0, nFields, 0, result); - return obj; - } - - protected final int load ( - Object obj, int i, int end, int column, ResultSet result) - throws SQLException - { - try { - while (i < end) { - FieldDescriptor fd = fields[i++]; - if (!fd.loadVariable(result, obj, ++column)) { - Object component = - fd.constructor.newInstance(constructorArgs); - fd.field.set(obj, component); - int nComponents = fd.inType - FieldDescriptor.tCompound; - column = load(component, i, i + nComponents, - column-1, result); - i += nComponents; - } - } - } - catch(IllegalAccessException ex) { throw new IllegalAccessError(); } - catch(InstantiationException ex) { throw new InstantiationError(); } - catch(InvocationTargetException ex) { - throw new InstantiationError("Exception was thrown by constructor"); - } - return column; - } - - protected final int bindUpdateVariables(PreparedStatement pstmt, - T obj, - FieldMask mask) - throws SQLException - { - return bindUpdateVariables(pstmt, obj, 0, nFields, 0, mask); - } - - protected final void bindQueryVariables(PreparedStatement pstmt, - T obj, - FieldMask mask) - throws SQLException - { - bindQueryVariables(pstmt, obj, 0, nFields, 0, mask); - } - - protected final void updateVariables(ResultSet result, T obj) - throws SQLException - { - updateVariables(result, obj, 0, nFields, 0); - result.updateRow(); - } - - protected final String buildUpdateWhere() - { - StringBuilder sql = new StringBuilder(); - sql.append(" where ").append(primaryKeys[0]).append(" = ?"); - for (int i = 1; i < primaryKeys.length; i++) { - sql.append(" and ").append(primaryKeys[i]).append(" = ?"); - } - return sql.toString(); - } - - protected final String buildQueryList(T qbe, FieldMask mask, boolean like) - { - StringBuilder buf = new StringBuilder(); - buildQueryList(buf, qbe, 0, nFields, mask, like); - if (buf.length() > 0) { - buf.insert(0, " where "); - } - return "select " + listOfFields + " from " + name + buf; - } - - protected final int bindUpdateVariables ( - PreparedStatement pstmt, Object obj, int i, int end, int column, - FieldMask mask) - throws SQLException - { - try { - while (i < end) { - FieldDescriptor fd = fields[i++]; - Object comp = null; - // skip non-modified fields - if (mask != null && !mask.isModified(i-1)) { - continue; - } - if (!fd.isBuiltin() && (comp = fd.field.get(obj)) == null) { - if (fd.isCompound()) { - int nComponents = fd.outType-FieldDescriptor.tCompound; - while (--nComponents >= 0) { - fd = fields[i++]; - if (!fd.isCompound()) { - pstmt.setNull(++column, - FieldDescriptor.sqlTypeMapping[fd.outType]); - } - } - } else { - pstmt.setNull( - ++column, - FieldDescriptor.sqlTypeMapping[fd.outType]); - } - } else { - if (!fd.bindVariable(pstmt, obj, ++column)) { - int nComponents = fd.outType-FieldDescriptor.tCompound; - column = bindUpdateVariables( - pstmt, comp, i, i+nComponents,column-1, mask); - i += nComponents; - } - } - } - } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } - return column; - } - - protected final int bindQueryVariables ( - PreparedStatement pstmt, Object obj, int i, int end, int column, - FieldMask mask) - throws SQLException - { - try { - while (i < end) { - Object comp; - FieldDescriptor fd = fields[i++]; - if (!fd.field.getDeclaringClass().isInstance(obj)) { - return column; - } - int nComponents = fd.isCompound() ? - fd.outType-FieldDescriptor.tCompound : 0; - - try { - // skip closure fields (because querying by them makes - // no sense) - if (fd.outType == FieldDescriptor.tClosure) { - continue; - } - - // if a field mask is specified, use that to determine - // whether or not the field should be skipped - if (mask != null) { - if (!mask.isModified(i-1)) { - continue; - } - } - - // look up the value of the field - comp = fd.field.get(obj); - - // if no field mask was specified, ignore builtin - // fields and those that are null - if (mask == null && (fd.isBuiltin() || comp == null)) { - continue; - } - - if (!fd.bindVariable(pstmt, obj, ++column)) { - column = bindQueryVariables( - pstmt, comp, i, i+nComponents, column-1, mask); - } - - } finally { - i += nComponents; - } - } - } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } - return column; - } - - protected final void buildQueryList ( - StringBuilder buf, Object qbe, int i, int end, FieldMask mask, - boolean like) - { - try { - while (i < end) { - Object comp; - FieldDescriptor fd = fields[i++]; - int nComponents = - fd.isCompound() ? fd.outType-FieldDescriptor.tCompound : 0; - - try { - // skip closure fields (because querying by them makes - // no sense) - if (fd.outType == FieldDescriptor.tClosure) { - continue; - } - - // if a field mask is specified, use that to determine - // whether or not the field should be skipped - if (mask != null) { - if (!mask.isModified(i-1)) { - continue; - } - } - - // look up the value of the field - comp = fd.field.get(qbe); - - // if no field mask was specified, ignore builtin - // fields and those that are null - if (mask == null && (fd.isBuiltin() || comp == null)) { - continue; - } - - if (nComponents != 0) { - buildQueryList(buf, comp, i, i+nComponents, mask, like); - } else { - if (buf.length() != 0) { - buf.append(" AND "); - } - buf.append(fd.name); - if (like && (comp instanceof String)) { - buf.append(" like?"); - } else { - buf.append("=?"); - } - } - - } finally { - i += nComponents; - } - } - } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } - } - - protected final int updateVariables ( - ResultSet result, Object obj, int i, int end, int column) - throws SQLException - { - try { - while (i < end) { - FieldDescriptor fd = fields[i++]; - Object comp = null; - if (!fd.isBuiltin() && (comp = fd.field.get(obj)) == null) { - if (fd.isCompound()) { - int nComponents = fd.outType-FieldDescriptor.tCompound; - while (--nComponents >= 0) { - fd = fields[i++]; - if (!fd.isCompound()) { - result.updateNull(++column); - } - } - } else { - result.updateNull(++column); - } - } else { - if (!fd.updateVariable(result, obj, ++column)) { - int nComponents = fd.outType-FieldDescriptor.tCompound; - column = updateVariables(result, comp, - i, i+nComponents, column-1); - i += nComponents; - } - } - } - } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } - return column; - } - - protected String name; - protected String listOfFields; - protected String qualifiedListOfFields; - protected String listOfAssignments; - protected Class _rowClass; - - protected boolean mixedCaseConvert = false; - - protected FieldDescriptor[] fields; - protected FieldMask fMask; - - protected int nFields; // length of "fields" array - protected int nColumns; // number of atomic fields in "fields" array - - protected String primaryKeys[]; - protected int primaryKeyIndices[]; - - protected Constructor constructor; - protected static Method setBypass; - - protected static Class serializableClass; - protected static final Object[] bypassFlag = { Boolean.TRUE }; - protected static final Object[] constructorArgs = {}; - - // used to identify byte[] fields - protected static final byte[] BYTE_PROTO = new byte[0]; - - static { - try { - serializableClass = Serializable.class; - Class c = Class.forName("java.lang.reflect.AccessibleObject"); - Class[] param = { Boolean.TYPE }; - setBypass = c.getMethod("setAccessible", param); - } catch(Exception ex) {} - } -} +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001-6 Konstantin Knizhnik, Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc.jora; + +import java.io.Serializable; +import java.util.*; +import java.sql.*; +import java.lang.reflect.*; + +import com.samskivert.jdbc.JDBCUtil; +import com.samskivert.util.StringUtil; + +/** + * Used to establish mapping between corteges of database tables and Java + * classes. This class is responsible for constructing SQL statements for + * extracting, updating and deleting records of the database table. + */ +public class Table +{ + /** + * Constructor for table object. Make association between Java class and + * database table. + * + * @param clazz the class that represents a row entry. + * @param tableName name of database table mapped on this Java class + * @param key table's primary key. This parameter is used in UPDATE/DELETE + * operations to locate record in the table. + * @param mixedCaseConvert whether or not to convert mixed case field + * names into underscore separated uppercase column names. + */ + public Table (Class clazz, String tableName, String key, + boolean mixedCaseConvert) + { + String[] keys = {key}; + init(clazz, tableName, keys, mixedCaseConvert); + } + + /** + * Constructor for table object. Make association between Java class and + * database table. + * + * @param clazz the class that represents a row entry. + * @param tableName name of database table mapped on this Java class + * @param key table's primary key. This parameter is used in UPDATE/DELETE + * operations to locate record in the table. + */ + public Table (Class clazz, String tableName, String key) { + String[] keys = {key}; + init(clazz, tableName, keys, false); + } + + /** + * Constructor for table object. Make association between Java class and + * database table. + * + * @param clazz the class that represents a row entry. + * @param tableName name of database table mapped on this Java class + * @param keys table primary keys. This parameter is used in UPDATE/DELETE + * operations to locate record in the table. + */ + public Table (Class clazz, String tableName, String[] keys) + { + init(clazz, tableName, keys, false); + } + + /** + * Constructor for table object. Make association between Java class and + * database table. + * + * @param clazz the class that represents a row entry. + * @param tableName name of database table mapped on this Java class + * @param keys table primary keys. This parameter is used in UPDATE/DELETE + * operations to locate record in the table. + * @param mixedCaseConvert whether or not to convert mixed case field + * names into underscore separated uppercase column names. + */ + public Table (Class clazz, String tableName, String[] keys, + boolean mixedCaseConvert) + { + init(clazz, tableName, keys, mixedCaseConvert); + } + + /** + * Returns the SQL name of the table on which we operate. + */ + public String getName () + { + return name; + } + + /** + * Select records from database table according to search condition + * + * @param condition valid SQL condition expression started with WHERE or + * empty string if all records should be fetched. + */ + public final Cursor select (Connection conn, String condition) + { + String query = "select " + listOfFields + " from " + name + + " " + condition; + return new Cursor(this, conn, query); + } + + /** + * Select records from database table according to search condition + * including the specified (comma separated) extra tables into the SELECT + * clause to facilitate a join in determining the key. + * + * @param tables the (comma separated) names of extra tables to include in + * the SELECT clause. + * @param condition valid SQL condition expression started with WHERE. + */ + public final Cursor select (Connection conn, String tables, + String condition) + { + String query = "select " + qualifiedListOfFields + + " from " + name + "," + tables + " " + condition; + return new Cursor(this, conn, query); + } + + /** + * Select records from database table according to search condition + * including the specified (comma separated) extra tables into the SELECT + * clause to facilitate a join in determining the key. To facilitate + * situations where data from multiple tables is being combined into a + * single object, the fields will not be qualified with the primary table + * name. + * + * @param tables the (comma separated) names of extra tables to include in + * the SELECT clause. + * @param condition valid SQL condition expression started with WHERE. + */ + public final Cursor join (Connection conn, String tables, + String condition) + { + String query = "select " + listOfFields + + " from " + name + "," + tables + " " + condition; + return new Cursor(this, conn, query); + } + + /** + * Like {@link #join} but does a straight join with the specified table. + */ + public final Cursor straightJoin (Connection conn, String table, + String condition) + { + String query = "select " + listOfFields + + " from " + name + " straight_join " + table + " " + condition; + return new Cursor(this, conn, query); + } + + /** + * Select records from database table using obj object as template. + * + * @param obj example object for search: selected objects should match all + * non-null fields. + */ + public final Cursor queryByExample (Connection conn, T obj) + { + return new Cursor(this, conn, obj, null, false); + } + + /** + * Select records from database table using obj object as template + * for selection. + * + * @param obj example object for search. + * @param mask field mask indicating which fields in the example object + * should be used when building the query. + */ + public final Cursor queryByExample (Connection conn, T obj, + FieldMask mask) + { + return new Cursor(this, conn, obj, mask, false); + } + + /** + * The same as the queryByExample, but string fields for the obj are + * matched using 'like' instead of equals, which allows you to send % in to + * do matching. + */ + public final Cursor queryByLikeExample (Connection conn, T obj) + { + return new Cursor(this, conn, obj, null, true); + } + + /** + * The same as the queryByExample, but string fields for the obj are + * matched using 'like' instead of equals, which allows you to send % in to + * do matching. + */ + public final Cursor queryByLikeExample (Connection conn, T obj, + FieldMask mask) + { + return new Cursor(this, conn, obj, mask, true); + } + + /** + * Insert new record in the table. Values of inserted record fields are + * taken from specifed object. + * + * @param obj object specifing values of inserted record fields + */ + public synchronized void insert (Connection conn, T obj) + throws SQLException + { + String sql = "insert into " + name + + " (" + listOfFields + ") values (?"; + for (int i = 1; i < nColumns; i++) { + sql += ",?"; + } + sql += ")"; + PreparedStatement insertStmt = conn.prepareStatement(sql); + bindUpdateVariables(insertStmt, obj, null); + insertStmt.executeUpdate(); + insertStmt.close(); + } + + /** + * Insert several new records in the table. Values of inserted records + * fields are taken from objects of specified array. + * + * @param objects array with objects specifing values of inserted record + * fields + */ + public synchronized void insert (Connection conn, T[] objects) + throws SQLException + { + String sql = "insert into " + name + + " (" + listOfFields + ") values (?"; + for (int i = 1; i < nColumns; i++) { + sql += ",?"; + } + sql += ")"; + PreparedStatement insertStmt = conn.prepareStatement(sql); + for (int i = 0; i < objects.length; i++) { + bindUpdateVariables(insertStmt, objects[i], null); + insertStmt.addBatch(); + } + insertStmt.executeBatch(); + insertStmt.close(); + } + + /** + * Returns a field mask that can be configured and used to update subsets + * of entire objects via calls to {@link #update(Connection,Object,FieldMask)}. + */ + public FieldMask getFieldMask () + { + return (FieldMask)fMask.clone(); + } + + /** + * Update record in the table using table's primary key to locate record in + * the table and values of fields of specified object obj to alter + * record fields. + * + * @param obj object specifing value of primary key and new values of + * updated record fields + * + * @return number of objects actually updated + */ + public int update (Connection conn, T obj) + throws SQLException + { + return update(conn, obj, null); + } + + /** + * Update record in the table using table's primary key to locate record in + * the table and values of fields of specified object obj to alter + * record fields. Only the fields marked as modified in the supplied field + * mask will be updated in the database. + * + * @param obj object specifing value of primary key and new values of + * updated record fields + * @param mask a {@link FieldMask} instance configured to indicate which of + * the object's fields are modified and should be written to the database. + * + * @return number of objects actually updated + */ + public synchronized int update (Connection conn, T obj, FieldMask mask) + throws SQLException + { + int nUpdated = 0; + String sql = "update " + name + " set " + + (mask != null ? buildListOfAssignments(mask) : listOfAssignments) + + buildUpdateWhere(); + PreparedStatement ustmt = conn.prepareStatement(sql); + int column = bindUpdateVariables(ustmt, obj, mask); + for (int i = 0; i < primaryKeys.length; i++) { + int fidx = primaryKeyIndices[i]; + fields[fidx].bindVariable(ustmt, obj, column+i+1); + } + nUpdated = ustmt.executeUpdate(); + ustmt.close(); + return nUpdated; + } + + /** + * Update set of records in the table using table's primary key to locate + * record in the table and values of fields of objects from sepecifed array + * objects to alter record fields. + * + * @param objects array of objects specifing primiray keys and and new + * values of updated record fields + * + * @return number of objects actually updated + */ + public synchronized int update (Connection conn, T[] objects) + throws SQLException + { + if (primaryKeys == null) { + throw new IllegalStateException( + "No primary key for table " + name + "."); + } + + int nUpdated = 0; + String sql = "update " + name + " set " + listOfAssignments + + buildUpdateWhere(); + PreparedStatement updateStmt = conn.prepareStatement(sql); + for (int i = 0; i < objects.length; i++) { + int column = bindUpdateVariables(updateStmt, objects[i], null); + for (int j = 0; j < primaryKeys.length; j++) { + int fidx = primaryKeyIndices[j]; + fields[fidx].bindVariable( + updateStmt, objects[i], column+1+j); + } + updateStmt.addBatch(); + } + int rc[] = updateStmt.executeBatch(); + for (int k = 0; k < rc.length; k++) { + nUpdated += rc[k]; + } + updateStmt.close(); + return nUpdated; + } + + /** + * Delete record with specified value of primary key from the table. + * + * @param obj object containing value of primary key. + */ + public synchronized int delete (Connection conn, T obj) + throws SQLException + { + if (primaryKeys == null) { + throw new IllegalStateException( + "No primary key for table " + name + "."); + } + int nDeleted = 0; + String sql = "delete from " + name + + " where " + primaryKeys[0] + " = ?"; + for (int i = 1; i < primaryKeys.length; i++) { + sql += " and " + primaryKeys[i] + " = ?"; + } + PreparedStatement deleteStmt = conn.prepareStatement(sql); + for (int i = 0; i < primaryKeys.length; i++) { + fields[primaryKeyIndices[i]].bindVariable(deleteStmt, obj,i+1); + } + nDeleted = deleteStmt.executeUpdate(); + deleteStmt.close(); + return nDeleted; + } + + /** + * Delete records with specified primary keys from the table. + * + * @param objects array of objects containing values of primary key. + * + * @return number of objects actually deleted + */ + public synchronized int delete (Connection conn, T[] objects) + throws SQLException + { + if (primaryKeys == null) { + throw new IllegalStateException( + "No primary key for table " + name + "."); + } + int nDeleted = 0; + String sql = "delete from " + name + + " where " + primaryKeys[0] + " = ?"; + for (int i = 1; i < primaryKeys.length; i++) { + sql += " and " + primaryKeys[i] + " = ?"; + } + PreparedStatement deleteStmt = conn.prepareStatement(sql); + for (int i = 0; i < objects.length; i++) { + for (int j = 0; j < primaryKeys.length; j++) { + fields[primaryKeyIndices[j]].bindVariable( + deleteStmt, objects[i], j+1); + } + deleteStmt.addBatch(); + } + int rc[] = deleteStmt.executeBatch(); + for (int k = 0; k < rc.length; k++) { + nDeleted += rc[k]; + } + deleteStmt.close(); + return nDeleted; + } + + /** + * Generates a string representation of this table. + */ + public String toString () + { + return "[name=" + name + + ", primaryKeys=" + StringUtil.toString(primaryKeys) + "]"; + } + + /** + * Spearator of name components of compound field. For example, if Java + * class constains component "location" of Point class, which has two + * components "x" and "y", then database table should have columns + * "location_x" and "location_y" (if '_' is used as separator). + */ + public static String fieldSeparator = "_"; + + protected final void init (Class clazz, String tableName, String[] keys, + boolean mixedCaseConvert) + { + name = tableName; + this.mixedCaseConvert = mixedCaseConvert; + _rowClass = clazz; + primaryKeys = keys; + listOfFields = ""; + qualifiedListOfFields = ""; + listOfAssignments = ""; + ArrayList fieldsVector = + new ArrayList(); + nFields = buildFieldsList(fieldsVector, _rowClass, ""); + fields = fieldsVector.toArray(new FieldDescriptor[nFields]); + fMask = new FieldMask(fields); + + try { + constructor = _rowClass.getDeclaredConstructor(new Class[0]); + setBypass.invoke(constructor, bypassFlag); + } catch(Exception ex) {} + + if (keys != null && keys.length > 0) { + primaryKeyIndices = new int[keys.length]; + for (int j = keys.length; --j >= 0;) { + int i = nFields; + while (--i >= 0) { + if (fields[i].name.equals(keys[j])) { + if (!fields[i].isAtomic()) { + throw new IllegalArgumentException( + "Non-atomic primary key provided"); + } + primaryKeyIndices[j] = i; + break; + } + } + if (i < 0) { + throw new NoSuchFieldError("No such field '" + keys[j] + + "' in table " + name); + } + } + } + } + + protected final String convertName (String name) + { + if (mixedCaseConvert) { + return StringUtil.unStudlyName(name); + } else { + return name; + } + } + + protected final int buildFieldsList (ArrayList buf, + Class _rowClass, String prefix) + { + Field[] f = _rowClass.getDeclaredFields(); + + Class superclass = _rowClass; + while ((superclass = superclass.getSuperclass()) != null) { + Field[] inheritedFields = superclass.getDeclaredFields(); + Field[] allFields = new Field[inheritedFields.length + f.length]; + System.arraycopy(inheritedFields, 0, allFields, 0, + inheritedFields.length); + System.arraycopy(f,0, allFields, inheritedFields.length, f.length); + f = allFields; + } + + try { + for (int i = f.length; --i>= 0;) { + setBypass.invoke(f[i], bypassFlag); + } + } catch(Exception ex) { + System.err.println("Failed to set bypass attribute"); + } + + int n = 0; + for (int i = 0; i < f.length; i++) { + if ((f[i].getModifiers()&(Modifier.TRANSIENT|Modifier.STATIC))==0) + { + String name = f[i].getName(); + Class fieldClass = f[i].getType(); + String fullName = prefix + convertName(name); + FieldDescriptor fd = new FieldDescriptor(f[i], fullName); + int type; + + buf.add(fd); + n += 1; + + String c = fieldClass.getName(); + if (c.equals("byte")) type = FieldDescriptor.t_byte; + else if (c.equals("short")) type = FieldDescriptor.t_short; + else if (c.equals("int")) type = FieldDescriptor.t_int; + else if (c.equals("long")) type = FieldDescriptor.t_long; + else if (c.equals("float")) type = FieldDescriptor.t_float; + else if (c.equals("double")) type = FieldDescriptor.t_double; + else if (c.equals("boolean")) type = FieldDescriptor.t_boolean; + else if (c.equals("java.lang.Byte")) + type = FieldDescriptor.tByte; + else if (c.equals("java.lang.Short")) + type = FieldDescriptor.tShort; + else if (c.equals("java.lang.Integer")) + type = FieldDescriptor.tInteger; + else if (c.equals("java.lang.Long")) + type = FieldDescriptor.tLong; + else if (c.equals("java.lang.Float")) + type = FieldDescriptor.tFloat; + else if (c.equals("java.lang.Double")) + type = FieldDescriptor.tDouble; + else if (c.equals("java.lang.Boolean")) + type = FieldDescriptor.tBoolean; + else if (c.equals("java.math.BigDecimal")) + type = FieldDescriptor.tDecimal; + else if (c.equals("java.lang.String")) + type = FieldDescriptor.tString; + else if (fieldClass.equals(BYTE_PROTO.getClass())) + type = FieldDescriptor.tBytes; + else if (c.equals("java.sql.Date")) + type = FieldDescriptor.tDate; + else if (c.equals("java.sql.Time")) + type = FieldDescriptor.tTime; + else if (c.equals("java.sql.Timestamp")) + type = FieldDescriptor.tTimestamp; + else if (c.equals("java.lang.InputStream")) + type = FieldDescriptor.tStream; + else if (c.equals("java.sql.BlobLocator")) + type = FieldDescriptor.tBlob; + else if (c.equals("java.sql.ClobLocator")) + type = FieldDescriptor.tClob; + else if (serializableClass.isAssignableFrom(fieldClass)) + type = FieldDescriptor.tClosure; + else { + int nComponents = buildFieldsList(buf, fieldClass, + fd.name+fieldSeparator); + fd.inType = fd.outType = + FieldDescriptor.tCompound + nComponents; + + try { + fd.constructor = + fieldClass.getDeclaredConstructor(new Class[0]); + setBypass.invoke(fd.constructor, bypassFlag); + } catch(Exception ex) {} + + n += nComponents; + continue; + } + if (listOfFields.length() != 0) { + listOfFields += ","; + qualifiedListOfFields += ","; + listOfAssignments += ","; + } + listOfFields += fullName; + qualifiedListOfFields += this.name + "." + fullName; + listOfAssignments += fullName + "=?"; + + fd.inType = fd.outType = type; + nColumns += 1; + } + } + return n; + } + + protected final String buildListOfAssignments (FieldMask mask) + { + StringBuilder sql = new StringBuilder(); + int fcount = fields.length; + for (int i = 0; i < fcount; i++) { + // skip non-modified fields + if (!mask.isModified(i)) { + continue; + } + // separate fields by a comma + if (sql.length() > 0) { + sql.append(","); + } + // append the necessary SQL to update this column + sql.append(fields[i].name).append("=?"); + } + return sql.toString(); + } + + protected final T load (ResultSet result) throws SQLException + { + T obj; + try { + obj = constructor.newInstance(constructorArgs); + } + catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + catch(InstantiationException ex) { throw new InstantiationError(); } + catch(Exception ex) { + throw new InstantiationError("Exception was thrown by constructor"); + } + load(obj, 0, nFields, 0, result); + return obj; + } + + protected final int load ( + Object obj, int i, int end, int column, ResultSet result) + throws SQLException + { + try { + while (i < end) { + FieldDescriptor fd = fields[i++]; + if (!fd.loadVariable(result, obj, ++column)) { + Object component = + fd.constructor.newInstance(constructorArgs); + fd.field.set(obj, component); + int nComponents = fd.inType - FieldDescriptor.tCompound; + column = load(component, i, i + nComponents, + column-1, result); + i += nComponents; + } + } + } + catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + catch(InstantiationException ex) { throw new InstantiationError(); } + catch(InvocationTargetException ex) { + throw new InstantiationError("Exception was thrown by constructor"); + } + return column; + } + + protected final int bindUpdateVariables(PreparedStatement pstmt, + T obj, + FieldMask mask) + throws SQLException + { + return bindUpdateVariables(pstmt, obj, 0, nFields, 0, mask); + } + + protected final void bindQueryVariables(PreparedStatement pstmt, + T obj, + FieldMask mask) + throws SQLException + { + bindQueryVariables(pstmt, obj, 0, nFields, 0, mask); + } + + protected final void updateVariables(ResultSet result, T obj) + throws SQLException + { + updateVariables(result, obj, 0, nFields, 0); + result.updateRow(); + } + + protected final String buildUpdateWhere() + { + StringBuilder sql = new StringBuilder(); + sql.append(" where ").append(primaryKeys[0]).append(" = ?"); + for (int i = 1; i < primaryKeys.length; i++) { + sql.append(" and ").append(primaryKeys[i]).append(" = ?"); + } + return sql.toString(); + } + + protected final String buildQueryList(T qbe, FieldMask mask, boolean like) + { + StringBuilder buf = new StringBuilder(); + buildQueryList(buf, qbe, 0, nFields, mask, like); + if (buf.length() > 0) { + buf.insert(0, " where "); + } + return "select " + listOfFields + " from " + name + buf; + } + + protected final int bindUpdateVariables ( + PreparedStatement pstmt, Object obj, int i, int end, int column, + FieldMask mask) + throws SQLException + { + try { + while (i < end) { + FieldDescriptor fd = fields[i++]; + Object comp = null; + // skip non-modified fields + if (mask != null && !mask.isModified(i-1)) { + continue; + } + if (!fd.isBuiltin() && (comp = fd.field.get(obj)) == null) { + if (fd.isCompound()) { + int nComponents = fd.outType-FieldDescriptor.tCompound; + while (--nComponents >= 0) { + fd = fields[i++]; + if (!fd.isCompound()) { + pstmt.setNull(++column, + FieldDescriptor.sqlTypeMapping[fd.outType]); + } + } + } else { + pstmt.setNull( + ++column, + FieldDescriptor.sqlTypeMapping[fd.outType]); + } + } else { + if (!fd.bindVariable(pstmt, obj, ++column)) { + int nComponents = fd.outType-FieldDescriptor.tCompound; + column = bindUpdateVariables( + pstmt, comp, i, i+nComponents,column-1, mask); + i += nComponents; + } + } + } + } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + return column; + } + + protected final int bindQueryVariables ( + PreparedStatement pstmt, Object obj, int i, int end, int column, + FieldMask mask) + throws SQLException + { + try { + while (i < end) { + Object comp; + FieldDescriptor fd = fields[i++]; + if (!fd.field.getDeclaringClass().isInstance(obj)) { + return column; + } + int nComponents = fd.isCompound() ? + fd.outType-FieldDescriptor.tCompound : 0; + + try { + // skip closure fields (because querying by them makes + // no sense) + if (fd.outType == FieldDescriptor.tClosure) { + continue; + } + + // if a field mask is specified, use that to determine + // whether or not the field should be skipped + if (mask != null) { + if (!mask.isModified(i-1)) { + continue; + } + } + + // look up the value of the field + comp = fd.field.get(obj); + + // if no field mask was specified, ignore builtin + // fields and those that are null + if (mask == null && (fd.isBuiltin() || comp == null)) { + continue; + } + + if (!fd.bindVariable(pstmt, obj, ++column)) { + column = bindQueryVariables( + pstmt, comp, i, i+nComponents, column-1, mask); + } + + } finally { + i += nComponents; + } + } + } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + return column; + } + + protected final void buildQueryList ( + StringBuilder buf, Object qbe, int i, int end, FieldMask mask, + boolean like) + { + try { + while (i < end) { + Object comp; + FieldDescriptor fd = fields[i++]; + int nComponents = + fd.isCompound() ? fd.outType-FieldDescriptor.tCompound : 0; + + try { + // skip closure fields (because querying by them makes + // no sense) + if (fd.outType == FieldDescriptor.tClosure) { + continue; + } + + // if a field mask is specified, use that to determine + // whether or not the field should be skipped + if (mask != null) { + if (!mask.isModified(i-1)) { + continue; + } + } + + // look up the value of the field + comp = fd.field.get(qbe); + + // if no field mask was specified, ignore builtin + // fields and those that are null + if (mask == null && (fd.isBuiltin() || comp == null)) { + continue; + } + + if (nComponents != 0) { + buildQueryList(buf, comp, i, i+nComponents, mask, like); + } else { + if (buf.length() != 0) { + buf.append(" AND "); + } + buf.append(fd.name); + if (like && (comp instanceof String)) { + buf.append(" like?"); + } else { + buf.append("=?"); + } + } + + } finally { + i += nComponents; + } + } + } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + } + + protected final int updateVariables ( + ResultSet result, Object obj, int i, int end, int column) + throws SQLException + { + try { + while (i < end) { + FieldDescriptor fd = fields[i++]; + Object comp = null; + if (!fd.isBuiltin() && (comp = fd.field.get(obj)) == null) { + if (fd.isCompound()) { + int nComponents = fd.outType-FieldDescriptor.tCompound; + while (--nComponents >= 0) { + fd = fields[i++]; + if (!fd.isCompound()) { + result.updateNull(++column); + } + } + } else { + result.updateNull(++column); + } + } else { + if (!fd.updateVariable(result, obj, ++column)) { + int nComponents = fd.outType-FieldDescriptor.tCompound; + column = updateVariables(result, comp, + i, i+nComponents, column-1); + i += nComponents; + } + } + } + } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + return column; + } + + protected String name; + protected String listOfFields; + protected String qualifiedListOfFields; + protected String listOfAssignments; + protected Class _rowClass; + + protected boolean mixedCaseConvert = false; + + protected FieldDescriptor[] fields; + protected FieldMask fMask; + + protected int nFields; // length of "fields" array + protected int nColumns; // number of atomic fields in "fields" array + + protected String primaryKeys[]; + protected int primaryKeyIndices[]; + + protected Constructor constructor; + protected static Method setBypass; + + protected static Class serializableClass; + protected static final Object[] bypassFlag = { Boolean.TRUE }; + protected static final Object[] constructorArgs = {}; + + // used to identify byte[] fields + protected static final byte[] BYTE_PROTO = new byte[0]; + + static { + try { + serializableClass = Serializable.class; + Class c = Class.forName("java.lang.reflect.AccessibleObject"); + Class[] param = { Boolean.TYPE }; + setBypass = c.getMethod("setAccessible", param); + } catch(Exception ex) {} + } +}