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