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