0001: package net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent;
0002:
0003: import java.awt.Color;
0004: import java.awt.Component;
0005: import java.io.FileInputStream;
0006: import java.io.FileOutputStream;
0007: import java.io.IOException;
0008: import java.lang.reflect.Method;
0009: import java.sql.PreparedStatement;
0010: import java.sql.ResultSet;
0011: import java.sql.Types;
0012: import java.util.ArrayList;
0013: import java.util.Arrays;
0014: import java.util.HashMap;
0015:
0016: import javax.swing.DefaultCellEditor;
0017: import javax.swing.JLabel;
0018: import javax.swing.JTable;
0019: import javax.swing.JTextArea;
0020: import javax.swing.JTextField;
0021: import javax.swing.table.DefaultTableCellRenderer;
0022: import javax.swing.table.TableCellRenderer;
0023:
0024: import net.sourceforge.squirrel_sql.fw.datasetviewer.ColumnDisplayDefinition;
0025: import net.sourceforge.squirrel_sql.fw.gui.OkJPanel;
0026: import net.sourceforge.squirrel_sql.fw.sql.ISQLDatabaseMetaData;
0027: import net.sourceforge.squirrel_sql.fw.util.log.ILogger;
0028: import net.sourceforge.squirrel_sql.fw.util.log.LoggerController;
0029:
0030: /**
0031: * @author gwg
0032: *
0033: * This class is used by other parts of SQuirreL to handle all
0034: * DataType-specific behavior for the ContentsTab.
0035: * This includes reading/updating the DB, formatting data for display,
0036: * validating user input, converting user input into an internal object
0037: * of the appropriate type, and saving the data to or reading from a file.
0038: * The actual work is handled by separate DataType-specific classes,
0039: * so this class is a facade that selects the class to use and calls
0040: * the desired method on that class. All of the DataType-specifc classes
0041: * implement the IDataTypeComponent interface.
0042: * <P>
0043: * At this time we use only the type of the data to determine which DataType
0044: * class to use for the requested component. In the future it may become
0045: * useful to include other factors, such as the specific table and column
0046: * being displayed. This info could be used to select a specialized class
0047: * (or a general class using an external resource file) to display table
0048: * and column specific translations of data, such as mapping an integer
0049: * code in the DB into a mnemonic representation (eg. 1='dial-up', 2='cable', 3='DSL').
0050: * <P>
0051: * The JTable is needed to allow the components to identify which cell
0052: * is being referred to by a double-click mouse event, which causes
0053: * a popup editing window to be generated.
0054: * <P>
0055: * <B>Creating new DataType handlers</B>
0056: * Plugins and other code may need to create and install handlers for
0057: * data types that are not included in the standard SQuirreL product.
0058: * This might be needed to handle DBMS-specific data types,
0059: * or to override the standard behavior for a specific data type.
0060: * For example:
0061: * <DL>
0062: * <LI>
0063: * PostgreSQL defines several non-standard data types, such as "bytea",
0064: * "tid", "xid", int2vector", etc. All of these have the same SQL type-code
0065: * of "1111", which means "OTHER".
0066: * The default ContesTab operation on type 1111 is to not display it
0067: * and not allow editing. However, if a plugin is able to define the
0068: * operations on those fields, it can register a handler that will
0069: * display the data appropriately and allow editing on those fields.
0070: * <LI>
0071: * If a DBMS defines a standard SQL data type in a non-standard way,
0072: * a plugin for that DBMS may need to override the normal DataType class
0073: * for that data type with another.
0074: * An example would be if a DBMS implemented SQL type SMALLINT,
0075: * which is handled internally as a Short, as an INTEGER, which is
0076: * handled as an Integer.
0077: * In order to correctly read and display values of that type in the ContentsTab,
0078: * the handler for SQL type SMALLINT (=5) should be changed from
0079: * DataTypeShort to DataTypeInteger.
0080: * </DL>
0081: * <P>
0082: * Here is how to create and register a DataType handler:
0083: * <DL>
0084: * <LI>
0085: * Using SQuirrel, connect to the DBMS.
0086: * Click on the "Data Types" tab.
0087: * Get the "TYPE_NAME" and "DATA_TYPE" values for the data type
0088: * for which you want to create a handler.
0089: * <LI>
0090: * Create a handler for that type of data.
0091: * The handler must implement the IDataTypeComponet interface.
0092: * The files whose names start with "DataType..."
0093: * in the package net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent
0094: * (i.e. the same place as this file)
0095: * are examples of how to handle different data types.
0096: * The data must be held in the JTable as a Java object.
0097: * You must first identify what class of that object is.
0098: * It may be your own local class or one of the standard Java classes
0099: * since all of the code outside of the DataType class just treats it as an Object.
0100: * The DataType class that you create must handle all transformations
0101: * between that internal Java class and the Database,
0102: * rendering in a cell, rendering in the Popup editing window
0103: * (which may be the same as in a cell), and export/import with files.
0104: * The DataType class also determines whether or not these verious translations
0105: * are allowed.
0106: * <LI>
0107: * As part of the initialization of the application or plugin,
0108: * register the DataType class as the handler for the data type.
0109: * This is done using the static method registerDataType() in this class.
0110: * The first argument is the fully-qualified name of the method,
0111: * and the other two arguments identify the data type.
0112: * For example:
0113: * <PRE>
0114: * CellComponentFactory.registerDataType(
0115: * "net.sourceforge.squirrel_sql.plugins.postgreSQLPlugin.DataTypeBytea",
0116: * 1111, "bytea");
0117: * </PRE>
0118: * Another example, in the case where a SMALLINT is actually handled
0119: * by the DBMS as an integer:
0120: * <PRE>
0121: * CellComponentFactory.registerDataType(
0122: * "net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeInteger",
0123: * 5, "SHORT");
0124: * </PRE>
0125: * Once the DataType class is registered,
0126: * that class is called to process that data type in all of the
0127: * associated data type.
0128: * </DL>
0129: * <P>
0130: * The DataType registration process does not associate DataType handlers
0131: * with particular DBMSs. Therefore, if two plugins for two different DBMSs
0132: * register exactly the same SQL Type code and data type name,
0133: * one of the databases will not be handled correctly.
0134: */
0135: public class CellComponentFactory {
0136:
0137: /* map of existing DataType objects for each column.
0138: * The key is the ColumnDisplayDefinition object, and the value
0139: * is the DataTypeObject for that column's data type.
0140: */
0141: static HashMap<ColumnDisplayDefinition, IDataTypeComponent> _colDataTypeObjects = new HashMap<ColumnDisplayDefinition, IDataTypeComponent>();
0142:
0143: /* map of DBMS-specific registered data handlers.
0144: * The key is a string of the form:
0145: * <SQL type as a string>:<SQL type name>
0146: * and the value is a factory that can create instances of DBMS-specific
0147: * DataTypeComponets.
0148: */
0149: static HashMap<String, IDataTypeComponentFactory> _pluginDataTypeFactories = new HashMap<String, IDataTypeComponentFactory>();
0150:
0151: /* The current JTable that we are working with.
0152: * This is used only to see when the user moves
0153: * to a different JTable so we know when to clear
0154: * the HashMap of DataTypeObjects.
0155: */
0156: static JTable _table = null;
0157:
0158: /* logging mechanism for errors */
0159: static private ILogger s_log = LoggerController
0160: .createLogger(CellComponentFactory.class);
0161:
0162: /**
0163: * Return the name of the Java class that is used to represent
0164: * this data type within the application.
0165: */
0166: public static String getClassName(ColumnDisplayDefinition colDef) {
0167: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0168: colDef);
0169: if (dataTypeObject != null)
0170: return dataTypeObject.getClassName();
0171: else
0172: return "java.lang.Object";
0173: }
0174:
0175: /**
0176: * Determine if the values of two objects are the same.
0177: */
0178: public static boolean areEqual(ColumnDisplayDefinition colDef,
0179: Object newValue, Object oldValue) {
0180:
0181: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0182: colDef);
0183: if (dataTypeObject != null)
0184: return dataTypeObject.areEqual(newValue, oldValue);
0185:
0186: // we should never get here because the areEqual function is only
0187: // called when we are trying to update the database, so we know
0188: // that we have a DataType object for this column (or we would
0189: // have been stopped from editing by the isEditableXXX methods),
0190: // but we need a return here to keep the compiler happy.
0191: return false;
0192: }
0193:
0194: /*
0195: * Operations for Text and in-cell work
0196: */
0197:
0198: /**
0199: * Render value of object as a string for text output.
0200: * Used by Text version of table.
0201: */
0202: public static String renderObject(Object value,
0203: ColumnDisplayDefinition colDef) {
0204: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0205: colDef);
0206:
0207: if (dataTypeObject != null)
0208: return dataTypeObject.renderObject(value);
0209:
0210: // default behavior: toString
0211: if (null == value) {
0212: return "<null>";
0213: } else {
0214: return value.toString();
0215: }
0216: }
0217:
0218: /**
0219: * Get a TableCellRenderer for the given column.
0220: */
0221: public static TableCellRenderer getTableCellRenderer(
0222: ColumnDisplayDefinition colDef) {
0223: return new CellRenderer(getDataTypeObject(null, colDef));
0224: }
0225:
0226: /**
0227: * The base component of a DefaultTableCellRenderer is a JLabel.
0228: * @author gwg
0229: */
0230: static private final class CellRenderer extends
0231: DefaultTableCellRenderer implements
0232: SquirrelTableCellRenderer {
0233: private static final long serialVersionUID = 1L;
0234: transient private final IDataTypeComponent _dataTypeObject;
0235:
0236: CellRenderer(IDataTypeComponent dataTypeObject) {
0237: super ();
0238:
0239: _dataTypeObject = dataTypeObject;
0240: }
0241:
0242: /**
0243: *
0244: * Returns the default table cell renderer - overridden from DefaultTableCellRenderer.
0245: *
0246: * @param table the <code>JTable</code>
0247: * @param value the value to assign to the cell at
0248: * <code>[row, column]</code>
0249: * @param isSelected true if cell is selected
0250: * @param hasFocus true if cell has focus
0251: * @param row the row of the cell to render
0252: * @param column the column of the cell to render
0253: * @return the default table cell renderer
0254: */
0255: public Component getTableCellRendererComponent(JTable table,
0256: Object value, boolean isSelected, boolean hasFocus,
0257: int row, int column) {
0258:
0259: JLabel label = (JLabel) super
0260: .getTableCellRendererComponent(table, value,
0261: isSelected, hasFocus, row, column);
0262:
0263: // if text cannot be edited in the cell but can be edited in
0264: // the popup, show that by changing the text colors.
0265: if (_dataTypeObject != null
0266: && _dataTypeObject.isEditableInCell(value) == false
0267: && _dataTypeObject.isEditableInPopup(value) == true) {
0268: // Use a CYAN background to indicate that the cell is
0269: // editable in the popup
0270: setBackground(Color.cyan);
0271: } else {
0272: // since the previous entry might have changed the color,
0273: // we need to reset the color back to default value for table cells,
0274: // taking into account whether the cell is selected or not.
0275: if (isSelected)
0276: setBackground(table.getSelectionBackground());
0277: else
0278: setBackground(table.getBackground());
0279: }
0280:
0281: return label;
0282: }
0283:
0284: public void setValue(Object value) {
0285: // default behavior if no DataType object is to use the
0286: // DefaultColumnRenderer with no modification.
0287: if (_dataTypeObject != null)
0288: super .setValue(_dataTypeObject.renderObject(value));
0289: else
0290: super .setValue(DefaultColumnRenderer.getInstance()
0291: .renderObject(value));
0292: }
0293:
0294: public Object renderValue(Object value) {
0295: if (_dataTypeObject != null) {
0296: return _dataTypeObject.renderObject(value);
0297: } else {
0298: return DefaultColumnRenderer.getInstance()
0299: .renderObject(value);
0300: }
0301: }
0302: }
0303:
0304: /**
0305: * Return true if the data type for the column may be edited
0306: * within the table cell, false if not.
0307: */
0308: public static boolean isEditableInCell(
0309: ColumnDisplayDefinition colDef, Object originalValue) {
0310: if (colDef.isAutoIncrement()) {
0311: return false;
0312: }
0313: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0314: colDef);
0315:
0316: if (dataTypeObject != null)
0317: return dataTypeObject.isEditableInCell(originalValue);
0318:
0319: // there was no data type object, so this data type is unknown
0320: // to squirrel and thus cannot be edited.
0321: return false;
0322: }
0323:
0324: /**
0325: * See if a value in a column has been limited in some way and
0326: * needs to be re-read before being used for editing.
0327: * For read-only tables this may actually return true since we want
0328: * to be able to view the entire contents of the cell even if it was not
0329: * completely loaded during the initial table setup.
0330: */
0331: public static boolean needToReRead(ColumnDisplayDefinition colDef,
0332: Object originalValue) {
0333: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0334: colDef);
0335:
0336: if (dataTypeObject != null)
0337: return dataTypeObject.needToReRead(originalValue);
0338:
0339: // default - if we do not know the data type, then we cannot re-read it
0340: return false;
0341: };
0342:
0343: /**
0344: * Return a DefaultCellEditor using a JTextField with appropriate
0345: * handlers to manage the type of input for the cell.
0346: */
0347: public static DefaultCellEditor getInCellEditor(JTable table,
0348: ColumnDisplayDefinition colDef) {
0349:
0350: DefaultCellEditor ed;
0351:
0352: IDataTypeComponent dataTypeObject = getDataTypeObject(table,
0353: colDef);
0354:
0355: JTextField textField;
0356:
0357: // Default behavior if no data type found is to use a restorable text field
0358: // with no other special behavior and hope the object has a toString().
0359: if (dataTypeObject != null)
0360: textField = dataTypeObject.getJTextField();
0361: else
0362: textField = new RestorableJTextField();
0363:
0364: textField.setBackground(Color.yellow);
0365:
0366: ed = new CellEditorUsingRenderer(textField, dataTypeObject);
0367: ed.setClickCountToStart(1);
0368: return ed;
0369: }
0370:
0371: /**
0372: * Call the validate and convert method in the appropriate
0373: * DataType object.
0374: */
0375: public static Object validateAndConvert(
0376: ColumnDisplayDefinition colDef, Object originalValue,
0377: String inputValue, StringBuffer messageBuffer) {
0378:
0379: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0380: colDef);
0381:
0382: if (dataTypeObject != null) {
0383: // we have an appropriate data type object
0384: return dataTypeObject.validateAndConvert(inputValue,
0385: originalValue, messageBuffer);
0386: }
0387:
0388: // No appropriate DataType for this column, so do the best
0389: // we can with what we know.
0390: //
0391: // THIS MAY NOT BE THE BEST BEHAVIOR HERE!!!!!!!
0392:
0393: // Default Operation
0394: if (inputValue.equals("<null>"))
0395: return null;
0396: else
0397: return inputValue;
0398: }
0399:
0400: /**
0401: * Return the flag from the component saying
0402: * whether to do editing in the special binary editing panel
0403: * or the component will handle all text input.
0404: */
0405: public static boolean useBinaryEditingPanel(
0406: ColumnDisplayDefinition colDef) {
0407: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0408: colDef);
0409:
0410: if (dataTypeObject != null) {
0411: // we have an appropriate data type object
0412: return dataTypeObject.useBinaryEditingPanel();
0413: }
0414: return false; // no object, so do not assume binary editing will work
0415: }
0416:
0417: /*
0418: * Operations for Popup work.
0419: */
0420:
0421: /**
0422: * Return true if the data type for the column may be edited
0423: * in the popup, false if not.
0424: */
0425: public static boolean isEditableInPopup(
0426: ColumnDisplayDefinition colDef, Object originalValue) {
0427: if (colDef != null && colDef.isAutoIncrement()) {
0428: return false;
0429: }
0430:
0431: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0432: colDef);
0433:
0434: if (dataTypeObject != null) {
0435: return dataTypeObject.isEditableInPopup(originalValue);
0436: }
0437:
0438: // there was no data type object, so this data type is unknown
0439: // to squirrel and thus cannot be edited.
0440: return false;
0441: }
0442:
0443: /**
0444: * Return a JTextArea with appropriate handlers for editing
0445: * the type of data in the cell.
0446: */
0447: public static JTextArea getJTextArea(
0448: ColumnDisplayDefinition colDef, Object value) {
0449:
0450: // The first argument is a JTable, which is only used by instances
0451: // of JTextField to convert coordinates on a double-click. Since that
0452: // cannot happen with the JTextArea, do not bother passing the table.
0453:
0454: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0455: colDef);
0456:
0457: if (dataTypeObject != null)
0458: return dataTypeObject.getJTextArea(value);
0459:
0460: // default behavior if no appropriate data type found is to create
0461: // a simple JTextArea with no special handling.
0462: //
0463: // In Theory, this cannot happen because if there is no data type object
0464: // for this column's data type, then isEditableInPopup returns false, so
0465: // we should not get here. If there IS a data type object, and isEditableInPopup
0466: // returns true, then we would have executed the return statement above.
0467: // Assume that the value can be represented as a string.
0468: RestorableJTextArea textArea = new RestorableJTextArea();
0469: if (value != null) {
0470: textArea.setText(value.toString());
0471: } else {
0472: textArea.setText("");
0473: }
0474: return textArea;
0475: }
0476:
0477: /**
0478: * Call the validate and convert method in the appropriate
0479: * DataType object.
0480: */
0481: public static Object validateAndConvertInPopup(
0482: ColumnDisplayDefinition colDef, Object originalValue,
0483: String inputValue, StringBuffer messageBuffer) {
0484:
0485: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0486: colDef);
0487:
0488: if (dataTypeObject != null) {
0489: // we have an appropriate data type object
0490: return dataTypeObject.validateAndConvertInPopup(inputValue,
0491: originalValue, messageBuffer);
0492: }
0493:
0494: // No appropriate DataType for this column, so do the best
0495: // we can with what we know.
0496: //
0497: // THIS MAY NOT BE THE BEST BEHAVIOR HERE!!!!!!!
0498:
0499: // Default Operation
0500: if (inputValue.equals("<null>"))
0501: return null;
0502: else
0503: return inputValue;
0504: }
0505:
0506: /*
0507: * DataBase-related functions
0508: */
0509:
0510: /**
0511: * Returns the result for the column at the specified index as determined
0512: * by a previously registered plugin DataTypeComponent. Will return null
0513: * if the type cannot be handled by any plugin-registered DataTypeComponent.
0514: *
0515: * @param rs
0516: * the ResultSet to read
0517: * @param sqlType
0518: * the Java SQL type of the column
0519: * @param sqlTypeName
0520: * the SQL type name of the column
0521: * @param index
0522: * the index of the column that should be read
0523: *
0524: * @return the value as interpreted by the plugin-registered
0525: * DataTypeComponent, or null if no plugin DataTypeComponent has
0526: * been registered for the specified sqlType and sqlTypename.
0527: *
0528: * @throws Exception
0529: */
0530: public static Object readResultWithPluginRegisteredDataType(
0531: ResultSet rs, int sqlType, String sqlTypeName, int index)
0532: throws Exception {
0533:
0534: Object result = null;
0535: String typeNameKey = getRegDataTypeKey(sqlType, sqlTypeName);
0536: if (_pluginDataTypeFactories.containsKey(typeNameKey)) {
0537: IDataTypeComponentFactory factory = _pluginDataTypeFactories
0538: .get(typeNameKey);
0539: IDataTypeComponent dtComp = factory
0540: .constructDataTypeComponent();
0541: ColumnDisplayDefinition colDef = new ColumnDisplayDefinition(
0542: rs, index);
0543: dtComp.setColumnDisplayDefinition(colDef);
0544: dtComp.setTable(_table);
0545: result = dtComp.readResultSet(rs, index, false);
0546: }
0547: return result;
0548: }
0549:
0550: /**
0551: * On input from the DB, read the data from the ResultSet into the appropriate
0552: * type of object to be stored in the table cell.
0553: */
0554: public static Object readResultSet(ColumnDisplayDefinition colDef,
0555: ResultSet rs, int index, boolean limitDataRead)
0556: throws java.sql.SQLException {
0557:
0558: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0559: colDef);
0560:
0561: if (dataTypeObject != null) {
0562: // we have an appropriate data type object
0563: return dataTypeObject.readResultSet(rs, index,
0564: limitDataRead);
0565: }
0566:
0567: //?? Best guess: read object?
0568: //?? This is probably the wrong thing to do here, but
0569: //?? I don't know what else to try.
0570: return rs.getObject(index);
0571: }
0572:
0573: /**
0574: * When updating the database, generate a string form of this object value
0575: * that can be used in the WHERE clause to match the value in the database.
0576: * A return value of null means that this column cannot be used in the WHERE
0577: * clause, while a return of "null" (or "is null", etc) means that the column
0578: * can be used in the WHERE clause and the value is actually a null value.
0579: * This function must also include the column label so that its output
0580: * is of the form:
0581: * "columnName = value"
0582: * or
0583: * "columnName is null"
0584: * or whatever is appropriate for this column in the database.
0585: */
0586: public static String getWhereClauseValue(
0587: ColumnDisplayDefinition colDef, Object value,
0588: ISQLDatabaseMetaData md) {
0589: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0590: colDef);
0591:
0592: if (dataTypeObject != null) {
0593: // we have an appropriate data type object
0594: return dataTypeObject.getWhereClauseValue(value, md);
0595: }
0596:
0597: // if no object for this data type, then cannot use value in where clause
0598: return null;
0599: }
0600:
0601: /**
0602: * When updating the database, insert the appropriate datatype into the
0603: * prepared statment at the given variable position.
0604: */
0605: public static void setPreparedStatementValue(
0606: ColumnDisplayDefinition colDef, PreparedStatement pstmt,
0607: Object value, int position) throws java.sql.SQLException {
0608:
0609: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0610: colDef);
0611:
0612: // We should never NOT have an object here because we only get here
0613: // when a DataType object has claimed that the column is editable.
0614: // If there is no DataType for the column, then the default in the
0615: // isEditableXXX() methods in this class is to say that the column
0616: // is not editable, and therefore we should never have this method
0617: // called in that case.
0618: if (dataTypeObject != null) {
0619: // we have an appropriate data type object
0620: dataTypeObject.setPreparedStatementValue(pstmt, value,
0621: position);
0622: }
0623: }
0624:
0625: /**
0626: * Get a default value for the table used to input data for a new row
0627: * to be inserted into the DB.
0628: */
0629: static public Object getDefaultValue(
0630: ColumnDisplayDefinition colDef, String dbDefaultValue) {
0631: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0632: colDef);
0633:
0634: if (dataTypeObject != null)
0635: return dataTypeObject.getDefaultValue(dbDefaultValue);
0636:
0637: // there was no data type object, so this data type is unknown
0638: // to squirrel and thus cannot be edited.
0639: return null;
0640: }
0641:
0642: /*
0643: * File IO related functions
0644: */
0645:
0646: /**
0647: * Say whether or not object can be exported to and imported from
0648: * a file. We put both export and import together in one test
0649: * on the assumption that all conversions can be done both ways.
0650: */
0651: public static boolean canDoFileIO(ColumnDisplayDefinition colDef) {
0652:
0653: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0654: colDef);
0655:
0656: // if no DataType object, then there is nothing to handle File IO,
0657: // so cannot do it
0658: if (dataTypeObject == null)
0659: return false;
0660:
0661: // let DataType object speak for itself
0662: return dataTypeObject.canDoFileIO();
0663: }
0664:
0665: /**
0666: * Read a file and construct a valid object from its contents.
0667: * Errors are returned by throwing an IOException containing the
0668: * cause of the problem as its message.
0669: */
0670: public static String importObject(ColumnDisplayDefinition colDef,
0671: FileInputStream inStream) throws IOException {
0672:
0673: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0674: colDef);
0675:
0676: // if no DataType object, then there is nothing to handle File IO,
0677: // so cannot do it
0678: if (dataTypeObject == null)
0679: throw new IOException(
0680: "No internal Data Type class for this column's SQL type");
0681:
0682: // let DataType object speak for itself
0683: return dataTypeObject.importObject(inStream);
0684: }
0685:
0686: /**
0687: * Given a text string from the Popup, validate that it makes sense
0688: * for the given DataType, then write it out to a file in the
0689: * appropriate format.
0690: * Errors are returned by throwing an IOException containing the
0691: * cause of the problem as its message.
0692: */
0693: public static void exportObject(ColumnDisplayDefinition colDef,
0694: FileOutputStream outStream, String text) throws IOException {
0695:
0696: IDataTypeComponent dataTypeObject = getDataTypeObject(null,
0697: colDef);
0698:
0699: // if no DataType object, then there is nothing to handle File IO,
0700: // so cannot do it
0701: if (dataTypeObject == null)
0702: throw new IOException(
0703: "No internal Data Type class for this column's SQL type");
0704:
0705: // let DataType object speak for itself
0706: dataTypeObject.exportObject(outStream, text);
0707: }
0708:
0709: /**
0710: * Constructs a key that is used to lookup previously registered custom
0711: * types.
0712: *
0713: * @param sqlType
0714: * the JDBC type code supplied by the driver
0715: * @param sqlTypeName
0716: * the JDBC type name supplied by the driver
0717: *
0718: * @return a key that can be used to store/retreive a custom type.
0719: */
0720: private static String getRegDataTypeKey(int sqlType,
0721: String sqlTypeName) {
0722: StringBuilder result = new StringBuilder();
0723: result.append(sqlType);
0724: result.append(":");
0725: result.append(sqlTypeName);
0726: return result.toString();
0727: }
0728:
0729: /**
0730: * Method for registering a DataTypeComponent factory for a non-standard
0731: * SQL type (or for overriding a standard handler).
0732: */
0733: public static void registerDataTypeFactory(
0734: IDataTypeComponentFactory factory, int sqlType,
0735: String sqlTypeName) {
0736: String typeName = getRegDataTypeKey(sqlType, sqlTypeName);
0737:
0738: _pluginDataTypeFactories.put(typeName, factory);
0739: }
0740:
0741: /*
0742: * Get control panels to let user adjust properties
0743: * on DataType classes.
0744: */
0745:
0746: /**
0747: * Get the Control Panels (JPanels containing controls) that let the
0748: * user adjust the properties of static properties in specific DataTypes.
0749: * The only DataType objects checked for here are:
0750: * - those that are registered through the registerDataType method, and
0751: * - those that are specifically listed in the variable initialClassNameList
0752: */
0753: public static OkJPanel[] getControlPanels() {
0754: ArrayList<OkJPanel> panelList = new ArrayList<OkJPanel>();
0755:
0756: /*
0757: * This is the list of names of classes that:
0758: * - support standard SQL type codes and thus do not need to be registered
0759: * - provide the getControlPanel method to allow manipulation of properties
0760: * These classes should all be named
0761: * net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeXXXX
0762: * because they are part of the standard delivery of the product, and thus should
0763: * be local to this directory.
0764: */
0765: String[] initialClassNameList = {
0766: net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeBlob.class
0767: .getName(),
0768: net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeClob.class
0769: .getName(),
0770: net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeString.class
0771: .getName(),
0772: net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeOther.class
0773: .getName(),
0774: net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeUnknown.class
0775: .getName(),
0776: net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeDate.class
0777: .getName(),
0778: net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeTime.class
0779: .getName(),
0780: net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeTimestamp.class
0781: .getName(),
0782: net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.FloatingPointBase.class
0783: .getName(), };
0784:
0785: // make a single list of all class names that we need to check.
0786: // Start with the names of known, standard classes that provide Control Panels
0787: ArrayList<String> classNameList = new ArrayList<String>(Arrays
0788: .asList(initialClassNameList));
0789:
0790: // add to that the list of all names that have been registered by plugins
0791: // Iterator<IDataTypeComponentFactory> pluginDataTypeFactories =
0792: // _registeredDataTypes.values().iterator();
0793: // while (pluginDataTypeFactories.hasNext()) {
0794: // TODO: add support for plugin-registered data-type preferences panels
0795: // when it is needed.
0796: // }
0797:
0798: // Now go through the list in the given order to get the panels
0799: for (int i = 0; i < classNameList.size(); i++) {
0800: String className = classNameList.get(i);
0801: Class<?>[] parameterTypes = new Class<?>[0];
0802: try {
0803: Method panelMethod = Class.forName(className)
0804: .getMethod("getControlPanel", parameterTypes);
0805:
0806: OkJPanel panel = (OkJPanel) panelMethod.invoke(null,
0807: (Object[]) null);
0808: panelList.add(panel);
0809: } catch (Exception e) {
0810: s_log.error("Unexpected exception: " + e.getMessage(),
0811: e);
0812: }
0813: }
0814:
0815: return panelList.toArray(new OkJPanel[0]);
0816: }
0817:
0818: /*
0819: * Internal method used for both cell and popup work.
0820: */
0821:
0822: /* Identify the type of data in the cell and get an instance
0823: * of the appropriate DataType object to work with it.
0824: *
0825: * The JTable argument is used only by the DataType objects, not here.
0826: * Also, since it is used only for converting coordinates when the user
0827: * double-clicks in a cell, the JTextArea component does not use it, so
0828: * it may be null in that case.
0829: *
0830: * NOTE: This currently gets a new copy of the DataType object for every
0831: * column even when multiple columns have the same SQL data type. JTable's
0832: * Render and Edit operations typically re-use the same CellRenderer and
0833: * CellEditor objects for every cell by moving the viewpoint of the component
0834: * to the location of the cell to be rendered/edited, setting the value in
0835: * the component to the value at that cell, telling the component to paint,
0836: * then moving that same component to the next cell. For us, the cells have
0837: * specific syntax or size constraints based on the SQL data type and the
0838: * metadata from the DB, so we use different CellRenderer/Editor components
0839: * for each column. However, JTable's rendering/editing algorithm allows
0840: * us to re-use the same component for all cells in the same column, which
0841: * is what we do. By saving the component, we avoid the need to create
0842: * new instances each time the userstarts editing, creates the popup dialog,
0843: * or does an operation requireing a static method call (e.g. validateAndConvert).
0844: */
0845: private static IDataTypeComponent getDataTypeObject(JTable table,
0846: ColumnDisplayDefinition colDef) {
0847:
0848: // keep a hash table of the column objects
0849: // so we can reuse them.
0850: if (table != _table) {
0851: // new table - clear hash map
0852: _colDataTypeObjects.clear();
0853: _table = table;
0854: }
0855: if (_colDataTypeObjects.containsKey(colDef))
0856: return _colDataTypeObjects.get(colDef);
0857:
0858: // we have not already created a DataType object for this column
0859: // so do that now and save it
0860: IDataTypeComponent dataTypeComponent = null;
0861:
0862: /* See if we have a custom data-type registered. */
0863: if (!_pluginDataTypeFactories.isEmpty()) {
0864: String typeName = getRegDataTypeKey(colDef.getSqlType(),
0865: colDef.getSqlTypeName());
0866: IDataTypeComponentFactory factory = _pluginDataTypeFactories
0867: .get(typeName);
0868: if (factory != null) {
0869: dataTypeComponent = factory
0870: .constructDataTypeComponent();
0871: if (colDef != null) {
0872: dataTypeComponent
0873: .setColumnDisplayDefinition(colDef);
0874: }
0875: if (table != null) {
0876: dataTypeComponent.setTable(table);
0877: } else if (_table != null) {
0878: dataTypeComponent.setTable(_table);
0879: }
0880: }
0881: }
0882:
0883: // Use the standard SQL type code to get the right handler
0884: // for this data type.
0885: if (dataTypeComponent == null) {
0886: switch (colDef.getSqlType()) {
0887: case Types.NULL: // should never happen
0888: //??
0889: break;
0890:
0891: case Types.BIT:
0892: case Types.BOOLEAN:
0893: dataTypeComponent = new DataTypeBoolean(table, colDef);
0894: break;
0895:
0896: case Types.TIME:
0897: dataTypeComponent = new DataTypeTime(table, colDef);
0898: break;
0899:
0900: case Types.DATE:
0901: // Some databases store a time component in DATE columns (Oracle)
0902: // The user can set a preference for DATEs that allows them
0903: // to be read as TIMESTAMP columns instead. This doesn't
0904: // appear to have ill effects for databases that are standards
0905: // compliant (such as MySQL or PostgreSQL). If the user
0906: // prefers it, use the TIMESTAMP data type instead of DATE.
0907: if (DataTypeDate.getReadDateAsTimestamp()) {
0908: colDef.setSqlType(Types.TIMESTAMP);
0909: colDef.setSqlTypeName("TIMESTAMP");
0910: dataTypeComponent = new DataTypeTimestamp(table,
0911: colDef);
0912: } else {
0913: dataTypeComponent = new DataTypeDate(table, colDef);
0914: }
0915: break;
0916:
0917: case Types.TIMESTAMP:
0918: case -101: // Oracle's 'TIMESTAMP WITH TIME ZONE' == -101
0919: case -102: // Oracle's 'TIMESTAMP WITH LOCAL TIME ZONE' == -102
0920: dataTypeComponent = new DataTypeTimestamp(table, colDef);
0921: break;
0922:
0923: case Types.BIGINT:
0924: dataTypeComponent = new DataTypeLong(table, colDef);
0925: break;
0926:
0927: case Types.DOUBLE:
0928: case Types.FLOAT:
0929: dataTypeComponent = new DataTypeDouble(table, colDef);
0930: break;
0931:
0932: case Types.REAL:
0933: dataTypeComponent = new DataTypeFloat(table, colDef);
0934: break;
0935:
0936: case Types.DECIMAL:
0937: case Types.NUMERIC:
0938: dataTypeComponent = new DataTypeBigDecimal(table,
0939: colDef);
0940: break;
0941:
0942: case Types.INTEGER:
0943: // set up for integers
0944: dataTypeComponent = new DataTypeInteger(table, colDef);
0945: break;
0946:
0947: case Types.SMALLINT:
0948: dataTypeComponent = new DataTypeShort(table, colDef);
0949: break;
0950:
0951: case Types.TINYINT:
0952: dataTypeComponent = new DataTypeByte(table, colDef);
0953: break;
0954:
0955: // TODO: Hard coded -. JDBC/ODBC bridge JDK1.4
0956: // brings back -9 for nvarchar columns in
0957: // MS SQL Server tables.
0958: case Types.CHAR:
0959: case Types.VARCHAR:
0960: case Types.LONGVARCHAR:
0961: case -9:
0962: // set up for string types
0963: dataTypeComponent = new DataTypeString(table, colDef);
0964: break;
0965:
0966: // -8 is ROWID in Oracle. It's a string, but it's auto-assigned
0967: case -8:
0968: dataTypeComponent = new DataTypeString(table, colDef);
0969: // Oracle jdbc driver doesn't properly identify this column
0970: // in ResultSetMetaData as read-only. For now, just use
0971: // isAutoIncrement flag to simulate this setting.
0972: colDef.setIsAutoIncrement(true);
0973: break;
0974:
0975: case Types.BINARY:
0976: case Types.VARBINARY:
0977: case Types.LONGVARBINARY:
0978: // set up for Binary types
0979: dataTypeComponent = new DataTypeBinary(table, colDef);
0980: break;
0981:
0982: case Types.BLOB:
0983: dataTypeComponent = new DataTypeBlob(table, colDef);
0984: break;
0985:
0986: case Types.CLOB:
0987: dataTypeComponent = new DataTypeClob(table, colDef);
0988: break;
0989:
0990: case Types.OTHER:
0991: dataTypeComponent = new DataTypeOther(table, colDef);
0992: break;
0993:
0994: //Add begin
0995: case Types.JAVA_OBJECT:
0996: dataTypeComponent = new DataTypeJavaObject(table,
0997: colDef);
0998: break;
0999: //Add end
1000:
1001: default:
1002: // data type is unknown to us.
1003: // It may be an unusual type like "JAVA OBJECT" or "ARRAY",
1004: // or it may be a DBMS-specific type
1005: dataTypeComponent = new DataTypeUnknown(table, colDef);
1006:
1007: }
1008: }
1009:
1010: // remember this DataType object so we can reuse it
1011: _colDataTypeObjects.put(colDef, dataTypeComponent);
1012:
1013: // If we get here, then no data type object was found for this column.
1014: // (should not get here because switch default returns null.)
1015: return dataTypeComponent;
1016: }
1017: }
|