View Javadoc
1   package fr.ifremer.quadrige2.ui.swing.common.table;
2   
3   /*-
4    * #%L
5    * Quadrige2 Core :: Quadrige2 UI Common
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2017 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  
27  import com.google.common.base.Preconditions;
28  import com.google.common.collect.Lists;
29  import com.google.common.collect.Sets;
30  import fr.ifremer.quadrige2.ui.swing.common.model.AbstractBeanUIModel;
31  import org.apache.commons.collections4.CollectionUtils;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  
35  import java.beans.PropertyChangeEvent;
36  import java.beans.PropertyChangeListener;
37  import java.util.Collection;
38  import java.util.Collections;
39  import java.util.List;
40  import java.util.Set;
41  
42  /**
43   * <p>Abstract AbstractTableUIModel class.</p>
44   *
45   * @param <B> type of incoming bean to edit
46   * @param <R> type of the row of the table model
47   * @param <M> type of this model
48   * @author Ludovic Pecquot <ludovic.pecquot@e-is.pro>
49   */
50  public abstract class AbstractTableUIModel<B, R extends AbstractRowUIModel, M extends AbstractTableUIModel<B, R, M>>
51          extends AbstractBeanUIModel<B, M> {
52  
53      private static final long serialVersionUID = 1L;
54  
55      private static final Log LOG = LogFactory.getLog(AbstractTableUIModel.class);
56  
57      /** Constant <code>PROPERTY_ROWS="rows"</code> */
58      public static final String PROPERTY_ROWS = "rows";
59  
60      /** Constant <code>PROPERTY_BEANS_LOADED="rowsLoaded"</code> */
61      public static final String PROPERTY_BEANS_LOADED = "rowsLoaded";
62  
63      /** Constant <code>PROPERTY_SINGLE_ROW_SELECTED="singleSelectedRow"</code> */
64      public static final String PROPERTY_SINGLE_ROW_SELECTED = "singleSelectedRow";
65  
66      /** Constant <code>PROPERTY_SELECTED_ROWS="selectedRows"</code> */
67      public static final String PROPERTY_SELECTED_ROWS = "selectedRows";
68  
69      /** Constant <code>PROPERTY_ROWS_IN_ERROR="rowsInError"</code> */
70      public static final String PROPERTY_ROWS_IN_ERROR = "rowsInError";
71  
72      /** Constant <code>PROPERTY_ROWS_ADDED="rowsAdded"</code> */
73      public static final String PROPERTY_ROWS_ADDED = "rowsAdded";
74  
75      /** Constant <code>PROPERTY_ROWS_DELETED="rowsDeleted"</code> */
76      public static final String PROPERTY_ROWS_DELETED = "rowsDeleted";
77  
78      protected List<R> rows;
79  
80      protected R singleSelectedRow;
81  
82      protected Set<R> selectedRows;
83  
84      protected Set<R> rowsInError;
85  
86      private AbstractTableModel tableModel;
87  
88      /**
89       * <p>Constructor for AbstractTableUIModel.</p>
90       */
91      protected AbstractTableUIModel() {
92          super(null, null);
93  
94          rows = Lists.newArrayList();
95          selectedRows = Sets.newHashSet();
96  
97          addPropertyChangeListener(PROPERTY_ROWS_IN_ERROR, new PropertyChangeListener() {
98  
99              @Override
100             public void propertyChange(PropertyChangeEvent evt) {
101                 if (LOG.isDebugEnabled()) {
102                     LOG.debug(PROPERTY_ROWS_IN_ERROR + " changed " + rowsInError.size());
103                 }
104                 setValid(CollectionUtils.isEmpty(rowsInError));
105             }
106         });
107         setRowsInError(Sets.<R>newHashSet());
108     }
109 
110     /**
111      * Get the model itself (useful for validators)
112      *
113      * @return the model itself
114      */
115     public AbstractTableUIModel getInstance() {
116         return this;
117     }
118 
119     /**
120      * Dummy setter (needed for validator using this instance directly)
121      *
122      * @param model dummy
123      */
124     public void setInstance(AbstractTableUIModel model) {
125 
126     }
127 
128     /**
129      * get the underlying table model
130      *
131      * @return the table model
132      */
133     public AbstractTableModel getTableModel() {
134         return tableModel;
135     }
136 
137     /**
138      * set the underlying table model
139      *
140      * @param tableModel the table model
141      */
142     public void setTableModel(AbstractTableModel tableModel) {
143         this.tableModel = tableModel;
144     }
145 
146     /**
147      * {@inheritDoc}
148      *
149      * this model don't support binders, so no need to return a new bean in implemented classes
150      */
151     @Override
152     protected final B newBean() {
153         return null;
154     }
155 
156     /**
157      * <p>Getter for the field <code>rows</code>.</p>
158      *
159      * @return a {@link List} object.
160      */
161     public List<R> getRows() {
162         return rows;
163     }
164 
165     /**
166      * <p>Setter for the field <code>rows</code>.</p>
167      *
168      * @param rows a {@link List} object.
169      */
170     public void setRows(List<R> rows) {
171         if (rows == null) {
172             this.rows = Lists.newArrayList();
173         } else {
174             this.rows = Lists.newArrayList(rows);
175         }
176         onRowsChanged();
177     }
178 
179     /**
180      * <p>addRow.</p>
181      *
182      * @param row a R object.
183      */
184     public void addRow(R row) {
185         addRows(Collections.singletonList(row));
186     }
187 
188     /**
189      * <p>addRows.</p>
190      *
191      * @param rows a {@link List} object.
192      */
193     public void addRows(List<R> rows) {
194         if (CollectionUtils.isEmpty(rows)) {
195             return;
196         }
197         this.rows.addAll(rows);
198         firePropertyChange(PROPERTY_ROWS_ADDED, null, rows);
199         onRowsChanged();
200     }
201 
202     /**
203      * <p>insertRowAfterSelected.</p>
204      *
205      * @param row a R object.
206      */
207     public void insertRowAfterSelected(R row) {
208         if (row == null) {
209             return;
210         }
211         if (singleSelectedRow == null || rows.indexOf(singleSelectedRow) + 1 >= rows.size()) {
212             addRow(row);
213         } else {
214             rows.add(rows.indexOf(singleSelectedRow) + 1, row);
215             firePropertyChange(PROPERTY_ROWS_ADDED, null, Collections.singletonList(row));
216             onRowsChanged();
217         }
218 
219     }
220 
221     private void onRowsChanged() {
222         rowsInError.clear();
223         if (CollectionUtils.isNotEmpty(rows)) {
224             for (R row : rows) {
225                 if (!row.isValid()) {
226                     rowsInError.add(row);
227                 }
228             }
229         }
230         setRowsInError(rowsInError);
231 
232         // always propagates (since empty list will not fire and we want it)
233         firePropertyChange(PROPERTY_ROWS, null, rows);
234 
235         populateSelectedRows();
236     }
237 
238     /** {@inheritDoc} */
239     @Override
240     public void setBean(B bean) {
241         setBeans(Collections.singletonList(bean));
242     }
243 
244     /**
245      * <p>setBeans.</p>
246      *
247      * @param beans a {@link Collection} object.
248      */
249     public void setBeans(Collection<B> beans) {
250         setBeans(beans, null);
251     }
252 
253     /**
254      * <p>setBeans.</p>
255      *
256      * @param beans a {@link Collection} object.
257      * @param calculated a {@link Boolean} object.
258      */
259     public void setBeans(Collection<B> beans, Boolean calculated) {
260         List<R> newRows = convertBeans(beans, calculated);
261         setRows(newRows);
262         firePropertyChange(PROPERTY_BEANS_LOADED, null, rows);
263     }
264 
265     /**
266      * <p>addBean.</p>
267      *
268      * @param bean a B object.
269      */
270     public void addBean(B bean) {
271         addBeans(Collections.singletonList(bean));
272     }
273 
274     /**
275      * <p>addBean.</p>
276      *
277      * @param bean a B object.
278      * @param calculated a {@link Boolean} object.
279      */
280     public void addBean(B bean, Boolean calculated) {
281         addBeans(Collections.singletonList(bean), calculated);
282     }
283 
284     /**
285      * <p>addBeans.</p>
286      *
287      * @param beans a {@link List} object.
288      */
289     public void addBeans(List<B> beans) {
290         addBeans(beans, null);
291     }
292 
293     /**
294      * <p>addBeans.</p>
295      *
296      * @param beans a {@link List} object.
297      * @param calculated a {@link Boolean} object.
298      */
299     public void addBeans(List<B> beans, Boolean calculated) {
300         List<R> newRows = convertBeans(beans, calculated);
301         addRows(newRows);
302     }
303 
304     /**
305      * <p>addNewRow.</p>
306      *
307      * @return a R object.
308      */
309     public R addNewRow() {
310         return addNewRow(null);
311     }
312 
313     /**
314      * <p>addNewRow.</p>
315      *
316      * @param bean a B object.
317      * @return a R object.
318      */
319     public R addNewRow(B bean) {
320         R row = convertBean(bean, null);
321         addRow(row);
322         return row;
323     }
324 
325     /**
326      * <p>insertNewRowAfterSelected.</p>
327      *
328      * @return a R object.
329      */
330     public R insertNewRowAfterSelected() {
331         R row = convertBean(null, null);
332         insertRowAfterSelected(row);
333         return row;
334     }
335 
336     private List<R> convertBeans(Collection<B> beans, Boolean calculated) {
337         List<R> newRows = Lists.newArrayList();
338         if (CollectionUtils.isNotEmpty(beans)) {
339             for (B bean : beans) {
340                 R row = convertBean(bean, calculated);
341                 if (row != null) {
342                     newRows.add(row);
343                 }
344             }
345         }
346         return newRows;
347     }
348 
349     @SuppressWarnings("unchecked")
350     private R convertBean(B bean, Boolean calculated) {
351         R row = null;
352         if (getTableModel() != null) {
353             row = (R) getTableModel().createNewRow();
354         }
355         if (row != null) {
356             if (bean != null) {
357                 row.fromBean(bean);
358                 row.setValid(true);
359             } else {
360                 row.setValid(false);
361             }
362             if (calculated != null) {
363                 row.setCalculated(calculated);
364             }
365         } else {
366             if (LOG.isWarnEnabled()) {
367                 LOG.warn("convertBean can't create a new row! check the underlying table model and its createNewRow method");
368             }
369         }
370         return row;
371     }
372 
373     /**
374      * <p>getBeans.</p>
375      *
376      * @return a {@link List} object.
377      */
378     public List<B> getBeans() {
379         List<B> beans = Lists.newArrayList();
380         if (CollectionUtils.isNotEmpty(rows)) {
381             for (R row : rows) {
382                 beans.add(convertRow(row));
383             }
384         }
385         return beans;
386     }
387 
388     @SuppressWarnings("unchecked")
389     private B convertRow(R row) {
390         return (B) row.toBean();
391     }
392 
393     /**
394      * <p>getRowCount.</p>
395      *
396      * @return a int.
397      */
398     public int getRowCount() {
399         return rows.size();
400     }
401 
402     /**
403      * <p>Getter for the field <code>rowsInError</code>.</p>
404      *
405      * @return a {@link Set} object.
406      */
407     public Set<R> getRowsInError() {
408         return rowsInError;
409     }
410 
411     /**
412      * <p>Setter for the field <code>rowsInError</code>.</p>
413      *
414      * @param rowsInError a {@link Set} object.
415      */
416     public final void setRowsInError(Set<R> rowsInError) {
417         this.rowsInError = rowsInError;
418         firePropertyChange(PROPERTY_ROWS_IN_ERROR, null, null);
419     }
420 
421     /**
422      * <p>addRowInError.</p>
423      *
424      * @param row a R object.
425      */
426     public void addRowInError(R row) {
427         rowsInError.add(row);
428         firePropertyChange(PROPERTY_ROWS_IN_ERROR, null, null);
429     }
430 
431     /**
432      * <p>removeRowInError.</p>
433      *
434      * @param row a R object.
435      */
436     public void removeRowInError(R row) {
437         rowsInError.remove(row);
438         firePropertyChange(PROPERTY_ROWS_IN_ERROR, null, null);
439     }
440 
441     /**
442      * <p>hasNoRowInError.</p>
443      *
444      * @return a boolean.
445      */
446     public boolean hasNoRowInError() {
447         return CollectionUtils.isEmpty(rowsInError);
448     }
449 
450     /**
451      * <p>hasAllRowMandatoryValid.</p>
452      *
453      * @return a boolean.
454      */
455     public boolean hasAllRowMandatoryValid() {
456         for (R row: rows) {
457             if (!row.isMandatoryValid()) return false;
458         }
459         return true;
460     }
461 
462     /**
463      * <p>Getter for the field <code>selectedRows</code>.</p>
464      *
465      * @return a {@link Set} object.
466      */
467     public Set<R> getSelectedRows() {
468         return selectedRows;
469     }
470 
471     /**
472      * <p>getSelectedBeans.</p>
473      *
474      * @return a {@link List} object.
475      */
476     public List<B> getSelectedBeans() {
477         List<B> beans = Lists.newArrayList();
478         if (CollectionUtils.isNotEmpty(selectedRows)) {
479             for (R row : selectedRows) {
480                 beans.add(convertRow(row));
481             }
482         }
483         return beans;
484     }
485 
486     /**
487      * <p>populateSelectedRows.</p>
488      */
489     public void populateSelectedRows() {
490         int previousHashCode = selectedRows.hashCode();
491         selectedRows = Sets.newHashSet();
492         if (CollectionUtils.isNotEmpty(rows)) {
493             for (R row : rows) {
494                 if (row.isSelected()) {
495                     selectedRows.add(row);
496                 }
497             }
498         }
499         if (previousHashCode != selectedRows.hashCode()) {
500             firePropertyChange(PROPERTY_SELECTED_ROWS, null, selectedRows);
501         }
502     }
503 
504     /**
505      * <p>deleteRow.</p>
506      *
507      * @param rowToDelete a R object.
508      */
509     public void deleteRow(R rowToDelete) {
510         deleteRows(Collections.singletonList(rowToDelete));
511     }
512 
513     /**
514      * <p>deleteRows.</p>
515      *
516      * @param rowsToDelete a {@link List} object.
517      */
518     public void deleteRows(List<R> rowsToDelete) {
519         if (CollectionUtils.isNotEmpty(rows) && rowsToDelete != null) {
520             Set<R> rowSet = Sets.newHashSet(rowsToDelete);
521             rowsInError.removeAll(rowSet);
522             firePropertyChange(PROPERTY_ROWS_IN_ERROR, null, null);
523             rows.removeAll(rowSet);
524             firePropertyChange(PROPERTY_ROWS_DELETED, null, rowSet);
525             onRowsChanged();
526         }
527     }
528 
529     /**
530      * <p>deleteSelectedRows.</p>
531      */
532     public void deleteSelectedRows() {
533         if (CollectionUtils.isNotEmpty(rows)) {
534             rowsInError.removeAll(selectedRows);
535             firePropertyChange(PROPERTY_ROWS_IN_ERROR, null, null);
536             rows.removeAll(selectedRows);
537             firePropertyChange(PROPERTY_ROWS_DELETED, null, selectedRows);
538             onRowsChanged();
539         }
540     }
541 
542     /**
543      * <p>updateBean.</p>
544      *
545      * @param oldRow a R object.
546      * @param newRowBean a B object.
547      */
548     @SuppressWarnings("unchecked")
549     public void updateBean(R oldRow, B newRowBean) {
550         Preconditions.checkNotNull(oldRow);
551         Preconditions.checkState(rows.contains(oldRow));
552         Preconditions.checkNotNull(newRowBean);
553 
554         oldRow.fromBean(newRowBean);
555     }
556 
557     /**
558      * <p>Getter for the field <code>singleSelectedRow</code>.</p>
559      *
560      * @return a R object.
561      */
562     public R getSingleSelectedRow() {
563         return singleSelectedRow;
564     }
565 
566     /**
567      * <p>Setter for the field <code>singleSelectedRow</code>.</p>
568      *
569      * @param singleSelectedRow a R object.
570      */
571     public void setSingleSelectedRow(R singleSelectedRow) {
572         R oldValue = getSingleSelectedRow();
573         this.singleSelectedRow = singleSelectedRow;
574         firePropertyChange(PROPERTY_SINGLE_ROW_SELECTED, oldValue, singleSelectedRow);
575     }
576 
577 }