1 package fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
50
51
52
53
54 public class SpeciesFrequencyTableModel extends AbstractApplicationTableModel<SpeciesFrequencyRowModel> {
55
56
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
87
88
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
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
233 continue;
234 }
235 if (row.getLengthStep() == null || row.getNumber() == null) {
236
237 continue;
238 }
239
240
241
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
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
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
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
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
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
420
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
438 Integer number = row.getNumber();
439 row.setNumber((number == null ? 0 : number) + 1);
440 updateRow(row);
441
442 } else {
443
444
445
446 row = createNewRow();
447 row.setLengthStep(lengthStep);
448 row.setNumber(1);
449 row.setValid(uiModel.isRowValid(row));
450
451
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
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
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
582 uiModel.recomputeTotalNumber();
583 uiModel.recomputeTotalWeight();
584
585
586
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
600 uiModel.computeRowWeightWithRtp(row);
601
602 Float lengthStep = row.getLengthStep();
603
604 if (lengthStep != null) {
605
606 if (!row.withNumber()) {
607
608
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
624 uiModel.recomputeTotalNumber();
625 uiModel.recomputeTotalWeight();
626
627
628
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
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
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);
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 }