001: package dinamica;
002:
003: import java.text.MessageFormat;
004: import java.util.ArrayList;
005: import java.util.Collections;
006: import java.util.Comparator;
007: import java.util.HashMap;
008: import java.util.Iterator;
009: import java.io.Serializable;
010: import java.io.StringWriter;
011: import java.io.PrintWriter;
012: import java.sql.*;
013:
014: /**
015: * Core-level framework class: Recordset - General purpose, tabular data structure.
016: * <br><br>
017: * Represents a disconnected recordset, a kind of multi-purpose ResultSet
018: * without connection to the database, much like
019: * the famous ADO disconnected recordset from VB6,
020: * can be created from a SQL query or manufactured
021: * by code to represent any tabular data. It is a kind of
022: * generic data container that may save a lot of work because
023: * it can be used to represent several entities avoiding the need
024: * to write custom classes to represent each specific entity.
025: * It is serializable, so it can be safely used in HTTP Sessions, JNDI, etc.
026: * <br><br>
027: * This recordset mantains all data in memory and does not need
028: * an open connection to the database, provides several constructors
029: * to isolate the programmer from the task of programming with the
030: * JDBC API, but also provides low level access if desired.
031: * <br><br>
032: * Each field in the recordset is mantained in its native data type
033: * as represented by the JDBC driver, or a java null value.
034: * <br>
035: * <br>
036: * Creation date: 10/09/2003<br>
037: * Last Update: 11/09/2003<br>
038: * (c) 2003 Martin Cordova<br>
039: * This code is released under the LGPL license<br>
040: * @author Martin Cordova (dinamica@martincordova.com)
041: */
042: public class Recordset implements Serializable {
043:
044: /**
045: *
046: */
047: private static final long serialVersionUID = 1L;
048:
049: /** contains list of fields */
050: private HashMap<String, RecordsetField> _fields = new HashMap<String, RecordsetField>();
051:
052: /** contains list of records */
053: private ArrayList<Record> _data = new ArrayList<Record>();
054:
055: /** recordset position (0...N-1)*/
056: private int _recordNumber = -1;
057:
058: /** paging support */
059: private int _pageCount = 0;
060: private int _pageSize = 0;
061: private int _currentPage = 0;
062:
063: /** recordset ID */
064: private String _ID = null;
065:
066: private String _lastSortColName = null;
067: private String _lastSortMode = null;
068:
069: /**
070: * Return records per page (page size) for recordsets
071: * used in paged views
072: * @return
073: */
074: public int getPageSize() {
075: return _pageSize;
076: }
077:
078: /**
079: * Create a recordset with one record containing
080: * general recordset parameters like: recordcount, pagecount, currentpage
081: * @return Recordset Object
082: * @throws Throwable
083: */
084: public Recordset getRecordsetInfo() throws Throwable {
085:
086: Recordset rs = new Recordset();
087: rs.append("recordcount", Types.INTEGER);
088: rs.append("pagecount", Types.INTEGER);
089: rs.append("currentpage", Types.INTEGER);
090: rs.append(_ID + ".recordcount", Types.INTEGER);
091:
092: rs.addNew();
093: rs.setValue("recordcount", new Integer(_data.size()));
094: rs.setValue("pagecount", new Integer(_pageCount));
095: rs.setValue("currentpage", new Integer(_currentPage));
096: rs.setValue(_ID + ".recordcount", new Integer(_data.size()));
097:
098: return rs;
099:
100: }
101:
102: /**
103: * Set recordset ID (an arbitrary String)
104: * @param id
105: */
106: public void setID(String id) {
107: _ID = id;
108: }
109:
110: /**
111: * Paging support.<br>
112: * Returns the number of record pages determined by setPageSize
113: */
114: public int getPageCount() {
115: return _pageCount;
116: }
117:
118: /**
119: * Paging support.<br>
120: * Returns the current page number (1...N)
121: */
122: public int getPageNumber() {
123: return _currentPage;
124: }
125:
126: /**
127: * Paging support.<br>
128: * Define page size (records per page)
129: */
130: public void setPageSize(int p) throws Throwable {
131:
132: if (p <= 0) {
133: throw new Throwable("Invalid page size, must be > 0!");
134: }
135:
136: if (_data.size() == 0) {
137: throw new Throwable(
138: "Invalid page size, recordset is empty!");
139: }
140:
141: _pageSize = p;
142:
143: java.math.BigDecimal b1 = new java.math.BigDecimal(_data.size());
144: java.math.BigDecimal b2 = new java.math.BigDecimal(_pageSize);
145: _pageCount = b1.divide(b2, java.math.BigDecimal.ROUND_UP)
146: .intValue();
147:
148: if (getRecordCount() > 0)
149: _currentPage = 1;
150:
151: }
152:
153: /**
154: * Paging support.<br>
155: * Get Recordset representing the requeste page of records
156: */
157: @SuppressWarnings("unchecked")
158: public Recordset getPage(int p) throws Throwable {
159:
160: if (p < 1 || p > _pageCount)
161: throw new Throwable("Invalid page number: " + p
162: + " - the Recordset contains " + _pageCount
163: + " pages.");
164:
165: _currentPage = p;
166:
167: /* calculate first and last row numbers for this page */
168: int row1 = (p - 1) * _pageSize;
169: int row2 = (p * _pageSize) - 1;
170: if (row2 > (_data.size() - 1))
171: row2 = _data.size() - 1;
172:
173: /*
174: create an identical recordset
175: containing only the records
176: for the requested page
177: */
178: ArrayList<Record> newData = new ArrayList<Record>(_pageSize);
179: for (int i = row1; i <= row2; i++) {
180: newData.add((Record) _data.get(i));
181: }
182: Recordset x = new Recordset();
183: x.setFields((HashMap<String, RecordsetField>) _fields.clone());
184: x.setData(newData);
185:
186: /* return "page" */
187: return x;
188:
189: }
190:
191: /**
192: * Feed the recordset metadata (column structure)
193: * @param fields HashMap containing the recordset field objects
194: */
195: @SuppressWarnings("unchecked")
196: protected void setFields(HashMap fields) {
197: this ._fields = fields;
198: }
199:
200: /**
201: * Feed the recordset data
202: * @param data ArrayList containing record objects
203: */
204: @SuppressWarnings("unchecked")
205: protected void setData(ArrayList data) {
206: this ._data = data;
207: }
208:
209: /**
210: * Returns the current record position (0...N-1)
211: * @return
212: */
213: public int getRecordNumber() {
214: return _recordNumber;
215: }
216:
217: /**
218: * Returns the number of records in recordset
219: * @return
220: */
221: public int getRecordCount() {
222: return _data.size();
223: }
224:
225: /**
226: * Returns number of fields in recordset
227: * @return
228: */
229: public int getFieldCount() {
230: return _fields.size();
231: }
232:
233: /**
234: * Returns HashMap containing RecordsetField objects
235: * representing the Recordset fields
236: * @return
237: */
238: public HashMap<String, RecordsetField> getFields() {
239: return _fields;
240: }
241:
242: /**
243: * Returns ArrayList containing the recordset data (the records)
244: * @return
245: */
246: public ArrayList<Record> getData() {
247: return _data;
248: }
249:
250: /**
251: * Append a field to the recordset structure.<br>
252: * It is used when creating a Recordset from a JDBC query
253: * @param fieldName Field Name
254: * @param nativeSqlType SQL native data type name
255: * @param type JDBC data type (java.sql.Types)
256: */
257: private void append(String fieldName, String nativeSqlType, int type) {
258: RecordsetField f = new RecordsetField(fieldName, nativeSqlType,
259: type);
260: _fields.put(fieldName, f);
261: }
262:
263: /**
264: * Append a field to the recordset structure.<br>
265: * It is used when manufacturing a Recordset from code
266: * @param fieldName Field Name
267: * @param nativeSqlType SQL native data type name
268: * @param type JDBC data type (java.sql.Types) - only INTEGER, LONG, VARCHAR, DATE, TIMESTAMP or DOUBLE are supported
269: */
270: public void append(String fieldName, int type)
271: throws RecordsetException {
272:
273: String sqlTypeName = null;
274:
275: switch (type) {
276: case Types.INTEGER:
277: sqlTypeName = "INTEGER";
278: break;
279:
280: case Types.BIGINT:
281: sqlTypeName = "LONG";
282: break;
283:
284: case Types.VARCHAR:
285: sqlTypeName = "VARCHAR";
286: break;
287:
288: case Types.DATE:
289: sqlTypeName = "DATE";
290: break;
291:
292: case Types.TIMESTAMP:
293: sqlTypeName = "TIMESTAMP";
294: break;
295:
296: case Types.DOUBLE:
297: sqlTypeName = "DOUBLE";
298: break;
299:
300: }
301:
302: if (sqlTypeName == null) {
303: String args[] = { String.valueOf(type) };
304: String msg = Errors.INVALID_DATATYPE;
305: msg = MessageFormat.format(msg, (Object[]) args);
306: throw new RecordsetException(msg);
307: }
308:
309: append(fieldName, sqlTypeName, type);
310:
311: }
312:
313: /**
314: * Add a record to the recordset and set record number position
315: * to the new inserted record
316: *
317: */
318: public void addNew() {
319:
320: HashMap<String, Object> values = new HashMap<String, Object>();
321: Iterator<String> i = _fields.keySet().iterator();
322: while (i.hasNext()) {
323: String f = (String) i.next();
324: values.put(f, null);
325: }
326:
327: _data.add(new Record(values));
328:
329: /* set record number */
330: _recordNumber++;
331:
332: }
333:
334: /**
335: * Set record position inside recordset
336: * @param recNum Record Number (0...getRecordCount()-1)
337: * @throws Throwable
338: */
339: public void setRecordNumber(int recNum) throws RecordsetException {
340:
341: checkRecordPosition(recNum);
342: _recordNumber = recNum;
343:
344: }
345:
346: /**
347: * Set field value for current record (determined by getRecordNumber())
348: * @param fieldName Field Name
349: * @param value Field Value (Date, String, int, double, null)
350: * @throws Throwable
351: */
352: public void setValue(String fieldName, Object value)
353: throws RecordsetException {
354:
355: checkRecordPosition();
356:
357: RecordsetField f;
358: try {
359: f = getField(fieldName);
360: } catch (Throwable e) {
361: throw new RecordsetException(e.getMessage());
362: }
363:
364: if (value != null) {
365: switch (f.getType()) {
366:
367: case java.sql.Types.DATE:
368: if (!(value instanceof java.util.Date))
369: throw new RecordsetException(
370: "Invalid data type of field: "
371: + fieldName
372: + "; passed value must be a DATE object.");
373: break;
374:
375: case java.sql.Types.INTEGER:
376: if (!(value instanceof java.lang.Integer))
377: throw new RecordsetException(
378: "Invalid data type of field: "
379: + fieldName
380: + "; passed value must be an INTEGER object.");
381: break;
382:
383: case java.sql.Types.DOUBLE:
384: if (!(value instanceof java.lang.Double))
385: throw new RecordsetException(
386: "Invalid data type of field: "
387: + fieldName
388: + "; passed value must be an DOUBLE object.");
389: break;
390: }
391: }
392:
393: Record rec = (Record) _data.get(_recordNumber);
394: rec.setValue(fieldName, value);
395:
396: }
397:
398: /**
399: * Return field value given a field name
400: * @param fieldName Field Name. May be reserved field names: _rowIndex (0...N-1) or _rowNumber (1...N)
401: * @return
402: * @throws Throwable
403: */
404: public Object getValue(String fieldName) throws Throwable {
405:
406: /* check for valid cursor position */
407: checkRecordPosition();
408:
409: /* special treatment for reserved field names */
410: if (fieldName.equals("_rowIndex")) {
411: return new Integer(_recordNumber);
412: } else if (fieldName.equals("_rowNumber")) {
413: return new Integer(_recordNumber + 1);
414: } else {
415: Record rec = (Record) _data.get(_recordNumber);
416: return rec.getFieldValue(fieldName);
417: }
418:
419: }
420:
421: /**
422: * Fill recordset with resultset data and metadata. It is the
423: * responsability of the caller of this method to close the resultset
424: * and other jdbc objects involved. The resultset must be positioned before
425: * the first record
426: * @param rs Resultset
427: * @throws Throwable
428: */
429: private void loadRecords(java.sql.ResultSet rs) throws Throwable {
430:
431: /* default fields */
432: //append("_rowIndex", "INTEGER", Types.INTEGER);
433: //append("_rowNumber", "INTEGER", Types.INTEGER);
434: /* load field definitions */
435: ResultSetMetaData md = rs.getMetaData();
436: int cols = md.getColumnCount();
437: for (int i = 1; i <= cols; i++) {
438: append(md.getColumnName(i).toLowerCase(), md
439: .getColumnTypeName(i), md.getColumnType(i));
440: }
441:
442: /* load data */
443: while (rs.next()) {
444: HashMap<String, Object> flds = new HashMap<String, Object>(
445: cols);
446: for (int i = 1; i <= cols; i++) {
447: flds.put(md.getColumnName(i).toLowerCase(), rs
448: .getObject(i));
449: }
450: _data.add(new Record(flds));
451: }
452:
453: }
454:
455: /**
456: * Create a recordset given a resultset. It is the
457: * responsability of the caller of this method to close the resultset
458: * and other jdbc objects involved.
459: * @param rs ResultSet positiones before the first record
460: * @throws Throwable
461: */
462: public Recordset(ResultSet rs) throws Throwable {
463: loadRecords(rs);
464: }
465:
466: /**
467: * Creates a recordset given a SQL query
468: * @param conn Database Connection
469: * @param sql SQL Query that returns a Resultset
470: * @throws Throwable
471: */
472: public Recordset(Connection conn, String sql) throws Throwable {
473: this (conn, sql, 0);
474: }
475:
476: /**
477: * Creates a recordset given a SQL query.
478: * @param conn Database Connection
479: * @param sql SQL Query that returns a Resultset
480: * @param limit Maximum number of rows to read from the DataBase
481: * @throws Throwable
482: */
483: public Recordset(java.sql.Connection conn, String sql, int limit)
484: throws Throwable {
485:
486: ResultSet rs = null;
487: Statement stmt = null;
488:
489: try {
490:
491: /* execute query */
492: stmt = conn.createStatement();
493:
494: if (limit > 0)
495: stmt.setMaxRows(limit);
496:
497: rs = stmt.executeQuery(sql);
498: loadRecords(rs);
499:
500: } catch (Throwable e) {
501: throw e;
502: } finally {
503: if (rs != null)
504: rs.close();
505: if (stmt != null)
506: stmt.close();
507: }
508:
509: }
510:
511: /**
512: * Default constructor used when creating
513: * the recordset from code (a "manufactured" recordset)
514: *
515: */
516: public Recordset() {
517: /* default fields */
518: //append("_rowIndex", "INTEGER", Types.INTEGER);
519: //append("_rowNumber", "INTEGER", Types.INTEGER);
520: }
521:
522: /**
523: * Move pointer to next record. Returns true if not EOF
524: * @return
525: */
526: public boolean next() {
527: if (_recordNumber < (_data.size() - 1)) {
528: _recordNumber++;
529: return true;
530: } else {
531: return false;
532: }
533: }
534:
535: /**
536: * Returns a text-formatted report with the
537: * structure of the recordset, very usefull for
538: * debugging purposes
539: */
540: public String toString() {
541: StringWriter sw = new StringWriter(1000);
542: PrintWriter pw = new PrintWriter(sw);
543:
544: pw.println("Recordset Information");
545: pw.println("Record Count: " + getRecordCount());
546: pw.println("Field Count: " + getFieldCount());
547: pw.println("Structure:");
548: pw.println("----------------------------------");
549: pw.println("NAME|SQL-TYPE-NAME|JDBC-TYPE-ID");
550:
551: Iterator<RecordsetField> i = _fields.values().iterator();
552: while (i.hasNext()) {
553: RecordsetField f = (RecordsetField) i.next();
554:
555: pw.println(f.getName() + "|" + f.getSqlTypeName() + "|"
556: + f.getType());
557: }
558:
559: return sw.toString();
560: }
561:
562: /**
563: * Set cursor position before first record,
564: * it is like a "rewind" command
565: *
566: */
567: public void top() {
568: _recordNumber = -1;
569: }
570:
571: /**
572: * Set cursor position on first record
573: * @throws Throwable
574: */
575: public void first() throws Throwable {
576: setRecordNumber(0);
577: }
578:
579: /**
580: * Set cursor position on last record
581: * @throws Throwable
582: */
583: public void last() throws Throwable {
584: setRecordNumber(_data.size() - 1);
585: }
586:
587: /**
588: * Delete record (from memory)
589: * @param recNum Record Number (0..N-1)
590: */
591: public void delete(int recNum) throws Throwable {
592: checkRecordPosition(recNum);
593: _data.remove(recNum);
594: _recordNumber--;
595: }
596:
597: /**
598: * Return a Recordset field object describing its properties
599: * @param fieldName Field name to locate the field object
600: * @return Reference to Recordset field
601: * @throws Throwable if fieldName does not exist in Recordset metadata
602: */
603: public RecordsetField getField(String fieldName) throws Throwable {
604: if (_fields.containsKey(fieldName))
605: return (RecordsetField) _fields.get(fieldName);
606: else
607: throw new Throwable("Field not found:" + fieldName);
608: }
609:
610: /**
611: * Copy record values from this recordset to
612: * a destination recordset, using the current
613: * record of both recordsets. Destination recordset
614: * fields that match names with source recordset fields
615: * must be of the same type too. It does not matter if some
616: * fields from the source recordset are not defined in the destination
617: * recordset. Only name-matching fields will be considered
618: * for the operation.
619: * @param rs Destination recordsets
620: * @throws Throwable
621: */
622: public void copyValues(Recordset rs) throws Throwable {
623: checkRecordPosition();
624: HashMap<String, RecordsetField> flds = rs.getFields();
625: Iterator<RecordsetField> i = _fields.values().iterator();
626: while (i.hasNext()) {
627: RecordsetField f = (RecordsetField) i.next();
628: String name = f.getName();
629: if (flds.containsKey(name)) {
630: rs.setValue(name, getValue(name));
631: }
632: }
633:
634: }
635:
636: /**
637: * Wrapper method for getValue() - avoids casting the data type
638: * @param colName Column name to retrieve its value from the current record
639: * @return The column value in its native data type
640: * @throws Throwable
641: */
642: public String getString(String colName) throws Throwable {
643: Object obj = getValue(colName);
644: if (obj != null)
645: return String.valueOf(obj);
646: else
647: return null;
648: }
649:
650: /**
651: * Wrapper method for getValue() - avoids casting the data type
652: * @param colName Column name to retrieve its value from the current record
653: * @return The column value in its native data type
654: * @throws Throwable
655: */
656: public java.util.Date getDate(String colName) throws Throwable {
657: java.util.Date d = null;
658: d = (java.util.Date) getValue(colName);
659: return d;
660: }
661:
662: /**
663: * Wrapper method for getValue() - avoids casting the data type
664: * @param colName Column name to retrieve its value from the current record
665: * @return The column value in its native data type
666: * @throws Throwable
667: */
668: public double getDouble(String colName) throws Throwable {
669: Double d = new Double(String.valueOf(getValue(colName)));
670: return d.doubleValue();
671: }
672:
673: /**
674: * Wrapper method for getValue() - avoids casting the data type
675: * @param colName Column name to retrieve its value from the current record
676: * @return The column value in its native data type
677: * @throws Throwable
678: */
679: public int getInt(String colName) throws Throwable {
680: Integer i = new Integer(String.valueOf(getValue(colName)));
681: return i.intValue();
682: }
683:
684: /**
685: * Wrapper method for getValue() - avoids casting the data type
686: * @param colName Column name to retrieve its value from the current record
687: * @return The column value in its native data type
688: * @throws Throwable
689: */
690: public Integer getInteger(String colName) throws Throwable {
691: Integer i = new Integer(String.valueOf(getValue(colName)));
692: return i;
693: }
694:
695: /**
696: * Tests if the give column value is null for the
697: * current record
698: * @param colName Column name
699: * @return TRUE if the value is null
700: * @throws Throwable If record position is not valid or the column does not exist in the recordset
701: */
702: public boolean isNull(String colName) throws Throwable {
703: if (getValue(colName) == null)
704: return true;
705: else
706: return false;
707: }
708:
709: /**
710: * Check if recordset contains field with a given name
711: * @param name Name of the field to check its existence
712: * @return TRUE if field exists, FALSE if not
713: */
714: public boolean containsField(String name) {
715: if (_fields.containsKey(name))
716: return true;
717: else
718: return false;
719: }
720:
721: /**
722: * Tests if a given record number represents
723: * a valid record position in the Recordset
724: * @param recNum Record number (between 0...N-1 where N is the number of records)
725: * @throws RecordsetException If the test fails
726: */
727: private void checkRecordPosition(int recNum)
728: throws RecordsetException {
729: if (recNum < 0 || recNum > _data.size() - 1) {
730:
731: StringBuffer errMsg = new StringBuffer();
732:
733: errMsg.append("Invalid record position: " + recNum + "; ");
734: if (recNum == -1)
735: errMsg
736: .append("After creating a Recordset you must move to a valid record using next(), first(), last() or setRecordNumber() methods before attempting read/write operations with any record of this Recordset; ");
737: errMsg
738: .append("This Recordset contains "
739: + _data.size()
740: + " record(s); Set the record position between 0 and N-1 where N is the number of records.");
741:
742: throw new RecordsetException(errMsg.toString());
743:
744: }
745: }
746:
747: /**
748: * Tests if the current record number represents
749: * a valid record position in the Recordset. This overload
750: * reuses the checkRecordPosition(int recNum) method.
751: * @throws RecordsetException If the test fails
752: */
753: private void checkRecordPosition() throws RecordsetException {
754: checkRecordPosition(this ._recordNumber);
755: }
756:
757: /**
758: * Set the children recordset for the current record
759: * @param rs Children recordset
760: * @throws Throwable If the record position is not valid
761: */
762: public void setChildrenRecordset(Recordset rs) throws Throwable {
763: checkRecordPosition();
764: Record rec = (Record) _data.get(_recordNumber);
765: rec.setChildren(rs);
766: }
767:
768: /**
769: * Retrieve current record's children recordset
770: * @return A reference to the children recordset or null if no children recordset exists
771: * @throws Throwable If the record position is not valid
772: */
773: public Recordset getChildrenRecordset() throws Throwable {
774: checkRecordPosition();
775: Record rec = (Record) _data.get(_recordNumber);
776: return rec.getChildren();
777: }
778:
779: /**
780: * Sort Recordset data in ascending order by the given column
781: * @param col Name of the column to be used for the sort
782: * @throws Throwable
783: */
784: @SuppressWarnings("unchecked")
785: public void sort(String col) throws Throwable {
786:
787: //patch 2007-06-19
788: //when ordering for 2nd time on the same column, apply descending order
789: if (_lastSortColName != null && _lastSortColName.equals(col)) {
790: if (_lastSortMode == null)
791: _lastSortMode = "desc";
792: else
793: _lastSortMode = null;
794:
795: } else {
796: _lastSortMode = null;
797: _lastSortColName = col;
798: }
799:
800: Comparator comp = new Comp(col, _lastSortMode);
801: Collections.sort(_data, comp);
802:
803: }
804:
805: /**
806: * Comparator inner class that provides sorting support
807: */
808: class Comp implements Serializable, Comparator<Record> {
809: /**
810: *
811: */
812: private static final long serialVersionUID = 1L;
813: private String _sortCol = null;
814: private String _sortMode = null;
815:
816: public Comp(String colName, String sortMode) throws Throwable {
817: _sortMode = sortMode;
818: _sortCol = colName;
819:
820: if (!containsField(colName))
821: throw new Throwable(
822: "Invalid column name passed to sort() method: "
823: + colName);
824: }
825:
826: @SuppressWarnings("unchecked")
827: public int compare(Record r1, Record r2) {
828:
829: /*Record r1 = (Record)o1;
830: Record r2 = (Record)o2;*/
831:
832: int result = 0;
833:
834: try {
835: if (r1.getFieldValue(_sortCol) == null) {
836: result = 0;
837: } else if (r2.getFieldValue(_sortCol) == null) {
838: result = 1;
839: } else {
840: Comparable<Object> x1 = (Comparable) r1
841: .getFieldValue(_sortCol);
842: result = x1.compareTo(r2.getFieldValue(_sortCol));
843:
844: //descending order?
845: if (_sortMode != null) {
846: if (result < 0)
847: result = 1;
848: else if (result > 0)
849: result = -1;
850: }
851: }
852: } catch (Throwable e) {
853: System.err.println("SORT ERROR: " + e.getMessage());
854: }
855:
856: return result;
857:
858: }
859:
860: }
861:
862: /**
863: * Retrieve recordset metadata. This method returns
864: * a Recordset containing the columns: name, typename, typeid.
865: * "typeid" is the java.sql.Type value<br>
866: * There will be one record for each column.
867: * @return Recordset
868: * @throws Throwable
869: */
870: public Recordset getMetaData() throws Throwable {
871:
872: // prepare the new Recordset structure
873: Recordset rs = new Recordset();
874: rs.append("name", java.sql.Types.VARCHAR);
875: rs.append("typename", java.sql.Types.VARCHAR);
876: rs.append("typeid", java.sql.Types.INTEGER);
877:
878: //get recordset structure
879: HashMap<String, RecordsetField> flds = this .getFields();
880: Iterator<RecordsetField> i = flds.values().iterator();
881:
882: // fill the structure with the data
883: while (i.hasNext()) {
884: RecordsetField f = (RecordsetField) i.next();
885: rs.addNew();
886: rs.setValue("name", f.getName());
887: rs.setValue("typename", f.getSqlTypeName());
888: rs.setValue("typeid", new Integer(f.getType()));
889: }
890:
891: return rs;
892:
893: }
894:
895: /**
896: * Duplicate the Recordset structure in a new Recordset without any data
897: * @return Empty Recordset with the same structure
898: * @throws Throwable
899: */
900: public Recordset copyStructure() throws Throwable {
901:
902: //new recordset
903: Recordset newRS = new Recordset();
904:
905: //get metadata
906: Recordset infoRS = getMetaData();
907: infoRS.top();
908: while (infoRS.next()) {
909: // obtain the column's data
910: String name = infoRS.getString("name");
911: int jdbcTypeId = infoRS.getInt("typeid");
912:
913: //add column def
914: newRS.append(name, jdbcTypeId);
915: }
916:
917: return newRS;
918:
919: }
920:
921: /**
922: * Clear current record values, set every field's value to null
923: */
924: public void clear() throws Throwable {
925: checkRecordPosition();
926: Iterator<RecordsetField> i = _fields.values().iterator();
927: while (i.hasNext()) {
928: RecordsetField f = (RecordsetField) i.next();
929: setValue(f.getName(), null);
930: }
931: }
932:
933: /**
934: * Find a record where a column's value matches a given value
935: * @param colName Column to use for the search
936: * @param value Value to search for
937: * @return Record position (0...N-1) or -1 if not found
938: * @throws Throwable
939: */
940: public int findRecord(String colName, int value) throws Throwable {
941: int rc = -1;
942:
943: top();
944: while (next()) {
945: if (value == getInt(colName)) {
946: rc = this .getRecordNumber();
947: break;
948: }
949: }
950:
951: return rc;
952: }
953:
954: /**
955: * Find a record where a column's value matches a given value
956: * @param colName Column to use for the search
957: * @param value Value to search for
958: * @return Record position (0...N-1) or -1 if not found
959: * @throws Throwable
960: */
961: public int findRecord(String colName, String value)
962: throws Throwable {
963: int rc = -1;
964:
965: top();
966: while (next()) {
967: if (value.equals(getString(colName))) {
968: rc = this .getRecordNumber();
969: break;
970: }
971: }
972:
973: return rc;
974: }
975:
976: /**
977: * Find a record where a column's value matches a given Date value
978: * @param colName Column to use for the search
979: * @param value Date value to search for
980: * @return Record position (0...N-1) or -1 if not found
981: * @throws Throwable
982: */
983: public int findRecord(String colName, java.util.Date value)
984: throws Throwable {
985: int rc = -1;
986:
987: top();
988: while (next()) {
989: if (value.compareTo(getDate(colName)) == 0) {
990: rc = this.getRecordNumber();
991: break;
992: }
993: }
994:
995: return rc;
996: }
997:
998: }
|