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:
023: import java.io.FileInputStream;
024: import java.io.FileOutputStream;
025: import java.io.InputStreamReader;
026: import java.io.OutputStreamWriter;
027: import java.io.IOException;
028:
029: import javax.swing.JTable;
030: import javax.swing.JTextField;
031: import javax.swing.JTextArea;
032: import javax.swing.SwingUtilities;
033: import javax.swing.text.JTextComponent;
034: import java.sql.PreparedStatement;
035: import java.sql.ResultSet;
036:
037: import net.sourceforge.squirrel_sql.fw.datasetviewer.CellDataPopup;
038: import net.sourceforge.squirrel_sql.fw.datasetviewer.ColumnDisplayDefinition;
039: import net.sourceforge.squirrel_sql.fw.sql.ISQLDatabaseMetaData;
040:
041: /**
042: * @author gwg
043: *
044: * This class provides the display components for handling Boolean data types,
045: * specifically SQL type BIT.
046: * The display components are for:
047: * <UL>
048: * <LI> read-only display within a table cell
049: * <LI> editing within a table cell
050: * <LI> read-only or editing display within a separate window
051: * </UL>
052: * The class also contains
053: * <UL>
054: * <LI> a function to compare two display values
055: * to see if they are equal. This is needed because the display format
056: * may not be the same as the internal format, and all internal object
057: * types may not provide an appropriate equals() function.
058: * <LI> a function to return a printable text form of the cell contents,
059: * which is used in the text version of the table.
060: * </UL>
061: * <P>
062: * The components returned from this class extend RestorableJTextField
063: * and RestorableJTextArea for use in editing table cells that
064: * contain values of this data type. It provides the special behavior for null
065: * handling and resetting the cell to the original value.
066: */
067:
068: public class DataTypeBoolean extends BaseDataTypeComponent implements
069: IDataTypeComponent {
070: /* whether nulls are allowed or not */
071: private boolean _isNullable;
072:
073: /* whether number is signed or unsigned */
074: private boolean _isSigned;
075:
076: /* the number of decimal digits allowed in the number */
077: private int _scale;
078:
079: /* table of which we are part (needed for creating popup dialog) */
080: private JTable _table;
081:
082: /* The JTextComponent that is being used for editing */
083: private IRestorableTextComponent _textComponent;
084:
085: /* The CellRenderer used for this data type */
086: //??? For now, use the same renderer as everyone else.
087: //??
088: //?? IN FUTURE: change this to use a new instance of renederer
089: //?? for this data type.
090: private DefaultColumnRenderer _renderer = DefaultColumnRenderer
091: .getInstance();
092:
093: /**
094: * Constructor - save the data needed by this data type.
095: */
096: public DataTypeBoolean(JTable table, ColumnDisplayDefinition colDef) {
097: _table = table;
098: _colDef = colDef;
099: _isNullable = colDef.isNullable();
100: }
101:
102: /**
103: * Return the name of the java class used to hold this data type.
104: */
105: public String getClassName() {
106: return "java.lang.Boolean";
107: }
108:
109: /**
110: * Determine if two objects of this data type contain the same value.
111: * Neither of the objects is null
112: */
113: public boolean areEqual(Object obj1, Object obj2) {
114: return ((Boolean) obj1).equals(obj2);
115: }
116:
117: /*
118: * First we have the methods for in-cell and Text-table operations
119: */
120:
121: /**
122: * Render a value into text for this DataType.
123: */
124: public String renderObject(Object value) {
125: return (String) _renderer.renderObject(value);
126: }
127:
128: /**
129: * This Data Type can be edited in a table cell.
130: */
131: public boolean isEditableInCell(Object originalValue) {
132: return true;
133: }
134:
135: /**
136: * See if a value in a column has been limited in some way and
137: * needs to be re-read before being used for editing.
138: * For read-only tables this may actually return true since we want
139: * to be able to view the entire contents of the cell even if it was not
140: * completely loaded during the initial table setup.
141: */
142: public boolean needToReRead(Object originalValue) {
143: // this DataType does not limit the data read during the initial load of the table,
144: // so there is no need to re-read the complete data later
145: return false;
146: }
147:
148: /**
149: * Return a JTextField usable in a CellEditor.
150: */
151: public JTextField getJTextField() {
152: _textComponent = new RestorableJTextField();
153:
154: // special handling of operations while editing this data type
155: ((RestorableJTextField) _textComponent)
156: .addKeyListener(new KeyTextHandler());
157:
158: //
159: // handle mouse events for double-click creation of popup dialog.
160: // This happens only in the JTextField, not the JTextArea, so we can
161: // make this an inner class within this method rather than a separate
162: // inner class as is done with the KeyTextHandler class.
163: //
164: ((RestorableJTextField) _textComponent)
165: .addMouseListener(new MouseAdapter() {
166: public void mousePressed(MouseEvent evt) {
167: if (evt.getClickCount() == 2) {
168: MouseEvent tableEvt = SwingUtilities
169: .convertMouseEvent(
170: (RestorableJTextField) DataTypeBoolean.this ._textComponent,
171: evt,
172: DataTypeBoolean.this ._table);
173: CellDataPopup.showDialog(
174: DataTypeBoolean.this ._table,
175: DataTypeBoolean.this ._colDef,
176: tableEvt, true);
177: }
178: }
179: }); // end of mouse listener
180:
181: return (JTextField) _textComponent;
182: }
183:
184: /**
185: * Implement the interface for validating and converting to internal object.
186: * Null is a valid successful return, so errors are indicated only by
187: * existance or not of a message in the messageBuffer.
188: */
189: public Object validateAndConvert(String value,
190: Object originalValue, StringBuffer messageBuffer) {
191: // handle null, which is shown as the special string "<null>"
192: if (value.equals("<null>") || value.equals(""))
193: return null;
194:
195: // Do the conversion into the object in a safe manner
196: try {
197: Object obj = Boolean.valueOf(value);
198: return obj;
199: } catch (Exception e) {
200: messageBuffer.append(e.toString() + "\n");
201: //?? do we need the message also, or is it automatically part of the toString()?
202: //messageBuffer.append(e.getMessage());
203: return null;
204: }
205: }
206:
207: /**
208: * If true, this tells the PopupEditableIOPanel to use the
209: * binary editing panel rather than a pure text panel.
210: * The binary editing panel assumes the data is an array of bytes,
211: * converts it into text form, allows the user to change how that
212: * data is displayed (e.g. Hex, Decimal, etc.), and converts
213: * the data back from text to bytes when the user editing is completed.
214: * If this returns false, this DataType class must
215: * convert the internal data into a text string that
216: * can be displayed (and edited, if allowed) in a TextField
217: * or TextArea, and must handle all
218: * user key strokes related to editing of that data.
219: */
220: public boolean useBinaryEditingPanel() {
221: return false;
222: }
223:
224: /*
225: * Now the functions for the Popup-related operations.
226: */
227:
228: /**
229: * Returns true if data type may be edited in the popup,
230: * false if not.
231: */
232: public boolean isEditableInPopup(Object originalValue) {
233: return true;
234: }
235:
236: /*
237: * Return a JTextArea usable in the CellPopupDialog
238: * and fill in the value.
239: */
240: public JTextArea getJTextArea(Object value) {
241: _textComponent = new RestorableJTextArea();
242:
243: // value is a simple string representation of the data,
244: // the same one used in Text and in-cell operations.
245: ((RestorableJTextArea) _textComponent)
246: .setText(renderObject(value));
247:
248: // special handling of operations while editing this data type
249: ((RestorableJTextArea) _textComponent)
250: .addKeyListener(new KeyTextHandler());
251:
252: return (RestorableJTextArea) _textComponent;
253: }
254:
255: /**
256: * Validating and converting in Popup is identical to cell-related operation.
257: */
258: public Object validateAndConvertInPopup(String value,
259: Object originalValue, StringBuffer messageBuffer) {
260: return validateAndConvert(value, originalValue, messageBuffer);
261: }
262:
263: /*
264: * The following is used in both cell and popup operations.
265: */
266:
267: /*
268: * Internal class for handling key events during editing
269: * of both JTextField and JTextArea.
270: */
271: private class KeyTextHandler extends KeyAdapter {
272: public void keyTyped(KeyEvent e) {
273: char c = e.getKeyChar();
274:
275: // as a coding convenience, create a reference to the text component
276: // that is typecast to JTextComponent. this is not essential, as we
277: // could typecast every reference, but this makes the code cleaner
278: JTextComponent _theComponent = (JTextComponent) DataTypeBoolean.this ._textComponent;
279: String text = _theComponent.getText();
280:
281: // let user enter T, t, Y, y, or 1 for true, and
282: // F, f, N, n, or 0 for false
283: if ("TtYy1".indexOf(c) > -1) {
284: // user wants field to be true
285: DataTypeBoolean.this ._textComponent.updateText("true");
286: e.consume();
287: return;
288: }
289: if ("FfNn0".indexOf(c) > -1) {
290: // user wants field to be false
291: DataTypeBoolean.this ._textComponent.updateText("false");
292: e.consume();
293: return;
294: }
295:
296: // tabs and newlines get put into the text before this check,
297: // so remove them
298: // This only applies to Popup editing since these chars are
299: // not passed to this level by the in-cell editor.
300: if (c == KeyEvent.VK_TAB || c == KeyEvent.VK_ENTER) {
301: // remove all instances of the offending char
302: int index = text.indexOf(c);
303: if (index != -1) {
304: if (index == text.length() - 1) {
305: text = text.substring(0, text.length() - 1); // truncate string
306: } else {
307: text = text.substring(0, index)
308: + text.substring(index + 1);
309: }
310: ((IRestorableTextComponent) _theComponent)
311: .updateText(text);
312: _theComponent.getToolkit().beep();
313: }
314: e.consume();
315: }
316:
317: // handle cases of null
318: // The only legal input in this case is a delete
319:
320: if (DataTypeBoolean.this ._isNullable
321: && (c == KeyEvent.VK_BACK_SPACE)
322: || (c == KeyEvent.VK_DELETE)) {
323:
324: // user enters something when field is null
325: if (text.equals("<null>")) {
326: // delete when null => original value
327: DataTypeBoolean.this ._textComponent.restoreText();
328: e.consume();
329: } else {
330: // when field is not null delete means set to null, so do it
331: DataTypeBoolean.this ._textComponent
332: .updateText("<null>");
333: e.consume();
334: }
335: } else {
336: // field is not nullable or input was not a delete
337: //
338: // we have already handled all legal input,
339: // so just tell user this is being ignored
340: _theComponent.getToolkit().beep();
341: e.consume();
342: }
343: }
344: }
345:
346: /*
347: * DataBase-related functions
348: */
349:
350: /**
351: * On input from the DB, read the data from the ResultSet into the appropriate
352: * type of object to be stored in the table cell.
353: */
354: public Object readResultSet(ResultSet rs, int index,
355: boolean limitDataRead) throws java.sql.SQLException {
356:
357: boolean data = rs.getBoolean(index);
358: if (rs.wasNull()) {
359: return null;
360: } else {
361: return Boolean.valueOf(data);
362: }
363: }
364:
365: /**
366: * When updating the database, generate a string form of this object value
367: * that can be used in the WHERE clause to match the value in the database.
368: * A return value of null means that this column cannot be used in the WHERE
369: * clause, while a return of "null" (or "is null", etc) means that the column
370: * can be used in the WHERE clause and the value is actually a null value.
371: * This function must also include the column label so that its output
372: * is of the form:
373: * "columnName = value"
374: * or
375: * "columnName is null"
376: * or whatever is appropriate for this column in the database.
377: */
378: public String getWhereClauseValue(Object value,
379: ISQLDatabaseMetaData md) {
380: if (value == null || value.toString() == null
381: || value.toString().length() == 0) {
382: return _colDef.getLabel() + " IS NULL";
383:
384: } else {
385: String bitValue = DatabaseSpecificBooleanValue
386: .getBooleanValue(value.toString(), md);
387: return _colDef.getLabel() + "=" + bitValue;
388: }
389: }
390:
391: /**
392: * When updating the database, insert the appropriate datatype into the
393: * prepared statment at the given variable position.
394: */
395: public void setPreparedStatementValue(PreparedStatement pstmt,
396: Object value, int position) throws java.sql.SQLException {
397: if (value == null) {
398: pstmt.setNull(position, _colDef.getSqlType());
399: } else {
400: pstmt
401: .setBoolean(position, ((Boolean) value)
402: .booleanValue());
403: }
404: }
405:
406: /**
407: * Get a default value for the table used to input data for a new row
408: * to be inserted into the DB.
409: */
410: public Object getDefaultValue(String dbDefaultValue) {
411: if (dbDefaultValue != null) {
412: // try to use the DB default value
413: StringBuffer mbuf = new StringBuffer();
414: Object newObject = validateAndConvert(dbDefaultValue, null,
415: mbuf);
416:
417: // if there was a problem with converting, then just fall through
418: // and continue as if there was no default given in the DB.
419: // Otherwise, use the converted object
420: if (mbuf.length() == 0)
421: return newObject;
422: }
423:
424: // no default in DB. If nullable, use null.
425: if (_isNullable) {
426: return null;
427: }
428: // field is not nullable, so create a reasonable default value
429: return Boolean.valueOf(true);
430: }
431:
432: /*
433: * File IO related functions
434: */
435:
436: /**
437: * Say whether or not object can be exported to and imported from
438: * a file. We put both export and import together in one test
439: * on the assumption that all conversions can be done both ways.
440: */
441: public boolean canDoFileIO() {
442: return true;
443: }
444:
445: /**
446: * Read a file and construct a valid object from its contents.
447: * Errors are returned by throwing an IOException containing the
448: * cause of the problem as its message.
449: * <P>
450: * DataType is responsible for validating that the imported
451: * data can be converted to an object, and then must return
452: * a text string that can be used in the Popup window text area.
453: * This object-to-text conversion is the same as is done by
454: * the DataType object internally in the getJTextArea() method.
455: *
456: * <P>
457: * File is assumed to be and ASCII string of digits
458: * representing a boolean value.
459: */
460: public String importObject(FileInputStream inStream)
461: throws IOException {
462:
463: InputStreamReader inReader = new InputStreamReader(inStream);
464:
465: int fileSize = inStream.available();
466:
467: char charBuf[] = new char[fileSize];
468:
469: int count = inReader.read(charBuf, 0, fileSize);
470:
471: if (count != fileSize)
472: throw new IOException("Could read only " + count
473: + " chars from a total file size of " + fileSize
474: + ". Import failed.");
475:
476: // convert file text into a string
477: // Special case: some systems tack a newline at the end of
478: // the text read. Assume that if last char is a newline that
479: // we want everything else in the line.
480: String fileText;
481: if (charBuf[count - 1] == KeyEvent.VK_ENTER)
482: fileText = new String(charBuf, 0, count - 1);
483: else
484: fileText = new String(charBuf);
485:
486: // test that the string is valid by converting it into an
487: // object of this data type
488: StringBuffer messageBuffer = new StringBuffer();
489: validateAndConvertInPopup(fileText, null, messageBuffer);
490: if (messageBuffer.length() > 0) {
491: // there was an error in the conversion
492: throw new IOException(
493: "Text does not represent data of type "
494: + getClassName() + ". Text was:\n"
495: + fileText);
496: }
497:
498: // return the text from the file since it does
499: // represent a valid data value
500: return fileText;
501: }
502:
503: /**
504: * Construct an appropriate external representation of the object
505: * and write it to a file.
506: * Errors are returned by throwing an IOException containing the
507: * cause of the problem as its message.
508: * <P>
509: * DataType is responsible for validating that the given text
510: * text from a Popup JTextArea can be converted to an object.
511: * This text-to-object conversion is the same as validateAndConvertInPopup,
512: * which may be used internally by the object to do the validation.
513: * <P>
514: * The DataType object must flush and close the output stream before returning.
515: * Typically it will create another object (e.g. an OutputWriter), and
516: * that is the object that must be flushed and closed.
517: *
518: * <P>
519: * File is assumed to be and ASCII string of digits
520: * representing a value of this data type.
521: */
522: public void exportObject(FileOutputStream outStream, String text)
523: throws IOException {
524:
525: OutputStreamWriter outWriter = new OutputStreamWriter(outStream);
526:
527: // check that the text is a valid representation
528: StringBuffer messageBuffer = new StringBuffer();
529: validateAndConvertInPopup(text, null, messageBuffer);
530: if (messageBuffer.length() > 0) {
531: // there was an error in the conversion
532: throw new IOException(new String(messageBuffer));
533: }
534:
535: // just send the text to the output file
536: outWriter.write(text);
537: outWriter.flush();
538: outWriter.close();
539: }
540: }
|