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.IOException;
026:
027: import javax.swing.JTextField;
028: import javax.swing.JTextArea;
029: import javax.swing.JTable;
030: import javax.swing.SwingUtilities;
031: import javax.swing.BorderFactory;
032: import javax.swing.text.JTextComponent;
033: import javax.swing.JCheckBox;
034: import java.sql.PreparedStatement;
035: import java.sql.ResultSet;
036:
037: import net.sourceforge.squirrel_sql.fw.sql.ISQLDatabaseMetaData;
038: import net.sourceforge.squirrel_sql.fw.util.StringManager;
039: import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory;
040: import net.sourceforge.squirrel_sql.fw.datasetviewer.CellDataPopup;
041: import net.sourceforge.squirrel_sql.fw.datasetviewer.ColumnDisplayDefinition;
042: import net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.IDataTypeComponent;
043: import net.sourceforge.squirrel_sql.fw.gui.OkJPanel;
044:
045: /**
046: * @author gwg
047: *
048: * This class provides the display components for handling SQL JAVA_OBJECT data types,
049: * specifically SQL type JAVA_OBJECT.
050: * <P>
051: * The default SQuirreL code can handle only JDBC-standard defined data types.
052: * Since this data type represents DBMS-specific enhancements or
053: * user-defined data types, we cannot do anything intelligent with the data.
054: * We allow the user to select one of two modes of operation:
055: * <DL>
056: * <LI>
057: * we can try to get the contents of the DB element and print it as a string, or,
058: * <LI>
059: * we will display an appropriately internationalized version of "<JAVA_OBJECT>".
060: * </DL>
061: * In either case, the data will be stored and processed as a String.
062: * <P>
063: * The user may not edit the contents of this field in either the cell or popup
064: * because we do not understand the structure or limitations of the contents,
065: * and therefore cannot validate it or put it back into the DB.
066: * The field is not used in the WHERE clause because we do not know whether
067: * or not it might contain binary data, and because we do not know how to
068: * format the data for SQL operations.
069: * <P>
070: * To handle these data types more intelligently and allow editing on them,
071: * DBMS-specific plug-ins will need to be developed to register handlers
072: * for instances of this type.
073: */
074: public class DataTypeJavaObject extends BaseDataTypeComponent implements
075: IDataTypeComponent {
076: /* whether nulls are allowed or not */
077: private boolean _isNullable;
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: /** Internationalized strings for this class, shared/copied from ResultSetReader. */
086: private static final StringManager s_stringMgr = StringManagerFactory
087: .getStringManager(DataTypeJavaObject.class);
088:
089: /* The CellRenderer used for this data type */
090: //??? For now, use the same renderer as everyone else.
091: //??
092: //?? IN FUTURE: change this to use a new instance of renederer
093: //?? for this data type.
094: private DefaultColumnRenderer _renderer = DefaultColumnRenderer
095: .getInstance();
096:
097: /**
098: * Name of this class, which is needed because the class name is needed
099: * by the static method getControlPanel, so we cannot use something
100: * like getClass() to find this name.
101: */
102: private static final String this ClassName = "net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeJavaObject";
103:
104: /*
105: * Properties settable by the user
106: */
107: // flag for whether we have already loaded the properties or not
108: private static boolean propertiesAlreadyLoaded = false;
109:
110: /** Read the contents of JavaObject from Result sets when first loading the tables. */
111: private static boolean _readSQLJavaObject = true;
112:
113: /**
114: * Constructor - save the data needed by this data type.
115: */
116: public DataTypeJavaObject(JTable table,
117: ColumnDisplayDefinition colDef) {
118: _table = table;
119: _colDef = colDef;
120: _isNullable = colDef.isNullable();
121:
122: loadProperties();
123: }
124:
125: /** Internal function to get the user-settable properties from the DTProperties,
126: * if they exist, and to ensure that defaults are set if the properties have
127: * not yet been created.
128: * <P>
129: * This method may be called from different places depending on whether
130: * an instance of this class is created before the user brings up the Session
131: * Properties window. In either case, the data is static and is set only
132: * the first time we are called.
133: */
134: private static void loadProperties() {
135:
136: //set the property values
137: // Note: this may have already been done by another instance of
138: // this DataType created to handle a different column.
139: if (propertiesAlreadyLoaded == false) {
140: // get parameters previously set by user, or set default values
141: _readSQLJavaObject = true; // set to the default
142: String readSQLJavaObjectString = DTProperties.get(
143: this ClassName, "readSQLJavaObject");
144: if (readSQLJavaObjectString != null
145: && readSQLJavaObjectString.equals("false"))
146: _readSQLJavaObject = false;
147:
148: propertiesAlreadyLoaded = true;
149: }
150: }
151:
152: /**
153: * Return the name of the java class used to hold this data type.
154: * For JavaObject, this will always be a string.
155: */
156: public String getClassName() {
157: return "java.lang.String";
158: }
159:
160: /**
161: * Determine if two objects of this data type contain the same value.
162: * Neither of the objects is null
163: */
164: public boolean areEqual(Object obj1, Object obj2) {
165: return obj1 == obj2 || obj1 != null && obj1.equals(obj2);
166: //return ((String)obj1).equals(obj2);
167: }
168:
169: /*
170: * First we have the cell-related and Text-table operations.
171: */
172:
173: /**
174: * Render a value into text for this DataType.
175: */
176: public String renderObject(Object value) {
177: return (String) _renderer.renderObject(value);
178: }
179:
180: /**
181: * This Data Type can be edited in a table cell.
182: */
183: public boolean isEditableInCell(Object originalValue) {
184: return false;
185: }
186:
187: /**
188: * See if a value in a column has been limited in some way and
189: * needs to be re-read before being used for editing.
190: * For read-only tables this may actually return true since we want
191: * to be able to view the entire contents of the cell even if it was not
192: * completely loaded during the initial table setup.
193: */
194: public boolean needToReRead(Object originalValue) {
195: // this DataType does not limit the data read during the initial load of the table,
196: // so there is no need to re-read the complete data later
197: return false;
198: }
199:
200: /**
201: * Return a JTextField usable in a CellEditor.
202: */
203: public JTextField getJTextField() {
204: _textComponent = new RestorableJTextField();
205:
206: // special handling of operations while editing this data type
207: ((RestorableJTextField) _textComponent)
208: .addKeyListener(new KeyTextHandler());
209:
210: //
211: // handle mouse events for double-click creation of popup dialog.
212: // This happens only in the JTextField, not the JTextArea, so we can
213: // make this an inner class within this method rather than a separate
214: // inner class as is done with the KeyTextHandler class.
215: //
216: ((RestorableJTextField) _textComponent)
217: .addMouseListener(new MouseAdapter() {
218: public void mousePressed(MouseEvent evt) {
219: if (evt.getClickCount() == 2) {
220: MouseEvent tableEvt = SwingUtilities
221: .convertMouseEvent(
222: (RestorableJTextField) DataTypeJavaObject.this ._textComponent,
223: evt,
224: DataTypeJavaObject.this ._table);
225: CellDataPopup.showDialog(
226: DataTypeJavaObject.this ._table,
227: DataTypeJavaObject.this ._colDef,
228: tableEvt, true);
229: }
230: }
231: }); // end of mouse listener
232:
233: return (JTextField) _textComponent;
234: }
235:
236: /**
237: * Implement the interface for validating and converting to internal object.
238: * Since we do not know how to convert JavaObject objects,
239: * just return null with no error in the messageBuffer
240: */
241: public Object validateAndConvert(String value,
242: Object originalValue, StringBuffer messageBuffer) {
243: return null;
244: }
245:
246: /**
247: * If true, this tells the PopupEditableIOPanel to use the
248: * binary editing panel rather than a pure text panel.
249: * The binary editing panel assumes the data is an array of bytes,
250: * converts it into text form, allows the user to change how that
251: * data is displayed (e.g. Hex, Decimal, etc.), and converts
252: * the data back from text to bytes when the user editing is completed.
253: * If this returns false, this DataType class must
254: * convert the internal data into a text string that
255: * can be displayed (and edited, if allowed) in a TextField
256: * or TextArea, and must handle all
257: * user key strokes related to editing of that data.
258: */
259: public boolean useBinaryEditingPanel() {
260: return false;
261: }
262:
263: /*
264: * Now define the Popup-related operations.
265: */
266:
267: /**
268: * Returns true if data type may be edited in the popup,
269: * false if not.
270: */
271: public boolean isEditableInPopup(Object originalValue) {
272: return false;
273: }
274:
275: /*
276: * Return a JTextArea usable in the CellPopupDialog.
277: */
278: public JTextArea getJTextArea(Object value) {
279: _textComponent = new RestorableJTextArea();
280:
281: // value is a simple string representation of the data,
282: // the same one used in the Text and in-cell operations.
283: ((RestorableJTextArea) _textComponent)
284: .setText(renderObject(value));
285:
286: // special handling of operations while editing this data type
287: ((RestorableJTextArea) _textComponent)
288: .addKeyListener(new KeyTextHandler());
289:
290: return (RestorableJTextArea) _textComponent;
291: }
292:
293: /**
294: * Validating and converting in Popup is identical to cell-related operation.
295: */
296: public Object validateAndConvertInPopup(String value,
297: Object originalValue, StringBuffer messageBuffer) {
298: return validateAndConvert(value, originalValue, messageBuffer);
299: }
300:
301: /*
302: * The following is used by both in-cell and Popup operations.
303: */
304:
305: /*
306: * Internal class for handling key events during editing
307: * of both JTextField and JTextArea.
308: * Since neither cell nor popup are allowed to edit, just ignore
309: * anything seen here.
310: */
311: private class KeyTextHandler extends KeyAdapter {
312: // special handling of operations while editing Strings
313: public void keyTyped(KeyEvent e) {
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) DataTypeJavaObject.this ._textComponent;
319: e.consume();
320: _theComponent.getToolkit().beep();
321: }
322: }
323:
324: /*
325: * DataBase-related functions
326: */
327:
328: /**
329: * On input from the DB, read the data from the ResultSet into the appropriate
330: * type of object to be stored in the table cell.
331: */
332: public Object readResultSet(ResultSet rs, int index,
333: boolean limitDataRead) throws java.sql.SQLException {
334:
335: String data = null;
336: if (_readSQLJavaObject) {
337: // Running getObject on a java class attempts
338: // to load the class in memory which we don't want.
339: // getString() just gets the value without loading
340: // the class (at least under PostgreSQL).
341: //row[i] = _rs.getObject(index);
342: //data = rs.getString(index);
343: try {
344: Object value = rs.getObject(index);
345: if (value == null || value instanceof Number
346: || value instanceof String
347: || value instanceof java.util.Date
348: || value instanceof java.net.URL) {
349: data = value + "";
350: }
351: } catch (Exception e) {
352: }
353: if (data == null) {
354: data = s_stringMgr.getString("DataTypeUnknown.unknown",
355: Integer.valueOf(_colDef.getSqlType()));
356: }
357: } else {
358: data = s_stringMgr.getString("DataTypeUnknown.unknown",
359: Integer.valueOf(_colDef.getSqlType()));
360: }
361:
362: if (rs.wasNull())
363: return null;
364: else
365: return data;
366:
367: // String data = rs.getString(index);
368: // if (rs.wasNull())
369: // return null;
370: // else return data;
371: }
372:
373: /**
374: * When updating the database, generate a string form of this object value
375: * that can be used in the WHERE clause to match the value in the database.
376: * A return value of null means that this column cannot be used in the WHERE
377: * clause, while a return of "null" (or "is null", etc) means that the column
378: * can be used in the WHERE clause and the value is actually a null value.
379: * This function must also include the column label so that its output
380: * is of the form:
381: * "columnName = value"
382: * or
383: * "columnName is null"
384: * or whatever is appropriate for this column in the database.
385: */
386: public String getWhereClauseValue(Object value,
387: ISQLDatabaseMetaData md) {
388: if (value == null || value.toString() == null)
389: return _colDef.getLabel() + " IS NULL";
390: else
391: return _colDef.getLabel() + "='" + value.toString() + "'";
392: }
393:
394: /**
395: * When updating the database, insert the appropriate datatype into the
396: * prepared statment at the given variable position.
397: */
398: public void setPreparedStatementValue(PreparedStatement pstmt,
399: Object value, int position) throws java.sql.SQLException {
400: if (value == null) {
401: pstmt.setNull(position, _colDef.getSqlType());
402: } else {
403: pstmt.setString(position, ((String) value));
404: }
405: }
406:
407: /**
408: * Get a default value for the table used to input data for a new row
409: * to be inserted into the DB.
410: */
411: public Object getDefaultValue(String dbDefaultValue) {
412: if (dbDefaultValue != null) {
413: // try to use the DB default value
414: StringBuffer mbuf = new StringBuffer();
415: Object newObject = validateAndConvert(dbDefaultValue, null,
416: mbuf);
417:
418: // if there was a problem with converting, then just fall through
419: // and continue as if there was no default given in the DB.
420: // Otherwise, use the converted object
421: if (mbuf.length() == 0)
422: return newObject;
423: }
424:
425: // no default in DB. If nullable, use null.
426: if (_isNullable)
427: return null;
428:
429: // field is not nullable, so create a reasonable default value
430: // cannot create default value for unknown data type
431: return null;
432: }
433:
434: /*
435: * File IO related functions
436: */
437:
438: /**
439: * Say whether or not object can be exported to and imported from
440: * a file. We put both export and import together in one test
441: * on the assumption that all conversions can be done both ways.
442: */
443: public boolean canDoFileIO() {
444: return false;
445: }
446:
447: /**
448: * Read a file and construct a valid object from its contents.
449: * Errors are returned by throwing an IOException containing the
450: * cause of the problem as its message.
451: * <P>
452: * DataType is responsible for validating that the imported
453: * data can be converted to an object, and then must return
454: * a text string that can be used in the Popup window text area.
455: * This object-to-text conversion is the same as is done by
456: * the DataType object internally in the getJTextArea() method.
457: *
458: * <P>
459: * File is assumed to be printable text characters,
460: * possibly including newlines and tabs but not characters
461: * that would require a binary representation to display
462: * to user.
463: */
464: public String importObject(FileInputStream inStream)
465: throws IOException {
466:
467: /* You can try those code:
468: java.io.ObjectInputStream objectInputStream = null;
469: try {
470: objectInputStream = new java.io.ObjectInputStream(inStream);
471: return objectInputStream.readObject()+"";
472: }catch (ClassNotFoundException cnfe) {
473: // throw new IOException("Class not found: "+ cnfe.toString()+" while reading serialized object");
474: throw new IOException("Can not import data type JAVA_OBJECT");
475: }catch (IOException ioe) {
476: // throw new IOException( ioe.toString() +" while reading serialized object");
477: throw new IOException("Can not import data type JAVA_OBJECT");
478: } */
479:
480: throw new IOException("Can not import data type JAVA_OBJECT");
481: }
482:
483: /**
484: * Construct an appropriate external representation of the object
485: * and write it to a file.
486: * Errors are returned by throwing an IOException containing the
487: * cause of the problem as its message.
488: * <P>
489: * DataType is responsible for validating that the given text
490: * text from a Popup JTextArea can be converted to an object.
491: * This text-to-object conversion is the same as validateAndConvertInPopup,
492: * which may be used internally by the object to do the validation.
493: * <P>
494: * The DataType object must flush and close the output stream before returning.
495: * Typically it will create another object (e.g. an OutputWriter), and
496: * that is the object that must be flushed and closed.
497: *
498: * <P>
499: * File is assumed to be printable text characters,
500: * possibly including newlines and tabs but not characters
501: * that would require a binary representation to display
502: * to user.
503: */
504: public void exportObject(FileOutputStream outStream, String text)
505: throws IOException {
506:
507: /* You can try those code:
508: java.io.ObjectOutputStream objectOutputStream=null;
509: try {
510: objectOutputStream = new java.io.ObjectOutputStream(outStream);
511: objectOutputStream.writeObject(text);
512: objectOutputStream.flush();
513: }catch (IOException e) {
514: throw new IOException("Can not export data type JAVA_OBJECT");
515: } */
516: throw new IOException("Can not export data type JAVA_OBJECT");
517: }
518:
519: /*
520: * Property change control panel
521: */
522:
523: /**
524: * Generate a JPanel containing controls that allow the user
525: * to adjust the properties for this DataType.
526: * All properties are static accross all instances of this DataType.
527: * However, the class may choose to apply the information differentially,
528: * such as keeping a list (also entered by the user) of table/column names
529: * for which certain properties should be used.
530: * <P>
531: * This is called ONLY if there is at least one property entered into the DTProperties
532: * for this class.
533: * <P>
534: * Since this method is called by reflection on the Method object derived from this class,
535: * it does not need to be included in the Interface.
536: * It would be nice to include this in the Interface for consistancy, documentation, etc,
537: * but the Interface does not seem to like static methods.
538: */
539: public static OkJPanel getControlPanel() {
540:
541: /*
542: * If you add this method to one of the standard DataTypes in the
543: * fw/datasetviewer/cellcomponent directory, you must also add the name
544: * of that DataType class to the list in CellComponentFactory, method
545: * getControlPanels, variable named initialClassNameList.
546: * If the class is being registered with the factory using registerDataType,
547: * then you should not include the class name in the list (it will be found
548: * automatically), but if the DataType is part of the case statement in the
549: * factory method getDataTypeObject, then it does need to be explicitly listed
550: * in the getControlPanels method also.
551: */
552:
553: // if this panel is called before any instances of the class have been
554: // created, we need to load the properties from the DTProperties.
555: loadProperties();
556:
557: return new SQLJavaObjectOkJPanel();
558: }
559:
560: /**
561: * Inner class that extends OkJPanel so that we can call the ok()
562: * method to save the data when the user is happy with it.
563: */
564: private static class SQLJavaObjectOkJPanel extends OkJPanel {
565:
566: private static final long serialVersionUID = 1353928067985854545L;
567: /*
568: * GUI components - need to be here because they need to be
569: * accessible from the event handlers to alter each other's state.
570: */
571: // check box for whether to read contents during table load or not
572: private JCheckBox _showSQLJavaObjectChk = new JCheckBox(
573: // i18n[dataTypeJavaObject.readContentsWhenLoaded=Read contents when table is first loaded and display as string]
574: s_stringMgr
575: .getString("dataTypeJavaObject.readContentsWhenLoaded"));
576:
577: public SQLJavaObjectOkJPanel() {
578:
579: /* set up the controls */
580: // checkbox for read/not-read on table load
581: _showSQLJavaObjectChk.setSelected(_readSQLJavaObject);
582:
583: /*
584: * Create the panel and add the GUI items to it
585: */
586:
587: // i18n[dataTypeJavaObject.sqlJavaObjectType=SQL JavaObject (SQL type 2000)]
588: setBorder(BorderFactory.createTitledBorder(s_stringMgr
589: .getString("dataTypeJavaObject.sqlJavaObjectType")));
590:
591: add(_showSQLJavaObjectChk);
592:
593: } // end of constructor for inner class
594:
595: /**
596: * User has clicked OK in the surrounding JPanel,
597: * so save the current state of all variables
598: */
599: public void ok() {
600: // get the values from the controls and set them in the static properties
601: _readSQLJavaObject = _showSQLJavaObjectChk.isSelected();
602: DTProperties.put(this ClassName, "readSQLJavaObject",
603: Boolean.valueOf(_readSQLJavaObject).toString());
604: }
605:
606: } // end of inner class
607: }
|