001: package net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent;
002:
003: /*
004: * Copyright (C) 2001-2003 Colin Bell
005: * colbell@users.sourceforge.net
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: */
021: import java.awt.event.*;
022: import java.awt.GridBagConstraints;
023: import java.awt.GridBagLayout;
024: import java.awt.Insets;
025:
026: import java.io.FileInputStream;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029:
030: import javax.swing.JTable;
031: import javax.swing.JTextField;
032: import javax.swing.JTextArea;
033: import javax.swing.SwingUtilities;
034: import javax.swing.text.JTextComponent;
035: import javax.swing.BorderFactory;
036: import javax.swing.JCheckBox;
037: import javax.swing.event.ChangeListener;
038: import javax.swing.event.ChangeEvent;
039:
040: import java.sql.PreparedStatement;
041: import java.sql.ResultSet;
042: import java.sql.Blob;
043: import java.io.ByteArrayInputStream;
044:
045: import net.sourceforge.squirrel_sql.fw.gui.RightLabel;
046: import net.sourceforge.squirrel_sql.fw.gui.ReadTypeCombo;
047: import net.sourceforge.squirrel_sql.fw.gui.IntegerField;
048: import net.sourceforge.squirrel_sql.fw.gui.OkJPanel;
049:
050: import net.sourceforge.squirrel_sql.fw.datasetviewer.CellDataPopup;
051: import net.sourceforge.squirrel_sql.fw.datasetviewer.ColumnDisplayDefinition;
052: import net.sourceforge.squirrel_sql.fw.sql.ISQLDatabaseMetaData;
053: import net.sourceforge.squirrel_sql.fw.util.StringManager;
054: import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory;
055:
056: /**
057: * @author gwg
058: *
059: * This class provides the display components for handling Blob data types,
060: * specifically SQL type BLOB.
061: * The display components are for:
062: * <UL>
063: * <LI> read-only display within a table cell
064: * <LI> editing within a table cell
065: * <LI> read-only or editing display within a separate window
066: * </UL>
067: * The class also contains
068: * <UL>
069: * <LI> a function to compare two display values
070: * to see if they are equal. This is needed because the display format
071: * may not be the same as the internal format, and all internal object
072: * types may not provide an appropriate equals() function.
073: * <LI> a function to return a printable text form of the cell contents,
074: * which is used in the text version of the table.
075: * </UL>
076: * <P>
077: * The components returned from this class extend RestorableJTextField
078: * and RestorableJTextArea for use in editing table cells that
079: * contain values of this data type. It provides the special behavior for null
080: * handling and resetting the cell to the original value.
081: */
082:
083: public class DataTypeBlob extends BaseDataTypeComponent implements
084: IDataTypeComponent {
085:
086: private static final StringManager s_stringMgr = StringManagerFactory
087: .getStringManager(DataTypeBlob.class);
088:
089: /* whether nulls are allowed or not */
090: private boolean _isNullable;
091:
092: /* table of which we are part (needed for creating popup dialog) */
093: private JTable _table;
094:
095: /* The JTextComponent that is being used for editing */
096: private IRestorableTextComponent _textComponent;
097:
098: /* The CellRenderer used for this data type */
099: //??? For now, use the same renderer as everyone else.
100: //??
101: //?? IN FUTURE: change this to use a new instance of renederer
102: //?? for this data type.
103: private DefaultColumnRenderer _renderer = DefaultColumnRenderer
104: .getInstance();
105:
106: /**
107: * Name of this class, which is needed because the class name is needed
108: * by the static method getControlPanel, so we cannot use something
109: * like getClass() to find this name.
110: */
111: private static final String this ClassName = "net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeBlob";
112:
113: /** Default length of BLOB to read */
114: private static int LARGE_COLUMN_DEFAULT_READ_LENGTH = 255;
115:
116: /*
117: * Properties settable by the user
118: */
119: // flag for whether we have already loaded the properties or not
120: private static boolean propertiesAlreadyLoaded = false;
121:
122: /** Read the contents of Blobs from Result sets when first loading the tables. */
123: private static boolean _readBlobs = false;
124:
125: /**
126: * If <TT>_readBlobs</TT> is <TT>true</TT> this specifies if the complete
127: * BLOB should be read in.
128: */
129: private static boolean _readCompleteBlobs = false;
130:
131: /**
132: * If <TT>_readBlobs</TT> is <TT>true</TT> and <TT>_readCompleteBlobs</TT>
133: * is <tt>false</TT> then this specifies the number of characters to read.
134: */
135: private static int _readBlobsSize = LARGE_COLUMN_DEFAULT_READ_LENGTH;
136:
137: /**
138: * Constructor - save the data needed by this data type.
139: */
140: public DataTypeBlob(JTable table, ColumnDisplayDefinition colDef) {
141: _table = table;
142: _colDef = colDef;
143: _isNullable = colDef.isNullable();
144:
145: loadProperties();
146: }
147:
148: /** Internal function to get the user-settable properties from the DTProperties,
149: * if they exist, and to ensure that defaults are set if the properties have
150: * not yet been created.
151: * <P>
152: * This method may be called from different places depending on whether
153: * an instance of this class is created before the user brings up the Session
154: * Properties window. In either case, the data is static and is set only
155: * the first time we are called.
156: */
157: private static void loadProperties() {
158:
159: //set the property values
160: // Note: this may have already been done by another instance of
161: // this DataType created to handle a different column.
162: if (propertiesAlreadyLoaded == false) {
163: // get parameters previously set by user, or set default values
164: _readBlobs = false; // set to the default
165: String readBlobsString = DTProperties.get(this ClassName,
166: "readBlobs");
167: if (readBlobsString != null
168: && readBlobsString.equals("true"))
169: _readBlobs = true;
170:
171: _readCompleteBlobs = false; // set to the default
172: String readCompleteBlobsString = DTProperties.get(
173: this ClassName, "readCompleteBlobs");
174: if (readCompleteBlobsString != null
175: && readCompleteBlobsString.equals("true"))
176: _readCompleteBlobs = true;
177:
178: _readBlobsSize = LARGE_COLUMN_DEFAULT_READ_LENGTH; // set to default
179: String readBlobsSizeString = DTProperties.get(
180: this ClassName, "readBlobsSize");
181: if (readBlobsSizeString != null)
182: _readBlobsSize = Integer.parseInt(readBlobsSizeString);
183:
184: propertiesAlreadyLoaded = true;
185: }
186: }
187:
188: /**
189: * Return the name of the java class used to hold this data type.
190: */
191: public String getClassName() {
192: return "net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.BlobDescriptor";
193: }
194:
195: /**
196: * Determine if two objects of this data type contain the same value.
197: * Neither of the objects is null
198: */
199: public boolean areEqual(Object obj1, Object obj2) {
200: if (obj1 == obj2)
201: return true;
202:
203: // if both objs are null, then they matched in the previous test,
204: // so at this point we know that at least one of them (or both) is not null.
205: // However, one of them may still be null, and we cannot call equals() on
206: // the null object, so make sure that the one we call it on is not null.
207: // The equals() method handles the other one being null, if it is.
208: if (obj1 != null)
209: return ((BlobDescriptor) obj1)
210: .equals((BlobDescriptor) obj2);
211: else
212: return ((BlobDescriptor) obj2)
213: .equals((BlobDescriptor) obj1);
214: }
215:
216: /*
217: * First we have the methods for in-cell and Text-table operations
218: */
219:
220: /**
221: * Render a value into text for this DataType.
222: */
223: public String renderObject(Object value) {
224: return (String) _renderer.renderObject(value);
225: }
226:
227: /**
228: * This Data Type can be edited in a table cell.
229: * This function is not called during the initial table load, or during
230: * normal table operations.
231: * It is called only when the user enters the cell, either to examine
232: * or to edit the data.
233: * The user may have set the DataType properties to
234: * minimize the data read during the initial table load (to speed it up),
235: * but when they enter this cell we would like to show them the entire
236: * contents of the BLOB.
237: * Therefore we use a call to this function as a trigger to make sure
238: * that we have all of the BLOB data, if that is possible.
239: */
240: public boolean isEditableInCell(Object originalValue) {
241: return wholeBlobRead((BlobDescriptor) originalValue);
242: }
243:
244: /**
245: * See if a value in a column has been limited in some way and
246: * needs to be re-read before being used for editing.
247: * For read-only tables this may actually return true since we want
248: * to be able to view the entire contents of the cell even if it was not
249: * completely loaded during the initial table setup.
250: */
251: public boolean needToReRead(Object originalValue) {
252: // BLOBs are different from normal data types in that what is actually
253: // read from the DB is a descriptor pointing to the data rather than the
254: // data itself. During the initial load of the table, the values read from the
255: // descriptor may have been limited, but the descriptor itself has been
256: // completely read, Therefore we do not need to re-read the datum
257: // from the Database because we know that we have the entire
258: // descriptor. If the contents of the BLOB have been limited during
259: // the initial table load, that will be discovered when we check if
260: // the cell is editable and the full data will be read at that time using
261: // this descriptor.
262: return false;
263: }
264:
265: /**
266: * Return a JTextField usable in a CellEditor.
267: */
268: public JTextField getJTextField() {
269: _textComponent = new RestorableJTextField();
270:
271: // special handling of operations while editing this data type
272: ((RestorableJTextField) _textComponent)
273: .addKeyListener(new KeyTextHandler());
274:
275: //
276: // handle mouse events for double-click creation of popup dialog.
277: // This happens only in the JTextField, not the JTextArea, so we can
278: // make this an inner class within this method rather than a separate
279: // inner class as is done with the KeyTextHandler class.
280: //
281: ((RestorableJTextField) _textComponent)
282: .addMouseListener(new MouseAdapter() {
283: public void mousePressed(MouseEvent evt) {
284: if (evt.getClickCount() == 2) {
285: MouseEvent tableEvt = SwingUtilities
286: .convertMouseEvent(
287: (RestorableJTextField) DataTypeBlob.this ._textComponent,
288: evt,
289: DataTypeBlob.this ._table);
290: CellDataPopup.showDialog(
291: DataTypeBlob.this ._table,
292: DataTypeBlob.this ._colDef,
293: tableEvt, true);
294: }
295: }
296: }); // end of mouse listener
297:
298: return (JTextField) _textComponent;
299: }
300:
301: /**
302: * Implement the interface for validating and converting to internal object.
303: * Null is a valid successful return, so errors are indicated only by
304: * existance or not of a message in the messageBuffer.
305: * If originalValue is null, then we are just checking that the data is
306: * in a valid format (for file import/export) and not actually converting
307: * the data.
308: */
309: public Object validateAndConvert(String value,
310: Object originalValue, StringBuffer messageBuffer) {
311: // handle null, which is shown as the special string "<null>"
312: if (value.equals("<null>"))
313: return null;
314:
315: // Do the conversion into the object in a safe manner
316:
317: //First convert the string representation into the binary bytes it is describing
318: Byte[] byteClassData;
319: try {
320: byteClassData = BinaryDisplayConverter.convertToBytes(
321: value, BinaryDisplayConverter.HEX, false);
322: } catch (Exception e) {
323: messageBuffer.append(e.toString() + "\n");
324: //?? do we need the message also, or is it automatically part of the toString()?
325: //messageBuffer.append(e.getMessage());
326: return null;
327: }
328:
329: byte[] byteData = new byte[byteClassData.length];
330: for (int i = 0; i < byteClassData.length; i++)
331: byteData[i] = byteClassData[i].byteValue();
332:
333: // if the original object is not null, then it contains a Blob object
334: // that we need to re-use, since that is the DBs reference to the blob data area.
335: // Otherwise, we set the original Blob to null, and the write method needs to
336: // know to set the field to null.
337: BlobDescriptor bdesc;
338: if (originalValue == null) {
339: // no existing blob to re-use
340: bdesc = new BlobDescriptor(null, byteData, true, true, 0);
341: } else {
342: // for convenience, cast the existing object
343: bdesc = (BlobDescriptor) originalValue;
344:
345: // create new object to hold the different value, but use the same internal BLOB pointer
346: // as the original
347: bdesc = new BlobDescriptor(bdesc.getBlob(), byteData, true,
348: true, 0);
349: }
350: return bdesc;
351:
352: }
353:
354: /**
355: * If true, this tells the PopupEditableIOPanel to use the
356: * binary editing panel rather than a pure text panel.
357: * The binary editing panel assumes the data is an array of bytes,
358: * converts it into text form, allows the user to change how that
359: * data is displayed (e.g. Hex, Decimal, etc.), and converts
360: * the data back from text to bytes when the user editing is completed.
361: * If this returns false, this DataType class must
362: * convert the internal data into a text string that
363: * can be displayed (and edited, if allowed) in a TextField
364: * or TextArea, and must handle all
365: * user key strokes related to editing of that data.
366: */
367: public boolean useBinaryEditingPanel() {
368: return true;
369: }
370:
371: /*
372: * Now the functions for the Popup-related operations.
373: */
374:
375: /**
376: * Returns true if data type may be edited in the popup,
377: * false if not.
378: */
379: public boolean isEditableInPopup(Object originalValue) {
380: // If all of the data has been read, then the blob can be edited in the Popup,
381: // otherwise it cannot
382: return isEditableInCell(originalValue);
383: }
384:
385: /*
386: * Return a JTextArea usable in the CellPopupDialog
387: * and fill in the value.
388: */
389: public JTextArea getJTextArea(Object value) {
390: _textComponent = new RestorableJTextArea();
391:
392: // value is a simple string representation of the data,
393: // the same one used in Text and in-cell operations.
394: ((RestorableJTextArea) _textComponent)
395: .setText(renderObject(value));
396:
397: // special handling of operations while editing this data type
398: ((RestorableJTextArea) _textComponent)
399: .addKeyListener(new KeyTextHandler());
400:
401: return (RestorableJTextArea) _textComponent;
402: }
403:
404: /**
405: * Validating and converting in Popup is identical to cell-related operation.
406: */
407: public Object validateAndConvertInPopup(String value,
408: Object originalValue, StringBuffer messageBuffer) {
409: return validateAndConvert(value, originalValue, messageBuffer);
410: }
411:
412: /*
413: * The following is used in both cell and popup operations.
414: */
415:
416: /*
417: * Internal class for handling key events during editing
418: * of both JTextField and JTextArea.
419: */
420: private class KeyTextHandler extends BaseKeyTextHandler {
421: public void keyTyped(KeyEvent e) {
422: char c = e.getKeyChar();
423:
424: // as a coding convenience, create a reference to the text component
425: // that is typecast to JTextComponent. this is not essential, as we
426: // could typecast every reference, but this makes the code cleaner
427: JTextComponent _theComponent = (JTextComponent) DataTypeBlob.this ._textComponent;
428: String text = _theComponent.getText();
429:
430: // handle cases of null
431: // The processing is different when nulls are allowed and when they are not.
432: //
433:
434: if (DataTypeBlob.this ._isNullable) {
435:
436: // user enters something when field is null
437: if (text.equals("<null>")) {
438: if ((c == KeyEvent.VK_BACK_SPACE)
439: || (c == KeyEvent.VK_DELETE)) {
440: // delete when null => original value
441: DataTypeBlob.this ._textComponent.restoreText();
442: e.consume();
443: } else {
444: // non-delete when null => clear field and add text
445: DataTypeBlob.this ._textComponent.updateText("");
446: // fall through to normal processing of this key stroke
447: }
448: } else {
449: // check for user deletes last thing in field
450: if ((c == KeyEvent.VK_BACK_SPACE)
451: || (c == KeyEvent.VK_DELETE)) {
452: if (text.length() <= 1) {
453: // about to delete last thing in field, so replace with null
454: DataTypeBlob.this ._textComponent
455: .updateText("<null>");
456: e.consume();
457: }
458: }
459: }
460: } else {
461: // field is not nullable
462: //
463: handleNotNullableField(text, c, e, _textComponent);
464: }
465: }
466: }
467:
468: /*
469: * Make sure the entire BLOB data is read in.
470: * Return true if it has been read successfully, and false if not.
471: */
472: private boolean wholeBlobRead(BlobDescriptor bdesc) {
473: if (bdesc == null)
474: return true; // can use an empty blob for editing
475:
476: if (bdesc.getWholeBlobRead())
477: return true; // the whole blob has been previously read in
478:
479: // data was not fully read in before, so try to do that now
480: try {
481: byte[] data = bdesc.getBlob().getBytes(1,
482: (int) bdesc.getBlob().length());
483:
484: // read succeeded, so reset the BlobDescriptor to match
485: bdesc.setBlobRead(true);
486: bdesc.setData(data);
487: bdesc.setWholeBlobRead(true);
488: bdesc.setUserSetBlobLimit(0);
489:
490: // we successfully read the whole thing
491: return true;
492: } catch (Exception ex) {
493: bdesc.setBlobRead(false);
494: bdesc.setWholeBlobRead(false);
495: bdesc.setData(null);
496: //?? What to do with this error?
497: //?? error message = "Could not read the complete data. Error was: "+ex.getMessage());
498: return false;
499: }
500: }
501:
502: /*
503: * DataBase-related functions
504: */
505:
506: public Object readResultSet(ResultSet rs, int index,
507: boolean limitDataRead) throws java.sql.SQLException {
508:
509: return staticReadResultSet(rs, index);
510: }
511:
512: /**
513: * On input from the DB, read the data from the ResultSet into the appropriate
514: * type of object to be stored in the table cell.
515: */
516: public static Object staticReadResultSet(ResultSet rs, int index)
517: throws java.sql.SQLException {
518:
519: // We always get the BLOB, even when we are not reading the contents.
520: // Since the BLOB is just a pointer to the BLOB data rather than the
521: // data itself, this operation should not take much time (as opposed
522: // to getting all of the data in the blob).
523: Blob blob = rs.getBlob(index);
524:
525: if (rs.wasNull())
526: return null;
527:
528: // BLOB exists, so try to read the data from it
529: // based on the user's directions
530: if (_readBlobs) {
531: // User said to read at least some of the data from the blob
532: byte[] blobData = null;
533: if (blob != null) {
534: int len = (int) blob.length();
535: if (len > 0) {
536: int charsToRead = len;
537: if (!_readCompleteBlobs) {
538: charsToRead = _readBlobsSize;
539: }
540: if (charsToRead > len) {
541: charsToRead = len;
542: }
543: blobData = blob.getBytes(1, charsToRead);
544: }
545: }
546:
547: // determine whether we read all there was in the blob or not
548: boolean wholeBlobRead = false;
549: if (_readCompleteBlobs || blobData.length < _readBlobsSize)
550: wholeBlobRead = true;
551:
552: return new BlobDescriptor(blob, blobData, true,
553: wholeBlobRead, _readBlobsSize);
554: } else {
555: // user said not to read any of the data from the blob
556: return new BlobDescriptor(blob, null, false, false, 0);
557: }
558:
559: }
560:
561: /**
562: * When updating the database, generate a string form of this object value
563: * that can be used in the WHERE clause to match the value in the database.
564: * A return value of null means that this column cannot be used in the WHERE
565: * clause, while a return of "null" (or "is null", etc) means that the column
566: * can be used in the WHERE clause and the value is actually a null value.
567: * This function must also include the column label so that its output
568: * is of the form:
569: * "columnName = value"
570: * or
571: * "columnName is null"
572: * or whatever is appropriate for this column in the database.
573: */
574: public String getWhereClauseValue(Object value,
575: ISQLDatabaseMetaData md) {
576: if (value == null || ((BlobDescriptor) value).getData() == null)
577: return _colDef.getLabel() + " IS NULL";
578: else
579: return ""; // BLOB cannot be used in WHERE clause
580: }
581:
582: /**
583: * When updating the database, insert the appropriate datatype into the
584: * prepared statment at the given variable position.
585: */
586: public void setPreparedStatementValue(PreparedStatement pstmt,
587: Object value, int position) throws java.sql.SQLException {
588: if (value == null || ((BlobDescriptor) value).getData() == null) {
589: pstmt.setNull(position, _colDef.getSqlType());
590: } else {
591: // for convenience cast the object to BlobDescriptor
592: BlobDescriptor bdesc = (BlobDescriptor) value;
593:
594: // There are a couple of possible ways to update the data in the DB.
595: // The first is to use setString like this:
596: // bdesc.getBlob().setString(0, bdesc.getData());
597: // However, the DB2 driver throws an exception saying that that function
598: // is not implemented, so we have to use the other method, which is to use a stream.
599: pstmt.setBinaryStream(position, new ByteArrayInputStream(
600: bdesc.getData()), bdesc.getData().length);
601: }
602: }
603:
604: /**
605: * Get a default value for the table used to input data for a new row
606: * to be inserted into the DB.
607: */
608: public Object getDefaultValue(String dbDefaultValue) {
609: if (dbDefaultValue != null) {
610: // try to use the DB default value
611: StringBuffer mbuf = new StringBuffer();
612: Object newObject = validateAndConvert(dbDefaultValue, null,
613: mbuf);
614:
615: // if there was a problem with converting, then just fall through
616: // and continue as if there was no default given in the DB.
617: // Otherwise, use the converted object
618: if (mbuf.length() == 0)
619: return newObject;
620: }
621:
622: // no default in DB. If nullable, use null.
623: if (_isNullable)
624: return null;
625:
626: // field is not nullable, so create a reasonable default value
627: return null;
628: }
629:
630: /*
631: * File IO related functions
632: */
633:
634: /**
635: * Say whether or not object can be exported to and imported from
636: * a file. We put both export and import together in one test
637: * on the assumption that all conversions can be done both ways.
638: */
639: public boolean canDoFileIO() {
640: return true;
641: }
642:
643: /**
644: * Read a file and construct a valid object from its contents.
645: * Errors are returned by throwing an IOException containing the
646: * cause of the problem as its message.
647: * <P>
648: * DataType is responsible for validating that the imported
649: * data can be converted to an object, and then must return
650: * a text string that can be used in the Popup window text area.
651: * This object-to-text conversion is the same as is done by
652: * the DataType object internally in the getJTextArea() method.
653: *
654: * <P>
655: * File is assumed to be and ASCII string of digits
656: * representing a value of this data type.
657: */
658: public String importObject(FileInputStream inStream)
659: throws IOException {
660:
661: int fileSize = inStream.available();
662:
663: byte[] buf = new byte[fileSize];
664:
665: int count = inStream.read(buf);
666:
667: if (count != fileSize) {
668: throw new IOException("Could read only " + count
669: + " bytes from a total file size of " + fileSize
670: + ". Import failed.");
671: }
672: // Convert bytes to Bytes
673: Byte[] bBytes = new Byte[count];
674: for (int i = 0; i < count; i++) {
675: bBytes[i] = Byte.valueOf(buf[i]);
676: }
677:
678: // return the text converted from the file
679: return BinaryDisplayConverter.convertToString(bBytes,
680: BinaryDisplayConverter.HEX, false);
681: }
682:
683: /**
684: * Construct an appropriate external representation of the object
685: * and write it to a file.
686: * Errors are returned by throwing an IOException containing the
687: * cause of the problem as its message.
688: * <P>
689: * DataType is responsible for validating that the given text
690: * text from a Popup JTextArea can be converted to an object.
691: * This text-to-object conversion is the same as validateAndConvertInPopup,
692: * which may be used internally by the object to do the validation.
693: * <P>
694: * The DataType object must flush and close the output stream before returning.
695: * Typically it will create another object (e.g. an OutputWriter), and
696: * that is the object that must be flushed and closed.
697: *
698: * <P>
699: * File is assumed to be and ASCII string of digits
700: * representing a value of this data type.
701: */
702: public void exportObject(FileOutputStream outStream, String text)
703: throws IOException {
704:
705: Byte[] bBytes = BinaryDisplayConverter.convertToBytes(text,
706: BinaryDisplayConverter.HEX, false);
707:
708: // check that the text is a valid representation
709: StringBuffer messageBuffer = new StringBuffer();
710: validateAndConvertInPopup(text, null, messageBuffer);
711: if (messageBuffer.length() > 0) {
712: // there was an error in the conversion
713: throw new IOException(new String(messageBuffer));
714: }
715:
716: // Convert Bytes to bytes
717: byte[] bytes = new byte[bBytes.length];
718: for (int i = 0; i < bytes.length; i++)
719: bytes[i] = bBytes[i].byteValue();
720:
721: // just send the text to the output file
722: outStream.write(bytes);
723: outStream.flush();
724: outStream.close();
725: }
726:
727: /*
728: * Property change control panel
729: */
730:
731: /**
732: * Generate a JPanel containing controls that allow the user
733: * to adjust the properties for this DataType.
734: * All properties are static accross all instances of this DataType.
735: * However, the class may choose to apply the information differentially,
736: * such as keeping a list (also entered by the user) of table/column names
737: * for which certain properties should be used.
738: * <P>
739: * This is called ONLY if there is at least one property entered into the DTProperties
740: * for this class.
741: * <P>
742: * Since this method is called by reflection on the Method object derived from this class,
743: * it does not need to be included in the Interface.
744: * It would be nice to include this in the Interface for consistancy, documentation, etc,
745: * but the Interface does not seem to like static methods.
746: */
747: public static OkJPanel getControlPanel() {
748:
749: /*
750: * If you add this method to one of the standard DataTypes in the
751: * fw/datasetviewer/cellcomponent directory, you must also add the name
752: * of that DataType class to the list in CellComponentFactory, method
753: * getControlPanels, variable named initialClassNameList.
754: * If the class is being registered with the factory using registerDataType,
755: * then you should not include the class name in the list (it will be found
756: * automatically), but if the DataType is part of the case statement in the
757: * factory method getDataTypeObject, then it does need to be explicitly listed
758: * in the getControlPanels method also.
759: */
760:
761: // if this panel is called before any instances of the class have been
762: // created, we need to load the properties from the DTProperties.
763: loadProperties();
764:
765: return new BlobOkJPanel();
766: }
767:
768: /**
769: * Inner class that extends OkJPanel so that we can call the ok()
770: * method to save the data when the user is happy with it.
771: */
772: private static class BlobOkJPanel extends OkJPanel {
773:
774: private static final long serialVersionUID = 2859310264477848330L;
775:
776: /*
777: * GUI components - need to be here because they need to be
778: * accessible from the event handlers to alter each other's state.
779: */
780: // check box for whether to read contents during table load or not
781: private JCheckBox _showBlobChk = new JCheckBox(
782: // i18n[dataTypeBlob.readOnFirstLoad=Read contents when table is first loaded:]
783: s_stringMgr.getString("dataTypeBlob.readOnFirstLoad"));
784:
785: // label for type combo - used to enable/disable text associated with the combo
786: // i18n[dataTypeBlob.read=Read]
787: private RightLabel _typeDropLabel = new RightLabel(s_stringMgr
788: .getString("dataTypeBlob.read"));
789:
790: // Combo box for read-all/read-part of blob
791: private ReadTypeCombo _blobTypeDrop = new ReadTypeCombo();
792:
793: // text field for how many bytes of Blob to read
794: private IntegerField _showBlobSizeField = new IntegerField(5);
795:
796: public BlobOkJPanel() {
797:
798: /* set up the controls */
799: // checkbox for read/not-read on table load
800: _showBlobChk.setSelected(_readBlobs);
801: _showBlobChk.addChangeListener(new ChangeListener() {
802: public void stateChanged(ChangeEvent e) {
803: _blobTypeDrop.setEnabled(_showBlobChk.isSelected());
804: _typeDropLabel
805: .setEnabled(_showBlobChk.isSelected());
806: _showBlobSizeField.setEnabled(_showBlobChk
807: .isSelected()
808: && (_blobTypeDrop.getSelectedIndex() == 0));
809: }
810: });
811:
812: // Combo box for read-all/read-part of blob
813: _blobTypeDrop = new ReadTypeCombo();
814: _blobTypeDrop
815: .setSelectedIndex((_readCompleteBlobs) ? 1 : 0);
816: _blobTypeDrop.addActionListener(new ActionListener() {
817: public void actionPerformed(ActionEvent e) {
818: _showBlobSizeField.setEnabled(_blobTypeDrop
819: .getSelectedIndex() == 0);
820: }
821: });
822:
823: _showBlobSizeField = new IntegerField(5);
824: _showBlobSizeField.setInt(_readBlobsSize);
825:
826: // handle cross-connection between fields
827: _blobTypeDrop.setEnabled(_readBlobs);
828: _typeDropLabel.setEnabled(_readBlobs);
829: _showBlobSizeField.setEnabled(_readBlobs
830: && !_readCompleteBlobs);
831:
832: /*
833: * Create the panel and add the GUI items to it
834: */
835:
836: setLayout(new GridBagLayout());
837:
838: // i18n[dataTypeBlob.blobType=BLOB (SQL type 2004)]
839: setBorder(BorderFactory.createTitledBorder(s_stringMgr
840: .getString("dataTypeBlob.blobType")));
841: final GridBagConstraints gbc = new GridBagConstraints();
842: gbc.fill = GridBagConstraints.HORIZONTAL;
843: gbc.insets = new Insets(4, 4, 4, 4);
844: gbc.anchor = GridBagConstraints.WEST;
845:
846: gbc.gridx = 0;
847: gbc.gridy = 0;
848:
849: gbc.gridwidth = 1;
850: add(_showBlobChk, gbc);
851:
852: ++gbc.gridx;
853: add(_typeDropLabel, gbc);
854:
855: ++gbc.gridx;
856: add(_blobTypeDrop, gbc);
857:
858: ++gbc.gridx;
859: add(_showBlobSizeField, gbc);
860:
861: } // end of constructor for inner class
862:
863: /**
864: * User has clicked OK in the surrounding JPanel,
865: * so save the current state of all variables
866: */
867: public void ok() {
868: // get the values from the controls and set them in the static properties
869: _readBlobs = _showBlobChk.isSelected();
870: DTProperties.put(this ClassName, "readBlobs", Boolean
871: .valueOf(_readBlobs).toString());
872:
873: _readCompleteBlobs = (_blobTypeDrop.getSelectedIndex() == 0) ? false
874: : true;
875: DTProperties.put(this ClassName, "readCompleteBlobs",
876: Boolean.valueOf(_readCompleteBlobs).toString());
877:
878: _readBlobsSize = _showBlobSizeField.getInt();
879: DTProperties.put(this ClassName, "readBlobsSize", Integer
880: .toString(_readBlobsSize));
881: }
882:
883: } // end of inner class
884: }
|