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