/** * @author gwg * <p>This class provides the display components for handling Time data types, specifically SQL * type TIME. The display components are for: * <UL> * <LI>read-only display within a table cell * <LI>editing within a table cell * <LI>read-only or editing display within a separate window * </UL> * The class also contains * <UL> * <LI>a function to compare two display values to see if they are equal. This is needed * because the display format may not be the same as the internal format, and all internal * object types may not provide an appropriate equals() function. * <LI>a function to return a printable text form of the cell contents, which is used in the * text version of the table. * </UL> * <p>The components returned from this class extend RestorableJTextField and * RestorableJTextArea for use in editing table cells that contain values of this data type. It * provides the special behavior for null handling and resetting the cell to the original value. */ public class DataTypeTime extends BaseDataTypeComponent implements IDataTypeComponent { private static final StringManager s_stringMgr = StringManagerFactory.getStringManager(DataTypeTime.class); /** Logger for this class. */ private static ILogger s_log = LoggerController.createLogger(DataTypeTime.class); /* whether nulls are allowed or not */ private boolean _isNullable; /* table of which we are part (needed for creating popup dialog) */ private JTable _table; /* The JTextComponent that is being used for editing */ private IRestorableTextComponent _textComponent; /* The CellRenderer used for this data type */ // ??? For now, use the same renderer as everyone else. // ?? // ?? IN FUTURE: change this to use a new instance of renederer // ?? for this data type. private DefaultColumnRenderer _renderer = DefaultColumnRenderer.getInstance(); /** * Name of this class, which is needed because the class name is needed by the static method * getControlPanel, so we cannot use something like getClass() to find this name. */ private static final String thisClassName = "net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeTime"; /** Default date format */ private static int DEFAULT_LOCALE_FORMAT = DateFormat.SHORT; /* * Properties settable by the user */ // flag for whether we have already loaded the properties or not private static boolean propertiesAlreadyLoaded = false; // flag for whether to use the default Java format (true) // or the Locale-dependent format (false) private static boolean useJavaDefaultFormat = true; // which locale-dependent format to use; short, medium, long, or full private static int localeFormat = DEFAULT_LOCALE_FORMAT; // Whether to force user to enter dates in exact format or use heuristics to guess it private static boolean lenient = true; // The DateFormat object to use for all locale-dependent formatting. // This is reset each time the user changes the previous settings. private static ThreadSafeDateFormat dateFormat = new ThreadSafeDateFormat(localeFormat, true); private boolean _renderExceptionHasBeenLogged; /** Constructor - save the data needed by this data type. */ public DataTypeTime(JTable table, ColumnDisplayDefinition colDef) { _table = table; _colDef = colDef; _isNullable = colDef.isNullable(); loadProperties(); } /** * Internal function to get the user-settable properties from the DTProperties, if they exist, and * to ensure that defaults are set if the properties have not yet been created. * * <p>This method may be called from different places depending on whether an instance of this * class is created before the user brings up the Session Properties window. In either case, the * data is static and is set only the first time we are called. */ private static void loadProperties() { // set the property values // Note: this may have already been done by another instance of // this DataType created to handle a different column. if (propertiesAlreadyLoaded == false) { // get parameters previously set by user, or set default values useJavaDefaultFormat = true; // set to use the Java default String useJavaDefaultFormatString = DTProperties.get(thisClassName, "useJavaDefaultFormat"); if (useJavaDefaultFormatString != null && useJavaDefaultFormatString.equals("false")) useJavaDefaultFormat = false; // get which locale-dependent format to use localeFormat = DateFormat.SHORT; // set to use the Java default String localeFormatString = DTProperties.get(thisClassName, "localeFormat"); if (localeFormatString != null) localeFormat = Integer.parseInt(localeFormatString); // use lenient input or force user to enter exact format lenient = true; // set to allow less stringent input String lenientString = DTProperties.get(thisClassName, "lenient"); if (lenientString != null && lenientString.equals("false")) lenient = false; /* * After loading the properties, we must initialize the dateFormat. * See Bug 3086444 */ initDateFormat(localeFormat, lenient); } } /** Defines the dateFormat with the specific format and lenient options */ private static void initDateFormat(int format, boolean lenient) { dateFormat = new ThreadSafeDateFormat(format, true); // lenient is set next dateFormat.setLenient(lenient); } /** Return the name of the java class used to hold this data type. */ public String getClassName() { return "java.sql.Time"; } /* * First we have the methods for in-cell and Text-table operations */ /** Render a value into text for this DataType. */ public String renderObject(Object value) { // use the Java default date-to-string if (useJavaDefaultFormat == true || value == null) return (String) _renderer.renderObject(value); // use a date formatter try { return (String) _renderer.renderObject(dateFormat.format(value)); } catch (Exception e) { if (false == _renderExceptionHasBeenLogged) { _renderExceptionHasBeenLogged = true; s_log.error("Could not format \"" + value + "\" as date type", e); } return (String) _renderer.renderObject(value); } } /** This Data Type can be edited in a table cell. */ public boolean isEditableInCell(Object originalValue) { return true; } /** * See if a value in a column has been limited in some way and needs to be re-read before being * used for editing. For read-only tables this may actually return true since we want to be able * to view the entire contents of the cell even if it was not completely loaded during the initial * table setup. */ public boolean needToReRead(Object originalValue) { // this DataType does not limit the data read during the initial load of the table, // so there is no need to re-read the complete data later return false; } /** Return a JTextField usable in a CellEditor. */ public JTextField getJTextField() { _textComponent = new RestorableJTextField(); // special handling of operations while editing this data type ((RestorableJTextField) _textComponent).addKeyListener(new KeyTextHandler()); // // handle mouse events for double-click creation of popup dialog. // This happens only in the JTextField, not the JTextArea, so we can // make this an inner class within this method rather than a separate // inner class as is done with the KeyTextHandler class. // ((RestorableJTextField) _textComponent) .addMouseListener( new MouseAdapter() { public void mousePressed(MouseEvent evt) { if (evt.getClickCount() == 2) { MouseEvent tableEvt = SwingUtilities.convertMouseEvent( (RestorableJTextField) DataTypeTime.this._textComponent, evt, DataTypeTime.this._table); CellDataPopup.showDialog( DataTypeTime.this._table, DataTypeTime.this._colDef, tableEvt, true); } } }); // end of mouse listener return (JTextField) _textComponent; } /** * Implement the interface for validating and converting to internal object. Null is a valid * successful return, so errors are indicated only by existance or not of a message in the * messageBuffer. */ public Object validateAndConvert(String value, Object originalValue, StringBuffer messageBuffer) { // handle null, which is shown as the special string "<null>" if (value.equals("<null>") || value.equals("")) return null; // Do the conversion into the object in a safe manner try { if (useJavaDefaultFormat) { // allow the user to enter just the hour or just hour and minute // and assume the un-entered values are 0 int firstColon = value.indexOf(":"); if (firstColon == -1) { // user just entered the hour, so append min & sec value = value + ":0:0"; } else { // user entered hour an min. See if they also entered secs if (value.indexOf(":", firstColon + 1) == -1) { // user did not enter seconds value = value + ":0"; } } Object obj = Time.valueOf(value); return obj; } else { // use the DateFormat to parse java.util.Date javaDate = dateFormat.parse(value); return new Time(javaDate.getTime()); } } catch (Exception e) { messageBuffer.append(e.toString() + "\n"); // ?? do we need the message also, or is it automatically part of the toString()? // messageBuffer.append(e.getMessage()); return null; } } /** * If true, this tells the PopupEditableIOPanel to use the binary editing panel rather than a pure * text panel. The binary editing panel assumes the data is an array of bytes, converts it into * text form, allows the user to change how that data is displayed (e.g. Hex, Decimal, etc.), and * converts the data back from text to bytes when the user editing is completed. If this returns * false, this DataType class must convert the internal data into a text string that can be * displayed (and edited, if allowed) in a TextField or TextArea, and must handle all user key * strokes related to editing of that data. */ public boolean useBinaryEditingPanel() { return false; } /* * Now the functions for the Popup-related operations. */ /** Returns true if data type may be edited in the popup, false if not. */ public boolean isEditableInPopup(Object originalValue) { return true; } /* * Return a JTextArea usable in the CellPopupDialog * and fill in the value. */ public JTextArea getJTextArea(Object value) { _textComponent = new RestorableJTextArea(); // value is a simple string representation of the data, // the same one used in Text and in-cell operations. ((RestorableJTextArea) _textComponent).setText(renderObject(value)); // special handling of operations while editing this data type ((RestorableJTextArea) _textComponent).addKeyListener(new KeyTextHandler()); return (RestorableJTextArea) _textComponent; } /** Validating and converting in Popup is identical to cell-related operation. */ public Object validateAndConvertInPopup( String value, Object originalValue, StringBuffer messageBuffer) { return validateAndConvert(value, originalValue, messageBuffer); } /* * The following is used in both cell and popup operations. */ /* * Internal class for handling key events during editing * of both JTextField and JTextArea. */ private class KeyTextHandler extends BaseKeyTextHandler { public void keyTyped(KeyEvent e) { char c = e.getKeyChar(); // as a coding convenience, create a reference to the text component // that is typecast to JTextComponent. this is not essential, as we // could typecast every reference, but this makes the code cleaner JTextComponent _theComponent = (JTextComponent) DataTypeTime.this._textComponent; String text = _theComponent.getText(); // tabs and newlines get put into the text before this check, // so remove them // This only applies to Popup editing since these chars are // not passed to this level by the in-cell editor. if (c == KeyEvent.VK_TAB || c == KeyEvent.VK_ENTER) { // remove all instances of the offending char int index = text.indexOf(c); if (index == text.length() - 1) { text = text.substring(0, text.length() - 1); // truncate string } else { text = text.substring(0, index) + text.substring(index + 1); } ((IRestorableTextComponent) _theComponent).updateText(text); _beepHelper.beep(_theComponent); e.consume(); } // handle cases of null // The processing is different when nulls are allowed and when they are not. // if (DataTypeTime.this._isNullable) { // user enters something when field is null if (text.equals("<null>")) { if ((c == KeyEvent.VK_BACK_SPACE) || (c == KeyEvent.VK_DELETE)) { // delete when null => original value DataTypeTime.this._textComponent.restoreText(); e.consume(); } else { // non-delete when null => clear field and add text DataTypeTime.this._textComponent.updateText(""); // fall through to normal processing of this key stroke } } else { // check for user deletes last thing in field if ((c == KeyEvent.VK_BACK_SPACE) || (c == KeyEvent.VK_DELETE)) { if (text.length() <= 1) { // about to delete last thing in field, so replace with null DataTypeTime.this._textComponent.updateText("<null>"); e.consume(); } } } } else { // field is not nullable // handleNotNullableField(text, c, e, _textComponent); } } } /* * DataBase-related functions */ /** * On input from the DB, read the data from the ResultSet into the appropriate type of object to * be stored in the table cell. */ public Object readResultSet(ResultSet rs, int index, boolean limitDataRead) throws java.sql.SQLException { Time data = rs.getTime(index); if (rs.wasNull()) return null; else return data; } /** * When updating the database, generate a string form of this object value that can be used in the * WHERE clause to match the value in the database. A return value of null means that this column * cannot be used in the WHERE clause, while a return of "null" (or "is null", etc) means that the * column can be used in the WHERE clause and the value is actually a null value. This function * must also include the column label so that its output is of the form: "columnName = value" or * "columnName is null" or whatever is appropriate for this column in the database. */ public IWhereClausePart getWhereClauseValue(Object value, ISQLDatabaseMetaData md) { if (value == null || value.toString() == null || value.toString().length() == 0) return new IsNullWhereClausePart(_colDef); else return new NoParameterWhereClausePart( _colDef, _colDef.getColumnName() + "={t '" + value.toString() + "'}"); } /** * When updating the database, insert the appropriate datatype into the prepared statment at the * given variable position. */ public void setPreparedStatementValue(PreparedStatement pstmt, Object value, int position) throws java.sql.SQLException { if (value == null) { pstmt.setNull(position, _colDef.getSqlType()); } else { pstmt.setTime(position, ((Time) value)); } } /** * Get a default value for the table used to input data for a new row to be inserted into the DB. */ public Object getDefaultValue(String dbDefaultValue) { if (dbDefaultValue != null) { // try to use the DB default value StringBuffer mbuf = new StringBuffer(); Object newObject = validateAndConvert(dbDefaultValue, null, mbuf); // if there was a problem with converting, then just fall through // and continue as if there was no default given in the DB. // Otherwise, use the converted object if (mbuf.length() == 0) return newObject; } // no default in DB. If nullable, use null. if (_isNullable) return null; // field is not nullable, so create a reasonable default value return new Time(new java.util.Date().getTime()); } /* * File IO related functions */ /** * Say whether or not object can be exported to and imported from a file. We put both export and * import together in one test on the assumption that all conversions can be done both ways. */ public boolean canDoFileIO() { return true; } /** * Read a file and construct a valid object from its contents. Errors are returned by throwing an * IOException containing the cause of the problem as its message. * * <p>DataType is responsible for validating that the imported data can be converted to an object, * and then must return a text string that can be used in the Popup window text area. This * object-to-text conversion is the same as is done by the DataType object internally in the * getJTextArea() method. * * <p>File is assumed to be and ASCII string of digits representing a value of this data type. */ public String importObject(FileInputStream inStream) throws IOException { InputStreamReader inReader = new InputStreamReader(inStream); int fileSize = inStream.available(); char charBuf[] = new char[fileSize]; int count = inReader.read(charBuf, 0, fileSize); if (count != fileSize) throw new IOException( "Could read only " + count + " chars from a total file size of " + fileSize + ". Import failed."); // convert file text into a string // Special case: some systems tack a newline at the end of // the text read. Assume that if last char is a newline that // we want everything else in the line. String fileText; if (charBuf[count - 1] == KeyEvent.VK_ENTER) fileText = new String(charBuf, 0, count - 1); else fileText = new String(charBuf); // test that the string is valid by converting it into an // object of this data type StringBuffer messageBuffer = new StringBuffer(); validateAndConvertInPopup(fileText, null, messageBuffer); if (messageBuffer.length() > 0) { // convert number conversion issue into IO issue for consistancy throw new IOException( "Text does not represent data of type " + getClassName() + ". Text was:\n" + fileText); } // return the text from the file since it does // represent a valid data value return fileText; } /** * Construct an appropriate external representation of the object and write it to a file. Errors * are returned by throwing an IOException containing the cause of the problem as its message. * * <p>DataType is responsible for validating that the given text text from a Popup JTextArea can * be converted to an object. This text-to-object conversion is the same as * validateAndConvertInPopup, which may be used internally by the object to do the validation. * * <p>The DataType object must flush and close the output stream before returning. Typically it * will create another object (e.g. an OutputWriter), and that is the object that must be flushed * and closed. * * <p>File is assumed to be and ASCII string of digits representing a value of this data type. */ public void exportObject(FileOutputStream outStream, String text) throws IOException { OutputStreamWriter outWriter = new OutputStreamWriter(outStream); // check that the text is a valid representation StringBuffer messageBuffer = new StringBuffer(); validateAndConvertInPopup(text, null, messageBuffer); if (messageBuffer.length() > 0) { // there was an error in the conversion throw new IOException(new String(messageBuffer)); } // just send the text to the output file outWriter.write(text); outWriter.flush(); outWriter.close(); } /* * Property change control panel */ /** * Generate a JPanel containing controls that allow the user to adjust the properties for this * DataType. All properties are static accross all instances of this DataType. However, the class * may choose to apply the information differentially, such as keeping a list (also entered by the * user) of table/column names for which certain properties should be used. * * <p>This is called ONLY if there is at least one property entered into the DTProperties for this * class. * * <p>Since this method is called by reflection on the Method object derived from this class, it * does not need to be included in the Interface. It would be nice to include this in the * Interface for consistancy, documentation, etc, but the Interface does not seem to like static * methods. */ public static OkJPanel getControlPanel() { /* * If you add this method to one of the standard DataTypes in the * fw/datasetviewer/cellcomponent directory, you must also add the name * of that DataType class to the list in CellComponentFactory, method * getControlPanels, variable named initialClassNameList. * If the class is being registered with the factory using registerDataType, * then you should not include the class name in the list (it will be found * automatically), but if the DataType is part of the case statement in the * factory method getDataTypeObject, then it does need to be explicitly listed * in the getControlPanels method also. */ // if this panel is called before any instances of the class have been // created, we need to load the properties from the DTProperties. loadProperties(); return new TimeOkJPanel(); } // Class that displays the various formats available for dates public static class DateFormatTypeCombo extends JComboBox { private static final long serialVersionUID = 1L; public DateFormatTypeCombo() { // i18n[dataTypeTime.full=Full ({0})] addItem( s_stringMgr.getString( "dataTypeTime.full", DateFormat.getTimeInstance(DateFormat.FULL).format(new java.util.Date()))); // i18n[dataTypeTime.long=Long ({0})] addItem( s_stringMgr.getString( "dataTypeTime.long", DateFormat.getTimeInstance(DateFormat.LONG).format(new java.util.Date()))); // i18n[dataTypeTime.medium=Medium ({0})] addItem( s_stringMgr.getString( "dataTypeTime.medium", DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new java.util.Date()))); // i18n[dataTypeTime.short=Short ({0})] addItem( s_stringMgr.getString( "dataTypeTime.short", DateFormat.getTimeInstance(DateFormat.SHORT).format(new java.util.Date()))); } public void setSelectedIndex(int option) { if (option == DateFormat.SHORT) super.setSelectedIndex(3); else if (option == DateFormat.MEDIUM) super.setSelectedIndex(2); else if (option == DateFormat.LONG) super.setSelectedIndex(1); else super.setSelectedIndex(0); } public int getValue() { if (getSelectedIndex() == 3) return DateFormat.SHORT; else if (getSelectedIndex() == 2) return DateFormat.MEDIUM; else if (getSelectedIndex() == 1) return DateFormat.LONG; else return DateFormat.FULL; } } /** * Inner class that extends OkJPanel so that we can call the ok() method to save the data when the * user is happy with it. */ private static class TimeOkJPanel extends OkJPanel { private static final long serialVersionUID = 1L; /* * GUI components - need to be here because they need to be * accessible from the event handlers to alter each other's state. */ // check box for whether to use Java Default or a Locale-dependent format private JCheckBox useJavaDefaultFormatChk = // i18n[dataTypeTime.useDefaultFormat=Use default format ({0})] new JCheckBox( s_stringMgr.getString( "dataTypeTime.useDefaultFormat", new Time(new java.util.Date().getTime()).toString())); // label for the date format combo, used to enable/disable text // i18n[dataTypeTime.useDefaultFormat2= or locale-dependent format:] private RightLabel dateFormatTypeDropLabel = new RightLabel(s_stringMgr.getString("dataTypeTime.useDefaultFormat2")); // Combo box for read-all/read-part of blob private DateFormatTypeCombo dateFormatTypeDrop = new DateFormatTypeCombo(); // checkbox for whether to interpret input leniently or not // i18n[dataTypeTime.inexact=allow inexact format on input] private JCheckBox lenientChk = new JCheckBox(s_stringMgr.getString("dataTypeTime.inexact")); public TimeOkJPanel() { /* set up the controls */ // checkbox for Java default/non-default format useJavaDefaultFormatChk.setSelected(useJavaDefaultFormat); useJavaDefaultFormatChk.addChangeListener( new ChangeListener() { public void stateChanged(ChangeEvent e) { dateFormatTypeDrop.setEnabled(!useJavaDefaultFormatChk.isSelected()); dateFormatTypeDropLabel.setEnabled(!useJavaDefaultFormatChk.isSelected()); lenientChk.setEnabled(!useJavaDefaultFormatChk.isSelected()); } }); // Combo box for read-all/read-part of blob dateFormatTypeDrop = new DateFormatTypeCombo(); dateFormatTypeDrop.setSelectedIndex(localeFormat); // lenient checkbox lenientChk.setSelected(lenient); // handle cross-connection between fields dateFormatTypeDrop.setEnabled(!useJavaDefaultFormatChk.isSelected()); dateFormatTypeDropLabel.setEnabled(!useJavaDefaultFormatChk.isSelected()); lenientChk.setEnabled(!useJavaDefaultFormatChk.isSelected()); /* * Create the panel and add the GUI items to it */ setLayout(new GridBagLayout()); // i18n[dataTypeTime.typeTime=Time (SQL type 92)] setBorder(BorderFactory.createTitledBorder(s_stringMgr.getString("dataTypeTime.typeTime"))); final GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(4, 4, 4, 4); gbc.anchor = GridBagConstraints.WEST; gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = GridBagConstraints.REMAINDER; add(useJavaDefaultFormatChk, gbc); gbc.gridwidth = 1; gbc.gridx = 0; ++gbc.gridy; add(dateFormatTypeDropLabel, gbc); ++gbc.gridx; add(dateFormatTypeDrop, gbc); gbc.gridx = 0; ++gbc.gridy; add(lenientChk, gbc); } // end of constructor for inner class /** User has clicked OK in the surrounding JPanel, so save the current state of all variables */ public void ok() { // get the values from the controls and set them in the static properties useJavaDefaultFormat = useJavaDefaultFormatChk.isSelected(); DTProperties.put( thisClassName, "useJavaDefaultFormat", Boolean.valueOf(useJavaDefaultFormat).toString()); localeFormat = dateFormatTypeDrop.getValue(); dateFormat = new ThreadSafeDateFormat(localeFormat, true); // lenient is set next DTProperties.put(thisClassName, "localeFormat", Integer.toString(localeFormat)); lenient = lenientChk.isSelected(); dateFormat.setLenient(lenient); DTProperties.put(thisClassName, "lenient", Boolean.valueOf(lenient).toString()); initDateFormat(localeFormat, lenient); } } // end of inner class }
/** * @author gwg * <p>This class provides the display components for handling Byte data types, specifically SQL * type TINYINT. The display components are for: * <UL> * <LI>read-only display within a table cell * <LI>editing within a table cell * <LI>read-only or editing display within a separate window * </UL> * The class also contains * <UL> * <LI>a function to compare two display values to see if they are equal. This is needed * because the display format may not be the same as the internal format, and all internal * object types may not provide an appropriate equals() function. * <LI>a function to return a printable text form of the cell contents, which is used in the * text version of the table. * </UL> * <p>The components returned from this class extend RestorableJTextField and * RestorableJTextArea for use in editing table cells that contain values of this data type. It * provides the special behavior for null handling and resetting the cell to the original value. */ public class DataTypeByte extends BaseDataTypeComponent implements IDataTypeComponent { /* whether nulls are allowed or not */ private boolean _isNullable; /* whether number is signed or unsigned */ private boolean _isSigned; /* the number of decimal digits allowed in the number */ private int _scale; /* table of which we are part (needed for creating popup dialog) */ private JTable _table; /* The JTextComponent that is being used for editing */ private IRestorableTextComponent _textComponent; /* The CellRenderer used for this data type */ // ??? For now, use the same renderer as everyone else. // ?? // ?? IN FUTURE: change this to use a new instance of renederer // ?? for this data type. private DefaultColumnRenderer _renderer = DefaultColumnRenderer.getInstance(); /** Constructor - save the data needed by this data type. */ public DataTypeByte(JTable table, ColumnDisplayDefinition colDef) { _table = table; _colDef = colDef; _isNullable = colDef.isNullable(); _isSigned = colDef.isSigned(); _scale = colDef.getScale(); } /** Return the name of the java class used to hold this data type. */ public String getClassName() { return "java.lang.Byte"; } /* * First we have the methods for in-cell and Text-table operations */ /** Render a value into text for this DataType. */ public String renderObject(Object value) { return (String) _renderer.renderObject(value); } /** This Data Type can be edited in a table cell. */ public boolean isEditableInCell(Object originalValue) { return true; } /** * See if a value in a column has been limited in some way and needs to be re-read before being * used for editing. For read-only tables this may actually return true since we want to be able * to view the entire contents of the cell even if it was not completely loaded during the initial * table setup. */ public boolean needToReRead(Object originalValue) { // this DataType does not limit the data read during the initial load of the table, // so there is no need to re-read the complete data later return false; } /** Return a JTextField usable in a CellEditor. */ public JTextField getJTextField() { _textComponent = new RestorableJTextField(); // special handling of operations while editing this data type ((RestorableJTextField) _textComponent).addKeyListener(new KeyTextHandler()); // // handle mouse events for double-click creation of popup dialog. // This happens only in the JTextField, not the JTextArea, so we can // make this an inner class within this method rather than a separate // inner class as is done with the KeyTextHandler class. // ((RestorableJTextField) _textComponent) .addMouseListener( new MouseAdapter() { public void mousePressed(MouseEvent evt) { if (evt.getClickCount() == 2) { MouseEvent tableEvt = SwingUtilities.convertMouseEvent( (RestorableJTextField) DataTypeByte.this._textComponent, evt, DataTypeByte.this._table); CellDataPopup.showDialog( DataTypeByte.this._table, DataTypeByte.this._colDef, tableEvt, true); } } }); // end of mouse listener return (JTextField) _textComponent; } /** * Implement the interface for validating and converting to internal object. Null is a valid * successful return, so errors are indicated only by existance or not of a message in the * messageBuffer. */ public Object validateAndConvert(String value, Object originalValue, StringBuffer messageBuffer) { // handle null, which is shown as the special string "<null>" if (value.equals("<null>") || value.equals("")) return null; // Do the conversion into the object in a safe manner try { Object obj = new Byte(value); return obj; } catch (Exception e) { messageBuffer.append(e.toString() + "\n"); // ?? do we need the message also, or is it automatically part of the toString()? // messageBuffer.append(e.getMessage()); return null; } } /** * If true, this tells the PopupEditableIOPanel to use the binary editing panel rather than a pure * text panel. The binary editing panel assumes the data is an array of bytes, converts it into * text form, allows the user to change how that data is displayed (e.g. Hex, Decimal, etc.), and * converts the data back from text to bytes when the user editing is completed. If this returns * false, this DataType class must convert the internal data into a text string that can be * displayed (and edited, if allowed) in a TextField or TextArea, and must handle all user key * strokes related to editing of that data. */ public boolean useBinaryEditingPanel() { return false; } /* * Now the functions for the Popup-related operations. */ /** Returns true if data type may be edited in the popup, false if not. */ public boolean isEditableInPopup(Object originalValue) { return true; } /* * Return a JTextArea usable in the CellPopupDialog * and fill in the value. */ public JTextArea getJTextArea(Object value) { _textComponent = new RestorableJTextArea(); // value is a simple string representation of the data, // the same one used in Text and in-cell operations. ((RestorableJTextArea) _textComponent).setText(renderObject(value)); // special handling of operations while editing this data type ((RestorableJTextArea) _textComponent).addKeyListener(new KeyTextHandler()); return (RestorableJTextArea) _textComponent; } /** Validating and converting in Popup is identical to cell-related operation. */ public Object validateAndConvertInPopup( String value, Object originalValue, StringBuffer messageBuffer) { return validateAndConvert(value, originalValue, messageBuffer); } /* * The following is used in both cell and popup operations. */ /* * Internal class for handling key events during editing * of both JTextField and JTextArea. */ private class KeyTextHandler extends BaseKeyTextHandler { public void keyTyped(KeyEvent e) { char c = e.getKeyChar(); // as a coding convenience, create a reference to the text component // that is typecast to JTextComponent. this is not essential, as we // could typecast every reference, but this makes the code cleaner JTextComponent _theComponent = (JTextComponent) DataTypeByte.this._textComponent; String text = _theComponent.getText(); // look for illegal chars if (!DataTypeByte.this._isSigned && c == '-') { // cannot use '-' when unsigned _beepHelper.beep(_theComponent); e.consume(); } // tabs and newlines get put into the text before this check, // so remove them // This only applies to Popup editing since these chars are // not passed to this level by the in-cell editor. if (c == KeyEvent.VK_TAB || c == KeyEvent.VK_ENTER) { // remove all instances of the offending char int index = text.indexOf(c); if (index != -1) { if (index == text.length() - 1) { text = text.substring(0, text.length() - 1); // truncate string } else { text = text.substring(0, index) + text.substring(index + 1); } ((IRestorableTextComponent) _theComponent).updateText(text); _beepHelper.beep(_theComponent); } e.consume(); } if (!(Character.isDigit(c) || (c == '-') || (c == KeyEvent.VK_BACK_SPACE) || (c == KeyEvent.VK_DELETE))) { _beepHelper.beep(_theComponent); e.consume(); } // check for max size reached (only works when DB provides non-zero scale info if (DataTypeByte.this._scale > 0 && text.length() == DataTypeByte.this._scale && c != KeyEvent.VK_BACK_SPACE && c != KeyEvent.VK_DELETE) { // max size reached e.consume(); _beepHelper.beep(_theComponent); } // handle cases of null // The processing is different when nulls are allowed and when they are not. // if (DataTypeByte.this._isNullable) { // user enters something when field is null if (text.equals("<null>")) { if ((c == KeyEvent.VK_BACK_SPACE) || (c == KeyEvent.VK_DELETE)) { // delete when null => original value DataTypeByte.this._textComponent.restoreText(); e.consume(); } else { // non-delete when null => clear field and add text DataTypeByte.this._textComponent.updateText(""); // fall through to normal processing of this key stroke } } else { // check for user deletes last thing in field if ((c == KeyEvent.VK_BACK_SPACE) || (c == KeyEvent.VK_DELETE)) { if (text.length() <= 1) { // about to delete last thing in field, so replace with null DataTypeByte.this._textComponent.updateText("<null>"); e.consume(); } } } } else { // field is not nullable // handleNotNullableField(text, c, e, _textComponent); } } } /* * DataBase-related functions */ /** * On input from the DB, read the data from the ResultSet into the appropriate type of object to * be stored in the table cell. */ public Object readResultSet(ResultSet rs, int index, boolean limitDataRead) throws java.sql.SQLException { byte data = rs.getByte(index); if (rs.wasNull()) { return null; } else { return Byte.valueOf(data); } } /** * When updating the database, generate a string form of this object value that can be used in the * WHERE clause to match the value in the database. A return value of null means that this column * cannot be used in the WHERE clause, while a return of "null" (or "is null", etc) means that the * column can be used in the WHERE clause and the value is actually a null value. This function * must also include the column label so that its output is of the form: "columnName = value" or * "columnName is null" or whatever is appropriate for this column in the database. */ public IWhereClausePart getWhereClauseValue(Object value, ISQLDatabaseMetaData md) { if (value == null || value.toString() == null || value.toString().length() == 0) return new IsNullWhereClausePart(_colDef); else return new ParameterWhereClausePart(_colDef, value, this); } /** * When updating the database, insert the appropriate datatype into the prepared statment at the * given variable position. */ public void setPreparedStatementValue(PreparedStatement pstmt, Object value, int position) throws java.sql.SQLException { if (value == null) { pstmt.setNull(position, _colDef.getSqlType()); } else { pstmt.setInt(position, ((Byte) value).intValue()); } } /** * Get a default value for the table used to input data for a new row to be inserted into the DB. */ public Object getDefaultValue(String dbDefaultValue) { if (dbDefaultValue != null) { // try to use the DB default value StringBuffer mbuf = new StringBuffer(); Object newObject = validateAndConvert(dbDefaultValue, null, mbuf); // if there was a problem with converting, then just fall through // and continue as if there was no default given in the DB. // Otherwise, use the converted object if (mbuf.length() == 0) return newObject; } // no default in DB. If nullable, use null. if (_isNullable) return null; // field is not nullable, so create a reasonable default value return Byte.valueOf((byte) 0); } /* * File IO related functions */ /** * Say whether or not object can be exported to and imported from a file. We put both export and * import together in one test on the assumption that all conversions can be done both ways. */ public boolean canDoFileIO() { return true; } /** * Read a file and construct a valid object from its contents. Errors are returned by throwing an * IOException containing the cause of the problem as its message. * * <p>DataType is responsible for validating that the imported data can be converted to an object, * and then must return a text string that can be used in the Popup window text area. This * object-to-text conversion is the same as is done by the DataType object internally in the * getJTextArea() method. * * <p>File is assumed to be and ASCII string of digits representing a value of this data type. */ public String importObject(FileInputStream inStream) throws IOException { InputStreamReader inReader = new InputStreamReader(inStream); int fileSize = inStream.available(); char charBuf[] = new char[fileSize]; int count = inReader.read(charBuf, 0, fileSize); if (count != fileSize) throw new IOException( "Could read only " + count + " chars from a total file size of " + fileSize + ". Import failed."); // convert file text into a string // Special case: some systems tack a newline at the end of // the text read. Assume that if last char is a newline that // we want everything else in the line. String fileText; if (charBuf[count - 1] == KeyEvent.VK_ENTER) fileText = new String(charBuf, 0, count - 1); else fileText = new String(charBuf); // test that the string is valid by converting it into an // object of this data type StringBuffer messageBuffer = new StringBuffer(); validateAndConvertInPopup(fileText, null, messageBuffer); if (messageBuffer.length() > 0) { // convert number conversion issue into IO issue for consistancy throw new IOException( "Text does not represent data of type " + getClassName() + ". Text was:\n" + fileText); } // return the text from the file since it does // represent a valid data value return fileText; } /** * Construct an appropriate external representation of the object and write it to a file. Errors * are returned by throwing an IOException containing the cause of the problem as its message. * * <p>DataType is responsible for validating that the given text text from a Popup JTextArea can * be converted to an object. This text-to-object conversion is the same as * validateAndConvertInPopup, which may be used internally by the object to do the validation. * * <p>The DataType object must flush and close the output stream before returning. Typically it * will create another object (e.g. an OutputWriter), and that is the object that must be flushed * and closed. * * <p>File is assumed to be and ASCII string of digits representing a value of this data type. */ public void exportObject(FileOutputStream outStream, String text) throws IOException { OutputStreamWriter outWriter = new OutputStreamWriter(outStream); // check that the text is a valid representation StringBuffer messageBuffer = new StringBuffer(); validateAndConvertInPopup(text, null, messageBuffer); if (messageBuffer.length() > 0) { // there was an error in the conversion throw new IOException(new String(messageBuffer)); } // just send the text to the output file outWriter.write(text); outWriter.flush(); outWriter.close(); } }