PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/eclipse-wtp-jpa-3.4.0/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TableModelAdapter.java

#
Java | 420 lines | 253 code | 55 blank | 112 comment | 21 complexity | cfee29c875b6078bbfc5a2b0e9ea9a0a MD5 | raw file
  1. /*******************************************************************************
  2. * Copyright (c) 2007, 2012 Oracle. All rights reserved.
  3. * This program and the accompanying materials are made available under the
  4. * terms of the Eclipse Public License v1.0, which accompanies this distribution
  5. * and is available at http://www.eclipse.org/legal/epl-v10.html.
  6. *
  7. * Contributors:
  8. * Oracle - initial API and implementation
  9. ******************************************************************************/
  10. package org.eclipse.jpt.common.utility.internal.model.value.swing;
  11. import java.util.ArrayList;
  12. import java.util.Iterator;
  13. import java.util.List;
  14. import javax.swing.event.TableModelListener;
  15. import javax.swing.table.AbstractTableModel;
  16. import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTListChangeListenerWrapper;
  17. import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper;
  18. import org.eclipse.jpt.common.utility.internal.model.value.CollectionListValueModelAdapter;
  19. import org.eclipse.jpt.common.utility.model.event.ListAddEvent;
  20. import org.eclipse.jpt.common.utility.model.event.ListChangeEvent;
  21. import org.eclipse.jpt.common.utility.model.event.ListClearEvent;
  22. import org.eclipse.jpt.common.utility.model.event.ListMoveEvent;
  23. import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent;
  24. import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent;
  25. import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent;
  26. import org.eclipse.jpt.common.utility.model.listener.ListChangeListener;
  27. import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener;
  28. import org.eclipse.jpt.common.utility.model.value.CollectionValueModel;
  29. import org.eclipse.jpt.common.utility.model.value.ListValueModel;
  30. import org.eclipse.jpt.common.utility.model.value.PropertyValueModel;
  31. import org.eclipse.jpt.common.utility.model.value.ModifiablePropertyValueModel;
  32. /**
  33. * This TableModel can be used to keep a TableModelListener (e.g. a JTable)
  34. * in synch with a ListValueModel that holds a collection of model objects,
  35. * each of which corresponds to a row in the table.
  36. * Typically, each column of the table will be bound to a different aspect
  37. * of the contained model objects.
  38. *
  39. * For example, a MWTable has an attribute 'databaseFields' that holds
  40. * a collection of MWDatabaseFields that would correspond to the rows of
  41. * a JTable; and each MWDatabaseField has a number
  42. * of attributes (e.g. name, type, size) that can be bound to the columns of
  43. * a row in the JTable. As these database fields are added, removed, and
  44. * changed, this model will keep the listeners aware of the changes.
  45. *
  46. * An instance of this TableModel must be supplied with a
  47. * list holder (e.g. the 'databaseFields'), which is a value
  48. * model on the bound collection This is required - the
  49. * collection itself can be null, but the list value model that
  50. * holds it is required. Typically this list will be sorted (@see
  51. * SortedListValueModelAdapter).
  52. *
  53. * This TableModel must also be supplied with a ColumnAdapter that
  54. * will be used to configure the headers, renderers, editors, and contents
  55. * of the various columns.
  56. *
  57. * Design decision:
  58. * Cell listener options (from low space/high time to high space/low time):
  59. * - 1 cell listener listening to every cell (this is the current implementation)
  60. * - 1 cell listener per row
  61. * - 1 cell listener per cell
  62. */
  63. public class TableModelAdapter<E>
  64. extends AbstractTableModel
  65. {
  66. /**
  67. * a list of user objects that are converted to
  68. * rows via the column adapter
  69. */
  70. private ListValueModel<? extends E> listHolder;
  71. private final ListChangeListener listChangeListener;
  72. /**
  73. * each row is an array of cell models
  74. */
  75. // declare as ArrayList so we can use #ensureCapacity(int)
  76. private final ArrayList<ModifiablePropertyValueModel<Object>[]> rows;
  77. /**
  78. * client-supplied adapter that provides with the various column
  79. * settings and converts the objects in the LVM
  80. * into an array of cell models
  81. */
  82. private final ColumnAdapter columnAdapter;
  83. /**
  84. * the single listener that listens to every cell's model
  85. */
  86. private final PropertyChangeListener cellListener;
  87. // ********** constructors **********
  88. /**
  89. * Construct a table model adapter for the specified objects
  90. * and adapter.
  91. */
  92. public TableModelAdapter(ListValueModel<? extends E> listHolder, ColumnAdapter columnAdapter) {
  93. super();
  94. if (listHolder == null) {
  95. throw new NullPointerException();
  96. }
  97. this.listHolder = listHolder;
  98. this.columnAdapter = columnAdapter;
  99. this.listChangeListener = this.buildListChangeListener();
  100. this.rows = new ArrayList<ModifiablePropertyValueModel<Object>[]>();
  101. this.cellListener = this.buildCellListener();
  102. }
  103. /**
  104. * Construct a table model adapter for the specified objects
  105. * and adapter.
  106. */
  107. public TableModelAdapter(CollectionValueModel<? extends E> collectionHolder, ColumnAdapter columnAdapter) {
  108. this(new CollectionListValueModelAdapter<E>(collectionHolder), columnAdapter);
  109. }
  110. // ********** initialization **********
  111. protected ListChangeListener buildListChangeListener() {
  112. return new AWTListChangeListenerWrapper(this.buildListChangeListener_());
  113. }
  114. protected ListChangeListener buildListChangeListener_() {
  115. return new ListChangeListener() {
  116. public void itemsAdded(ListAddEvent event) {
  117. TableModelAdapter.this.addRows(event.getIndex(), event.getItemsSize(), this.getItems(event));
  118. }
  119. public void itemsRemoved(ListRemoveEvent event) {
  120. TableModelAdapter.this.removeRows(event.getIndex(), event.getItemsSize());
  121. }
  122. public void itemsReplaced(ListReplaceEvent event) {
  123. TableModelAdapter.this.replaceRows(event.getIndex(), this.getNewItems(event));
  124. }
  125. public void itemsMoved(ListMoveEvent event) {
  126. TableModelAdapter.this.moveRows(event.getTargetIndex(), event.getSourceIndex(), event.getLength());
  127. }
  128. public void listCleared(ListClearEvent event) {
  129. TableModelAdapter.this.clearTable();
  130. }
  131. public void listChanged(ListChangeEvent event) {
  132. TableModelAdapter.this.rebuildTable();
  133. }
  134. // minimized scope of suppressed warnings
  135. @SuppressWarnings("unchecked")
  136. protected Iterable<Object> getItems(ListAddEvent event) {
  137. return (Iterable<Object>) event.getItems();
  138. }
  139. // minimized scope of suppressed warnings
  140. @SuppressWarnings("unchecked")
  141. protected Iterable<Object> getNewItems(ListReplaceEvent event) {
  142. return (Iterable<Object>) event.getNewItems();
  143. }
  144. @Override
  145. public String toString() {
  146. return "list listener"; //$NON-NLS-1$
  147. }
  148. };
  149. }
  150. protected PropertyChangeListener buildCellListener() {
  151. return new AWTPropertyChangeListenerWrapper(this.buildCellListener_());
  152. }
  153. protected PropertyChangeListener buildCellListener_() {
  154. return new PropertyChangeListener() {
  155. @SuppressWarnings("unchecked")
  156. public void propertyChanged(PropertyChangeEvent event) {
  157. TableModelAdapter.this.cellChanged((ModifiablePropertyValueModel<Object>) event.getSource());
  158. }
  159. @Override
  160. public String toString() {
  161. return "cell listener"; //$NON-NLS-1$
  162. }
  163. };
  164. }
  165. // ********** TableModel implementation **********
  166. public int getColumnCount() {
  167. return this.columnAdapter.columnCount();
  168. }
  169. public int getRowCount() {
  170. return this.rows.size();
  171. }
  172. @Override
  173. public String getColumnName(int column) {
  174. return this.columnAdapter.columnName(column);
  175. }
  176. @Override
  177. public Class<?> getColumnClass(int columnIndex) {
  178. return this.columnAdapter.columnClass(columnIndex);
  179. }
  180. @Override
  181. public boolean isCellEditable(int rowIndex, int columnIndex) {
  182. return this.columnAdapter.columnIsEditable(columnIndex);
  183. }
  184. public Object getValueAt(int rowIndex, int columnIndex) {
  185. ModifiablePropertyValueModel<Object>[] row = this.rows.get(rowIndex);
  186. return row[columnIndex].getValue();
  187. }
  188. @Override
  189. public void setValueAt(Object value, int rowIndex, int columnIndex) {
  190. ModifiablePropertyValueModel<Object>[] row = this.rows.get(rowIndex);
  191. row[columnIndex].setValue(value);
  192. }
  193. /**
  194. * Extend to start listening to the underlying model if necessary.
  195. */
  196. @Override
  197. public void addTableModelListener(TableModelListener l) {
  198. if (this.hasNoTableModelListeners()) {
  199. this.engageModel();
  200. }
  201. super.addTableModelListener(l);
  202. }
  203. /**
  204. * Extend to stop listening to the underlying model if necessary.
  205. */
  206. @Override
  207. public void removeTableModelListener(TableModelListener l) {
  208. super.removeTableModelListener(l);
  209. if (this.hasNoTableModelListeners()) {
  210. this.disengageModel();
  211. }
  212. }
  213. // ********** public API **********
  214. /**
  215. * Return the underlying list model.
  216. */
  217. public ListValueModel<? extends E> getModel() {
  218. return this.listHolder;
  219. }
  220. /**
  221. * Set the underlying list model.
  222. */
  223. public void setModel(ListValueModel<E> listHolder) {
  224. if (listHolder == null) {
  225. throw new NullPointerException();
  226. }
  227. boolean hasListeners = this.hasTableModelListeners();
  228. if (hasListeners) {
  229. this.disengageModel();
  230. }
  231. this.listHolder = listHolder;
  232. if (hasListeners) {
  233. this.engageModel();
  234. this.fireTableDataChanged();
  235. }
  236. }
  237. /**
  238. * Set the underlying collection model.
  239. */
  240. public void setModel(CollectionValueModel<E> collectionHolder) {
  241. this.setModel(new CollectionListValueModelAdapter<E>(collectionHolder));
  242. }
  243. // ********** queries **********
  244. /**
  245. * Return whether this model has no listeners.
  246. */
  247. protected boolean hasNoTableModelListeners() {
  248. return this.listenerList.getListenerCount(TableModelListener.class) == 0;
  249. }
  250. /**
  251. * Return whether this model has any listeners.
  252. */
  253. protected boolean hasTableModelListeners() {
  254. return ! this.hasNoTableModelListeners();
  255. }
  256. // ********** behavior **********
  257. /**
  258. * Start listening to the list of objects and the various aspects
  259. * of the objects that make up the rows.
  260. */
  261. private void engageModel() {
  262. this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener);
  263. this.engageAllCells();
  264. }
  265. /**
  266. * Convert the objects into rows and listen to the cells.
  267. */
  268. private void engageAllCells() {
  269. this.rows.ensureCapacity(this.listHolder.size());
  270. for (Iterator<? extends E> stream = this.listHolder.iterator(); stream.hasNext(); ) {
  271. ModifiablePropertyValueModel<Object>[] row = this.columnAdapter.cellModels(stream.next());
  272. this.engageRow(row);
  273. this.rows.add(row);
  274. }
  275. }
  276. /**
  277. * Listen to the cells in the specified row.
  278. */
  279. private void engageRow(ModifiablePropertyValueModel<Object>[] row) {
  280. for (int i = row.length; i-- > 0; ) {
  281. row[i].addPropertyChangeListener(PropertyValueModel.VALUE, this.cellListener);
  282. }
  283. }
  284. /**
  285. * Stop listening.
  286. */
  287. private void disengageModel() {
  288. this.disengageAllCells();
  289. this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener);
  290. }
  291. private void disengageAllCells() {
  292. for (ModifiablePropertyValueModel<Object>[] row : this.rows) {
  293. this.disengageRow(row);
  294. }
  295. this.rows.clear();
  296. }
  297. private void disengageRow(ModifiablePropertyValueModel<Object>[] row) {
  298. for (int i = row.length; i-- > 0; ) {
  299. row[i].removePropertyChangeListener(PropertyValueModel.VALUE, this.cellListener);
  300. }
  301. }
  302. /**
  303. * brute-force search for the cell(s) that changed...
  304. */
  305. void cellChanged(ModifiablePropertyValueModel<Object> cellHolder) {
  306. for (int i = this.rows.size(); i-- > 0; ) {
  307. ModifiablePropertyValueModel<Object>[] row = this.rows.get(i);
  308. for (int j = row.length; j-- > 0; ) {
  309. if (row[j] == cellHolder) {
  310. this.fireTableCellUpdated(i, j);
  311. }
  312. }
  313. }
  314. }
  315. /**
  316. * convert the items to rows
  317. */
  318. void addRows(int index, int size, Iterable<Object> items) {
  319. List<ModifiablePropertyValueModel<Object>[]> newRows = new ArrayList<ModifiablePropertyValueModel<Object>[]>(size);
  320. for (Object item : items) {
  321. ModifiablePropertyValueModel<Object>[] row = this.columnAdapter.cellModels(item);
  322. this.engageRow(row);
  323. newRows.add(row);
  324. }
  325. this.rows.addAll(index, newRows);
  326. this.fireTableRowsInserted(index, index + size - 1);
  327. }
  328. void removeRows(int index, int size) {
  329. for (int i = 0; i < size; i++) {
  330. this.disengageRow(this.rows.remove(index));
  331. }
  332. this.fireTableRowsDeleted(index, index + size - 1);
  333. }
  334. void replaceRows(int index, Iterable<Object> items) {
  335. int i = index;
  336. for (Object item : items) {
  337. ModifiablePropertyValueModel<Object>[] row = this.rows.get(i);
  338. this.disengageRow(row);
  339. row = this.columnAdapter.cellModels(item);
  340. this.engageRow(row);
  341. this.rows.set(i, row);
  342. i++;
  343. }
  344. this.fireTableRowsUpdated(index, i - 1);
  345. }
  346. void moveRows(int targetIndex, int sourceIndex, int length) {
  347. ArrayList<ModifiablePropertyValueModel<Object>[]> temp = new ArrayList<ModifiablePropertyValueModel<Object>[]>(length);
  348. for (int i = 0; i < length; i++) {
  349. temp.add(this.rows.remove(sourceIndex));
  350. }
  351. this.rows.addAll(targetIndex, temp);
  352. int start = Math.min(targetIndex, sourceIndex);
  353. int end = Math.max(targetIndex, sourceIndex) + length - 1;
  354. this.fireTableRowsUpdated(start, end);
  355. }
  356. void clearTable() {
  357. this.disengageAllCells();
  358. this.fireTableDataChanged();
  359. }
  360. void rebuildTable() {
  361. this.disengageAllCells();
  362. this.engageAllCells();
  363. this.fireTableDataChanged();
  364. }
  365. }