View Javadoc
1   package fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency;
2   
3   /*
4    * #%L
5    * Tutti :: UI
6    * %%
7    * Copyright (C) 2012 - 2014 Ifremer
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as
11   * published by the Free Software Foundation, either version 3 of the 
12   * License, or (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public 
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/gpl-3.0.html>.
22   * #L%
23   */
24  
25  import com.google.common.base.Preconditions;
26  import fr.ifremer.tutti.persistence.entities.data.CopyIndividualObservationMode;
27  import fr.ifremer.tutti.type.WeightUnit;
28  import fr.ifremer.tutti.util.Numbers;
29  import fr.ifremer.tutti.util.Weights;
30  import org.apache.commons.collections4.CollectionUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.jdesktop.swingx.table.TableColumnModelExt;
34  import org.nuiton.jaxx.application.swing.table.AbstractApplicationTableModel;
35  import org.nuiton.jaxx.application.swing.table.ColumnIdentifier;
36  
37  import java.beans.PropertyChangeListener;
38  import java.util.ArrayList;
39  import java.util.Collections;
40  import java.util.HashSet;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Optional;
44  import java.util.Set;
45  
46  import static org.nuiton.i18n.I18n.n;
47  
48  /**
49   * Model of the species frequency table.
50   *
51   * @author Tony Chemit - chemit@codelutin.com
52   * @since 0.2
53   */
54  public class SpeciesFrequencyTableModel extends AbstractApplicationTableModel<SpeciesFrequencyRowModel> {
55  
56      /** Logger. */
57      private static final Log log = LogFactory.getLog(SpeciesFrequencyTableModel.class);
58  
59      private static final long serialVersionUID = 1L;
60  
61      public static final ColumnIdentifier<SpeciesFrequencyRowModel> LENGTH_STEP = ColumnIdentifier.newId(
62              SpeciesFrequencyRowModel.PROPERTY_LENGTH_STEP,
63              n("tutti.editSpeciesFrequencies.table.header.lengthStep"),
64              n("tutti.editSpeciesFrequencies.table.header.lengthStep"));
65  
66      public static final ColumnIdentifier<SpeciesFrequencyRowModel> NUMBER = ColumnIdentifier.newId(
67              SpeciesFrequencyRowModel.PROPERTY_NUMBER,
68              n("tutti.editSpeciesFrequencies.table.header.number"),
69              n("tutti.editSpeciesFrequencies.table.header.number"));
70  
71      public static final ColumnIdentifier<SpeciesFrequencyRowModel> WEIGHT = ColumnIdentifier.newId(
72              SpeciesFrequencyRowModel.PROPERTY_WEIGHT,
73              n("tutti.editSpeciesFrequencies.table.header.weight"),
74              n("tutti.editSpeciesFrequencies.table.header.weight"));
75  
76      public static final ColumnIdentifier<SpeciesFrequencyRowModel> RTP_COMPUTED_WEIGHT = ColumnIdentifier.newId(
77              SpeciesFrequencyRowModel.PROPERTY_RTP_COMPUTED_WEIGHT,
78              n("tutti.editSpeciesFrequencies.table.header.rtpComputedWeight"),
79              n("tutti.editSpeciesFrequencies.table.header.rtpComputedWeight"));
80  
81      private final SpeciesFrequencyUIModel uiModel;
82  
83      private final SpeciesFrequencyUIModelCache modelCache;
84  
85      /**
86       * Weight unit.
87       *
88       * @since 2.5
89       */
90      protected final WeightUnit weightUnit;
91  
92      protected final WeightUnit individualObservationWeightUnit;
93  
94      protected transient PropertyChangeListener onLengthStepChangedListener;
95  
96      protected transient PropertyChangeListener onWeightChangedListener;
97  
98      protected transient PropertyChangeListener onNumberChangedListener;
99  
100     public SpeciesFrequencyTableModel(WeightUnit weightUnit,
101                                       WeightUnit individualObservationWeightUnit,
102                                       TableColumnModelExt columnModel,
103                                       SpeciesFrequencyUIModel uiModel) {
104         super(columnModel, true, true);
105         this.weightUnit = weightUnit;
106         this.individualObservationWeightUnit = individualObservationWeightUnit;
107         this.uiModel = uiModel;
108         this.modelCache = uiModel.getCache();
109         setNoneEditableCols(RTP_COMPUTED_WEIGHT);
110     }
111 
112     @Override
113     public boolean isCreateNewRow() {
114         return uiModel.isCopyIndividualObservationNothing() && super.isCreateNewRow();
115     }
116 
117     @Override
118     public SpeciesFrequencyRowModel createNewRow() {
119         return createNewRow(true, true);
120     }
121 
122     public SpeciesFrequencyRowModel createNewRow(boolean useDefaultStep, boolean attachListeners) {
123         Float defaultStep = null;
124 
125         int rowCount = getRowCount();
126         if (useDefaultStep && rowCount > 0) {
127 
128             SpeciesFrequencyRowModel rowModel = getEntry(rowCount - 1);
129             Float lengthStep = rowModel.getLengthStep();
130             if (lengthStep != null) {
131                 defaultStep = uiModel.getLengthStep(lengthStep + uiModel.getStep());
132             }
133         }
134         SpeciesFrequencyRowModel result = new SpeciesFrequencyRowModel(weightUnit);
135 
136         if (attachListeners) {
137             attachListeners(result);
138         }
139 
140         result.setLengthStepCaracteristic(uiModel.getLengthStepCaracteristic());
141         result.setLengthStep(defaultStep);
142         result.setValid(defaultStep != null);
143 
144         return result;
145     }
146 
147     @Override
148     protected boolean isCellEditable(int rowIndex, int columnIndex, ColumnIdentifier<SpeciesFrequencyRowModel> propertyName) {
149         boolean result;
150 
151         CopyIndividualObservationMode copyIndividualObservationMode = uiModel.getCopyIndividualObservationMode();
152 
153         result = super.isCellEditable(rowIndex, columnIndex, propertyName);
154 
155         if (result && copyIndividualObservationMode != null) {
156             switch (copyIndividualObservationMode) {
157                 case ALL:
158                     result = false;
159                     break;
160                 case NOTHING:
161                     result = !(WEIGHT.equals(propertyName) && uiModel.isCopyRtpWeights());
162                     break;
163                 case SIZE:
164                     result = WEIGHT.equals(propertyName) && !uiModel.isCopyRtpWeights();
165                     break;
166             }
167         }
168         return result;
169     }
170 
171     @Override
172     public void setValueAt(Object aValue,
173                            int rowIndex,
174                            int columnIndex,
175                            ColumnIdentifier<SpeciesFrequencyRowModel> propertyName,
176                            SpeciesFrequencyRowModel entry) {
177         super.setValueAt(aValue, rowIndex, columnIndex, propertyName, entry);
178         uiModel.setModify(true);
179         // TODO Rebuild the computedWeight if possible...
180     }
181 
182     @Override
183     protected void onRowAdded(int rowIndex, SpeciesFrequencyRowModel row) {
184 
185         uiModel.recomputeCanEditLengthStep();
186         uiModel.setModify(true);
187 
188     }
189 
190     @Override
191     protected void onRowUpdated(int rowIndex, SpeciesFrequencyRowModel row) {
192 
193         uiModel.recomputeCanEditLengthStep();
194         uiModel.setModify(true);
195 
196     }
197 
198     @Override
199     protected void onRowRemoved(int rowIndex, SpeciesFrequencyRowModel row) {
200 
201         dettachListeners(row);
202         uiModel.recomputeCanEditLengthStep();
203 
204     }
205 
206     @Override
207     protected void onBeforeRowsChanged(List<SpeciesFrequencyRowModel> oldRows) {
208 
209         if (oldRows != null) {
210             oldRows.forEach(this::dettachListeners);
211         }
212 
213     }
214 
215     @Override
216     protected void onRowsChanged(List<SpeciesFrequencyRowModel> newRows) {
217 
218         if (newRows != null) {
219             newRows.forEach(this::attachListeners);
220         }
221 
222         uiModel.recomputeCanEditLengthStep();
223 
224     }
225 
226     public boolean recomputeCanEditLengthStep() {
227         boolean result = true;
228         if (rows != null) {
229             for (SpeciesFrequencyRowModel row : rows) {
230 
231                 if (row.isEmpty()) {
232                     // la ligne est vide
233                     continue;
234                 }
235                 if (row.getLengthStep() == null || row.getNumber() == null) {
236                     // la ligne n'est pas complete
237                     continue;
238                 }
239 
240                 // une ligne non vide et complete a ete trouvee
241                 // on ne peut plus editer
242                 result = false;
243                 break;
244             }
245         }
246         return result;
247     }
248 
249     public void decrementNumberForLengthStep(Float lengthStep) {
250 
251         SpeciesFrequencyRowModel row = modelCache.getRowCache().get(lengthStep);
252 
253         if (row != null) {
254 
255             Integer number = row.getNumber();
256 
257             if (number != null) {
258 
259                 if (number > 1) {
260                     row.setNumber(number - 1);
261                 } else {
262                     row.setNumber(null);
263                 }
264 
265                 int rowIndex = getRowIndex(row);
266 
267                 fireTableRowsUpdated(rowIndex, rowIndex);
268 
269             }
270 
271             uiModel.recomputeCanEditLengthStep();
272 
273         }
274 
275     }
276 
277     public SpeciesFrequencyRowModel getOrCreateRowForLengthStep(float lengthstep) {
278 
279         // obtenir la classe de taille (en fonction de la précision de la méthode de mensuration)
280         float realLengthStep = uiModel.getLengthStep(lengthstep);
281 
282         Map<Float, SpeciesFrequencyRowModel> rowCache = modelCache.getRowCache();
283 
284         SpeciesFrequencyRowModel row = rowCache.get(realLengthStep);
285 
286         if (row == null) {
287             row = createNewRow(false, true);
288             row.setLengthStep(realLengthStep);
289             rowCache.put(realLengthStep, row);
290 
291             // get new index
292             List<Float> steps = new ArrayList<>(rowCache.keySet());
293             steps.add(realLengthStep);
294             Collections.sort(steps);
295 
296             int indexToInsert = steps.indexOf(realLengthStep);
297             addNewRow(indexToInsert, row);
298 
299         }
300 
301         return row;
302 
303     }
304 
305     public Optional<SpeciesFrequencyRowModel> getOptionalRowForLengthStep(float lengthstep) {
306 
307         // obtenir la classe de taille (en fonction de la précision de la méthode de mensuration)
308         float realLengthStep = uiModel.getLengthStep(lengthstep);
309 
310         Map<Float, SpeciesFrequencyRowModel> rowCache = modelCache.getRowCache();
311 
312         SpeciesFrequencyRowModel row = rowCache.get(realLengthStep);
313         return Optional.ofNullable(row);
314 
315     }
316 
317     public void incrementFrequencyRowsNumbers(float lengthStepToIncrement) {
318 
319         if (log.isDebugEnabled()) {
320             log.debug("incrementFrequencyRowsNumbers" + lengthStepToIncrement);
321         }
322 
323         SpeciesFrequencyRowModel row = getOrCreateRowForLengthStep(lengthStepToIncrement);
324 
325         row.incNumber();
326         updateRow(row);
327 
328     }
329 
330     public void decrementFrequencyRowsNumbers(float lengthStepToDecrement) {
331 
332         if (log.isDebugEnabled()) {
333             log.debug("decrementFrequencyRowsNumbers " + lengthStepToDecrement);
334         }
335 
336         Optional<SpeciesFrequencyRowModel> optionalRow = getOptionalRowForLengthStep(lengthStepToDecrement);
337 
338         if (optionalRow.isPresent()) {
339 
340             SpeciesFrequencyRowModel row = optionalRow.get();
341             row.decNumber();
342 
343             if (!row.withNumber()) {
344 
345                 // Plus d'occurrence de cette classe de taille, on la supprime
346                 if (log.isInfoEnabled()) {
347                     log.info("Remove length class " + row + " from frequencies.");
348                 }
349 
350                 removeRow(row);
351 
352             } else {
353                 updateRow(row);
354             }
355 
356         }
357 
358     }
359 
360     public Float convertWeightFromIndividualObservation(float weight) {
361         return Weights.convert(uiModel.getIndividualObservationWeightUnit(), weightUnit, weight);
362     }
363 
364     public void addWeightToFrequencyRow(float lengthStep, float weight) {
365 
366         if (log.isDebugEnabled()) {
367             log.debug("add weight to frequency (lengthStep: " + lengthStep + "): " + weight);
368         }
369 
370         Preconditions.checkState(!weightUnit.isSmallerThanZero(weight));
371 
372         SpeciesFrequencyRowModel row = getOrCreateRowForLengthStep(lengthStep);
373 
374         row.addToWeight(weight);
375 
376         updateRow(row);
377 
378     }
379 
380     public void removeWeightToFrequencyRow(float lengthStep, float weight) {
381 
382         if (log.isDebugEnabled()) {
383             log.debug("remove weight to frequency (lengthStep: " + lengthStep + "): " + weight);
384         }
385 
386         Preconditions.checkState(!weightUnit.isSmallerThanZero(weight));
387 
388         Optional<SpeciesFrequencyRowModel> optionalRow = getOptionalRowForLengthStep(lengthStep);
389 
390         if (optionalRow.isPresent()) {
391 
392             SpeciesFrequencyRowModel row = optionalRow.get();
393 
394             row.removeFromWeight(weight);
395 
396             updateRow(row);
397 
398         }
399 
400     }
401 
402     public List<SpeciesFrequencyRowModel> loadRows(List<SpeciesFrequencyRowModel> incomingRows) {
403 
404         List<SpeciesFrequencyRowModel> result = new ArrayList<>();
405 
406         //FIXME Faire un check sur la méthode de mensuration qui doit être la même
407         if (CollectionUtils.isNotEmpty(incomingRows)) {
408 
409             for (SpeciesFrequencyRowModel rowModel : incomingRows) {
410 
411                 SpeciesFrequencyRowModel newRow = createNewRow(false, false);
412                 newRow.copy(rowModel);
413                 result.add(newRow);
414 
415             }
416 
417         }
418 
419         // always sort row by their length
420         // see http://forge.codelutin.com/issues/2482
421         Collections.sort(result);
422 
423         return result;
424 
425     }
426 
427     public SpeciesFrequencyRowModel addRafaleRow(float lengthStep) {
428 
429         Map<Float, SpeciesFrequencyRowModel> rowsByStep = modelCache.getRowCache();
430 
431         SpeciesFrequencyRowModel row = rowsByStep.get(lengthStep);
432 
433         int rowIndex;
434 
435         if (row != null) {
436 
437             // increments current row
438             Integer number = row.getNumber();
439             row.setNumber((number == null ? 0 : number) + 1);
440             updateRow(row);
441 
442         } else {
443 
444             // create a new row
445 
446             row = createNewRow();
447             row.setLengthStep(lengthStep);
448             row.setNumber(1);
449             row.setValid(uiModel.isRowValid(row));
450 
451             // get new index
452             List<Float> steps = new ArrayList<>(rowsByStep.keySet());
453             steps.add(lengthStep);
454 
455             Collections.sort(steps);
456 
457             rowIndex = steps.indexOf(lengthStep);
458 
459             addNewRow(rowIndex, row);
460         }
461 
462         return row;
463 
464     }
465 
466     public void generateRows() {
467 
468         float minStep = uiModel.getLengthStep(uiModel.getMinStep());
469         float maxStep = uiModel.getLengthStep(uiModel.getMaxStep());
470 
471         Map<Float, SpeciesFrequencyRowModel> rowsByStep = modelCache.getRowCache();
472         Set<Float> existingKeys = new HashSet<>(rowsByStep.keySet());
473         List<SpeciesFrequencyRowModel> rows = new ArrayList<>(rowsByStep.values());
474 
475         for (float lengthClass = minStep, step = uiModel.getStep(); lengthClass <= maxStep; lengthClass = Numbers.getRoundedLengthStep(lengthClass + step, true)) {
476 
477             if (!existingKeys.contains(lengthClass)) {
478 
479                 // add it
480                 SpeciesFrequencyRowModel newRow = createNewRow(false, true);
481                 newRow.setLengthStep(lengthClass);
482                 rows.add(newRow);
483 
484             }
485 
486         }
487 
488         Collections.sort(rows);
489         uiModel.setRows(rows);
490 
491     }
492 
493     public void reloadRowsFromIndividualObservations() {
494 
495         if (uiModel.mustCopyIndividualObservationSize()) {
496 
497             if (log.isInfoEnabled()) {
498                 log.info("Generate frequencies from individual observations (mode: " + uiModel.getCopyIndividualObservationMode() + ")");
499             }
500 
501             boolean copyWeights = uiModel.isCopyIndividualObservationAll();
502 
503             rows.clear();
504 
505             uiModel.getValidIndividualObservations().stream()
506                    .filter(IndividualObservationBatchRowModel::withSize)
507                    .forEachOrdered(individualObservation -> {
508 
509                        SpeciesFrequencyRowModel row = getOrCreateRowForLengthStep(individualObservation.getSize());
510                        row.incNumber();
511 
512                        if (copyWeights && individualObservation.withWeight()) {
513                            Float weight = convertWeightFromIndividualObservation(individualObservation.getWeight());
514                            row.addToWeight(weight);
515                        }
516 
517                    });
518 
519         } else {
520             if (rows.isEmpty()) {
521                 addNewRow();
522             }
523         }
524 
525         Collections.sort(rows);
526 
527         uiModel.reloadRows();
528 
529         fireTableDataChanged();
530 
531     }
532 
533 
534     private void lengthStepWasRemoved(Float lengthStep) {
535 
536         uiModel.getAverageWeightsHistogramModel().removeValue(lengthStep);
537         uiModel.getFrequenciesHistogramModel().removeValue(lengthStep);
538 
539     }
540 
541     private void lengthStepOrNumberWasUpdated(SpeciesFrequencyRowModel row) {
542 
543         if (row.withNumber()) {
544 
545             uiModel.getAverageWeightsHistogramModel().addOrUpdate(row);
546             uiModel.getFrequenciesHistogramModel().addOrUpdate(row);
547 
548         }
549 
550     }
551 
552     private PropertyChangeListener getOnLengthStepChangedListener() {
553         if (onLengthStepChangedListener == null) {
554             onLengthStepChangedListener = evt -> {
555 
556                 SpeciesFrequencyRowModel row = (SpeciesFrequencyRowModel) evt.getSource();
557 
558                 // recompute the weight with the rtp
559                 uiModel.computeRowWeightWithRtp(row);
560 
561                 Float oldValue = (Float) evt.getOldValue();
562                 if (oldValue != null) {
563 
564                     modelCache.removeLengthStep(oldValue);
565                     lengthStepWasRemoved(oldValue);
566 
567                 }
568 
569                 Float newValue = (Float) evt.getNewValue();
570                 if (newValue != null) {
571 
572                     modelCache.addLengthStep(row);
573                     lengthStepOrNumberWasUpdated(row);
574 
575                 }
576 
577                 uiModel.recomputeCanEditLengthStep();
578                 uiModel.recomputeRowsValidateState();
579                 uiModel.updateEmptyRow(row);
580 
581                 // Can recompute total number and weight only after valid flag change
582                 uiModel.recomputeTotalNumber();
583                 uiModel.recomputeTotalWeight();
584 
585                 //FIXME Explain why this ?
586 //                fireTableDataChanged();
587 
588             };
589         }
590         return onLengthStepChangedListener;
591     }
592 
593     private PropertyChangeListener getOnNumberChangedListener() {
594         if (onNumberChangedListener == null) {
595             onNumberChangedListener = evt -> {
596 
597                 SpeciesFrequencyRowModel row = (SpeciesFrequencyRowModel) evt.getSource();
598 
599                 // recompute the weight with the rtp
600                 uiModel.computeRowWeightWithRtp(row);
601 
602                 Float lengthStep = row.getLengthStep();
603 
604                 if (lengthStep != null) {
605 
606                     if (!row.withNumber()) {
607 
608                         // remove the value for the lengthStep
609                         lengthStepWasRemoved(lengthStep);
610 
611                     } else {
612 
613                         lengthStepOrNumberWasUpdated(row);
614 
615                     }
616 
617                 }
618 
619                 uiModel.recomputeCanEditLengthStep();
620                 uiModel.recomputeRowValidState(row);
621                 uiModel.updateEmptyRow(row);
622 
623                 // Can recompute total number and weight only after valid flag change
624                 uiModel.recomputeTotalNumber();
625                 uiModel.recomputeTotalWeight();
626 
627                 //FIXME Explain why this ?
628 //                fireTableDataChanged();
629 
630             };
631         }
632         return onNumberChangedListener;
633     }
634 
635     private PropertyChangeListener getOnWeightChangedListener() {
636         if (onWeightChangedListener == null) {
637             onWeightChangedListener = evt -> {
638 
639                 SpeciesFrequencyRowModel row = (SpeciesFrequencyRowModel) evt.getSource();
640                 modelCache.updateRowWithWeight(row);
641 
642                 Float lengthStep = row.getLengthStep();
643 
644                 if (lengthStep != null) {
645 
646                     if (!row.withWeight()) {
647 
648                         // remove the value for the lengthStep
649                         uiModel.getAverageWeightsHistogramModel().removeValue(lengthStep);
650 
651                     } else {
652 
653                         uiModel.getAverageWeightsHistogramModel().addOrUpdate(row);
654 
655                     }
656 
657                 }
658 
659                 uiModel.recomputeRowsValidateState();
660                 uiModel.updateEmptyRow(row);
661 
662                 // Can recompute total number and weight only after valid flag change
663                 uiModel.recomputeTotalWeight();
664                 uiModel.recomputeTotalNumber();
665 
666             };
667         }
668         return onWeightChangedListener;
669     }
670 
671     private void dettachListeners(SpeciesFrequencyRowModel result) {
672 
673         result.removePropertyChangeListener(SpeciesFrequencyRowModel.PROPERTY_LENGTH_STEP, getOnLengthStepChangedListener());
674         result.removePropertyChangeListener(SpeciesFrequencyRowModel.PROPERTY_WEIGHT, getOnWeightChangedListener());
675         result.removePropertyChangeListener(SpeciesFrequencyRowModel.PROPERTY_NUMBER, getOnNumberChangedListener());
676 
677     }
678 
679     private void attachListeners(SpeciesFrequencyRowModel result) {
680 
681         dettachListeners(result); // prevent leaks!
682 
683         result.addPropertyChangeListener(SpeciesFrequencyRowModel.PROPERTY_LENGTH_STEP, getOnLengthStepChangedListener());
684         result.addPropertyChangeListener(SpeciesFrequencyRowModel.PROPERTY_WEIGHT, getOnWeightChangedListener());
685         result.addPropertyChangeListener(SpeciesFrequencyRowModel.PROPERTY_NUMBER, getOnNumberChangedListener());
686 
687     }
688 
689     private void removeRow(SpeciesFrequencyRowModel row) {
690 
691         int rowIndex = getRowIndex(row);
692         removeRow(rowIndex);
693         modelCache.getRowCache().remove(row.getLengthStep());
694 
695     }
696 
697 }