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