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 Other data types,
049: * specifically SQL type OTHER.
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 "<OTHER>".
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 DataTypeOther 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(DataTypeOther.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.DataTypeOther";
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 _readSQLOther = false;
112:
113: /**
114: * Constructor - save the data needed by this data type.
115: */
116: public DataTypeOther(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: _readSQLOther = false; // set to the default
141: String readSQLOtherString = DTProperties.get(this ClassName,
142: "readSQLOther");
143: if (readSQLOtherString != null
144: && readSQLOtherString.equals("true"))
145: _readSQLOther = 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 Other, 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 cell-related 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) DataTypeOther.this ._textComponent,
221: evt,
222: DataTypeOther.this ._table);
223: CellDataPopup.showDialog(
224: DataTypeOther.this ._table,
225: DataTypeOther.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 Other 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 define 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: */
276: public JTextArea getJTextArea(Object value) {
277: _textComponent = new RestorableJTextArea();
278:
279: // value is a simple string representation of the data,
280: // the same one used in the Text and in-cell operations.
281: ((RestorableJTextArea) _textComponent)
282: .setText(renderObject(value));
283:
284: // special handling of operations while editing this data type
285: ((RestorableJTextArea) _textComponent)
286: .addKeyListener(new KeyTextHandler());
287:
288: return (RestorableJTextArea) _textComponent;
289: }
290:
291: /**
292: * Validating and converting in Popup is identical to cell-related operation.
293: */
294: public Object validateAndConvertInPopup(String value,
295: Object originalValue, StringBuffer messageBuffer) {
296: return validateAndConvert(value, originalValue, messageBuffer);
297: }
298:
299: /*
300: * The following is used by both in-cell and Popup operations.
301: */
302:
303: /*
304: * Internal class for handling key events during editing
305: * of both JTextField and JTextArea.
306: * Since neither cell nor popup are allowed to edit, just ignore
307: * anything seen here.
308: */
309: private class KeyTextHandler extends KeyAdapter {
310: // special handling of operations while editing Strings
311: public void keyTyped(KeyEvent e) {
312: // as a coding convenience, create a reference to the text component
313: // that is typecast to JTextComponent. this is not essential, as we
314: // could typecast every reference, but this makes the code cleaner
315: JTextComponent _theComponent = (JTextComponent) DataTypeOther.this ._textComponent;
316: e.consume();
317: _theComponent.getToolkit().beep();
318: }
319: }
320:
321: /*
322: * DataBase-related functions
323: */
324:
325: /**
326: * On input from the DB, read the data from the ResultSet into the appropriate
327: * type of object to be stored in the table cell.
328: */
329: public Object readResultSet(ResultSet rs, int index,
330: boolean limitDataRead) throws java.sql.SQLException {
331:
332: String data = null;
333: if (_readSQLOther) {
334: // Running getObject on a java class attempts
335: // to load the class in memory which we don't want.
336: // getString() just gets the value without loading
337: // the class (at least under PostgreSQL).
338: //row[i] = _rs.getObject(index);
339: data = rs.getString(index);
340: } else {
341: data = s_stringMgr.getString("DataTypeOther.other");
342: }
343:
344: if (rs.wasNull())
345: return null;
346: else
347: return data;
348:
349: // String data = rs.getString(index);
350: // if (rs.wasNull())
351: // return null;
352: // else return data;
353: }
354:
355: /**
356: * When updating the database, generate a string form of this object value
357: * that can be used in the WHERE clause to match the value in the database.
358: * A return value of null means that this column cannot be used in the WHERE
359: * clause, while a return of "null" (or "is null", etc) means that the column
360: * can be used in the WHERE clause and the value is actually a null value.
361: * This function must also include the column label so that its output
362: * is of the form:
363: * "columnName = value"
364: * or
365: * "columnName is null"
366: * or whatever is appropriate for this column in the database.
367: */
368: public String getWhereClauseValue(Object value,
369: ISQLDatabaseMetaData md) {
370: if (value == null || value.toString() == null)
371: return _colDef.getLabel() + " IS NULL";
372: else
373: return _colDef.getLabel() + "='" + value.toString() + "'";
374: }
375:
376: /**
377: * When updating the database, insert the appropriate datatype into the
378: * prepared statment at the given variable position.
379: */
380: public void setPreparedStatementValue(PreparedStatement pstmt,
381: Object value, int position) throws java.sql.SQLException {
382: if (value == null) {
383: pstmt.setNull(position, _colDef.getSqlType());
384: } else {
385: pstmt.setString(position, ((String) value));
386: }
387: }
388:
389: /**
390: * Get a default value for the table used to input data for a new row
391: * to be inserted into the DB.
392: */
393: public Object getDefaultValue(String dbDefaultValue) {
394: if (dbDefaultValue != null) {
395: // try to use the DB default value
396: StringBuffer mbuf = new StringBuffer();
397: Object newObject = validateAndConvert(dbDefaultValue, null,
398: mbuf);
399:
400: // if there was a problem with converting, then just fall through
401: // and continue as if there was no default given in the DB.
402: // Otherwise, use the converted object
403: if (mbuf.length() == 0)
404: return newObject;
405: }
406:
407: // no default in DB. If nullable, use null.
408: if (_isNullable)
409: return null;
410:
411: // field is not nullable, so create a reasonable default value
412: // cannot create default value for unknown data type
413: return null;
414: }
415:
416: /*
417: * File IO related functions
418: */
419:
420: /**
421: * Say whether or not object can be exported to and imported from
422: * a file. We put both export and import together in one test
423: * on the assumption that all conversions can be done both ways.
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 printable text characters,
442: * possibly including newlines and tabs but not characters
443: * that would require a binary representation to display
444: * to user.
445: */
446: public String importObject(FileInputStream inStream)
447: throws IOException {
448:
449: throw new IOException("Can not import data type OTHER");
450: }
451:
452: /**
453: * Construct an appropriate external representation of the object
454: * and write it to a file.
455: * Errors are returned by throwing an IOException containing the
456: * cause of the problem as its message.
457: * <P>
458: * DataType is responsible for validating that the given text
459: * text from a Popup JTextArea can be converted to an object.
460: * This text-to-object conversion is the same as validateAndConvertInPopup,
461: * which may be used internally by the object to do the validation.
462: * <P>
463: * The DataType object must flush and close the output stream before returning.
464: * Typically it will create another object (e.g. an OutputWriter), and
465: * that is the object that must be flushed and closed.
466: *
467: * <P>
468: * File is assumed to be printable text characters,
469: * possibly including newlines and tabs but not characters
470: * that would require a binary representation to display
471: * to user.
472: */
473: public void exportObject(FileOutputStream outStream, String text)
474: throws IOException {
475:
476: throw new IOException("Can not export data type OTHER");
477: }
478:
479: /*
480: * Property change control panel
481: */
482:
483: /**
484: * Generate a JPanel containing controls that allow the user
485: * to adjust the properties for this DataType.
486: * All properties are static accross all instances of this DataType.
487: * However, the class may choose to apply the information differentially,
488: * such as keeping a list (also entered by the user) of table/column names
489: * for which certain properties should be used.
490: * <P>
491: * This is called ONLY if there is at least one property entered into the DTProperties
492: * for this class.
493: * <P>
494: * Since this method is called by reflection on the Method object derived from this class,
495: * it does not need to be included in the Interface.
496: * It would be nice to include this in the Interface for consistancy, documentation, etc,
497: * but the Interface does not seem to like static methods.
498: */
499: public static OkJPanel getControlPanel() {
500:
501: /*
502: * If you add this method to one of the standard DataTypes in the
503: * fw/datasetviewer/cellcomponent directory, you must also add the name
504: * of that DataType class to the list in CellComponentFactory, method
505: * getControlPanels, variable named initialClassNameList.
506: * If the class is being registered with the factory using registerDataType,
507: * then you should not include the class name in the list (it will be found
508: * automatically), but if the DataType is part of the case statement in the
509: * factory method getDataTypeObject, then it does need to be explicitly listed
510: * in the getControlPanels method also.
511: */
512:
513: // if this panel is called before any instances of the class have been
514: // created, we need to load the properties from the DTProperties.
515: loadProperties();
516:
517: return new SQLOtherOkJPanel();
518: }
519:
520: /**
521: * Inner class that extends OkJPanel so that we can call the ok()
522: * method to save the data when the user is happy with it.
523: */
524: private static class SQLOtherOkJPanel extends OkJPanel {
525:
526: private static final long serialVersionUID = 9034966488591013288L;
527: /*
528: * GUI components - need to be here because they need to be
529: * accessible from the event handlers to alter each other's state.
530: */
531: // check box for whether to read contents during table load or not
532: private JCheckBox _showSQLOtherChk = new JCheckBox(
533: // i18n[dataTypeOther.readContentsWhenLoaded=Read contents when table is first loaded and display as string]
534: s_stringMgr
535: .getString("dataTypeOther.readContentsWhenLoaded"));
536:
537: public SQLOtherOkJPanel() {
538:
539: /* set up the controls */
540: // checkbox for read/not-read on table load
541: _showSQLOtherChk.setSelected(_readSQLOther);
542:
543: /*
544: * Create the panel and add the GUI items to it
545: */
546:
547: // i18n[dataTypeOther.sqlOtherType=SQL Other (SQL type 1111)]
548: setBorder(BorderFactory.createTitledBorder(s_stringMgr
549: .getString("dataTypeOther.sqlOtherType")));
550:
551: add(_showSQLOtherChk);
552:
553: } // end of constructor for inner class
554:
555: /**
556: * User has clicked OK in the surrounding JPanel,
557: * so save the current state of all variables
558: */
559: public void ok() {
560: // get the values from the controls and set them in the static properties
561: _readSQLOther = _showSQLOtherChk.isSelected();
562: DTProperties.put(this ClassName, "readSQLOther", Boolean
563: .valueOf(_readSQLOther).toString());
564: }
565:
566: } // end of inner class
567: }
|