001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.mashup.db.ui.wizard;
042:
043: import java.util.ArrayList;
044: import java.util.Collection;
045: import java.util.Collections;
046: import java.util.Comparator;
047: import java.util.HashSet;
048: import java.util.Iterator;
049: import java.util.List;
050: import java.util.Set;
051:
052: import javax.swing.event.TableModelEvent;
053: import javax.swing.event.TableModelListener;
054: import javax.swing.table.TableModel;
055: import net.java.hulp.i18n.Logger;
056: import org.netbeans.modules.etl.logger.Localizer;
057: import org.netbeans.modules.etl.logger.LogUtil;
058:
059: /**
060: * TableModel implementation that manages instances of classes which implement the
061: * RowEntry interface (defined within as a static class).
062: *
063: * @author Jonathan Giron
064: * @author Ahimanikya Satapathy
065: * @version $Revision$
066: */
067: public class RowEntryTableModel implements TableModel {
068:
069: private static transient final Logger mLogger = LogUtil
070: .getLogger(RowEntryTableModel.class.getName());
071: private static transient final Localizer mLoc = Localizer.get();
072:
073: /**
074: * Defines contract for implementation classes that want to be rendered by a JTable
075: * through the RowEntryTableModel.
076: *
077: * @author Jonathan Giron
078: * @version $Revision$
079: */
080: public static interface RowEntry {
081:
082: /**
083: * method getValue is used to get a row entry value
084: *
085: * @param column is the value to get.
086: * @return Object which is the value of the row entry.
087: */
088: public Object getValue(int column);
089:
090: /**
091: * method isEditable returns true if an object is editable
092: *
093: * @param column is the column to be checked
094: * @return boolean true if editable
095: */
096: public boolean isEditable(int column);
097:
098: /**
099: * method setEditable is used to set a row object editable.
100: *
101: * @param column is the column to be set.
102: * @param newState is the state for the object.
103: */
104: public void setEditable(int column, boolean newState);
105:
106: /**
107: * method setValue is used to set a row entry value
108: *
109: * @param column is the value to set.
110: * @param newValue is the new value to use.
111: */
112: public void setValue(int column, Object newValue);
113: }
114:
115: /* Default column header prefix to use if columnHeaders is null. */
116: private static final String DEF_HEADER = "Column ";
117:
118: /* Log4J category string */
119: private static final String LOG_CATEGORY = RowEntryTableModel.class
120: .getName();
121: /** (Optional) Holds column header names */
122: protected String[] columnHeaders;
123: /** Holds default editability state for each column */
124: protected boolean[] editable;
125: /** rows is the list of rows in the table. */
126: protected ArrayList rows;
127:
128: /* Set of registered TableModelListeners */
129: private Set listeners;
130:
131: /**
132: * Creates a new instance of RowEntryTableModel
133: *
134: * @param defaultEditable array of booleans indicating default editability for each
135: * column
136: */
137: public RowEntryTableModel(boolean[] defaultEditable) {
138: this ();
139:
140: if (defaultEditable.length == 0) {
141: throw new IllegalArgumentException(
142: "Must supply a non-empty boolean[] instance for defaultEditable."); // NOI18N
143: }
144:
145: editable = defaultEditable;
146: }
147:
148: /**
149: * Creates a new instance of RowEntryTableModel
150: *
151: * @param headerLabels array of Strings representing column header labels
152: * @param defaultEditable array of booleans indicating default editability for each
153: * column
154: */
155: public RowEntryTableModel(String[] headerLabels,
156: boolean[] defaultEditable) {
157: this (defaultEditable);
158:
159: if ((headerLabels != null)
160: && (headerLabels.length != editable.length)) {
161: throw new IllegalArgumentException(
162: "When supplied, headerLabels must have same array size as defaultEditable.");
163: }
164:
165: columnHeaders = headerLabels;
166: }
167:
168: /** Creates a new default instance of RowEntryTableModel. */
169: protected RowEntryTableModel() {
170: listeners = new HashSet(1);
171: rows = new ArrayList(10);
172: }
173:
174: /**
175: * Adds the given RowEntry to the end of the table.
176: *
177: * @param newData RowEntry to add
178: */
179: public void addRowEntry(RowEntry newData) {
180: int index = 0;
181: synchronized (rows) {
182: rows.add(newData);
183: index = rows.size();
184: }
185:
186: fireTableModelEvent(new TableModelEvent(this , index + 1,
187: index + 1, TableModelEvent.ALL_COLUMNS,
188: TableModelEvent.INSERT));
189: }
190:
191: /**
192: * Adds a listener to the list that is notified each time a change to the data model
193: * occurs.
194: *
195: * @param l the TableModelListener
196: */
197: public void addTableModelListener(TableModelListener l) {
198: synchronized (listeners) {
199: listeners.add(l);
200: }
201: }
202:
203: /**
204: * Clears model of all row entries.
205: */
206: public void clear() {
207: synchronized (rows) {
208: int victimCount = rows.size();
209: if (victimCount != 0) {
210: rows.clear();
211: fireTableModelEvent(new TableModelEvent(this , 0,
212: victimCount - 1, TableModelEvent.DELETE));
213: }
214: }
215: }
216:
217: /**
218: * Signals to all listeners that table data have changed, and that views should
219: * refresh their renderings to reflect the new state of the model.
220: */
221: public void fireTableDataChanged() {
222: fireTableModelEvent(new TableModelEvent(this ));
223: }
224:
225: /**
226: * Returns the most specific superclass for all the cell values in the column. This is
227: * used by the <code>JTable</code> to set up a default renderer and editor for the
228: * column.
229: *
230: * @param columnIndex the index of the column
231: * @return the common ancestor class of the object values in the model.
232: */
233: public Class getColumnClass(int columnIndex) {
234: Object o = getValueAt(0, columnIndex);
235: return (o != null) ? o.getClass() : Object.class;
236: }
237:
238: /**
239: * Returns the number of columns in the model. A <code>JTable</code> uses this
240: * method to determine how many columns it should create and display by default.
241: *
242: * @return the number of columns in the model
243: * @see #getRowCount
244: */
245: public int getColumnCount() {
246: return editable.length;
247: }
248:
249: /**
250: * Returns the name of the column at <code>columnIndex</code>. This is used to
251: * initialize the table's column header name. Note: this name does not need to be
252: * unique; two columns in a table can have the same name.
253: *
254: * @param columnIndex the index of the column
255: * @return the name of the column
256: */
257: public String getColumnName(int columnIndex) {
258: return (columnHeaders != null) ? columnHeaders[columnIndex]
259: : (DEF_HEADER + (columnIndex + 1));
260: }
261:
262: /**
263: * Returns the number of rows in the model. A <code>JTable</code> uses this method
264: * to determine how many rows it should display. This method should be quick, as it is
265: * called frequently during rendering.
266: *
267: * @return the number of rows in the model
268: * @see #getColumnCount
269: */
270: public int getRowCount() {
271: return rows.size();
272: }
273:
274: /**
275: * method getRowEntries returns a list of row entries.
276: *
277: * @return List of entries
278: */
279: public synchronized List getRowEntries() {
280: return (ArrayList) rows.clone();
281: }
282:
283: /**
284: * Gets the RowEntries at the given row indices.
285: *
286: * @param indices array of indices indicating RowEntry items to get.
287: * @return List of deleted RowEntry items
288: */
289: public synchronized List getRowEntries(int[] indices) {
290: ArrayList group = new ArrayList(indices.length);
291:
292: for (int i = 0; i < indices.length; i++) {
293: Object o = rows.get(indices[i]);
294: if (o instanceof RowEntry) {
295: group.add(o);
296: }
297: }
298:
299: return group;
300: }
301:
302: /**
303: * Gets the RowEntry at the given row index.
304: *
305: * @param rowIndex location of RowEntry to retrieve
306: * @return RowEntry at rowIndex
307: */
308: public RowEntry getRowEntry(int rowIndex) {
309: return (RowEntry) rows.get(rowIndex);
310: }
311:
312: /**
313: * Returns the value for the cell at <code>columnIndex</code> and
314: * <code>rowIndex</code>.
315: *
316: * @param rowIndex the row whose value is to be queried
317: * @param columnIndex the column whose value is to be queried
318: * @return the value Object at the specified cell
319: */
320: public Object getValueAt(int rowIndex, int columnIndex) {
321: Object value = null;
322: Object rowData = rows.get(rowIndex);
323:
324: if (rowData instanceof RowEntry) {
325: value = ((RowEntry) rowData).getValue(columnIndex);
326: }
327:
328: return value;
329: }
330:
331: /**
332: * Returns true if the cell at <code>rowIndex</code> and <code>columnIndex</code>
333: * is editable. Otherwise, <code>setValueAt</code> on the cell will not change the
334: * value of that cell. NOTE: Editable state uses AND logic to combine the default
335: * state of a column, as supplied in the constructor, with the per-row state. If a
336: * column is defined as uneditable by default, the per-row state is ignored.
337: *
338: * @param rowIndex the row whose value to be queried
339: * @param columnIndex the column whose value to be queried
340: * @return true if the cell is editable
341: * @see #setValueAt
342: */
343: public boolean isCellEditable(int rowIndex, int columnIndex) {
344: boolean defaultState = editable[columnIndex];
345:
346: Object rowData = rows.get(rowIndex);
347: if (rowData instanceof RowEntry) {
348: // Editable state uses AND logic: if default state is uneditable,
349: // it's always uneditable.
350: defaultState &= ((RowEntry) rowData)
351: .isEditable(columnIndex);
352: }
353:
354: return defaultState;
355: }
356:
357: /**
358: * Checks whether the given RowEntry exists in the model, using the given comparator
359: * as the standard of comparison.
360: *
361: * @param aRow RowEntry to compare against contents of model
362: * @param comparator Comparator instance defining the meaning of duplication between
363: * two given RowEntries.
364: * @return true if comparator determines that aRow matches an entry in this model,
365: * false otherwise.
366: */
367: public boolean isDuplicated(RowEntry aRow, Comparator comparator) {
368: boolean result = false;
369:
370: if (rows != null && aRow != null && comparator != null) {
371: // Since we're sorting (an irreversible operation), use a clone.
372: ArrayList rowsCopy = (ArrayList) rows.clone();
373:
374: Collections.sort(rowsCopy, comparator); // must sort before searching
375: result = (Collections.binarySearch(rowsCopy, aRow,
376: comparator) == 0);
377: }
378:
379: return result;
380: }
381:
382: /**
383: * Removes a listener from the list that is notified each time a change to the data
384: * model occurs.
385: *
386: * @param l the TableModelListener
387: */
388: public void removeTableModelListener(TableModelListener l) {
389: synchronized (listeners) {
390: listeners.remove(l);
391: }
392: }
393:
394: /**
395: * Adds all RowEntry elements in the given List to the model, after clearing the
396: * current contents of the list.
397: *
398: * @param rowEntries List of elements to be added. Any elements which do not implement
399: * RowEntry will not be added.
400: */
401: public synchronized void setRowEntries(Collection rowEntries) {
402: if (rowEntries == null) {
403: throw new IllegalArgumentException(
404: "Must supply non-null reference for rowEntries.");
405: }
406:
407: synchronized (rows) {
408: rows.clear();
409:
410: Iterator iter = rowEntries.iterator();
411: while (iter.hasNext()) {
412: Object o = iter.next();
413: if (o instanceof RowEntry) {
414: rows.add(o);
415: }
416: }
417:
418: //fireTableModelEvent(new TableModelEvent(this));
419: }
420: }
421:
422: /**
423: * Sets the value in the cell at <code>columnIndex</code> and <code>rowIndex</code>
424: * to <code>aValue</code>.
425: *
426: * @param aValue the new value
427: * @param rowIndex the row whose value is to be changed
428: * @param columnIndex the column whose value is to be changed
429: * @see #getValueAt
430: * @see #isCellEditable
431: */
432: public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
433: doSetValueAt(aValue, rowIndex, columnIndex);
434: fireTableModelEvent(new TableModelEvent(this , rowIndex,
435: rowIndex, columnIndex));
436: }
437:
438: /**
439: * Actually performs the low-level work of setting the value, without firing a table
440: * model event. The calling method is responsible for creating the appropriate model
441: * event and firing it off.
442: *
443: * @param aValue the new value
444: * @param rowIndex the row whose value is to be changed
445: * @param columnIndex the column whose value is to be changed
446: * @return true if set succeeded, false otherwise.
447: */
448: private boolean doSetValueAt(Object aValue, int rowIndex,
449: int columnIndex) {
450: boolean success = false;
451:
452: Object rowData = rows.get(rowIndex);
453: if (rowData instanceof RowEntry) {
454: try {
455: ((RowEntry) rowData).setValue(columnIndex, aValue);
456: success = true;
457: } catch (Exception e) {
458: mLogger
459: .errorNoloc(
460: mLoc
461: .t(
462: "PRSR081: Error while setting value for row {0}, col{1}",
463: rowIndex, columnIndex),
464: e);
465: success = false;
466: }
467: mLogger
468: .infoNoloc(mLoc
469: .t(
470: "PRSR082: After setValue at row{0}, col{1}: value ={2}; rowData ={3}",
471: rowIndex, columnIndex, aValue,
472: rowData));
473: }
474:
475: return success;
476: }
477:
478: /* Fires given TableModelEvent to all registered listeners. */
479: private void fireTableModelEvent(TableModelEvent ev) {
480: Iterator it;
481: synchronized (listeners) {
482: it = new HashSet(listeners).iterator();
483: }
484:
485: while (it.hasNext()) {
486: ((TableModelListener) it.next()).tableChanged(ev);
487: }
488: }
489: }
|