0001: package net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent;
0002:
0003: /*
0004: * Copyright (C) 2001-2003 Colin Bell
0005: * colbell@users.sourceforge.net
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2.1 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this library; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0020: */
0021: import java.awt.GridBagConstraints;
0022: import java.awt.GridBagLayout;
0023: import java.awt.Insets;
0024: import java.awt.event.KeyEvent;
0025: import java.awt.event.MouseAdapter;
0026: import java.awt.event.MouseEvent;
0027: import java.io.FileInputStream;
0028: import java.io.FileOutputStream;
0029: import java.io.IOException;
0030: import java.io.InputStreamReader;
0031: import java.io.OutputStreamWriter;
0032: import java.sql.PreparedStatement;
0033: import java.sql.ResultSet;
0034: import java.sql.Types;
0035: import java.util.HashMap;
0036: import java.util.Iterator;
0037:
0038: import javax.swing.BorderFactory;
0039: import javax.swing.JCheckBox;
0040: import javax.swing.JScrollPane;
0041: import javax.swing.JTable;
0042: import javax.swing.JTextArea;
0043: import javax.swing.JTextField;
0044: import javax.swing.SwingUtilities;
0045: import javax.swing.event.ChangeEvent;
0046: import javax.swing.event.ChangeListener;
0047: import javax.swing.text.JTextComponent;
0048:
0049: import net.sourceforge.squirrel_sql.fw.datasetviewer.CellDataPopup;
0050: import net.sourceforge.squirrel_sql.fw.datasetviewer.ColumnDisplayDefinition;
0051: import net.sourceforge.squirrel_sql.fw.gui.IntegerField;
0052: import net.sourceforge.squirrel_sql.fw.gui.OkJPanel;
0053: import net.sourceforge.squirrel_sql.fw.sql.ISQLDatabaseMetaData;
0054: import net.sourceforge.squirrel_sql.fw.util.StringManager;
0055: import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory;
0056:
0057: /**
0058: * @author gwg
0059: *
0060: * This class provides the display components for handling String data types,
0061: * specifically SQL types CHAR, VARCHAR, and LONGVARCHAR.
0062: * The display components are for:
0063: * <UL>
0064: * <LI> read-only display within a table cell
0065: * <LI> editing within a table cell
0066: * <LI> read-only or editing display within a separate window
0067: * </UL>
0068: * The class also contains
0069: * <UL>
0070: * <LI> a function to compare two display values
0071: * to see if they are equal. This is needed because the display format
0072: * may not be the same as the internal format, and all internal object
0073: * types may not provide an appropriate equals() function.
0074: * <LI> a function to return a printable text form of the cell contents,
0075: * which is used in the text version of the table.
0076: * </UL>
0077: * <P>
0078: * The components returned from this class extend RestorableJTextField
0079: * and RestorableJTextArea for use in editing table cells that
0080: * contain values of this data type. It provides the special behavior for null
0081: * handling and resetting the cell to the original value.
0082: */
0083: public class DataTypeString extends BaseDataTypeComponent implements
0084: IDataTypeComponent {
0085: private static final StringManager s_stringMgr = StringManagerFactory
0086: .getStringManager(DataTypeString.class);
0087:
0088: /* whether nulls are allowed or not */
0089: private boolean _isNullable;
0090:
0091: /* the number of characters allowed in this field */
0092: private int _columnSize;
0093:
0094: /* table of which we are part (needed for creating popup dialog) */
0095: private JTable _table;
0096:
0097: /* The JTextComponent that is being used for editing */
0098: private IRestorableTextComponent _textComponent;
0099:
0100: /* The CellRenderer used for this data type */
0101: //??? For now, use the same renderer as everyone else.
0102: //??
0103: //?? IN FUTURE: change this to use a new instance of renederer
0104: //?? for this data type.
0105: private DefaultColumnRenderer _renderer = DefaultColumnRenderer
0106: .getInstance();
0107:
0108: /**
0109: * default length of strings when truncated
0110: */
0111: private final static int DEFAULT_LIMIT_READ_LENGTH = 100;
0112:
0113: /**
0114: * Name of this class, which is needed because the class name is needed
0115: * by the static method getControlPanel, so we cannot use something
0116: * like getClass() to find this name.
0117: */
0118: private static final String this ClassName = "net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeString";
0119:
0120: /*
0121: * Properties settable by the user
0122: */
0123: // flag for whether we have already loaded the properties or not
0124: private static boolean propertiesAlreadyLoaded = false;
0125:
0126: /**
0127: * If <tt>true</tt> then show newlines as "\n" for the in-cell display,
0128: * otherwise do not display newlines in the in-cell display
0129: * (i.e. they are thrown out by JTextField when it loads the text document behind the cell).
0130: */
0131: private static boolean _makeNewlinesVisibleInCell = true;
0132:
0133: /**
0134: * If <tt>true</tt> then use the LONGVARCHAR data type in the WHERE clause,
0135: * otherwise do not include it.
0136: * Oracle does not allow that type to be used in a WHERE clause
0137: */
0138: private static boolean _useLongInWhere = true;
0139:
0140: /**
0141: * If <tt>true</tt> then limit the size of string data that is read
0142: * during the initial table load.
0143: */
0144: private static boolean _limitRead = false;
0145:
0146: /**
0147: * If <tt>_limitRead</tt> is <tt>true</tt> then this is how many characters
0148: * to read during the initial table load.
0149: */
0150: private static int _limitReadLength = DEFAULT_LIMIT_READ_LENGTH;
0151:
0152: /**
0153: * If <tt>_limitRead</tt> is <tt>true</tt> and
0154: * this is <tt>true</tt>, then only columns whose label is listed in
0155: * <tt>_limitReadColumnList</tt> are limited.
0156: */
0157: private static boolean _limitReadOnSpecificColumns = false;
0158:
0159: /**
0160: * If <tt>_limitRead</tt> is <tt>true</tt> and
0161: * <tt>_limitReadOnSpecificColumns is <tt>true</tt>, then only columns whose label is listed here.
0162: * The column names are converted to ALL CAPS before being put on this list
0163: * so that they will match the label retrieved from _colDef.
0164: */
0165: private static HashMap<String, String> _limitReadColumnNameMap = new HashMap<String, String>();
0166:
0167: /**
0168: * Constructor - save the data needed by this data type.
0169: */
0170: public DataTypeString(JTable table, ColumnDisplayDefinition colDef) {
0171: _table = table;
0172: _colDef = colDef;
0173: _isNullable = colDef.isNullable();
0174: _columnSize = colDef.getColumnSize();
0175:
0176: loadProperties();
0177: }
0178:
0179: /**
0180: * For sub-classes
0181: */
0182: protected DataTypeString() {
0183:
0184: }
0185:
0186: /** Internal function to get the user-settable properties from the DTProperties,
0187: * if they exist, and to ensure that defaults are set if the properties have
0188: * not yet been created.
0189: * <P>
0190: * This method may be called from different places depending on whether
0191: * an instance of this class is created before the user brings up the Session
0192: * Properties window. In either case, the data is static and is set only
0193: * the first time we are called.
0194: */
0195: private static void loadProperties() {
0196:
0197: if (propertiesAlreadyLoaded == false) {
0198: // get parameters previously set by user, or set default values
0199: _makeNewlinesVisibleInCell = true; // set to the default
0200: String makeNewlinesVisibleString = DTProperties.get(
0201: this ClassName, "makeNewlinesVisibleInCell");
0202: if (makeNewlinesVisibleString != null
0203: && makeNewlinesVisibleString.equals("false"))
0204: _makeNewlinesVisibleInCell = false;
0205:
0206: _useLongInWhere = true; // set to the default
0207: String useLongInWhereString = DTProperties.get(
0208: this ClassName, "useLongInWhere");
0209: if (useLongInWhereString != null
0210: && useLongInWhereString.equals("false"))
0211: _useLongInWhere = false;
0212:
0213: _limitRead = false; // set to default
0214: String limitReadString = DTProperties.get(this ClassName,
0215: "limitRead");
0216: if (limitReadString != null
0217: && limitReadString.equals("true"))
0218: _limitRead = true;
0219:
0220: _limitReadLength = DEFAULT_LIMIT_READ_LENGTH; // set to default
0221: String limitReadLengthString = DTProperties.get(
0222: this ClassName, "limitReadLength");
0223: if (limitReadLengthString != null)
0224: _limitReadLength = Integer
0225: .parseInt(limitReadLengthString);
0226:
0227: _limitReadOnSpecificColumns = false; // set to default
0228: String limitReadOnSpecificColumnsString = DTProperties.get(
0229: this ClassName, "limitReadOnSpecificColumns");
0230: if (limitReadOnSpecificColumnsString != null
0231: && limitReadOnSpecificColumnsString.equals("true"))
0232: _limitReadOnSpecificColumns = true;
0233:
0234: // the list of specific column names is in comma-separated format
0235: // with a comma in front of the first entry as well
0236: _limitReadColumnNameMap.clear(); // empty the map of old values
0237:
0238: String nameString = DTProperties.get(this ClassName,
0239: "limitReadColumnNames");
0240: int start = 0;
0241: int end;
0242: String name;
0243:
0244: while (nameString != null && start < nameString.length()) {
0245: end = nameString.indexOf(',', start + 1);
0246: if (end > -1) {
0247: name = nameString.substring(start + 1, end);
0248: start = end;
0249: } else {
0250: name = nameString.substring(start + 1);
0251: start = nameString.length();
0252: }
0253:
0254: _limitReadColumnNameMap.put(name, null);
0255: }
0256:
0257: propertiesAlreadyLoaded = true;
0258: }
0259: }
0260:
0261: /**
0262: * Return the name of the java class used to hold this data type.
0263: */
0264: public String getClassName() {
0265: return "java.lang.String";
0266: }
0267:
0268: /**
0269: * Determine if two objects of this data type contain the same value.
0270: * Neither of the objects is null
0271: */
0272: public boolean areEqual(Object obj1, Object obj2) {
0273: return ((String) obj1).equals(obj2);
0274: }
0275:
0276: /*
0277: * First we have the cell-related and Text-table operations.
0278: */
0279:
0280: /**
0281: * Render a value into text for this DataType.
0282: */
0283: public String renderObject(Object value) {
0284: String text = (String) _renderer.renderObject(value);
0285: if (_makeNewlinesVisibleInCell) {
0286: text = text.replaceAll("\n", "/\\n");
0287: }
0288: return text;
0289: }
0290:
0291: /**
0292: * This Data Type can be edited in a table cell.
0293: * <P>
0294: * If the data includes newlines, the user must not be allowed to edit it
0295: * in the cell because the CellEditor uses a JTextField which filters out newlines.
0296: * If we try to use anything other than a JTextField, or use a JTextField with no
0297: * newline filtering, the text is not visible in the cell, so the user cannot even read
0298: * the text, much less edit it. The simplest solution is to allow editing of multi-line
0299: * text only in the Popup window.
0300: */
0301: public boolean isEditableInCell(Object originalValue) {
0302: // prevent editing if text contains newlines
0303: if (originalValue != null
0304: && ((String) originalValue).indexOf('\n') > -1)
0305: return false;
0306: else
0307: return true;
0308: }
0309:
0310: /**
0311: * See if a value in a column has been limited in some way and
0312: * needs to be re-read before being used for editing.
0313: * For read-only tables this may actually return true since we want
0314: * to be able to view the entire contents of the cell even if it was not
0315: * completely loaded during the initial table setup.
0316: */
0317: public boolean needToReRead(Object originalValue) {
0318: // if we are not limiting anything, return false
0319: if (_limitRead == false)
0320: return false;
0321:
0322: // if the value is null, then it was read ok
0323: if (originalValue == null)
0324: return false;
0325:
0326: // we are limiting some things.
0327: // if the string we have is less than the limit, then we are ok
0328: // and do not need to re-read (because we already have the whole thing).
0329: if (((String) originalValue).length() < _limitReadLength)
0330: return false;
0331:
0332: // if the data is longer than the limit, then we have previously
0333: // re-read the contents and we do not need to re-read it again
0334: if (((String) originalValue).length() > _limitReadLength)
0335: return false;
0336:
0337: // if we are limiting all columns, then we need to re-read
0338: // because we do not know if we have all the data or not
0339: if (_limitReadOnSpecificColumns == false)
0340: return true;
0341:
0342: // check for the case where we are limiting some columns
0343: // but not limiting this particular column
0344: if (_limitReadColumnNameMap.containsKey(_colDef.getLabel()))
0345: return true; // column is limited and length == limit, so need to re-read
0346: else
0347: return false; // column is not limited, so we have the whole thing
0348: }
0349:
0350: /**
0351: * Return a JTextField usable in a CellEditor.
0352: */
0353: public JTextField getJTextField() {
0354: _textComponent = new RestorableJTextField();
0355:
0356: // special handling of operations while editing this data type
0357: ((RestorableJTextField) _textComponent)
0358: .addKeyListener(new KeyTextHandler());
0359:
0360: //
0361: // handle mouse events for double-click creation of popup dialog.
0362: // This happens only in the JTextField, not the JTextArea, so we can
0363: // make this an inner class within this method rather than a separate
0364: // inner class as is done with the KeyTextHandler class.
0365: //
0366: ((RestorableJTextField) _textComponent)
0367: .addMouseListener(new MouseAdapter() {
0368: public void mousePressed(MouseEvent evt) {
0369: if (evt.getClickCount() == 2) {
0370: MouseEvent tableEvt = SwingUtilities
0371: .convertMouseEvent(
0372: (RestorableJTextField) DataTypeString.this ._textComponent,
0373: evt,
0374: DataTypeString.this ._table);
0375: CellDataPopup.showDialog(
0376: DataTypeString.this ._table,
0377: DataTypeString.this ._colDef,
0378: tableEvt, true);
0379: }
0380: }
0381: }); // end of mouse listener
0382:
0383: return (JTextField) _textComponent;
0384: }
0385:
0386: /**
0387: * Implement the interface for validating and converting to internal object.
0388: * Null is a valid successful return, so errors are indicated only by
0389: * existance or not of a message in the messageBuffer.
0390: */
0391: public Object validateAndConvert(String value,
0392: Object originalValue, StringBuffer messageBuffer) {
0393: // handle null, which is shown as the special string "<null>"
0394: if (value.equals("<null>"))
0395: return null;
0396:
0397: // Do the conversion into the object in a safe manner
0398: return value; // Special case: the input is exactly the output
0399: }
0400:
0401: /**
0402: * If true, this tells the PopupEditableIOPanel to use the
0403: * binary editing panel rather than a pure text panel.
0404: * The binary editing panel assumes the data is an array of bytes,
0405: * converts it into text form, allows the user to change how that
0406: * data is displayed (e.g. Hex, Decimal, etc.), and converts
0407: * the data back from text to bytes when the user editing is completed.
0408: * If this returns false, this DataType class must
0409: * convert the internal data into a text string that
0410: * can be displayed (and edited, if allowed) in a TextField
0411: * or TextArea, and must handle all
0412: * user key strokes related to editing of that data.
0413: */
0414: public boolean useBinaryEditingPanel() {
0415: return false;
0416: }
0417:
0418: /*
0419: * Now define the Popup-related operations.
0420: */
0421:
0422: /**
0423: * Returns true if data type may be edited in the popup,
0424: * false if not.
0425: */
0426: public boolean isEditableInPopup(Object originalValue) {
0427: // The only thing that would prevent us from editing a string in the popup
0428: // is if that string has been truncated when read from the DB.
0429: // Thus, being able to edit the string is the same as not needing to re-read
0430: // the data.
0431: return !needToReRead(originalValue);
0432: }
0433:
0434: /*
0435: * Return a JTextArea usable in the CellPopupDialog.
0436: */
0437: public JTextArea getJTextArea(Object value) {
0438: _textComponent = new RestorableJTextArea();
0439:
0440: // value is a simple string representation of the data,
0441: // but NOT the same one used in the Text and in-cell operations.
0442: // The in-cell version may replace newline chars with "\n" while this version
0443: // does not. In other respects it is the same as the in-cell version because both
0444: // use the _renderer object to do the rendering.
0445: ((RestorableJTextArea) _textComponent)
0446: .setText((String) _renderer.renderObject(value));
0447:
0448: // special handling of operations while editing this data type
0449: ((RestorableJTextArea) _textComponent)
0450: .addKeyListener(new KeyTextHandler());
0451:
0452: return (RestorableJTextArea) _textComponent;
0453: }
0454:
0455: /**
0456: * Validating and converting in Popup is identical to cell-related operation.
0457: */
0458: public Object validateAndConvertInPopup(String value,
0459: Object originalValue, StringBuffer messageBuffer) {
0460: return validateAndConvert(value, originalValue, messageBuffer);
0461: }
0462:
0463: /*
0464: * The following is used by both in-cell and Popup operations.
0465: */
0466:
0467: /*
0468: * Internal class for handling key events during editing
0469: * of both JTextField and JTextArea.
0470: */
0471: private class KeyTextHandler extends BaseKeyTextHandler {
0472: // special handling of operations while editing Strings
0473: public void keyTyped(KeyEvent e) {
0474: char c = e.getKeyChar();
0475:
0476: // as a coding convenience, create a reference to the text component
0477: // that is typecast to JTextComponent. this is not essential, as we
0478: // could typecast every reference, but this makes the code cleaner
0479: JTextComponent _theComponent = (JTextComponent) DataTypeString.this ._textComponent;
0480: String text = _theComponent.getText();
0481:
0482: //?? Is there any way to check for invalid input? Valid input includes
0483: //?? at least any printable character, but could it also include unprintable
0484: //?? characters?
0485:
0486: // check for max size reached (only works when DB provides non-zero scale info
0487: if (DataTypeString.this ._columnSize > 0
0488: && text.length() >= DataTypeString.this ._columnSize
0489: && c != KeyEvent.VK_BACK_SPACE
0490: && c != KeyEvent.VK_DELETE) {
0491: // max size reached
0492: e.consume();
0493: _theComponent.getToolkit().beep();
0494:
0495: // Note: tabs and newlines are allowed in string fields, even though they are unusual.
0496: }
0497:
0498: // handle cases of null
0499: // The processing is different when nulls are allowed and when they are not.
0500: //
0501:
0502: if (DataTypeString.this ._isNullable) {
0503:
0504: // user enters something when field is null
0505: if (text.equals("<null>")) {
0506: if ((c == KeyEvent.VK_BACK_SPACE)
0507: || (c == KeyEvent.VK_DELETE)) {
0508: // delete when null => original value
0509: DataTypeString.this ._textComponent
0510: .restoreText();
0511: e.consume();
0512: } else {
0513: // non-delete when null => clear field and add text
0514: DataTypeString.this ._textComponent
0515: .updateText("");
0516: // fall through to normal processing of this key stroke
0517: }
0518: } else {
0519: // for strings, a "blank" field is allowed, so only
0520: // switch to null when there is nothing left in the field
0521: // and user does delete
0522: if ((c == KeyEvent.VK_BACK_SPACE)
0523: || (c == KeyEvent.VK_DELETE)) {
0524: if (text.length() == 0) {
0525: // about to delete last thing in field, so replace with null
0526: DataTypeString.this ._textComponent
0527: .updateText("<null>");
0528: e.consume();
0529: }
0530: }
0531: }
0532: } else {
0533: // field is not nullable
0534: //
0535: handleNotNullableField(text, c, e, _textComponent);
0536: }
0537: }
0538: }
0539:
0540: /*
0541: * DataBase-related functions
0542: */
0543:
0544: /**
0545: * On input from the DB, read the data from the ResultSet into the appropriate
0546: * type of object to be stored in the table cell.
0547: */
0548: public Object readResultSet(ResultSet rs, int index,
0549: boolean limitDataRead) throws java.sql.SQLException {
0550:
0551: String data = rs.getString(index);
0552: if (rs.wasNull())
0553: return null;
0554: else {
0555: // if this column is being limited, then truncate the data if needed
0556: // (start with a quick check for the data being shorter than the limit,
0557: // in which case we don't need to worry about it).
0558: if (limitDataRead == true && _limitRead == true
0559: && data.length() >= _limitReadLength) {
0560:
0561: // data is longer than the limit, so we need to do more checking
0562: if (_limitReadOnSpecificColumns == false
0563: || (_limitReadOnSpecificColumns == true && _limitReadColumnNameMap
0564: .containsKey(_colDef.getLabel()))) {
0565: // this column is limited, so truncate the data
0566: data = data.substring(0, _limitReadLength);
0567: }
0568:
0569: }
0570: return data;
0571:
0572: }
0573: }
0574:
0575: /**
0576: * When updating the database, generate a string form of this object value
0577: * that can be used in the WHERE clause to match the value in the database.
0578: * A return value of null means that this column cannot be used in the WHERE
0579: * clause, while a return of "null" (or "is null", etc) means that the column
0580: * can be used in the WHERE clause and the value is actually a null value.
0581: * This function must also include the column label so that its output
0582: * is of the form:
0583: * "columnName = value"
0584: * or
0585: * "columnName is null"
0586: * or whatever is appropriate for this column in the database.
0587: */
0588: public String getWhereClauseValue(Object value,
0589: ISQLDatabaseMetaData md) {
0590: // first do special check to see if we should use LONGVARCHAR
0591: // in the WHERE clause.
0592: // (Oracle does not allow this.)
0593: if (_colDef.getSqlType() == Types.LONGVARCHAR
0594: && _useLongInWhere == false)
0595: return ""; // this column cannot be used in a WHERE clause
0596:
0597: if (value == null || value.toString() == null)
0598: return _colDef.getLabel() + " IS NULL";
0599: else {
0600: // We cannot use this data in the WHERE clause if it has been truncated.
0601: // Since being truncated is the same as needing to re-read,
0602: // only use this in the WHERE clause if we do not need to re-read
0603: if (!needToReRead(value))
0604: return _colDef.getLabel() + "='"
0605: + escapeLine(value.toString(), md) + "'";
0606: else
0607: return ""; // value is truncated, so do not use in WHERE clause
0608: }
0609: }
0610:
0611: /**
0612: * When updating the database, insert the appropriate datatype into the
0613: * prepared statment at the given variable position.
0614: */
0615: public void setPreparedStatementValue(PreparedStatement pstmt,
0616: Object value, int position) throws java.sql.SQLException {
0617: if (value == null) {
0618: pstmt.setNull(position, _colDef.getSqlType());
0619: } else {
0620: pstmt.setString(position, ((String) value));
0621: }
0622: }
0623:
0624: /**
0625: * Get a default value for the table used to input data for a new row
0626: * to be inserted into the DB.
0627: */
0628: public Object getDefaultValue(String dbDefaultValue) {
0629: if (dbDefaultValue != null) {
0630: // try to use the DB default value
0631: StringBuffer mbuf = new StringBuffer();
0632: Object newObject = validateAndConvert(dbDefaultValue, null,
0633: mbuf);
0634:
0635: // if there was a problem with converting, then just fall through
0636: // and continue as if there was no default given in the DB.
0637: // Otherwise, use the converted object
0638: if (mbuf.length() == 0)
0639: return newObject;
0640: }
0641:
0642: // no default in DB. If nullable, use null.
0643: if (_isNullable)
0644: return null;
0645:
0646: // field is not nullable, so create a reasonable default value
0647: return "";
0648: }
0649:
0650: /**
0651: * When strings are used in the WHERE clause, any single quote characters must be
0652: * "escaped" so that they are not confused with the "end of string"
0653: * single quote used by SQL. The escape sequence is that a single quote
0654: * is represented by two single quotes in a row.
0655: */
0656: static public String escapeLine(String s, ISQLDatabaseMetaData md) {
0657: String retvalue = s;
0658: if (s.indexOf("'") != -1) {
0659: StringBuffer hold = new StringBuffer();
0660: char c;
0661: for (int i = 0; i < s.length(); i++) {
0662: if ((c = s.charAt(i)) == '\'') {
0663: hold.append("''");
0664: } else {
0665: hold.append(c);
0666: }
0667: }
0668: retvalue = hold.toString();
0669: }
0670: return DatabaseSpecificEscape.escapeSQL(retvalue, md);
0671: }
0672:
0673: /*
0674: * File IO related functions
0675: */
0676:
0677: /**
0678: * Say whether or not object can be exported to and imported from
0679: * a file. We put both export and import together in one test
0680: * on the assumption that all conversions can be done both ways.
0681: */
0682: public boolean canDoFileIO() {
0683: return true;
0684: }
0685:
0686: /**
0687: * Read a file and construct a valid object from its contents.
0688: * Errors are returned by throwing an IOException containing the
0689: * cause of the problem as its message.
0690: * <P>
0691: * DataType is responsible for validating that the imported
0692: * data can be converted to an object, and then must return
0693: * a text string that can be used in the Popup window text area.
0694: * This object-to-text conversion is the same as is done by
0695: * the DataType object internally in the getJTextArea() method.
0696: *
0697: * <P>
0698: * File is assumed to be printable text characters,
0699: * possibly including newlines and tabs but not characters
0700: * that would require a binary representation to display
0701: * to user.
0702: */
0703: public String importObject(FileInputStream inStream)
0704: throws IOException {
0705:
0706: InputStreamReader inReader = new InputStreamReader(inStream);
0707:
0708: int fileSize = inStream.available();
0709:
0710: char charBuf[] = new char[fileSize];
0711:
0712: int count = inReader.read(charBuf, 0, fileSize);
0713:
0714: if (count != fileSize)
0715: throw new IOException("Could read only " + count
0716: + " chars from a total file size of " + fileSize
0717: + ". Import failed.");
0718:
0719: // convert to string
0720: // Special case: some systems tack a newline at the end of
0721: // the text read. Assume that if last char is a newline that
0722: // we want everything else in the line.
0723: String fileText;
0724: if (charBuf[count - 1] == KeyEvent.VK_ENTER)
0725: fileText = new String(charBuf, 0, count - 1);
0726: else
0727: fileText = new String(charBuf);
0728:
0729: // data must fit into the column's max size
0730: if (_columnSize > 0 && fileText.length() > _columnSize)
0731: throw new IOException(
0732: "File contains "
0733: + fileText.length()
0734: + " characters which exceeds this column's limit of "
0735: + _columnSize + ".\nImport Aborted.");
0736:
0737: return fileText;
0738: }
0739:
0740: /**
0741: * Construct an appropriate external representation of the object
0742: * and write it to a file.
0743: * Errors are returned by throwing an IOException containing the
0744: * cause of the problem as its message.
0745: * <P>
0746: * DataType is responsible for validating that the given text
0747: * text from a Popup JTextArea can be converted to an object.
0748: * This text-to-object conversion is the same as validateAndConvertInPopup,
0749: * which may be used internally by the object to do the validation.
0750: * <P>
0751: * The DataType object must flush and close the output stream before returning.
0752: * Typically it will create another object (e.g. an OutputWriter), and
0753: * that is the object that must be flushed and closed.
0754: *
0755: * <P>
0756: * File is assumed to be printable text characters,
0757: * possibly including newlines and tabs but not characters
0758: * that would require a binary representation to display
0759: * to user.
0760: */
0761: public void exportObject(FileOutputStream outStream, String text)
0762: throws IOException {
0763:
0764: OutputStreamWriter outWriter = new OutputStreamWriter(outStream);
0765:
0766: // for string, just send the text to the output file
0767: outWriter.write(text);
0768: outWriter.flush();
0769: outWriter.close();
0770: }
0771:
0772: /*
0773: * Property change control panel
0774: */
0775:
0776: /**
0777: * Generate a JPanel containing controls that allow the user
0778: * to adjust the properties for this DataType.
0779: * All properties are static accross all instances of this DataType.
0780: * However, the class may choose to apply the information differentially,
0781: * such as keeping a list (also entered by the user) of table/column names
0782: * for which certain properties should be used.
0783: * <P>
0784: * This is called ONLY if there is at least one property entered into the DTProperties
0785: * for this class.
0786: * <P>
0787: * Since this method is called by reflection on the Method object derived from this class,
0788: * it does not need to be included in the Interface.
0789: * It would be nice to include this in the Interface for consistancy, documentation, etc,
0790: * but the Interface does not seem to like static methods.
0791: */
0792: public static OkJPanel getControlPanel() {
0793:
0794: /*
0795: * If you add this method to one of the standard DataTypes in the
0796: * fw/datasetviewer/cellcomponent directory, you must also add the name
0797: * of that DataType class to the list in CellComponentFactory, method
0798: * getControlPanels, variable named initialClassNameList.
0799: * If the class is being registered with the factory using registerDataType,
0800: * then you should not include the class name in the list (it will be found
0801: * automatically), but if the DataType is part of the case statement in the
0802: * factory method getDataTypeObject, then it does need to be explicitly listed
0803: * in the getControlPanels method also.
0804: */
0805:
0806: // if this panel is called before any instances of the class have been
0807: // created, we need to load the properties from the DTProperties.
0808: loadProperties();
0809:
0810: return new ClobOkJPanel();
0811: }
0812:
0813: /**
0814: * Inner class that extends OkJPanel so that we can call the ok()
0815: * method to save the data when the user is happy with it.
0816: */
0817: private static class ClobOkJPanel extends OkJPanel {
0818: /*
0819: * GUI components - need to be here because they need to be
0820: * accessible from the event handlers to alter each other's state.
0821: */
0822:
0823: private static final long serialVersionUID = -578848466466561988L;
0824:
0825: // check box for whether to show newlines as "\n" for in-cell display
0826: private JCheckBox _makeNewlinesVisibleInCellChk =
0827: // i18n[dataTypeString.newlines=Show newlines as \\n within cells]
0828: new JCheckBox(s_stringMgr.getString("dataTypeString.newlines"));
0829:
0830: // check box for whether to use LONGVARCHAR in WHERE clause
0831: // (Oracle does not allow that type in WHERE clause)
0832: private JCheckBox _useLongInWhereChk =
0833: // i18n[dataTypeString.allowLongVarchar=Allow LONGVARCHAR type to be used in WHERE clause]
0834: new JCheckBox(s_stringMgr
0835: .getString("dataTypeString.allowLongVarchar"));
0836:
0837: // check box for whether to do any limiting of the data read during initial table load
0838: private JCheckBox _limitReadChk =
0839: // i18n[dataTypeString.limitSize=Limit size of strings read during initial table load to max of:]
0840: new JCheckBox(s_stringMgr.getString("dataTypeString.limitSize"));
0841:
0842: // check box for whether to show newlines as "\n" for in-cell display
0843: private IntegerField _limitReadLengthTextField = new IntegerField(
0844: 5);
0845:
0846: // check box for whether to show newlines as "\n" for in-cell display
0847: private JCheckBox _limitReadOnSpecificColumnsChk =
0848: // i18n[dataTypeString.limitReadOnly=Limit read only on columns with these names:]
0849: new JCheckBox(s_stringMgr
0850: .getString("dataTypeString.limitReadOnly"));
0851:
0852: // check box for whether to show newlines as "\n" for in-cell display
0853: private JTextArea _limitReadColumnNameTextArea = new JTextArea(
0854: 5, 12);
0855:
0856: public ClobOkJPanel() {
0857:
0858: /* set up the controls */
0859:
0860: // checkbox for displaying newlines as \n in-cell
0861: _makeNewlinesVisibleInCellChk
0862: .setSelected(_makeNewlinesVisibleInCell);
0863:
0864: // checkbox for using LONG in WHERE clause
0865: _useLongInWhereChk.setSelected(_useLongInWhere);
0866:
0867: // checkbox for limit/no-limit on data read during initial table load
0868: _limitReadChk.setSelected(_limitRead);
0869: _limitReadChk.addChangeListener(new ChangeListener() {
0870: public void stateChanged(ChangeEvent e) {
0871: _limitReadLengthTextField.setEnabled(_limitReadChk
0872: .isSelected());
0873: _limitReadOnSpecificColumnsChk
0874: .setEnabled(_limitReadChk.isSelected());
0875: _limitReadColumnNameTextArea
0876: .setEnabled(_limitReadChk.isSelected()
0877: && (_limitReadOnSpecificColumnsChk
0878: .isSelected()));
0879: }
0880: });
0881:
0882: // fill in the current limit length
0883: _limitReadLengthTextField.setInt(_limitReadLength);
0884:
0885: // set the flag for whether or not to limit only on specific fields
0886: _limitReadOnSpecificColumnsChk
0887: .setSelected(_limitReadOnSpecificColumns);
0888: _limitReadOnSpecificColumnsChk
0889: .addChangeListener(new ChangeListener() {
0890: public void stateChanged(ChangeEvent e) {
0891: _limitReadColumnNameTextArea
0892: .setEnabled(_limitReadOnSpecificColumnsChk
0893: .isSelected());
0894: }
0895: });
0896:
0897: // fill in list of column names to check against
0898: Iterator<String> names = _limitReadColumnNameMap.keySet()
0899: .iterator();
0900: StringBuffer namesText = new StringBuffer();
0901: while (names.hasNext()) {
0902: if (namesText.length() > 0)
0903: namesText.append("\n" + names.next());
0904: else
0905: namesText.append(names.next());
0906: }
0907: _limitReadColumnNameTextArea.setText(namesText.toString());
0908:
0909: // handle cross-connection between fields
0910: _limitReadLengthTextField.setEnabled(_limitReadChk
0911: .isSelected());
0912: _limitReadOnSpecificColumnsChk.setEnabled(_limitReadChk
0913: .isSelected());
0914: _limitReadColumnNameTextArea.setEnabled(_limitReadChk
0915: .isSelected()
0916: && (_limitReadOnSpecificColumnsChk.isSelected()));
0917: ;
0918:
0919: /*
0920: * Create the panel and add the GUI items to it
0921: */
0922:
0923: setLayout(new GridBagLayout());
0924:
0925: setBorder(BorderFactory.createTitledBorder(
0926: // i18n[dataTypeString.typeChar=CHAR, VARCHAR, LONGVARCHAR (SQL types 1, 12, -1)]
0927: s_stringMgr.getString("dataTypeString.typeChar")));
0928: final GridBagConstraints gbc = new GridBagConstraints();
0929: gbc.fill = GridBagConstraints.HORIZONTAL;
0930: gbc.insets = new Insets(4, 4, 4, 4);
0931: gbc.anchor = GridBagConstraints.WEST;
0932:
0933: gbc.gridx = 0;
0934: gbc.gridy = 0;
0935:
0936: gbc.gridwidth = GridBagConstraints.REMAINDER;
0937: add(_makeNewlinesVisibleInCellChk, gbc);
0938:
0939: gbc.gridx = 0;
0940: gbc.gridy++;
0941: gbc.gridwidth = GridBagConstraints.REMAINDER;
0942: add(_useLongInWhereChk, gbc);
0943:
0944: gbc.gridy++;
0945: gbc.gridx = 0;
0946: gbc.gridwidth = 1;
0947: add(_limitReadChk, gbc);
0948:
0949: gbc.gridx++;
0950: gbc.gridwidth = 1;
0951: add(_limitReadLengthTextField, gbc);
0952:
0953: gbc.gridy++;
0954: gbc.gridx = 0;
0955: gbc.gridwidth = 1;
0956: add(_limitReadOnSpecificColumnsChk, gbc);
0957:
0958: gbc.gridx++;
0959: gbc.gridwidth = GridBagConstraints.REMAINDER;
0960: JScrollPane scrollPane = new JScrollPane();
0961:
0962: // If we don't always show the scrollbars the whole DataTypePreferencesPanel is flickering like hell.
0963: scrollPane
0964: .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
0965: scrollPane
0966: .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
0967:
0968: scrollPane.setViewportView(_limitReadColumnNameTextArea);
0969: add(scrollPane, gbc);
0970:
0971: } // end of constructor for inner class
0972:
0973: /**
0974: * User has clicked OK in the surrounding JPanel,
0975: * so save the current state of all variables
0976: */
0977: public void ok() {
0978: // get the values from the controls and set them in the static properties
0979: _makeNewlinesVisibleInCell = _makeNewlinesVisibleInCellChk
0980: .isSelected();
0981: DTProperties.put(this ClassName,
0982: "makeNewlinesVisibleInCell", Boolean.valueOf(
0983: _makeNewlinesVisibleInCell).toString());
0984:
0985: _useLongInWhere = _useLongInWhereChk.isSelected();
0986: DTProperties.put(this ClassName, "useLongInWhere", Boolean
0987: .valueOf(_useLongInWhere).toString());
0988:
0989: _limitRead = _limitReadChk.isSelected();
0990: DTProperties.put(this ClassName, "limitRead", Boolean
0991: .valueOf(_limitRead).toString());
0992:
0993: _limitReadLength = _limitReadLengthTextField.getInt();
0994: DTProperties.put(this ClassName, "limitReadLength", Integer
0995: .toString(_limitReadLength));
0996:
0997: _limitReadOnSpecificColumns = _limitReadOnSpecificColumnsChk
0998: .isSelected();
0999: DTProperties.put(this ClassName,
1000: "limitReadOnSpecificColumns", Boolean.valueOf(
1001: _limitReadOnSpecificColumns).toString());
1002:
1003: // Handle list of column names
1004:
1005: // remove old name list from map
1006: _limitReadColumnNameMap.clear();
1007: // extract column names from text area
1008: String columnNameText = _limitReadColumnNameTextArea
1009: .getText();
1010:
1011: int start = 0;
1012: int end;
1013: String name;
1014: String propertyString = "";
1015:
1016: while (start < columnNameText.length()) {
1017: // find the next name in the text
1018: end = columnNameText.indexOf('\n', start + 1);
1019: if (end > -1) {
1020: name = columnNameText.substring(start, end);
1021: start = end;
1022: } else {
1023: name = columnNameText.substring(start);
1024: start = columnNameText.length();
1025: }
1026:
1027: // cleanup and standardize the name, and add it to the map
1028: name = name.trim().toUpperCase();
1029: if (name.length() == 0)
1030: continue; // skip blank lines
1031:
1032: _limitReadColumnNameMap.put(name.trim().toUpperCase(),
1033: null);
1034:
1035: // add name to comma-separated string for saving in properties
1036: propertyString += "," + name.trim().toUpperCase();
1037: } // end while
1038:
1039: DTProperties.put(this ClassName, "limitReadColumnNames",
1040: propertyString);
1041:
1042: } // end ok
1043:
1044: } // end of inner class
1045:
1046: }
|