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