001: /*
002: * DataStoreTableModel.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.gui.components;
013:
014: import java.awt.Toolkit;
015: import java.sql.SQLException;
016: import java.sql.Types;
017: import javax.swing.event.EventListenerList;
018: import javax.swing.event.TableModelEvent;
019: import javax.swing.table.AbstractTableModel;
020: import workbench.db.ConnectionProfile;
021: import workbench.db.WbConnection;
022: import workbench.gui.WbSwingUtilities;
023: import workbench.log.LogMgr;
024: import workbench.resource.ResourceMgr;
025: import workbench.storage.DataStore;
026: import workbench.storage.NamedSortDefinition;
027: import workbench.storage.ResultInfo;
028: import workbench.storage.SortDefinition;
029: import workbench.storage.filter.FilterExpression;
030: import workbench.util.ConverterException;
031: import workbench.util.SqlUtil;
032: import workbench.util.StringUtil;
033: import workbench.util.WbThread;
034:
035: /**
036: * TableModel for displaying the contents of a {@link workbench.storage.DataStore }
037: *
038: * @author support@sql-workbench.net
039: */
040: public class DataStoreTableModel extends AbstractTableModel {
041: private DataStore dataCache;
042: private boolean showStatusColumn = false;
043: private int columnStartIndex = 0;
044:
045: private int lockColumn = -1;
046:
047: private SortDefinition sortColumns = new SortDefinition();
048:
049: private boolean allowEditing = true;
050: private final Object model_change_lock = new Object();
051:
052: public DataStoreTableModel(DataStore aDataStore)
053: throws IllegalArgumentException {
054: if (aDataStore == null)
055: throw new IllegalArgumentException(
056: "DataStore cannot be null");
057: this .setDataStore(aDataStore);
058: }
059:
060: public DataStore getDataStore() {
061: return this .dataCache;
062: }
063:
064: public void setDataStore(DataStore newData) {
065: this .dispose();
066: this .dataCache = newData;
067: this .showStatusColumn = false;
068: this .columnStartIndex = 0;
069: this .fireTableStructureChanged();
070: }
071:
072: /**
073: * Return the contents of the field at the given position
074: * in the result set.
075: * @param row - The row to get. Counting starts at zero.
076: * @param col - The column to get. Counting starts at zero.
077: */
078: public Object getValueAt(int row, int col) {
079: if (this .showStatusColumn && col == 0) {
080: return this .dataCache.getRowStatus(row);
081: }
082:
083: try {
084: Object result;
085: result = this .dataCache.getValue(row, col
086: - this .columnStartIndex);
087: return result;
088: } catch (Exception e) {
089: LogMgr.logError("DataStoreTableModel.getValue()",
090: "Error retrieving value at: " + row + "/" + col, e);
091: return "Error";
092: }
093: }
094:
095: public int findColumn(String aColname) {
096: int index = this .dataCache.getColumnIndex(aColname);
097: if (index == -1)
098: return -1;
099: return index + this .columnStartIndex;
100: }
101:
102: /**
103: * Shows or hides the status column.
104: * The status column will display an indicator if the row has
105: * been modified or was inserted
106: */
107: public void setShowStatusColumn(boolean aFlag) {
108: if (aFlag == this .showStatusColumn)
109: return;
110: synchronized (model_change_lock) {
111: if (aFlag) {
112: this .columnStartIndex = 1;
113: } else {
114: this .columnStartIndex = 0;
115: }
116: this .showStatusColumn = aFlag;
117: }
118: this .fireTableStructureChanged();
119: }
120:
121: public boolean getShowStatusColumn() {
122: return this .showStatusColumn;
123: }
124:
125: public boolean isUpdateable() {
126: if (this .dataCache == null)
127: return false;
128: return this .dataCache.isUpdateable();
129: }
130:
131: private boolean isNull(Object value, int column) {
132: if (value == null)
133: return true;
134: String s = value.toString();
135: int type = this .dataCache.getColumnType(column);
136: if (SqlUtil.isCharacterType(type)) {
137: WbConnection con = this .dataCache.getOriginalConnection();
138: ConnectionProfile profile = (con != null ? con.getProfile()
139: : null);
140: if (profile == null || profile.getEmptyStringIsNull()) {
141: return (s.length() == 0);
142: }
143: return false;
144: }
145: return StringUtil.isEmptyString(s);
146: }
147:
148: public void setValueAt(Object aValue, int row, int column) {
149: // Updates to the status column shouldn't happen anyway ....
150: if (this .showStatusColumn && column == 0)
151: return;
152:
153: if (isNull(aValue, column - this .columnStartIndex)) {
154: this .dataCache.setValue(row,
155: column - this .columnStartIndex, null);
156: } else {
157: try {
158: this .dataCache.setInputValue(row, column
159: - this .columnStartIndex, aValue);
160: } catch (ConverterException ce) {
161: int type = this .getColumnType(column);
162: LogMgr.logError(this ,
163: "Error converting input >" + aValue
164: + "< to column type "
165: + SqlUtil.getTypeName(type) + " ("
166: + type + ")", ce);
167: Toolkit.getDefaultToolkit().beep();
168: String msg = ResourceMgr.getString("MsgConvertError");
169: msg = msg + "\r\n" + ce.getLocalizedMessage();
170: WbSwingUtilities.showErrorMessage(msg);
171: return;
172: }
173: }
174: WbSwingUtilities.invoke(new Runnable() {
175: public void run() {
176: fireTableDataChanged();
177: }
178: });
179: }
180:
181: /**
182: * Return the number of columns in the model.
183: * This will return the number of columns of the underlying DataStore (plus one
184: * if the status column is enabled)
185: */
186: public int getColumnCount() {
187: return this .dataCache.getColumnCount() + this .columnStartIndex;
188: }
189:
190: /**
191: * Returns the current width of the given column.
192: * It returns the value of {@link workbench.storage.DataStore#getColumnDisplaySize(int)}
193: * for every column which is not the status column.
194: *
195: * @param aColumn the column index
196: *
197: * @return the width of the column as defined by the DataStore or 0
198: * @see workbench.storage.DataStore#getColumnDisplaySize(int)
199: * @see #findColumn(String)
200: */
201: public int getColumnWidth(int aColumn) {
202: if (this .showStatusColumn && aColumn == 0)
203: return 5;
204: if (this .dataCache == null)
205: return 0;
206: try {
207: return this .dataCache.getColumnDisplaySize(aColumn);
208: } catch (Exception e) {
209: LogMgr.logWarning("DataStoreTableModel.getColumnWidth()",
210: "Error retrieving display size for column "
211: + aColumn, e);
212: return 100;
213: }
214: }
215:
216: /**
217: * Returns the name of the datatype (according to java.sql.Types) of the
218: * given column.
219: */
220: public String getColumnTypeName(int aColumn) {
221: if (aColumn == 0)
222: return "";
223: return SqlUtil.getTypeName(this .getColumnType(aColumn));
224: }
225:
226: public String getDbmsType(int col) {
227: if (this .dataCache == null)
228: return null;
229: if (this .showStatusColumn && col == 0)
230: return null;
231: try {
232: ResultInfo info = this .dataCache.getResultInfo();
233: return info.getDbmsTypeName(col - this .columnStartIndex);
234: } catch (Exception e) {
235: return null;
236: }
237:
238: }
239:
240: /**
241: * Returns the type (java.sql.Types) of the given column.
242: */
243: public int getColumnType(int aColumn) {
244: if (this .dataCache == null)
245: return Types.NULL;
246: if (this .showStatusColumn && aColumn == 0)
247: return 0;
248:
249: try {
250: return this .dataCache.getColumnType(aColumn
251: - this .columnStartIndex);
252: } catch (Exception e) {
253: e.printStackTrace();
254: return Types.VARCHAR;
255: }
256: }
257:
258: /**
259: * Number of rows in the result set
260: */
261: public int getRowCount() {
262: if (this .dataCache == null)
263: return 0;
264: return this .dataCache.getRowCount();
265: }
266:
267: public Class getColumnClass(int aColumn) {
268: if (this .dataCache == null)
269: return null;
270: if (aColumn == 0 && this .showStatusColumn)
271: return Integer.class;
272: return this .dataCache
273: .getColumnClass(aColumn - columnStartIndex);
274: }
275:
276: public int insertRow(int afterRow) {
277: int row = this .dataCache.insertRowAfter(afterRow);
278: this .fireTableRowsInserted(row, row);
279: return row;
280: }
281:
282: public int addRow() {
283: int row = this .dataCache.addRow();
284: this .fireTableRowsInserted(row, row);
285: return row;
286: }
287:
288: public void deleteRow(int aRow, boolean withDependencies)
289: throws SQLException {
290: if (withDependencies) {
291: this .dataCache.deleteRowWithDependencies(aRow);
292: } else {
293: this .dataCache.deleteRow(aRow);
294: }
295: this .fireTableRowsDeleted(aRow, aRow);
296: }
297:
298: public int duplicateRow(int aRow) {
299: int row = this .dataCache.duplicateRow(aRow);
300: this .fireTableRowsInserted(row, row);
301: return row;
302: }
303:
304: public void fileImported() {
305: int row = this .getRowCount();
306: this .fireTableRowsInserted(0, row - 1);
307: }
308:
309: /**
310: * Clears the EventListenerList and empties the DataStore
311: */
312: public void dispose() {
313: this .listenerList = new EventListenerList();
314: if (this .dataCache != null) {
315: this .dataCache.reset();
316: this .dataCache = null;
317: }
318: }
319:
320: /** Return the name of the column as defined by the ResultSetData.
321: */
322: public String getColumnName(int aColumn) {
323: if (this .showStatusColumn && aColumn == 0)
324: return " ";
325:
326: try {
327: String name = this .dataCache.getColumnName(aColumn
328: - this .columnStartIndex);
329: return name;
330: } catch (Exception e) {
331: return "(n/a)";
332: }
333: }
334:
335: public boolean isCellEditable(int row, int column) {
336: if (this .lockColumn > -1) {
337: return (column != lockColumn && this .allowEditing);
338: } else if (this .columnStartIndex > 0
339: && column < this .columnStartIndex) {
340: return false;
341: } else {
342: return this .allowEditing;// && !SqlUtil.isBlobType(this.dataCache.getColumnType(column));
343: }
344: }
345:
346: /**
347: * Clear the locked column. After a call to clearLockedColumn()
348: * all columns (except the status column) are editable
349: * when the table is in edit mode.
350: * @see #setLockedColumn(int)
351: */
352: public void clearLockedColumn() {
353: this .lockColumn = -1;
354: }
355:
356: /**
357: * Define a column that may not be edited even if the
358: * table is in "Edit mode"
359: * @param column the column to be set as non-editable
360: * @see #clearLockedColumn()
361: */
362: public void setLockedColumn(int column) {
363: this .lockColumn = column;
364: }
365:
366: public void setAllowEditing(boolean aFlag) {
367: this .allowEditing = aFlag;
368: }
369:
370: /**
371: * Clears the filter that is currently defined on the underlying
372: * DataStore. A tableDataChanged Event will be fired after this
373: */
374: public void resetFilter() {
375: if (isSortInProgress())
376: return;
377:
378: dataCache.clearFilter();
379: // sort() will already fire a tableDataChanged()
380: // if a sort column was defined
381: if (!sort()) {
382: fireTableDataChanged();
383: }
384: }
385:
386: /**
387: * Applys the given filter to the underlying
388: * DataStore. A tableDataChanged Event will be fired after this
389: */
390: public void applyFilter(FilterExpression filter) {
391: if (isSortInProgress())
392: return;
393:
394: dataCache.applyFilter(filter);
395: // sort() will already fire a tableDataChanged()
396: // if a sort column was defined
397: if (!sort()) {
398: this .fireTableDataChanged();
399: }
400: }
401:
402: private void setSortInProgress(final boolean flag) {
403: this .sortingInProgress = flag;
404: }
405:
406: private boolean isSortInProgress() {
407: return this .sortingInProgress;
408: }
409:
410: /**
411: * Return true if the data is sorted in ascending order.
412: * @return True if sorted in ascending order
413: */
414: public boolean isSortAscending(int col) {
415: return this .sortColumns.isSortAscending(col - columnStartIndex);
416: }
417:
418: public boolean isPrimarySortColumn(int col) {
419: return this .sortColumns.isPrimarySortColumn(col
420: - columnStartIndex);
421: }
422:
423: /**
424: * Check if the table is sorted by a column
425: * @return true if the given column is a sort column
426: * @see #isSortAscending(int)
427: */
428: public boolean isSortColumn(int col) {
429: return this .sortColumns.isSortColumn(col - columnStartIndex);
430: }
431:
432: /**
433: * Returns a snapshot of the current sort columns identified
434: * by their names instead of their column index (as done by SortDefinition)
435: *
436: * @return the current sort definition with named columns
437: */
438: public NamedSortDefinition getSortDefinition() {
439: return new NamedSortDefinition(this .dataCache, this .sortColumns);
440: }
441:
442: public void setSortDefinition(NamedSortDefinition definition) {
443: if (definition == null)
444: return;
445: SortDefinition newSort = definition
446: .getSortDefinition(dataCache);
447: if (!newSort.equals(this .sortColumns)) {
448: this .sortColumns = newSort;
449: applySortColumns();
450: }
451: }
452:
453: /**
454: * Sort the data by the given column. If the data is already
455: * sorted by this column, then the sort order will be reversed
456: */
457: public void sortByColumn(int column) {
458: // if the column was not sorted at all isSortAscending will return false
459: // thus negating ascending will sort ascending for non-sorted
460: // columns and will toggle the sort direction for an existing sort column
461: boolean ascending = !isSortAscending(column);
462: sortByColumn(column, ascending, false);
463: }
464:
465: /**
466: * Re-apply the last sort order defined.
467: * If no sort order was defined this method does nothing
468: */
469: public boolean sort() {
470: if (this .sortColumns == null)
471: return false;
472: applySortColumns();
473: return true;
474: }
475:
476: public void removeSortColumn(int column) {
477: boolean isPrimaryColumn = this .sortColumns
478: .isPrimarySortColumn(column);
479: this .sortColumns.removeSortColumn(column);
480:
481: // if the primary (== first) column was removed
482: // we have to re-apply the sort definition
483: if (isPrimaryColumn) {
484: applySortColumns();
485: }
486: }
487:
488: /**
489: * Sort the data by the given column in the defined order
490: */
491: public void sortByColumn(int column, boolean ascending,
492: boolean addSortColumn) {
493: if (addSortColumn) {
494: sortColumns.addSortColumn(column - columnStartIndex,
495: ascending);
496: } else {
497: sortColumns.setSortColumn(column - columnStartIndex,
498: ascending);
499: }
500: applySortColumns();
501: }
502:
503: private void applySortColumns() {
504: if (this .sortColumns == null)
505: return;
506: if (this .dataCache == null)
507: return;
508:
509: synchronized (this .dataCache) {
510: try {
511: setSortInProgress(true);
512: this .dataCache.sort(this .sortColumns);
513: } catch (Throwable th) {
514: LogMgr.logError("DataStoreTableModel.sortByColumn()",
515: "Error when sorting data", th);
516: } finally {
517: setSortInProgress(false);
518: }
519: }
520: final TableModelEvent event = new TableModelEvent(this );
521: WbSwingUtilities.invoke(new Runnable() {
522: public void run() {
523: fireTableChanged(event);
524: }
525: });
526: }
527:
528: private boolean sortingInProgress = false;
529:
530: public void sortInBackground(WbTable table, int aColumn,
531: boolean addSortColumn) {
532: if (sortingInProgress)
533: return;
534:
535: if (aColumn < 0 && aColumn >= this .getColumnCount()) {
536: LogMgr.logWarning("DataStoreTableModel",
537: "Wrong column index for sorting specified!");
538: return;
539: }
540: boolean ascending = !this .isSortAscending(aColumn);
541: sortInBackground(table, aColumn, ascending, addSortColumn);
542: }
543:
544: /**
545: * Start a new thread to sort the data.
546: * Any call to this method while the thread is running, will be ignored
547: */
548: public void sortInBackground(final WbTable table,
549: final int aColumn, final boolean ascending,
550: final boolean addSortColumn) {
551: if (isSortInProgress())
552: return;
553:
554: Thread t = new WbThread("Data Sort") {
555: public void run() {
556: try {
557: table.sortingStarted();
558: sortByColumn(aColumn, ascending, addSortColumn);
559: } finally {
560: table.sortingFinished();
561: }
562: }
563: };
564: t.start();
565: }
566:
567: }
|