0001: /*
0002: * The contents of this file are subject to the Mozilla Public License
0003: * Version 1.1 (the "License"); you may not use this file except in
0004: * compliance with the License. You may obtain a copy of the License at
0005: * http://www.mozilla.org/MPL/
0006: *
0007: * Software distributed under the License is distributed on an "AS IS"
0008: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
0009: * License for the specific language governing rights and limitations
0010: * under the License.
0011: *
0012: * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
0013: *
0014: * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
0015: * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
0016: *
0017: * Contributor(s):
0018: * Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
0019: *
0020: * If you didn't download this code from the following link, you should check
0021: * if you aren't using an obsolete version: http://www.isqlviewer.com
0022: */
0023: package org.isqlviewer.swing.table;
0024:
0025: import java.awt.datatransfer.DataFlavor;
0026: import java.awt.datatransfer.Transferable;
0027: import java.awt.datatransfer.UnsupportedFlavorException;
0028: import java.util.ArrayList;
0029: import java.util.Arrays;
0030: import java.util.Collection;
0031: import java.util.HashMap;
0032: import java.util.Iterator;
0033: import java.util.Map;
0034: import java.util.regex.Pattern;
0035: import java.util.regex.PatternSyntaxException;
0036:
0037: import javax.swing.event.TableModelEvent;
0038: import javax.swing.event.TableModelListener;
0039: import javax.swing.table.TableModel;
0040:
0041: import org.isqlviewer.util.LoggableObject;
0042:
0043: /**
0044: * An enhanced rendition of the TableModel interface.
0045: * <p>
0046: * This implementation of the TableModel interface mainly address the lack of some column oriented methods, also i liked
0047: * some of the methods provided by the default DefaultTableModel like removeRow addRow, etc.
0048: * <p>
0049: * This is a fairly good table mode to use if you need a fast WORM type table model, i do not recommend using this class
0050: * if you have to make lots of changes since the data storage is only synchronized when it is set or removed and not
0051: * read.
0052: *
0053: * @author Markus A. Kobold <mkobold at sprintpcs dot com>
0054: * @version 1.0
0055: */
0056: public class EnhancedTableModel extends LoggableObject implements
0057: Cloneable, Sortable, TableModel, Transferable {
0058:
0059: /**
0060: * Default transfer flavor as a java object.
0061: */
0062: public static final DataFlavor FLAVOR_LOCAL_OBJECT = new DataFlavor(
0063: EnhancedTableModel.class, "java/EnhancedTableModel");
0064: /**
0065: * Default transfer flavor as a java object.
0066: */
0067: public static final DataFlavor FLAVOR_REMOTE_OBJECT = new DataFlavor(
0068: EnhancedTableModel.class,
0069: DataFlavor.javaSerializedObjectMimeType);
0070: /**
0071: * Transfer flavor for representing this as tab strings.
0072: * <p>
0073: * This data flavor seems to make DnD to native apps like Excel work.
0074: */
0075: public static final DataFlavor FLAVOR_TAB_STRINGS = new DataFlavor(
0076: String.class, "text/tab-separated-values");
0077: /**
0078: * Transfer flavor for representing plain text.
0079: */
0080: public static final DataFlavor FLAVOR_PLAIN_TEXT = new DataFlavor(
0081: String.class, "text/plain");
0082: /**
0083: * The main data collection this should be an ArrayList where the elements are also ArrayLists.
0084: */
0085: protected ArrayList<ArrayList<Object>> dataStore = new ArrayList<ArrayList<Object>>();
0086: /**
0087: * This is the collection of column names required for the TableModel interface.
0088: */
0089: protected ArrayList<String> columns = new ArrayList<String>();
0090: /**
0091: * A list of TableModel listeners this will be lazily created when a table model listener is required.
0092: */
0093: protected transient ArrayList<TableModelListener> listeners = null;
0094: /**
0095: * A marker to determine the number of rows per page.
0096: */
0097: protected int pageSize = 1024 * 4; // 4k
0098: // rows
0099: /**
0100: * Current page offset for use in paging methods.
0101: */
0102: protected int pageOffset = 0;
0103: /**
0104: * Mapping for column class by the column number.
0105: * <p>
0106: * This makes for easier explicit declarations of what class a column rather than based on what it is. This is
0107: * paticularly useful when used in conjuction of JTable that is basing a renderer on an interface class.
0108: */
0109: protected HashMap<Integer, Class> classMappings = new HashMap<Integer, Class>();
0110: private ArrayList<Integer> filteredRows = new ArrayList<Integer>();
0111: private String lastFilterPattern = null;
0112:
0113: /**
0114: * Default constructor for creating an empty table model.
0115: * <p>
0116: * This method will create an empty table model with no columns or rows of any kind.
0117: *
0118: * @see #EnhancedTableModel(String[])
0119: */
0120: public EnhancedTableModel() {
0121:
0122: this (null);
0123: }
0124:
0125: /**
0126: * Creates an TableModel with the given column names.
0127: * <p>
0128: * Creates a table model with the following column names initially, The model will have no rows of any kind though.
0129: * <p>
0130: * This will also poll the SystemConfig object to automatically set the preferred paging size based on the
0131: * KEY_TABLE_PAGE_SIZE property key.
0132: *
0133: * @param columns array of strings to be column names.
0134: */
0135: public EnhancedTableModel(String[] columns) {
0136:
0137: setColumns(columns);
0138: }
0139:
0140: /**
0141: * @see javax.swing.table.TableModel#isCellEditable(int, int)
0142: */
0143: public boolean isCellEditable(int rowIndex, int columnIndex) {
0144:
0145: return false;
0146: }
0147:
0148: /*
0149: * (non-Javadoc)
0150: *
0151: * @see java.awt.datatransfer.Transferable#getTransferData(java.awt.datatransfer.DataFlavor)
0152: */
0153: public Object getTransferData(DataFlavor flavor)
0154: throws UnsupportedFlavorException {
0155:
0156: if (FLAVOR_LOCAL_OBJECT.equals(flavor))
0157: return this ;
0158: else if (DataFlavor.stringFlavor.equals(flavor)
0159: || FLAVOR_PLAIN_TEXT.equals(flavor)
0160: || FLAVOR_TAB_STRINGS.equals(flavor))
0161: return toString();
0162: else
0163: throw new UnsupportedFlavorException(flavor);
0164: }
0165:
0166: /*
0167: * (non-Javadoc)
0168: *
0169: * @see java.awt.datatransfer.Transferable#getTransferDataFlavors()
0170: */
0171: public DataFlavor[] getTransferDataFlavors() {
0172:
0173: return new DataFlavor[] { FLAVOR_REMOTE_OBJECT,
0174: FLAVOR_LOCAL_OBJECT, FLAVOR_TAB_STRINGS,
0175: FLAVOR_PLAIN_TEXT, DataFlavor.stringFlavor, };
0176: }
0177:
0178: /*
0179: * (non-Javadoc)
0180: *
0181: * @see java.awt.datatransfer.Transferable#isDataFlavorSupported(java.awt.datatransfer.DataFlavor)
0182: */
0183: public boolean isDataFlavorSupported(DataFlavor flavor) {
0184:
0185: return FLAVOR_LOCAL_OBJECT.equals(flavor)
0186: || FLAVOR_REMOTE_OBJECT.equals(flavor)
0187: || FLAVOR_TAB_STRINGS.equals(flavor)
0188: || DataFlavor.stringFlavor.equals(flavor)
0189: || FLAVOR_PLAIN_TEXT.equals(flavor);
0190: }
0191:
0192: /**
0193: * @see javax.swing.table.TableModel#setValueAt(java.lang.Object, int, int)
0194: */
0195: public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
0196:
0197: int realRow = translateRow(rowIndex);
0198: synchronized (dataStore) {
0199: ArrayList<Object> row = dataStore.get(realRow);
0200: row.set(columnIndex, aValue);
0201: }
0202: fireTableCellUpdated(rowIndex, columnIndex);
0203: }
0204:
0205: /**
0206: * @see javax.swing.table.TableModel#getColumnCount()
0207: */
0208: public int getColumnCount() {
0209:
0210: return columns.size();
0211: }
0212:
0213: /**
0214: * @see javax.swing.table.TableModel#getRowCount()
0215: */
0216: public int getRowCount() {
0217:
0218: int rc = (filteredRows.isEmpty() ? dataStore.size()
0219: : filteredRows.size());
0220: if (rc <= getPageSize())
0221: return rc;
0222: else if (getPageOffset() == getPageCount() - 1) {
0223: return rc - (getPageOffset() * getPageSize());
0224: } else {
0225: return getPageSize();
0226: }
0227: }
0228:
0229: /**
0230: * @see javax.swing.table.TableModel#getValueAt(int, int)
0231: */
0232: public Object getValueAt(int rowIndex, int columnIndex) {
0233:
0234: ArrayList row = getRow(rowIndex);
0235: return row.get(columnIndex);
0236: }
0237:
0238: /**
0239: * @see javax.swing.table.TableModel#addTableModelListener(javax.swing.event.TableModelListener)
0240: */
0241: public synchronized void addTableModelListener(TableModelListener l) {
0242:
0243: if (listeners == null) {
0244: listeners = new ArrayList<TableModelListener>(1);
0245: }
0246:
0247: if (!listeners.contains(l)) {
0248: synchronized (listeners) {
0249: listeners.add(l);
0250: }
0251: }
0252: }
0253:
0254: /**
0255: * @see javax.swing.table.TableModel#removeTableModelListener(javax.swing.event.TableModelListener)
0256: */
0257: public void removeTableModelListener(TableModelListener l) {
0258:
0259: if (listeners != null && listeners.contains(l)) {
0260: synchronized (listeners) {
0261: listeners.remove(l);
0262: }
0263: }
0264: }
0265:
0266: /**
0267: * @see javax.swing.table.TableModel#getColumnName(int)
0268: */
0269: public String getColumnName(int column) {
0270:
0271: try {
0272: return columns.get(column);
0273: } catch (Throwable t) {
0274: return null;
0275: }
0276: }
0277:
0278: /**
0279: * @see javax.swing.table.TableModel#getColumnClass(int)
0280: */
0281: public Class<?> getColumnClass(int columnIndex) {
0282:
0283: if (classMappings.containsKey(Integer.toString(columnIndex))) {
0284: return classMappings.get(Integer.toString(columnIndex));
0285: }
0286:
0287: try {
0288: ArrayList<Object> row = dataStore.get(0);
0289: Class clazz = row.get(columnIndex).getClass();
0290: return clazz;
0291: } catch (Throwable t) {
0292: return Object.class;
0293: }
0294: }
0295:
0296: /**
0297: * @see org.isqlviewer.util.Sortable#getIndexOfColumnName(String)
0298: */
0299: public int getIndexOfColumnName(String name) {
0300:
0301: try {
0302: return columns.indexOf(name);
0303: } catch (Throwable t) {
0304: return -1;
0305: }
0306: }
0307:
0308: /**
0309: * @see org.isqlviewer.util.Sortable#sort(int, boolean)
0310: */
0311: public void sort(int column, boolean asc) {
0312:
0313: if (column >= 0 && column < getColumnCount()) {
0314: try {
0315: synchronized (dataStore) {
0316: ArrayList<Object>[] arr = dataStore
0317: .toArray(new ArrayList[dataStore.size()]);
0318: Arrays.sort(arr, new RowComparator(column, asc));
0319: clearData();
0320: dataStore.addAll(Arrays.asList(arr));
0321: }
0322: applyFilter(lastFilterPattern);
0323: } catch (Throwable t) {
0324: } finally {
0325: fireTableRowsUpdated(0, getRowCount() - 1);
0326: }
0327: }
0328: }
0329:
0330: public boolean canSort(int column, boolean ascending) {
0331:
0332: return (column >= 0 && column < columns.size())
0333: && dataStore.size() >= 1;
0334: }
0335:
0336: /**
0337: * Creates a copy of this TableModel.
0338: * <p>
0339: * This method will not copy the existing TableModelListeners to the copied TableModel. However all other members
0340: * values will copied and cloned.
0341: *
0342: * @see java.lang.Object#clone()
0343: */
0344: @Override
0345: public Object clone() {
0346:
0347: EnhancedTableModel clone = new EnhancedTableModel(getColumns());
0348: clone.dataStore = new ArrayList<ArrayList<Object>>();
0349: if (filteredRows.isEmpty()) {
0350: synchronized (dataStore) {
0351: Iterator<ArrayList<Object>> itr = dataStore.iterator();
0352: while (itr.hasNext()) {
0353: ArrayList<Object> row = itr.next();
0354: clone.addRow(row);
0355: }
0356: }
0357: } else {
0358: synchronized (filteredRows) {
0359: Iterator<Integer> itr = filteredRows.iterator();
0360: while (itr.hasNext()) {
0361: int rowNumber = itr.next().intValue();
0362: synchronized (dataStore) {
0363: ArrayList<Object> row = dataStore
0364: .get(rowNumber);
0365: clone.addRow(row);
0366: }
0367: }
0368: }
0369: }
0370: clone.classMappings.putAll(classMappings);
0371: clone.pageOffset = pageOffset;
0372: clone.pageSize = pageSize;
0373: return clone;
0374: }
0375:
0376: @Override
0377: public String toString() {
0378:
0379: StringBuffer buff = new StringBuffer("");
0380: StringBuffer row = new StringBuffer("");
0381: for (int r = 0; r < getRowCount(); r++) {
0382: for (int c = 0; c < getColumnCount(); c++) {
0383: row.append(getValueAt(r, c));
0384: row.append("\t");
0385: }
0386: if (row.toString().trim().length() >= 1) {
0387: buff.append(row);
0388: buff.append(System.getProperty("line.seperator", "\n"));
0389: }
0390: row.setLength(0);
0391: }
0392: return buff.toString();
0393: }
0394:
0395: /**
0396: * Sets the class type for the given column.
0397: * <p>
0398: * Explicitly declares that the given column is of type clazz, this method a complement to the getColumnClass(int)
0399: * method. This method can help when rendering this model correctly within a JTable. Paticularly if you have
0400: * configured a custom renderer on an interface class e.g. CLOB/BLOB. The JTable currently doesn't aquire the
0401: * correct renderer if an object is just an instance of something.
0402: * <p>
0403: * e.g.
0404: *
0405: * <pre>
0406: * Tablemodel mdl = new EnhancedTableModel();
0407: * JTable tab = new JTabel(mdl);
0408: * tab.setDefaultRenderer(Clob.class, new ClobRenderer());
0409: * mdl.addRow(new ArrayList(Arrays.asList(new MyCLOB[]{new MyClob()})));
0410: * </pre>
0411: *
0412: * Currently the JTable would not get the right render for the MyCLOB class even though it is an instanceof CLOB the
0413: * JTable will use the DefaultRenderer for the CLOB object.
0414: *
0415: * <pre>
0416: * mdl.setClassForColumn(0, Clob.class);
0417: * </pre>
0418: *
0419: * invocation will cause the JTable to get the Clob.class for column 0 and get the correct renderer for the object
0420: * within the model. <br>
0421: * <em>This will be the same effect on CellEditors as well since the JTable, uses similar logic
0422: * to aquire the appropriate editor</em>
0423: *
0424: * @see javax.swing.JTable#setDefaultRenderer(java.lang.Class, javax.swing.table.TableCellRenderer)
0425: * @see #getColumnClass(int)
0426: * @param column id to declare it's class type for.
0427: * @param clazz class type based on the column number.
0428: */
0429: public void setClassforColumn(int column, Class clazz) {
0430:
0431: synchronized (classMappings) {
0432: classMappings.put(new Integer(column), clazz);
0433: }
0434: }
0435:
0436: /**
0437: * Removes the specifed row at the given index.
0438: * <p>
0439: * This will remove the specified data declared at rowIndex, if the rowIndex is out of range this method will return
0440: * normally.
0441: * <p>
0442: * upon succesfull removal a fireTableRowsDeleted() will be notified to model listeners.
0443: *
0444: * @param rowIndex the row to be removed from this table.
0445: */
0446: public void removeRow(int rowIndex) {
0447:
0448: try {
0449: synchronized (dataStore) {
0450: dataStore.remove(translateRow(rowIndex));
0451: }
0452: fireTableRowsDeleted(rowIndex, rowIndex);
0453: applyFilter(lastFilterPattern);
0454: } catch (Throwable t) {
0455: }
0456: }
0457:
0458: /**
0459: * Decreases the page offset by one.
0460: * <p>
0461: * This will change the offset by -1 if it is still > 0 afterwards a TableChanged event will be then be fired.
0462: *
0463: * @see #getPageCount()
0464: * @see #pageDown()
0465: * @return boolean on success of the method.
0466: */
0467: public boolean pageUp() {
0468:
0469: if (pageOffset > 0) {
0470: pageOffset--;
0471: fireTableDataChanged();
0472: return true;
0473: }
0474:
0475: return false;
0476: }
0477:
0478: /**
0479: * Increases the page offset by one.
0480: * <p>
0481: * This will change the offset by +1 if it is still < page count afterwards a TableChanged event will be then be
0482: * fired.
0483: *
0484: * @see #getPageCount()
0485: * @see #pageUp()
0486: * @return boolean on success of the method.
0487: */
0488: public boolean pageDown() {
0489:
0490: if (pageOffset < getPageCount() - 1) {
0491: pageOffset++;
0492: fireTableDataChanged();
0493: return true;
0494: }
0495:
0496: return false;
0497: }
0498:
0499: /**
0500: * Current paging size for this model.
0501: * <p>
0502: * This the number of rows that determines a *page* this is the number of rows that will show in a JTable at a given
0503: * time.
0504: *
0505: * @see #getPageCount()
0506: * @return int
0507: */
0508: public int getPageSize() {
0509:
0510: return pageSize;
0511: }
0512:
0513: /**
0514: * Number of pages this model contains.
0515: * <p>
0516: * This will take the true row count divided by the page size. the number returned will always be >= 1
0517: *
0518: * @see #setPageSize(int)
0519: * @return int
0520: */
0521: public int getPageCount() {
0522:
0523: double total = getTrueRowCount();
0524: double page = getPageSize();
0525: return (int) Math.ceil(total / page);
0526: }
0527:
0528: /**
0529: * Sets the current page size for this model.
0530: * <p>
0531: */
0532: public boolean setPageSize(final int newSize) {
0533:
0534: int localSize = newSize;
0535: if (localSize >= 0 && localSize != pageSize) {
0536: if (localSize <= getTrueRowCount()) {
0537: localSize = getTrueRowCount();
0538: }
0539: int old = pageSize;
0540: pageSize = localSize;
0541: if (pageSize < old) {
0542: fireTableRowsDeleted(pageSize, old - 1);
0543: } else {
0544: fireTableRowsInserted(old, pageSize - 1);
0545: }
0546: return true;
0547: }
0548:
0549: return false;
0550: }
0551:
0552: public int getTrueRowCount() {
0553:
0554: return dataStore.size();
0555: }
0556:
0557: public int getPageOffset() {
0558:
0559: return pageOffset;
0560: }
0561:
0562: public EnhancedTableModel subModel(int[] rows, int[] columnidxs) {
0563:
0564: EnhancedTableModel sub = new EnhancedTableModel(new String[0]);
0565: String[] headers = new String[columnidxs.length];
0566: for (int r = 0; r < rows.length; r++) {
0567: int row = rows[r];
0568: ArrayList<Object> data = getRow(row);
0569: ArrayList<Object> clone = new ArrayList<Object>(
0570: columnidxs.length);
0571: for (int c = 0; c < columnidxs.length; c++) {
0572: int column = columnidxs[c];
0573: clone.add(data.get(column));
0574: }
0575: sub.addRow(clone);
0576: }
0577:
0578: for (int c = 0; c < columnidxs.length; c++) {
0579: int column = columnidxs[c];
0580: headers[c] = getColumnName(column);
0581: Class clazz = classMappings.get(new Integer(column));
0582: if (clazz != null) {
0583: sub.setClassforColumn(c, clazz);
0584: }
0585: }
0586: sub.setColumns(headers);
0587: return sub;
0588: }
0589:
0590: /**
0591: * This will clear the table of all data.
0592: * <p>
0593: * This method should not be confused with the clearAll() method, this will only clear the data, and not the column
0594: * information.
0595: * <p>
0596: * This will fire a TableDataChanged event to TableModel listeners.
0597: *
0598: * @see #clearAll()
0599: */
0600: public void clear() {
0601:
0602: clearData();
0603: fireTableStructureChanged();
0604: }
0605:
0606: /**
0607: * This clears all data and column information for this table.
0608: * <p>
0609: * This will fire a TableStructureChanged() when done and this table will be completely empty.
0610: *
0611: * @see #clear()
0612: */
0613: public void clearAll() {
0614:
0615: clearAllData();
0616: fireTableStructureChanged();
0617: }
0618:
0619: /**
0620: * This will determine if there is data contained in this table.
0621: * <p>
0622: * This will return true if there is no data in the underlying data collection. the number of columns will not
0623: * affect the results of this methods returned value.
0624: *
0625: * @return boolean to determine the emptiness of this tablemodel data.
0626: */
0627: public boolean isEmpty() {
0628:
0629: return dataStore.isEmpty();
0630: }
0631:
0632: /**
0633: * This will add a row to this TableModel.
0634: * <p>
0635: * This method will clone the given ArrayList and then add that reference to underlying data collection. If the
0636: * given ArrayList is null this method immediately returns.
0637: * <p>
0638: * There is no requirement for the size of the given list as long as it is >= to specified column size you should
0639: * not experience any erratic behaviour. So it is ok for the given ArrayList to have a size greater than this
0640: * TableModel Column size.
0641: * <p>
0642: * a fireRowsInserted() will be fired to model listeners.
0643: *
0644: * @param lst Collection of objects to add as row to this table.
0645: */
0646: public void addRow(ArrayList<? extends Object> lst) {
0647:
0648: if (lst == null) {
0649: return;
0650: }
0651:
0652: synchronized (dataStore) {
0653: ArrayList<Object> copy = new ArrayList<Object>(lst);
0654: dataStore.add(copy);
0655: }
0656: fireTableRowsInserted(getRowCount(), getRowCount());
0657: }
0658:
0659: /**
0660: * This will ensure that the underlying data collection can hold the row count.
0661: * <p>
0662: * If the given size is less than the current size this will remove any extra rows to get this table model down to
0663: * the correct row count.
0664: *
0665: * @see ArrayList#ensureCapacity(int)
0666: * @see #setColumnCount(int)
0667: * @param size the new desire row count for this table model.
0668: */
0669: public void setRowCount(int size) {
0670:
0671: int current = getRowCount();
0672: if (size <= current) {
0673: for (int i = current; i > size; i++) {
0674: synchronized (dataStore) {
0675: dataStore.remove(i);
0676: }
0677: }
0678: }
0679: synchronized (dataStore) {
0680: dataStore.ensureCapacity(size);
0681: }
0682: fireTableDataChanged();
0683: }
0684:
0685: /**
0686: * Sets the column size of this table model for all rows.
0687: * <p>
0688: * This will add blanks strings if current column count is less than the given size other wise column items will be
0689: * removed to fit the given size.
0690: * <p>
0691: * For each row that doesn't meet the given column count an empty Object will be put in place. other wise each row
0692: * will be trimmed down to the given size.
0693: * <p>
0694: * This will fire a TableStructureChanged to model listeners.
0695: *
0696: * @param size the new desired column count.
0697: */
0698: public void setColumnCount(int size) {
0699:
0700: synchronized (dataStore) {
0701: Iterator<ArrayList<Object>> itr = dataStore.iterator();
0702: synchronized (columns) {
0703: try {
0704: if (columns.size() > size) {
0705: for (int i = columns.size(); i > size; i--) {
0706: columns.remove(i);
0707: }
0708: columns.trimToSize();
0709: } else {
0710: for (int i = columns.size(); i < size; i++) {
0711: columns.add(Integer.toString(i));
0712: }
0713: }
0714: } catch (Throwable t) {
0715: return;
0716: }
0717: }
0718: while (itr.hasNext()) {
0719: try {
0720: ArrayList<Object> lst = itr.next();
0721: if (lst.size() > size) {
0722: for (int i = columns.size(); i > size; i--) {
0723: columns.remove(i);
0724: }
0725: lst.trimToSize();
0726: } else {
0727: for (int i = lst.size(); i < size; i++) {
0728: columns.add("");
0729: }
0730: }
0731: } catch (Throwable t) {
0732: }
0733: }
0734: }
0735: fireTableStructureChanged();
0736: }
0737:
0738: /**
0739: * Updates a column name at a given index.
0740: * <p>
0741: * This will replace the exisitng column name string with the given name at the specified index. if the index is
0742: * invalid it will be silently ignored.
0743: * <p>
0744: * This will fire a TableStructedChanged() to model listeners.
0745: *
0746: * @param idx the specidex column index to change
0747: * @param name is the new column to use, can be null.
0748: */
0749: public void setColumnName(int idx, String name) {
0750:
0751: synchronized (columns) {
0752: try {
0753: columns.set(idx, name);
0754: fireTableStructureChanged();
0755: } catch (Throwable t) {
0756: return;
0757: }
0758: }
0759: }
0760:
0761: public String[] getColumns() {
0762:
0763: synchronized (columns) {
0764: return columns.toArray(new String[columns.size()]);
0765: }
0766: }
0767:
0768: public synchronized Map<String, Object> getRowMap(int rowIndex) {
0769:
0770: HashMap<String, Object> rowMap = new HashMap<String, Object>();
0771: for (int i = 0; i < getColumnCount(); i++) {
0772: rowMap.put(getColumnName(i), getValueAt(rowIndex, i));
0773: }
0774: return rowMap;
0775: }
0776:
0777: /**
0778: * This will remove the desired column from this table model.
0779: * <p>
0780: * This will also ensure that all the
0781: *
0782: * @param columnIndex
0783: */
0784: public void removeColumn(int columnIndex) {
0785:
0786: Iterator itr = dataStore.iterator();
0787: synchronized (dataStore) {
0788: try {
0789: columns.remove(columnIndex);
0790: } catch (Throwable t) {
0791: return;
0792: }
0793: while (itr.hasNext()) {
0794: try {
0795: ArrayList lst = (ArrayList) itr.next();
0796: lst.remove(columnIndex);
0797: } catch (Throwable t) {
0798: }
0799: }
0800: }
0801: fireTableStructureChanged();
0802: }
0803:
0804: /**
0805: * Notifies all listeners that all cell values in the table's rows may have changed. The number of rows may also
0806: * have changed and the <code>JTable</code> should redraw the table from scratch. The structure of the table (as
0807: * in the order of the columns) is assumed to be the same.
0808: *
0809: * @see TableModelEvent
0810: */
0811: public void fireTableDataChanged() {
0812:
0813: fireTableChanged(new TableModelEvent(this ));
0814: }
0815:
0816: /**
0817: * Notifies all listeners that the table's structure has changed. The number of columns in the table, and the names
0818: * and types of the new columns may be different from the previous state. If the <code>JTable</code> receives this
0819: * event and its <code>autoCreateColumnsFromModel</code> flag is set it discards any table columns that it had and
0820: * reallocates default columns in the order they appear in the model. This is the same as calling
0821: * <code>setModel(TableModel)</code> on the <code>JTable</code>.
0822: *
0823: * @see TableModelEvent
0824: */
0825: public void fireTableStructureChanged() {
0826:
0827: fireTableChanged(new TableModelEvent(this ,
0828: TableModelEvent.HEADER_ROW));
0829: }
0830:
0831: /**
0832: * Notifies all listeners that rows in the range <code>[firstRow, lastRow]</code>, inclusive, have been inserted.
0833: *
0834: * @param firstRow the first row
0835: * @param lastRow the last row
0836: * @see TableModelEvent
0837: */
0838: public void fireTableRowsInserted(int firstRow, int lastRow) {
0839:
0840: fireTableChanged(new TableModelEvent(this , firstRow, lastRow,
0841: TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
0842: }
0843:
0844: /**
0845: * Notifies all listeners that rows in the range <code>[firstRow, lastRow]</code>, inclusive, have been updated.
0846: *
0847: * @param firstRow the first row
0848: * @param lastRow the last row
0849: * @see TableModelEvent
0850: */
0851: public void fireTableRowsUpdated(int firstRow, int lastRow) {
0852:
0853: fireTableChanged(new TableModelEvent(this , firstRow, lastRow,
0854: TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE));
0855: }
0856:
0857: /**
0858: * Notifies all listeners that rows in the range <code>[firstRow, lastRow]</code>, inclusive, have been deleted.
0859: *
0860: * @param firstRow the first row
0861: * @param lastRow the last row
0862: * @see TableModelEvent
0863: */
0864: public void fireTableRowsDeleted(int firstRow, int lastRow) {
0865:
0866: fireTableChanged(new TableModelEvent(this , firstRow, lastRow,
0867: TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
0868: }
0869:
0870: /**
0871: * Notifies all listeners that the value of the cell at <code>[row, column]</code> has been updated.
0872: *
0873: * @param row row of cell which has been updated
0874: * @param column column of cell which has been updated
0875: * @see TableModelEvent
0876: */
0877: public void fireTableCellUpdated(int row, int column) {
0878:
0879: fireTableChanged(new TableModelEvent(this , row, row, column));
0880: }
0881:
0882: /**
0883: * Forwards the given notification event to all <code>TableModelListeners</code> that registered themselves as
0884: * listeners for this table model.
0885: *
0886: * @param e the event to be forwarded
0887: * @see #addTableModelListener
0888: * @see TableModelEvent
0889: */
0890: public void fireTableChanged(TableModelEvent e) {
0891:
0892: if (listeners != null) {
0893: Iterator itr = listeners.iterator();
0894: while (itr.hasNext()) {
0895: TableModelListener tml = (TableModelListener) itr
0896: .next();
0897: try {
0898: tml.tableChanged(e);
0899: } catch (Throwable t) {
0900: }
0901: }
0902: }
0903: }
0904:
0905: /**
0906: * Returns a column given its name. Implementation is naive so this should be overridden if this method is to be
0907: * called often. This method is not in the <code>TableModel</code> interface and is not used by the
0908: * <code>JTable</code>.
0909: *
0910: * @param columnName string containing name of column to be located
0911: * @return the column with <code>columnName</code>, or -1 if not found
0912: */
0913: public int findColumn(String columnName) {
0914:
0915: if (columnName != null) {
0916: for (int i = 0; i < getColumnCount(); i++) {
0917: if (columnName.equals(getColumnName(i))) {
0918: return i;
0919: }
0920: }
0921: }
0922: return -1;
0923: }
0924:
0925: public boolean matchesFilter(int rowIndex, int columnIndex) {
0926:
0927: try {
0928: if (filteredRows.isEmpty()) {
0929: return false;
0930: }
0931:
0932: ArrayList row = getRow(rowIndex);
0933: Object column = row.get(columnIndex);
0934: Pattern pattern = Pattern.compile(lastFilterPattern,
0935: Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);
0936: return doFilter(column, pattern);
0937: } catch (Exception ignored) {
0938: return false;
0939: }
0940: }
0941:
0942: public int applyFilter(String text) {
0943:
0944: if (text == null || text.length() == 0) {
0945: lastFilterPattern = "";
0946: clearFilter();
0947: } else {
0948: Pattern pattern = null;
0949: try {
0950: pattern = Pattern.compile(text, Pattern.UNICODE_CASE
0951: | Pattern.CASE_INSENSITIVE);
0952: filteredRows.clear();
0953: lastFilterPattern = text;
0954: } catch (PatternSyntaxException pse) {
0955: return -1;
0956: }
0957:
0958: for (int r = 0; r < getTrueRowCount(); r++) {
0959: ArrayList<Object> row = null;
0960: synchronized (dataStore) {
0961: row = dataStore.get(r);
0962: }
0963: Iterator itr = row.iterator();
0964: int c = 0;
0965: while (itr.hasNext()) {
0966: if (canSearchColumn(c++)) {
0967: Object next = itr.next();
0968: if (doFilter(next, pattern)) {
0969: filteredRows.add(new Integer(r));
0970: fireTableDataChanged();
0971: break;
0972: }
0973: }
0974: }
0975: }
0976: if (filteredRows.isEmpty()) {
0977: fireTableDataChanged();
0978: }
0979: return filteredRows.size();
0980: }
0981: return -1;
0982: }
0983:
0984: public void clearFilter() {
0985:
0986: if (!filteredRows.isEmpty()) {
0987: filteredRows.clear();
0988: fireTableDataChanged();
0989: }
0990: }
0991:
0992: public int translateRow(int rowIndex) {
0993:
0994: if (filteredRows.isEmpty()) {
0995: return rowIndex + (pageOffset * pageSize);
0996: }
0997: return filteredRows.get(rowIndex).intValue();
0998: }
0999:
1000: protected boolean doFilter(Object data, Pattern pattern) {
1001:
1002: if (data != null) {
1003: return pattern.matcher(data.toString()).find();
1004: }
1005:
1006: return false;
1007: }
1008:
1009: protected boolean canSearchColumn(int column) {
1010:
1011: return column >= 0 && column < getColumnCount();
1012: }
1013:
1014: /**
1015: * Adds a full column to underlying data collection.
1016: * <p>
1017: * This method will iterate through the given collection c, and for each row in the underlying data store, the item
1018: * is added to the each row. If the collection does not contain the correct number of items which should correspond
1019: * to the current number of rows, empty object will put in place.
1020: * <p>
1021: * This method will fire a TableStructureChanged() to model listeners.
1022: *
1023: * @param c a collection of data representing a table column.
1024: * @param name of the column represting this collection.
1025: */
1026: protected void addColumn(String name, Collection c) {
1027:
1028: Object x = new Object();
1029: synchronized (columns) {
1030: Iterator citr = c.iterator();
1031: columns.add(name == null ? "" : name);
1032: synchronized (dataStore) {
1033: Iterator<ArrayList<Object>> itr = dataStore.iterator();
1034: while (itr.hasNext()) {
1035: ArrayList<Object> lst = itr.next();
1036: if (citr.hasNext())
1037: lst.add(citr.next());
1038: else
1039: lst.add(x);
1040: }
1041: }
1042: }
1043: fireTableStructureChanged();
1044: }
1045:
1046: /**
1047: * This is a helper method to clear the datastore without firing a model event.
1048: * <p>
1049: * If you need to clear the data without sending a potentially misleading event extenders will want to use this
1050: * method versus the public clear.
1051: *
1052: * @see #clear()
1053: */
1054: protected void clearData() {
1055:
1056: synchronized (dataStore) {
1057: dataStore.clear();
1058: }
1059: }
1060:
1061: /**
1062: * This is a helper method to clear all the data without firing a model event.
1063: * <p>
1064: * If you need to clear the data without sending a potentially misleading event extenders will want to use this
1065: * method versus the public clearAll.
1066: *
1067: * @see #clearAll()
1068: */
1069: protected void clearAllData() {
1070:
1071: synchronized (dataStore) {
1072: dataStore.clear();
1073: synchronized (columns) {
1074: columns.clear();
1075: }
1076: }
1077: }
1078:
1079: /**
1080: * This assumes a collection of ArrayLists as a way to add a multiple of table rows to the model.
1081: * <p>
1082: * If the collection contains an object that cannot be type cast to an ArrayList then the object will be skipped, so
1083: * Vector and Stacks can be in the given collection since they are sub-class of the ArrayList.
1084: * <p>
1085: * Note this method does not mess with the column data information so if you are adding a bunch of rows, where the
1086: * column counts are off it won't show till you update the column information
1087: * <p>
1088: * This will fire a TableRowsInserted() for the number of ArrayLists actually added.
1089: *
1090: * @param c is a collection of ArrayLists to be used as the data store for this model.
1091: */
1092: protected void setData(Collection<ArrayList<Object>> c) {
1093:
1094: int count = 0;
1095: synchronized (dataStore) {
1096: dataStore.clear();
1097: Iterator<ArrayList<Object>> itr = c.iterator();
1098: while (itr.hasNext()) {
1099: ArrayList<Object> lst = itr.next();
1100: dataStore.add(lst);
1101: count++;
1102: }
1103: }
1104: fireTableRowsInserted(-1, count);
1105: }
1106:
1107: protected ArrayList<Object> getRow(int rowIndex) {
1108:
1109: try {
1110: int translated = translateRow(rowIndex);
1111: synchronized (dataStore) {
1112: return dataStore.get(translated);
1113: }
1114: } catch (Throwable t) {
1115: return null;
1116: }
1117: }
1118:
1119: /**
1120: * Replacing existing column information with the given array of Strings.
1121: * <p>
1122: * This will clear all the exisiting column information and the new colum count will be the length of the given
1123: * String array.
1124: * <p>
1125: * If the given String[] is null a NullPointerException will most likely be thrown.
1126: * <p>
1127: * This will fire a TableStructureChanged() to model listeners.
1128: *
1129: * @param s is the new set of Columns names for this model.
1130: */
1131: protected void setColumns(String[] s) {
1132:
1133: synchronized (columns) {
1134: columns.clear();
1135: if (s != null)
1136: columns.addAll(Arrays.asList(s));
1137: }
1138: fireTableStructureChanged();
1139: }
1140:
1141: /**
1142: * @param rowAffected
1143: * @param rowData
1144: */
1145: void insertRow(int rowIndex, Map<String, Object> rowData) {
1146:
1147: try {
1148: int translated = translateRow(rowIndex);
1149: boolean inserted = false;
1150: boolean added = false;
1151: synchronized (dataStore) {
1152: ArrayList<Object> newRow = new ArrayList<Object>(
1153: getColumnCount());
1154: Iterator keys = rowData.keySet().iterator();
1155: while (keys.hasNext()) {
1156: String key = (String) keys.next();
1157: int idx = findColumn(key);
1158: newRow.add(idx, rowData.get(key));
1159: }
1160:
1161: if (translated < dataStore.size()) {
1162: dataStore.add(translated, newRow);
1163: inserted = true;
1164: } else {
1165: dataStore.add(newRow);
1166: added = true;
1167: }
1168: }
1169:
1170: if (inserted) {
1171: fireTableRowsInserted(translated, translated);
1172: }
1173: if (added) {
1174: fireTableStructureChanged();
1175: }
1176: } catch (Throwable t) {
1177:
1178: }
1179: }
1180: }
|