001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.db.sql.execute;
043:
044: import java.io.IOException;
045: import java.io.InputStream;
046: import java.io.Reader;
047: import java.math.BigDecimal;
048: import java.sql.Blob;
049: import java.sql.Clob;
050: import java.sql.DatabaseMetaData;
051: import java.sql.ResultSet;
052: import java.sql.ResultSetMetaData;
053: import java.sql.SQLException;
054: import java.sql.Time;
055: import java.sql.Timestamp;
056: import java.sql.Types;
057: import java.util.ArrayList;
058: import java.util.Date;
059: import java.util.HashMap;
060: import java.util.List;
061: import java.util.Map;
062: import java.util.logging.Level;
063: import java.util.logging.Logger;
064:
065: /**
066: * This class is used to create a TableModel for a ResultSet.
067: *
068: * @author Andrei Badea
069: */
070: public class ResultSetTableModelSupport {
071:
072: private static Logger LOGGER = Logger
073: .getLogger(ResultSetTableModelSupport.class.getName());
074:
075: /**
076: * Holds the ColumnTypeDef for all the types in java.sql.Types.
077: * Not private because of unit tests.
078: */
079: static final Map<Integer, ColumnTypeDef> TYPE_TO_DEF = new HashMap<Integer, ColumnTypeDef>();
080:
081: /**
082: * The default implementation of ColumnTypeDef used for SQL types for which
083: * there is no value in {@link #TYPE_TO_DEF}.
084: */
085: private static ColumnTypeDef DEFAULT_COLUMN_DEF;
086:
087: static {
088: // editable types
089:
090: ColumnTypeDef booleanTypeDef = new GenericWritableColumnDef(
091: Boolean.class);
092:
093: TYPE_TO_DEF.put(Integer.valueOf(Types.BOOLEAN), booleanTypeDef);
094: TYPE_TO_DEF.put(Integer.valueOf(Types.BIT), booleanTypeDef);
095:
096: ColumnTypeDef integerTypeDef = new GenericWritableColumnDef(
097: Integer.class);
098:
099: TYPE_TO_DEF.put(Integer.valueOf(Types.TINYINT), integerTypeDef);
100: TYPE_TO_DEF
101: .put(Integer.valueOf(Types.SMALLINT), integerTypeDef);
102: TYPE_TO_DEF.put(Integer.valueOf(Types.INTEGER), integerTypeDef);
103:
104: ColumnTypeDef charTypeDef = new GenericWritableColumnDef(
105: String.class);
106:
107: TYPE_TO_DEF.put(Integer.valueOf(Types.CHAR), charTypeDef);
108: TYPE_TO_DEF.put(Integer.valueOf(Types.VARCHAR), charTypeDef);
109:
110: // Issue 15248 - JDBC introduced NCHAR(-15), and NVARCHAR (-9),
111: TYPE_TO_DEF.put(Integer.valueOf(-15), charTypeDef);
112: TYPE_TO_DEF.put(Integer.valueOf(-9), charTypeDef);
113:
114: ColumnTypeDef longTypeDef = new GenericWritableColumnDef(
115: Long.class);
116:
117: TYPE_TO_DEF.put(Integer.valueOf(Types.BIGINT), longTypeDef);
118:
119: ColumnTypeDef floatTypeDef = new GenericWritableColumnDef(
120: Double.class);
121:
122: TYPE_TO_DEF.put(Integer.valueOf(Types.FLOAT), floatTypeDef);
123: TYPE_TO_DEF.put(Integer.valueOf(Types.DOUBLE), floatTypeDef);
124:
125: ColumnTypeDef decimalTypeDef = new GenericWritableColumnDef(
126: BigDecimal.class);
127:
128: TYPE_TO_DEF.put(Integer.valueOf(Types.REAL), decimalTypeDef);
129: TYPE_TO_DEF.put(Integer.valueOf(Types.NUMERIC), decimalTypeDef);
130: TYPE_TO_DEF.put(Integer.valueOf(Types.DECIMAL), decimalTypeDef);
131:
132: ColumnTypeDef dateTypeDef = new GenericWritableColumnDef(
133: Date.class);
134:
135: TYPE_TO_DEF.put(Integer.valueOf(Types.DATE), dateTypeDef);
136:
137: // TIME type must displayed as time -- issue 72607
138:
139: ColumnTypeDef timeTypeDef = new ColumnTypeDef() {
140: public boolean isWritable() {
141: return true;
142: }
143:
144: public Class getColumnClass() {
145: return Time.class;
146: }
147:
148: public Object getColumnValue(ResultSet rs, int column)
149: throws SQLException, IOException {
150: return rs.getTime(column);
151: }
152: };
153:
154: TYPE_TO_DEF.put(Integer.valueOf(Types.TIME), timeTypeDef);
155:
156: // TIMESTAMP type -- ensure that it is displayed as date and time
157: // issue 64165, issue 70521
158:
159: TYPE_TO_DEF.put(Integer.valueOf(Types.TIMESTAMP),
160: new ColumnTypeDef() {
161: public boolean isWritable() {
162: return true;
163: }
164:
165: public Class getColumnClass() {
166: return Timestamp.class;
167: }
168:
169: public Object getColumnValue(ResultSet rs,
170: int column) throws SQLException,
171: IOException {
172: return rs.getTimestamp(column);
173: }
174: });
175:
176: // binary types -- we can't edit them, and
177: // we display them like "0xdeadbeef..."
178:
179: ColumnTypeDef binaryTypeDef = new ColumnTypeDef() {
180: public boolean isWritable() {
181: return false;
182: }
183:
184: public Class getColumnClass() {
185: return Object.class;
186: }
187:
188: public Object getColumnValue(ResultSet rs, int column)
189: throws SQLException, IOException {
190: return BinaryColumnValue.forBinaryColumn(rs, column);
191: }
192: };
193:
194: TYPE_TO_DEF.put(Integer.valueOf(Types.BINARY), binaryTypeDef);
195: TYPE_TO_DEF
196: .put(Integer.valueOf(Types.VARBINARY), binaryTypeDef);
197: TYPE_TO_DEF.put(Integer.valueOf(Types.LONGVARBINARY),
198: binaryTypeDef);
199:
200: // blob type -- we can't edit it, and
201: // we display it like "0xdeadbeef..."
202:
203: ColumnTypeDef blobTypeDef = new ColumnTypeDef() {
204: public boolean isWritable() {
205: return false;
206: }
207:
208: public Class getColumnClass() {
209: return Object.class;
210: }
211:
212: public Object getColumnValue(ResultSet rs, int column)
213: throws SQLException, IOException {
214: return BinaryColumnValue.forBlobColumn(rs, column);
215: }
216: };
217:
218: TYPE_TO_DEF.put(Integer.valueOf(Types.BLOB), blobTypeDef);
219:
220: // long varchar type -- we don't retrieve the full contents (it is too
221: // long), and we display only the first n characters
222:
223: ColumnTypeDef longVarCharTypeDef = new ColumnTypeDef() {
224: public boolean isWritable() {
225: return false;
226: }
227:
228: public Class getColumnClass() {
229: return Object.class;
230: }
231:
232: public Object getColumnValue(ResultSet rs, int column)
233: throws SQLException, IOException {
234: return LongVarCharColumnValue.forCharColumn(rs, column);
235: }
236: };
237:
238: // Issue 125248 - JDBC4 added LONGNVARCHAR(-16) and SQLXML(2009)
239: // We can't use the mnemonic because it would fail to compile
240: // on JDK 6. We can fix this once we only build on JDK6+
241: TYPE_TO_DEF.put(Integer.valueOf(Types.LONGVARCHAR),
242: longVarCharTypeDef);
243: TYPE_TO_DEF.put(Integer.valueOf(-16), longVarCharTypeDef);
244: TYPE_TO_DEF.put(Integer.valueOf(2009), longVarCharTypeDef);
245:
246: // clob type -- we don't retrieve the full contents (it is are too
247: // long), and we display only the first n characters
248:
249: ColumnTypeDef clobTypeDef = new ColumnTypeDef() {
250: public boolean isWritable() {
251: return false;
252: }
253:
254: public Class getColumnClass() {
255: return Object.class;
256: }
257:
258: public Object getColumnValue(ResultSet rs, int column)
259: throws SQLException, IOException {
260: return LongVarCharColumnValue.forClobColumn(rs, column);
261: }
262: };
263:
264: TYPE_TO_DEF.put(Integer.valueOf(Types.CLOB), clobTypeDef);
265:
266: // Issue 125248 - JDBC 4 added NCLOB. We have to use the hardcoded
267: // value because otherwise this class would not build under JDK 5
268: TYPE_TO_DEF.put(Integer.valueOf(2011), clobTypeDef);
269:
270: // other types -- we can hardly edit them
271:
272: ColumnTypeDef otherTypeDef = new ColumnTypeDef() {
273: public boolean isWritable() {
274: return false;
275: }
276:
277: public Class getColumnClass() {
278: return Object.class;
279: }
280:
281: public Object getColumnValue(ResultSet rs, int column)
282: throws SQLException {
283: return rs.getObject(column);
284: }
285: };
286:
287: TYPE_TO_DEF.put(Integer.valueOf(Types.NULL), otherTypeDef);
288: TYPE_TO_DEF.put(Integer.valueOf(Types.OTHER), otherTypeDef);
289: TYPE_TO_DEF.put(Integer.valueOf(Types.JAVA_OBJECT),
290: otherTypeDef);
291: TYPE_TO_DEF.put(Integer.valueOf(Types.DISTINCT), otherTypeDef);
292: TYPE_TO_DEF.put(Integer.valueOf(Types.STRUCT), otherTypeDef);
293: TYPE_TO_DEF.put(Integer.valueOf(Types.ARRAY), otherTypeDef);
294: TYPE_TO_DEF.put(Integer.valueOf(Types.REF), otherTypeDef);
295: TYPE_TO_DEF.put(Integer.valueOf(Types.DATALINK), otherTypeDef);
296:
297: // Issue 125248 - JDBC 4 introduced Types.ROWID. Can't refer to it
298: // directly because it will cause build failure on JDK 5. So using
299: // the hardcoded value of -8 until we start building on JDK 6 only.
300: TYPE_TO_DEF.put(Integer.valueOf(-8), integerTypeDef);
301:
302: }
303:
304: /**
305: * Describes how we handle columns.
306: */
307: private interface ColumnTypeDef {
308:
309: /**
310: * Do we know how to edit this column?
311: */
312: public boolean isWritable();
313:
314: /**
315: * The class used in the table model.
316: */
317: public Class getColumnClass();
318:
319: /**
320: * The value displayed in the table.
321: */
322: public Object getColumnValue(ResultSet rs, int column)
323: throws SQLException, IOException;
324: }
325:
326: /**
327: * Helper ColumnTypeDef for writable types.
328: */
329: private static final class GenericWritableColumnDef implements
330: ColumnTypeDef {
331:
332: private Class columnClass;
333:
334: public GenericWritableColumnDef(Class columnClass) {
335: this .columnClass = columnClass;
336: }
337:
338: public boolean isWritable() {
339: return true;
340: }
341:
342: public Class getColumnClass() {
343: return columnClass;
344: }
345:
346: public Object getColumnValue(ResultSet rs, int column)
347: throws SQLException, IOException {
348: return rs.getObject(column);
349: }
350: }
351:
352: /**
353: * Default ColumnTypeDef implementation: not writable and using
354: * ResultSet.getObject() to read column values.
355: */
356: private static final class DefaultColumnDef implements
357: ColumnTypeDef {
358:
359: public boolean isWritable() {
360: return false;
361: }
362:
363: public Class getColumnClass() {
364: return Object.class;
365: }
366:
367: public Object getColumnValue(ResultSet rs, int column)
368: throws SQLException, IOException {
369: return rs.getObject(column);
370: }
371: }
372:
373: /**
374: * Represents the value of a long varchar or clob column. Instances of this
375: * class are placed in the table model for the result set.
376: */
377: private static final class LongVarCharColumnValue {
378:
379: private static final int COUNT = 100;
380:
381: private String data;
382:
383: public static LongVarCharColumnValue forCharColumn(
384: ResultSet rs, int column) throws SQLException,
385: IOException {
386: Reader reader = rs.getCharacterStream(column);
387: if (reader == null) {
388: return null;
389: }
390: try {
391: return new LongVarCharColumnValue(reader);
392: } finally {
393: reader.close();
394: }
395: }
396:
397: public static LongVarCharColumnValue forClobColumn(
398: ResultSet rs, int column) throws SQLException,
399: IOException {
400: Clob clob = rs.getClob(column);
401: if (clob == null) {
402: return null;
403: }
404: Reader reader = clob.getCharacterStream();
405: if (reader == null) {
406: return null;
407: }
408: try {
409: return new LongVarCharColumnValue(reader);
410: } finally {
411: reader.close();
412: }
413: }
414:
415: private LongVarCharColumnValue(Reader reader)
416: throws SQLException, IOException {
417: char[] charData = new char[COUNT];
418: int read = reader.read(charData, 0, charData.length);
419:
420: if (read >= 0) {
421: data = new String(charData, 0, read);
422:
423: // display an ellipsis if there are more characters in the stream
424: if (reader.read() != -1) {
425: data += "..."; // NOI18N
426: }
427: } else {
428: data = ""; // NOI18N
429: }
430: }
431:
432: public String toString() {
433: return data;
434: }
435: }
436:
437: /**
438: * Represents the value of a binary or blob column. Instances of this
439: * class are placed in the table model for the result set.
440: */
441: private static final class BinaryColumnValue {
442:
443: private static final int COUNT = 100;
444:
445: private String data;
446:
447: public static BinaryColumnValue forBinaryColumn(ResultSet rs,
448: int column) throws SQLException, IOException {
449: InputStream input = rs.getBinaryStream(column);
450: if (input == null) {
451: return null;
452: }
453: try {
454: return new BinaryColumnValue(input);
455: } finally {
456: input.close();
457: }
458: }
459:
460: public static BinaryColumnValue forBlobColumn(ResultSet rs,
461: int column) throws SQLException, IOException {
462: Blob blob = rs.getBlob(column);
463: if (blob == null) {
464: return null;
465: }
466: InputStream input = blob.getBinaryStream();
467: if (input == null) {
468: return null;
469: }
470: try {
471: return new BinaryColumnValue(input);
472: } finally {
473: input.close();
474: }
475: }
476:
477: private BinaryColumnValue(InputStream input)
478: throws SQLException, IOException {
479: byte[] byteData = new byte[COUNT];
480: int read = input.read(byteData, 0, byteData.length);
481:
482: if (read > 0) {
483: StringBuffer buffer = new StringBuffer(2 + 2 * read);
484:
485: buffer.append("0x"); // NOI18N
486: for (int i = 0; i < read; i++) {
487: int b = byteData[i];
488: if (b < 0) {
489: b += 256;
490: }
491: if (b < 16) {
492: buffer.append('0');
493: }
494: buffer.append(Integer.toHexString(b).toUpperCase());
495: }
496:
497: // display an ellipsis if there are more characters in the stream
498: if (input.read() != -1) {
499: buffer.append("..."); // NOI18N
500: }
501:
502: data = buffer.toString();
503: } else {
504: data = ""; // NOI18N
505: }
506: }
507:
508: public String toString() {
509: return data;
510: }
511: }
512:
513: /**
514: * Not private because of unit tests.
515: */
516: static ColumnTypeDef getColumnTypeDef(DatabaseMetaData dbmd,
517: int type) {
518: // Issue 49994: Oracle DATE type needs to be retrieved as full
519: // date and time
520: if (type == Types.DATE && isOracle(dbmd)) {
521: type = Types.TIMESTAMP;
522: }
523:
524: ColumnTypeDef result = (ColumnTypeDef) TYPE_TO_DEF.get(Integer
525: .valueOf(type));
526: if (result != null) {
527: return result;
528: }
529:
530: synchronized (ResultSetTableModelSupport.class) {
531: if (DEFAULT_COLUMN_DEF == null) {
532: DEFAULT_COLUMN_DEF = new DefaultColumnDef();
533: }
534: return DEFAULT_COLUMN_DEF;
535: }
536: }
537:
538: /**
539: * Returns a List of ColumnDef objects or null if the calling thread was
540: * interrupted.
541: */
542: public static List<ColumnDef> createColumnDefs(
543: DatabaseMetaData dbmd, ResultSetMetaData rsmd)
544: throws SQLException {
545: int count = rsmd.getColumnCount();
546: List<ColumnDef> columns = new ArrayList<ColumnDef>(count);
547:
548: for (int i = 1; i <= count; i++) {
549: if (Thread.currentThread().isInterrupted()) {
550: return null;
551: }
552:
553: int type = rsmd.getColumnType(i);
554: ColumnTypeDef ctd = getColumnTypeDef(dbmd, type);
555:
556: // TODO: does writable depend on the result set type (updateable?)
557:
558: // issue 75700: the demo version of the Teradata DB throws SQLException on RSMD.isWritable()
559: boolean writable = false;
560: try {
561: writable = rsmd.isWritable(i) && ctd.isWritable();
562: } catch (SQLException e) {
563: // ignore
564: }
565:
566: ColumnDef column = new ColumnDef(rsmd.getColumnName(i),
567: writable, ctd.getColumnClass());
568:
569: columns.add(column);
570: }
571: return columns;
572: }
573:
574: private static boolean isOracle(DatabaseMetaData dbmd) {
575: try {
576: return "Oracle".equals(dbmd.getDatabaseProductName());
577: } catch (SQLException sqle) {
578: LOGGER.log(Level.WARNING,
579: "Unable to obtain database product name", sqle);
580: return false;
581: }
582: }
583:
584: public static List<List<Object>> retrieveRows(
585: DatabaseMetaData dbmd, ResultSet rs,
586: ResultSetMetaData rsmd, FetchLimitHandler handler)
587: throws SQLException, IOException {
588: List<List<Object>> rows = new ArrayList<List<Object>>();
589: int columnCount = rsmd.getColumnCount();
590: int fetchLimit = handler.getFetchLimit();
591:
592: while (rs.next()) {
593: if (Thread.currentThread().isInterrupted()) {
594: return null;
595: }
596:
597: int fetchCount = rows.size();
598: if (fetchLimit > 0 && fetchCount >= fetchLimit) {
599: fetchLimit = handler.fetchLimitReached(fetchCount);
600: if (fetchLimit != 0 && fetchLimit <= fetchCount) {
601: break;
602: }
603: }
604:
605: List<Object> row = new ArrayList<Object>();
606: for (int i = 1; i <= columnCount; i++) {
607: if (Thread.currentThread().isInterrupted()) {
608: return null;
609: }
610:
611: int type = rsmd.getColumnType(i);
612: ColumnTypeDef ctd = getColumnTypeDef(dbmd, type);
613: Object value = ctd.getColumnValue(rs, i);
614: row.add(value != null ? value : NullValue.getDefault());
615: }
616: rows.add(row);
617: }
618: return rows;
619: }
620: }
|