001: /* ====================================================================
002: * The QueryForm License, Version 1.1
003: *
004: * Copyright (c) 1998 - 2003 David F. Glasser. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution,
020: * if any, must include the following acknowledgment:
021: * "This product includes software developed by
022: * David F. Glasser."
023: * Alternately, this acknowledgment may appear in the software itself,
024: * if and wherever such third-party acknowledgments normally appear.
025: *
026: * 4. The names "QueryForm" and "David F. Glasser" must
027: * not be used to endorse or promote products derived from this
028: * software without prior written permission. For written
029: * permission, please contact dglasser@pobox.com.
030: *
031: * 5. Products derived from this software may not be called "QueryForm",
032: * nor may "QueryForm" appear in their name, without prior written
033: * permission of David F. Glasser.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL DAVID F. GLASSER, THE APACHE SOFTWARE
039: * FOUNDATION OR ITS CONTRIBUTORS, OR ANY AUTHORS OR DISTRIBUTORS
040: * OF THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This product includes software developed by the
051: * Apache Software Foundation (http://www.apache.org/).
052: *
053: * ====================================================================
054: *
055: * $Source: /cvsroot/qform/qform/src/org/glasser/sql/ResultSetBuffer.java,v $
056: * $Revision: 1.9 $
057: * $Author: dglasser $
058: * $Date: 2005/01/26 02:32:13 $
059: *
060: * --------------------------------------------------------------------
061: */
062: package org.glasser.sql;
063:
064: import java.util.*;
065: import java.sql.*;
066: import org.glasser.util.*;
067: import org.glasser.util.comparators.MutableListComparator;
068:
069: public class ResultSetBuffer extends Vector {
070:
071: public static boolean debug = System
072: .getProperty("ResultSetBuffer.debug") != null;
073:
074: protected final static int DEFAULT_READAHEAD = 200;
075:
076: protected int readAhead = DEFAULT_READAHEAD;
077:
078: protected int columnCount = 0;
079:
080: protected ResultSet resultSet = null;
081:
082: protected Connection conn = null;
083:
084: protected boolean endOfResultsReached = false;
085:
086: protected SmartEventListenerList listeners = new SmartEventListenerList();
087:
088: protected String[] columnNames = null;
089:
090: protected Class[] columnClasses = null;
091:
092: protected int[] columnTypes = null;
093:
094: protected String[] nonDisplayableColumnStrings = null;
095:
096: protected int cursor = -1;
097:
098: protected boolean readAheadEnabled = true;
099:
100: protected MutableListComparator comparator = new MutableListComparator();
101:
102: /**
103: * This is the index of the last column on which the buffer was sorted.
104: * Its value is -1 if the buffer has not yet been sorted.
105: */
106: protected int sortColumn = -1;
107:
108: /**
109: * This indicates whether the last sort applied to the buffer was in
110: * descending order or not.
111: */
112: protected boolean descendingSort = false;
113:
114: public void replaceCurrentRow(ResultSet rs) throws SQLException {
115: Vector newRow = readRow(rs);
116: set(cursor, newRow);
117: }
118:
119: /**
120: * Constructs an empty ResultSetBuffer.
121: */
122: public ResultSetBuffer(Column[] columns, Class[] columnClasses) {
123: columnCount = columns.length;
124: columnNames = new String[columnCount];
125: columnTypes = new int[columnCount];
126: this .columnClasses = columnClasses;
127: nonDisplayableColumnStrings = new String[columnCount];
128: for (int j = 0; j < columnCount; j++) {
129: Column column = columns[j];
130: columnNames[j] = column.getColumnName();
131: columnTypes[j] = column.getDataType();
132: if (false == DBUtil.isDisplayableType(columnTypes[j])) {
133: String columnTypeName = column.getTypeName();
134: nonDisplayableColumnStrings[j] = "<" + columnTypeName
135: + ">";
136: }
137: }
138:
139: endOfResultsReached = true;
140: readAheadEnabled = false;
141: }
142:
143: /**
144: * Constructs an empty ResultSetBuffer.
145: */
146: public ResultSetBuffer(Column[] columns) {
147: this (columns, null);
148: columnClasses = new Class[columnCount];
149: for (int j = 0; j < columnCount; j++) {
150: columnClasses[j] = this .getClassForSqlType(columnTypes[j]);
151: }
152: }
153:
154: public ResultSetBuffer(ResultSet rs, int readAhead)
155: throws SQLException {
156: super (readAhead * 2, readAhead);
157: if (readAhead < 1) {
158: throw new IllegalArgumentException(
159: "ResultSetBuffer(ResultSet rs, int readAhead): readAhead argument must be 1 or greater.");
160: }
161: this .readAhead = readAhead;
162: this .resultSet = rs;
163: ResultSetMetaData rsmd = resultSet.getMetaData();
164: columnCount = rsmd.getColumnCount();
165: columnNames = new String[columnCount];
166: columnTypes = new int[columnCount];
167: columnClasses = new Class[columnCount];
168: nonDisplayableColumnStrings = new String[columnCount];
169: for (int j = 0; j < columnCount; j++) {
170: columnNames[j] = rsmd.getColumnName(j + 1);
171: try {
172:
173: columnTypes[j] = rsmd.getColumnType(j + 1);
174:
175: // get the Class name for this column.
176: String columnClassName = null;
177: try {
178: columnClassName = rsmd.getColumnClassName(j + 1);
179: if (debug)
180: System.out.println("columnClassName for "
181: + columnNames[j] + " is "
182: + columnClassName);
183: } catch (SQLException sqlex) {
184: System.out
185: .println("WARNING: ResultSetMetaData.getColumnClassName() failed for column "
186: + (j + 1)
187: + ", SQL Type = "
188: + columnTypes[j] + ": " + sqlex);
189: columnClassName = this .getClassForSqlType(
190: columnTypes[j]).getName();
191: }
192:
193: // now get the Class for this column
194: boolean classNotFound = false;
195: try {
196: columnClasses[j] = Class.forName(columnClassName);
197: if (debug)
198: System.out.println("column class for "
199: + columnNames[j] + " found: "
200: + columnClasses[j].getName());
201: } catch (Exception ex) {
202: System.err.println("column class ("
203: + columnClassName + ") for "
204: + columnNames[j] + " NOT FOUND.");
205: if (DBUtil.isDisplayableType(columnTypes[j])) {
206: columnClasses[j] = getClassForSqlType(columnTypes[j]);
207: } else {
208: classNotFound = true;
209: }
210: }
211: ;
212:
213: // handle non-displayable columns
214: if (classNotFound
215: || false == DBUtil
216: .isDisplayableType(columnTypes[j])
217: || columnClasses[j].isArray()) {
218:
219: String columnTypeName = rsmd
220: .getColumnTypeName(j + 1);
221:
222: // use String.class as the column class, because non-displayable values are not stored in the buffer,
223: // only their nonDisplayableColumnString, to indicate that the column is non-null for
224: // that row.
225: columnClasses[j] = java.lang.String.class;
226: nonDisplayableColumnStrings[j] = "<"
227: + columnTypeName + ">";
228: }
229:
230: } catch (Exception ex) {
231: ex.printStackTrace();
232: columnClasses[j] = Object.class;
233: }
234: }
235: readMoreRows(2 * readAhead);
236: if (size() > 0)
237: cursor = 0;
238:
239: }
240:
241: public void addResultSetBufferListener(
242: ResultSetBufferListener listener) {
243: listeners.add(ResultSetBufferListener.class, listener);
244: }
245:
246: public void removeResultSetBufferListener(
247: ResultSetBufferListener listener) {
248: listeners.remove(ResultSetBufferListener.class, listener);
249: }
250:
251: public void setReadAheadEnabled(boolean b) {
252: this .readAheadEnabled = b;
253: }
254:
255: public boolean isReadAheadEnabled() {
256: return readAheadEnabled;
257: }
258:
259: public ResultSetBuffer(ResultSet rs) throws SQLException {
260: this (rs, DEFAULT_READAHEAD);
261: }
262:
263: public String[] getColumnNames() {
264: return (String[]) columnNames.clone();
265: }
266:
267: public Class[] getColumnClasses() {
268: return columnClasses;
269: }
270:
271: public boolean isEndOfResultsReached() {
272: return endOfResultsReached;
273: }
274:
275: private void readMoreRows() throws SQLException {
276: readMoreRows(readAhead);
277: }
278:
279: private synchronized int readMoreRows(int numRowsToRead)
280: throws SQLException {
281:
282: // when the ResultSetBuffer is "locked",
283: // then no more rows will be read from the resultset.
284: if (readAheadEnabled == false)
285: return 0;
286:
287: if (endOfResultsReached)
288: return 0;
289: double begin = System.currentTimeMillis();
290: int j = 0, rowsRead = 0;
291: for (j = 0; j < numRowsToRead; j++) {
292: if (resultSet.next()) {
293: super .add(readRow(resultSet));
294: rowsRead++;
295: } else {
296: endOfResultsReached = true;
297: break;
298: }
299: }
300: double end = System.currentTimeMillis();
301: if (debug)
302: System.out.println("Reading ahead " + j + " records took "
303: + ((end - begin) / 1000.0d) + " seconds.");
304: if (rowsRead > 0) {
305: ResultSetBufferEvent e = new ResultSetBufferEvent(this ,
306: ResultSetBufferEvent.MORE_ROWS_READ);
307: listeners.fireSmartEvent(e);
308: }
309: if (endOfResultsReached) {
310: ResultSetBufferEvent e = new ResultSetBufferEvent(this ,
311: ResultSetBufferEvent.END_OF_RESULTS_REACHED);
312: listeners.fireSmartEvent(e);
313: }
314:
315: return rowsRead;
316: }
317:
318: protected final Vector readRow(ResultSet rs) throws SQLException {
319: Vector row = new Vector(columnCount);
320: for (int col = 1; col <= columnCount; col++) {
321: Object val = null;
322: try {
323: val = rs.getObject(col);
324: } catch (SQLException ex) {
325: System.out.println("COL: " + col);
326: ex.printStackTrace();
327: val = null;
328: // throw ex;
329: }
330:
331: if (nonDisplayableColumnStrings[col - 1] != null
332: && val != null) {
333: row.add(nonDisplayableColumnStrings[col - 1]);
334: } else {
335: row.add(val);
336: }
337: }
338: return row;
339: }
340:
341: public Vector getPreviousRow() {
342: if (cursor < 1)
343: return null;
344: cursor--;
345: return (Vector) get(cursor);
346: }
347:
348: public Vector getCurrentRow() {
349: if (cursor < 0)
350: return null;
351: return (Vector) get(cursor);
352: }
353:
354: public Vector getCurrentRowClone() {
355: Vector v = getCurrentRow();
356: if (v == null)
357: return null;
358: return (Vector) v.clone();
359: }
360:
361: /**
362: * Removes the row at the cursor and repositions the cursor
363: * if necessary. If the buffer is empty after the remove, the cursor
364: * is set at -1.
365: */
366: public void removeCurrentRow() throws SQLException {
367: if (cursor < 0)
368: return;
369: remove(cursor);
370:
371: // if the cursor is beyond the end of the buffer, attempt
372: // to read more rows
373: if (cursor >= size()) {
374: readMoreRows();
375: if (cursor >= size()) {
376: // if cursor is still beyond the end of the buffer, it means
377: // no more rows could be read, so move the cursor back one position.
378: // if the buffer is now empty, it means the row at position 0 was
379: // removed and the cursor will now be at -1.
380: cursor = size() - 1;
381: }
382: }
383: }
384:
385: public Vector getNextRow() {
386: //System.out.println("getNextRow - cursor = " + cursor + " size = " + size());
387: if (endOfResultsReached && (cursor >= (size() - 1)))
388: return null;
389: int temp = cursor;
390: try {
391: cursor++;
392: return getRowAt(cursor);
393: } catch (RuntimeException ex) {
394: cursor = temp;
395: throw ex;
396: }
397: }
398:
399: public Vector getRowAt(int row) {
400: // System.out.println("getRowAt(" + row + ")");
401: if (endOfResultsReached == false && (size() - readAhead) <= row) {
402: try {
403: readMoreRows();
404: } catch (SQLException ex) {
405: ex.printStackTrace();
406: throw new RuntimeException(
407: "An SQLException occurred in ResultSetBuffer.getRowAt("
408: + row + ")");
409: }
410: }
411: return (Vector) super .get(row);
412: }
413:
414: public Object get(int index) {
415: return getRowAt(index);
416: }
417:
418: public Object elementAt(int index) {
419: return getRowAt(index);
420: }
421:
422: public Vector getFirstRow() {
423: if (isEmpty())
424: return null;
425: cursor = 0;
426: return (Vector) get(cursor);
427: }
428:
429: public boolean isAtBeginning() {
430: return (cursor == 0);
431: }
432:
433: public boolean isAtEnd() {
434: return (cursor >= size() - 1);
435: }
436:
437: public int getCursor() {
438: return cursor;
439: }
440:
441: public Vector[] getCurrentRowset() {
442: return (Vector[]) toArray(new Vector[0]);
443: }
444:
445: public void setCursor(int value) {
446: if (value > size() - 1) {
447: new Throwable().printStackTrace();
448: throw new ArrayIndexOutOfBoundsException(
449: "ResultSetBuffer.setCursor(): Cursor value "
450: + value + " is out of range. (Size is "
451: + size() + ".)");
452: }
453: cursor = value;
454: }
455:
456: /**
457: * This method can be called with values that are greater than the current
458: * number of records in the buffer. If the value is out of range, it will
459: * attempt to read in enough records from the ResultSet so that the cursor
460: * can be set to the new value. If it is successful, true is returned, if
461: * not, false is returned.
462: */
463: public synchronized boolean maybeSetCursor(int value) {
464: int size = size();
465: if (value < size) {
466: cursor = value;
467: return true;
468: } else if (endOfResultsReached) {
469: return false;
470: }
471:
472: // at this point we know there are unread rows in
473: // the resultset, so calculate how many we'll need
474: // to read in.
475:
476: int rowsNeeded = value - size + 1;
477:
478: // attempt to read in some multiple of the "readAhead" value
479: // that will bring in the needed number of rows, while keeping
480: // the cursor location more than "readAhead" rows from
481: // the end of the buffer.
482:
483: int multiple = (rowsNeeded / readAhead) + 2;
484: int rowsToRead = multiple * readAhead;
485:
486: try {
487: int rowsRead = readMoreRows(rowsToRead);
488: if (rowsRead >= rowsNeeded) {
489: cursor = value;
490: return true;
491: } else {
492: return false;
493: }
494: } catch (SQLException ex) {
495: ex.printStackTrace();
496: throw new RuntimeException(
497: "An SQLException occurred in ResultSetBuffer.maybeSetCusror("
498: + value + ")");
499: }
500: }
501:
502: public void sort(int columnIndex, boolean sortDescending) {
503:
504: if (debug)
505: System.out.println("sort(" + columnIndex + ", "
506: + sortDescending + ")");
507:
508: boolean saveReadAheadEnabled = readAheadEnabled;
509:
510: try {
511:
512: setReadAheadEnabled(false);
513: this .comparator.setElementIndex(columnIndex);
514: this .comparator.setSortDescending(sortDescending);
515:
516: Vector selectedRow = null;
517: if (cursor > -1) {
518: selectedRow = this .getRowAt(cursor);
519: if (debug) {
520: System.out.println("Before sort, selected row is "
521: + cursor);
522: }
523:
524: }
525: Collections.sort(this , comparator);
526:
527: if (selectedRow != null) {
528: int newCursor = indexOf(selectedRow);
529: if (newCursor > -1) {
530: cursor = newCursor;
531: if (debug) {
532: System.out
533: .println("After sort, selected row is "
534: + cursor);
535: }
536: }
537: }
538:
539: // remember the parameters of this sort
540: sortColumn = columnIndex;
541: descendingSort = sortDescending;
542:
543: // notify listeners.
544: ResultSetBufferEvent e = new ResultSetBufferEvent(this ,
545: ResultSetBufferEvent.BUFFER_SORTED);
546: listeners.fireSmartEvent(e);
547: } finally {
548: setReadAheadEnabled(saveReadAheadEnabled);
549: }
550: }
551:
552: /**
553: * Sorts the buffer on the indicated column, and returns true if the sort was in
554: * descending order, or false if it was in ascending order. The order
555: * will usually be ascending, unless the same column is sorted multiple times
556: * consecutively, in which case the sort order is reversed each time.
557: */
558: public boolean sort(int columnIndex) {
559: boolean desc = false;
560:
561: // if the column to be sorted is the same as the last column
562: // that was sorted, then reverse the last sort order.
563: if (this .sortColumn == columnIndex)
564: desc = !descendingSort;
565:
566: sort(columnIndex, desc);
567:
568: return desc;
569:
570: }
571:
572: /**
573: * This is called when the Java Class corresponding to a particular columns SQL type
574: * cannot be determined through ResultSetMetaData.getColumnClassName(). If the general types
575: * returned by this method (String, Number, java.util.Date and Object) are used with
576: * actual data, for instance, by a TableModel, then problems could occur if the
577: * types are not actually correct.
578: */
579: private Class getClassForSqlType(int sqlType) {
580: if (DBUtil.isCharType(sqlType))
581: return java.lang.String.class;
582: else if (DBUtil.isNumericType(sqlType))
583: return java.lang.Number.class;
584: else if (DBUtil.isDateTimeType(sqlType))
585: return java.util.Date.class;
586: else
587: return Object.class;
588: }
589:
590: }
|