1 package fr.ifremer.reefdb.ui.swing.content.observation.shared;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import com.google.common.base.Joiner;
27 import com.google.common.collect.ArrayListMultimap;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.Maps;
30 import com.google.common.collect.Multimap;
31 import fr.ifremer.quadrige3.core.dao.technical.Assert;
32 import fr.ifremer.quadrige3.core.dao.technical.factorization.pmfm.AllowedQualitativeValuesMap;
33 import fr.ifremer.quadrige3.ui.core.dto.DirtyAware;
34 import fr.ifremer.quadrige3.ui.swing.table.editor.ExtendedComboBoxCellEditor;
35 import fr.ifremer.reefdb.decorator.DecoratorService;
36 import fr.ifremer.reefdb.dto.ErrorDTO;
37 import fr.ifremer.reefdb.dto.ReefDbBeanFactory;
38 import fr.ifremer.reefdb.dto.ReefDbBeans;
39 import fr.ifremer.reefdb.dto.configuration.control.ControlRuleDTO;
40 import fr.ifremer.reefdb.dto.configuration.control.PreconditionRuleDTO;
41 import fr.ifremer.reefdb.dto.configuration.control.RuleGroupDTO;
42 import fr.ifremer.reefdb.dto.configuration.control.RulePmfmDTO;
43 import fr.ifremer.reefdb.dto.data.measurement.MeasurementAware;
44 import fr.ifremer.reefdb.dto.data.measurement.MeasurementDTO;
45 import fr.ifremer.reefdb.dto.enums.ControlElementValues;
46 import fr.ifremer.reefdb.dto.enums.ControlFeatureMeasurementValues;
47 import fr.ifremer.reefdb.dto.enums.FilterTypeValues;
48 import fr.ifremer.reefdb.dto.referential.DepartmentDTO;
49 import fr.ifremer.reefdb.dto.referential.TaxonDTO;
50 import fr.ifremer.reefdb.dto.referential.TaxonGroupDTO;
51 import fr.ifremer.reefdb.dto.referential.pmfm.PmfmDTO;
52 import fr.ifremer.reefdb.dto.referential.pmfm.QualitativeValueDTO;
53 import fr.ifremer.reefdb.service.ReefDbTechnicalException;
54 import fr.ifremer.reefdb.ui.swing.content.observation.operation.measurement.grouped.OperationMeasurementsGroupedRowModel;
55 import fr.ifremer.reefdb.ui.swing.util.ReefDbUI;
56 import fr.ifremer.reefdb.ui.swing.util.table.AbstractReefDbTableUIHandler;
57 import fr.ifremer.reefdb.ui.swing.util.table.PmfmTableColumn;
58 import fr.ifremer.reefdb.ui.swing.util.table.ReefDbColumnIdentifier;
59 import fr.ifremer.reefdb.ui.swing.util.table.ReefDbPmfmColumnIdentifier;
60 import org.apache.commons.collections4.CollectionUtils;
61 import org.apache.commons.lang3.StringUtils;
62 import org.apache.commons.logging.Log;
63 import org.apache.commons.logging.LogFactory;
64
65 import javax.swing.JComponent;
66 import javax.swing.JDialog;
67 import javax.swing.JScrollPane;
68 import javax.swing.SwingUtilities;
69 import java.awt.Dimension;
70 import java.awt.Insets;
71 import java.util.*;
72 import java.util.concurrent.atomic.AtomicInteger;
73 import java.util.stream.Collectors;
74
75 import static org.nuiton.i18n.I18n.t;
76
77
78
79
80 public abstract class AbstractMeasurementsGroupedTableUIHandler<
81 R extends AbstractMeasurementsGroupedRowModel<MeasurementDTO, R>,
82 M extends AbstractMeasurementsGroupedTableUIModel<MeasurementDTO, R, M>,
83 UI extends ReefDbUI<M, ?>>
84 extends AbstractReefDbTableUIHandler<R, M, UI> {
85
86 private static final Log LOG = LogFactory.getLog(AbstractMeasurementsGroupedTableUIHandler.class);
87
88
89 protected ExtendedComboBoxCellEditor<TaxonGroupDTO> taxonGroupCellEditor;
90
91 protected ExtendedComboBoxCellEditor<TaxonDTO> taxonCellEditor;
92
93 protected ExtendedComboBoxCellEditor<DepartmentDTO> departmentCellEditor;
94
95 public AbstractMeasurementsGroupedTableUIHandler(String... properties) {
96 super(properties);
97 }
98
99 @Override
100 protected String[] getRowPropertiesToIgnore() {
101 return new String[]{
102 AbstractMeasurementsGroupedRowModel.PROPERTY_INPUT_TAXON_ID,
103 AbstractMeasurementsGroupedRowModel.PROPERTY_INPUT_TAXON_NAME
104 };
105 }
106
107 @Override
108 public void beforeInit(UI ui) {
109 super.beforeInit(ui);
110 ui.setContextValue(createNewModel());
111 }
112
113 protected abstract M createNewModel();
114
115 protected abstract void initTable();
116
117 @Override
118 public void afterInit(UI ui) {
119
120 initUI(ui);
121
122 createTaxonGroupCellEditor();
123 createTaxonCellEditor();
124 createDepartmentCellEditor();
125
126 initTable();
127
128 initListeners();
129 }
130
131 @Override
132 public abstract AbstractMeasurementsGroupedTableModel<R> getTableModel();
133
134 private void createTaxonCellEditor() {
135
136
137 taxonCellEditor = newExtendedComboBoxCellEditor(null, TaxonDTO.class, DecoratorService.WITH_CITATION_AND_REFERENT, false);
138
139 taxonCellEditor.setAction("unfilter-taxon", "reefdb.common.unfilter.taxon", e -> {
140
141 updateTaxonCellEditor(getModel().getSingleSelectedRow(), true);
142 });
143
144 }
145
146 private void updateTaxonCellEditor(R row, boolean forceNoFilter) {
147
148
149 taxonCellEditor.getCombo().setActionEnabled(!forceNoFilter );
150
151
152 List<TaxonDTO> taxons = getModel().getObservationUIHandler().getAvailableTaxons(row == null || forceNoFilter ? null : row.getTaxonGroup(), false);
153 taxonCellEditor.getCombo().setData(taxons);
154
155
156
157
158
159
160
161
162
163 }
164
165 private void createTaxonGroupCellEditor() {
166
167 taxonGroupCellEditor = newExtendedComboBoxCellEditor(null, TaxonGroupDTO.class, false);
168
169 taxonGroupCellEditor.setAction("unfilter-taxon", "reefdb.common.unfilter.taxon", e -> {
170
171 updateTaxonGroupCellEditor(getModel().getSingleSelectedRow(), true);
172 });
173
174 }
175
176 private void updateTaxonGroupCellEditor(R row, boolean forceNoFilter) {
177
178
179 taxonGroupCellEditor.getCombo().setActionEnabled(!forceNoFilter );
180
181
182 List<TaxonGroupDTO> taxonGroups = getModel().getObservationUIHandler().getAvailableTaxonGroups(row == null || forceNoFilter ? null : row.getTaxon(), false);
183
184 taxonGroupCellEditor.getCombo().setData(taxonGroups);
185
186
187
188
189
190
191
192
193
194 }
195
196 private void createDepartmentCellEditor() {
197
198 departmentCellEditor = newExtendedComboBoxCellEditor(null, DepartmentDTO.class, false);
199
200 departmentCellEditor.setAction("unfilter", "reefdb.common.unfilter", e -> {
201 if (!askBefore(t("reefdb.common.unfilter"), t("reefdb.common.unfilter.confirmation"))) {
202 return;
203 }
204
205 updateDepartmentCellEditor(true);
206 });
207
208 }
209
210 private void updateDepartmentCellEditor(boolean forceNoFilter) {
211
212 departmentCellEditor.getCombo().setActionEnabled(!forceNoFilter
213 && getContext().getDataContext().isContextFiltered(FilterTypeValues.DEPARTMENT));
214
215 departmentCellEditor.getCombo().setData(getContext().getObservationService().getAvailableDepartments(forceNoFilter));
216 }
217
218 protected void resetCellEditors() {
219
220 updateTaxonGroupCellEditor(null, false);
221 updateTaxonCellEditor(null, false);
222 updateDepartmentCellEditor(false);
223 }
224
225 protected void initListeners() {
226
227 getModel().addPropertyChangeListener(evt -> {
228
229 switch (evt.getPropertyName()) {
230
231 case AbstractMeasurementsGroupedTableUIModel.PROPERTY_SURVEY:
232
233 loadMeasurements();
234 break;
235
236 case AbstractMeasurementsGroupedTableUIModel.EVENT_INDIVIDUAL_MEASUREMENTS_LOADED:
237
238 detectPreconditionedPmfms();
239 detectGroupedIdentifiers();
240 break;
241
242 case AbstractMeasurementsGroupedTableUIModel.PROPERTY_MEASUREMENT_FILTER:
243
244 filterMeasurements();
245 break;
246
247 case AbstractMeasurementsGroupedTableUIModel.PROPERTY_SINGLE_ROW_SELECTED:
248
249 if (getModel().getSingleSelectedRow() != null && !getModel().getSingleSelectedRow().isEditable()) {
250 return;
251 }
252
253
254 updateTaxonCellEditor(getModel().getSingleSelectedRow(), false);
255 updateTaxonGroupCellEditor(getModel().getSingleSelectedRow(), false);
256 updateDepartmentCellEditor(false);
257 updatePmfmCellEditors(getModel().getSingleSelectedRow(), null, false);
258 break;
259
260 }
261 });
262 }
263
264 protected abstract void filterMeasurements();
265
266
267
268
269 private void loadMeasurements() {
270
271 SwingUtilities.invokeLater(() -> {
272
273
274 uninstallSaveTableStateListener();
275
276
277 resetCellEditors();
278
279
280
281
282 ReefDbColumnIdentifier<R> insertPosition = getTableModel().getPmfmInsertPosition();
283 addPmfmColumns(
284 getModel().getPmfms(),
285 AbstractMeasurementsGroupedRowModel.PROPERTY_INDIVIDUAL_PMFMS,
286 DecoratorService.NAME_WITH_UNIT,
287 insertPosition);
288
289 boolean notEmpty = CollectionUtils.isNotEmpty(getModel().getPmfms());
290
291
292 getModel().setRows(buildRows(!getModel().getSurvey().isEditable()));
293
294 recomputeRowsValidState();
295
296
297 filterMeasurements();
298
299
300 restoreTableState();
301
302
303
304
305 forceColumnVisible(AbstractMeasurementsGroupedTableModel.ANALYST, notEmpty);
306
307
308 ensureColumnsWithErrorAreVisible(getModel().getRows());
309
310
311 installSaveTableStateListener();
312
313 getModel().fireMeasurementsLoaded();
314 });
315
316 }
317
318 protected List<R> buildRows(boolean readOnly) {
319
320 List<R> rows = new ArrayList<>();
321
322 List<? extends MeasurementAware> measurementAwareBeans = getMeasurementAwareModels();
323
324 for (MeasurementAware bean : measurementAwareBeans) {
325
326 List<MeasurementDTO> measurements = bean.getIndividualMeasurements().stream()
327
328 .sorted(Comparator.comparingInt(MeasurementDTO::getIndividualId))
329 .collect(Collectors.toList());
330
331 R row = null;
332 for (final MeasurementDTO measurement : measurements) {
333
334 if (row != null) {
335
336
337 if (!row.getIndividualId().equals(measurement.getIndividualId())) {
338 row = null;
339 }
340
341
342 else if (!Objects.equals(row.getTaxonGroup(), measurement.getTaxonGroup())
343 || !Objects.equals(row.getTaxon(), measurement.getTaxon())
344 || !Objects.equals(row.getInputTaxonId(), measurement.getInputTaxonId())
345 || !Objects.equals(row.getInputTaxonName(), measurement.getInputTaxonName())) {
346
347
348 if (row.getTaxonGroup() == null) {
349 row.setTaxonGroup(measurement.getTaxonGroup());
350 } else if (measurement.getTaxonGroup() != null && !row.getTaxonGroup().equals(measurement.getTaxonGroup())) {
351
352 LOG.error(String.format("taxon group in measurement (id=%s) differs with taxon group in previous measurements with same individual id (=%s) !",
353 measurement.getId(), measurement.getIndividualId()));
354 }
355 if (row.getTaxon() == null) {
356 row.setTaxon(measurement.getTaxon());
357 } else if (measurement.getTaxon() != null && !row.getTaxon().equals(measurement.getTaxon())) {
358
359 LOG.error(String.format("taxon in measurement (id=%s) differs with taxon in previous measurements with same individual id (=%s) !",
360 measurement.getId(), measurement.getIndividualId()));
361 }
362 if (row.getInputTaxonId() == null) {
363 row.setInputTaxonId(measurement.getInputTaxonId());
364 } else if (measurement.getInputTaxonId() != null && !row.getInputTaxonId().equals(measurement.getInputTaxonId())) {
365
366 LOG.error(String.format("input taxon id in measurement (id=%s) differs with input taxon id in previous measurements with same individual id (=%s) !",
367 measurement.getId(), measurement.getIndividualId()));
368 }
369 if (StringUtils.isBlank(row.getInputTaxonName())) {
370 row.setInputTaxonName(measurement.getInputTaxonName());
371 } else if (measurement.getInputTaxonName() != null && !row.getInputTaxonName().equals(measurement.getInputTaxonName())) {
372
373 LOG.error(String.format("input taxon name in measurement (id=%s) differs with input taxon name in previous measurements with same individual id (=%s) !",
374 measurement.getId(), measurement.getIndividualId()));
375 }
376 }
377 }
378
379
380 if (row == null) {
381 row = createNewRow(readOnly, bean);
382 row.setIndividualPmfms(new ArrayList<>(getModel().getPmfms()));
383 row.setIndividualId(measurement.getIndividualId());
384 row.setTaxonGroup(measurement.getTaxonGroup());
385 row.setTaxon(measurement.getTaxon());
386 row.setInputTaxonId(measurement.getInputTaxonId());
387 row.setInputTaxonName(measurement.getInputTaxonName());
388 row.setAnalyst(measurement.getAnalyst());
389
390
391
392 row.setValid(true);
393
394 rows.add(row);
395
396
397 List<ErrorDTO> errors = ReefDbBeans.filterCollection(bean.getErrors(),
398 input -> input != null
399 && ControlElementValues.MEASUREMENT.equals(input.getControlElementCode())
400 && Objects.equals(input.getIndividualId(), measurement.getIndividualId())
401 );
402 ReefDbBeans.addUniqueErrors(row, errors);
403 }
404
405
406 row.getIndividualMeasurements().add(measurement);
407
408
409 ReefDbBeans.addUniqueErrors(row, measurement.getErrors());
410
411 }
412
413 }
414
415
416 for (R row : rows) {
417
418
419 row.setComment(ReefDbBeans.getUnifiedCommentFromIndividualMeasurements(row));
420 }
421
422 return rows;
423 }
424
425 protected abstract List<? extends MeasurementAware> getMeasurementAwareModels();
426
427 protected abstract MeasurementAware getMeasurementAwareModelForRow(R row);
428
429 protected abstract R createNewRow(boolean readOnly, MeasurementAware parentBean);
430
431 @Override
432 protected void onRowsAdded(List<R> addedRows) {
433 super.onRowsAdded(addedRows);
434
435 if (addedRows.size() == 1) {
436 R newRow = addedRows.get(0);
437
438
439 if (CollectionUtils.isEmpty(newRow.getIndividualPmfms()) && CollectionUtils.isEmpty(newRow.getIndividualMeasurements())) {
440
441
442 newRow.setIndividualPmfms(new ArrayList<>(getModel().getPmfms()));
443
444
445 ReefDbBeans.createEmptyMeasurements(newRow);
446
447
448 initAddedRow(newRow);
449 }
450
451
452 setDefaultAnalyst(newRow);
453
454
455 resetCellEditors();
456
457 getModel().setModify(true);
458
459
460 setFocusOnCell(newRow);
461
462 } else {
463
464
465 setDefaultAnalyst(addedRows);
466 }
467 }
468
469 protected void setDefaultAnalyst(R row) {
470 setDefaultAnalyst(Collections.singleton(row));
471 }
472
473 protected void setDefaultAnalyst(Collection<R> rows) {
474
475 if (CollectionUtils.isEmpty(rows))
476 return;
477
478 DepartmentDTO analyst = getContext().getProgramStrategyService().getAnalysisDepartmentOfAppliedStrategyBySurvey(
479 getModel().getSurvey(),
480 getModel().getPmfms()
481 );
482
483 rows.forEach(row -> row.setAnalyst(analyst));
484 }
485
486 protected void initAddedRow(R row) {
487 if (getModel().getTaxonGroupFilter() != null) {
488 row.setTaxonGroup(getModel().getTaxonGroupFilter());
489 }
490 if (getModel().getTaxonFilter() != null) {
491 row.setTaxon(getModel().getTaxonFilter());
492 }
493 }
494
495 @Override
496 protected void onRowModified(int rowIndex, R row, String propertyName, Integer propertyIndex, Object oldValue, Object newValue) {
497
498
499 if (AbstractMeasurementsGroupedRowModel.PROPERTY_INDIVIDUAL_PMFMS.equals(propertyName)) {
500
501
502 if (oldValue == newValue) return;
503
504
505 if (!getModel().isAdjusting())
506 updatePmfmCellEditors(row, propertyIndex, newValue != null);
507 }
508
509
510 if (AbstractMeasurementsGroupedRowModel.PROPERTY_TAXON_GROUP.equals(propertyName)) {
511
512 updateTaxonCellEditor(row, false);
513
514
515 if ((newValue == null ^ oldValue == null) && row.getTaxon() == null) {
516
517 resetIndividualMeasurementIds(row);
518 }
519 }
520
521 if (AbstractMeasurementsGroupedRowModel.PROPERTY_TAXON.equals(propertyName)) {
522
523 updateTaxonGroupCellEditor(row, false);
524
525
526 if ((newValue == null ^ oldValue == null) && row.getTaxonGroup() == null) {
527
528 resetIndividualMeasurementIds(row);
529 }
530
531
532 TaxonDTO taxon = (TaxonDTO) newValue;
533 row.setInputTaxonId(taxon != null ? taxon.getId() : null);
534 row.setInputTaxonName(taxon != null ? taxon.getName() : null);
535 }
536
537
538 super.onRowModified(rowIndex, row, propertyName, propertyIndex, oldValue, newValue);
539
540
541 if (oldValue != newValue) {
542
543
544
545
546
547 recomputeRowsValidState();
548 }
549
550 }
551
552 protected void resetIndividualMeasurementIds(R row) {
553 if (row != null && CollectionUtils.isNotEmpty(row.getIndividualMeasurements())) {
554 row.getIndividualMeasurements().forEach(measurement -> measurement.setId(null));
555 }
556 }
557
558 protected void setDirty(MeasurementAware bean) {
559 if (bean instanceof DirtyAware) ((DirtyAware) bean).setDirty(true);
560 }
561
562 protected void updateMeasurementFromRow(MeasurementDTO measurement, R row) {
563 measurement.setIndividualId(row.getIndividualId());
564 measurement.setTaxonGroup(row.getTaxonGroup());
565 measurement.setTaxon(row.getTaxon());
566 measurement.setInputTaxonId(row.getInputTaxonId());
567 measurement.setInputTaxonName(row.getInputTaxonName());
568 measurement.setComment(row.getComment());
569 measurement.setAnalyst(row.getAnalyst());
570 }
571
572 public void removeIndividualMeasurements() {
573
574 if (getModel().getSelectedRows().isEmpty()) {
575 LOG.warn("Can't remove rows: no row selected");
576 return;
577 }
578
579 if (askBeforeDelete(t("reefdb.action.delete.survey.measurement.titre"), t("reefdb.action.delete.survey.measurement.message"))) {
580
581
582 Collection<MeasurementAware> modelsToUpdate = getModel().getSelectedRows().stream()
583 .map(this::getMeasurementAwareModelForRow)
584 .collect(Collectors.toSet());
585
586
587 getModel().deleteSelectedRows();
588
589 modelsToUpdate.forEach(this::setDirty);
590 getModel().setModify(true);
591 recomputeRowsValidState();
592 }
593
594 }
595
596 public void duplicateSelectedRow() {
597
598 if (getModel().getSelectedRows().size() == 1 && getModel().getSingleSelectedRow() != null) {
599
600 R rowToDuplicate = getModel().getSingleSelectedRow();
601 MeasurementAware bean = getMeasurementAwareModelForRow(rowToDuplicate);
602 R row = createNewRow(false, bean);
603 row.setTaxonGroup(rowToDuplicate.getTaxonGroup());
604 row.setTaxon(rowToDuplicate.getTaxon());
605 row.setInputTaxonId(rowToDuplicate.getInputTaxonId());
606 row.setInputTaxonName(rowToDuplicate.getInputTaxonName());
607 row.setComment(rowToDuplicate.getComment());
608 row.setIndividualPmfms(rowToDuplicate.getIndividualPmfms());
609
610 row.setAnalyst(rowToDuplicate.getAnalyst());
611
612 row.setIndividualMeasurements(ReefDbBeans.duplicate(rowToDuplicate.getIndividualMeasurements()));
613
614
615 getModel().insertRowAfterSelected(row);
616
617 setDirty(bean);
618 recomputeRowsValidState();
619 getModel().setModify(true);
620 setFocusOnCell(row);
621 }
622 }
623
624
625
626
627
628
629
630 @Override
631 public void recomputeRowsValidState() {
632
633
634 super.recomputeRowsValidState();
635
636
637 hasNoTaxonPerfectDuplicates();
638 }
639
640 @Override
641 protected boolean isRowValid(R row) {
642 boolean valid = super.isRowValid(row);
643
644 if (!valid && !row.isMandatoryValid()) {
645
646 new ArrayList<>(row.getInvalidMandatoryIdentifiers()).forEach(invalidIdentifier -> {
647 if (row.getMultipleValuesOnIdentifier().contains(invalidIdentifier)) {
648
649 row.getErrors().removeIf(error -> error.getPropertyName().size() == 1 && error.getPropertyName().contains(invalidIdentifier.getPropertyName()));
650 row.getInvalidMandatoryIdentifiers().remove(invalidIdentifier);
651 }
652 });
653 valid = row.isMandatoryValid();
654 }
655
656 boolean noUnicityDuplicates = hasNoUnicityDuplicates(row);
657 boolean noPreconditionErrors = hasNoPreconditionErrors(row);
658 boolean noGroupedRuleErrors = hasNoGroupedRuleErrors(row);
659 boolean hasAnalyst = hasAnalyst(row);
660
661 return valid && noUnicityDuplicates && noPreconditionErrors && noGroupedRuleErrors && hasAnalyst;
662 }
663
664 protected boolean hasAnalyst(R row) {
665 if (!row.getMultipleValuesOnIdentifier().contains(AbstractMeasurementsGroupedTableModel.ANALYST)
666 && row.getAnalyst() == null && row.getIndividualMeasurements().stream().anyMatch(this::isMeasurementNotEmpty)) {
667 ReefDbBeans.addError(row,
668 t("reefdb.validator.error.analyst.required"),
669 AbstractMeasurementsGroupedRowModel.PROPERTY_ANALYST);
670 return false;
671 }
672 return true;
673 }
674
675 protected boolean isMeasurementNotEmpty(MeasurementDTO measurement) {
676 return !ReefDbBeans.isMeasurementEmpty(measurement);
677 }
678
679
680
681
682 protected void hasNoTaxonPerfectDuplicates() {
683
684 if (getModel().getRowCount() < 2) {
685
686 return;
687 }
688
689 Map<String, List<R>> duplicatedMap = getModel().getRows().stream().collect(Collectors.groupingBy(r -> r.rowWithMeasurementsHashCode()));
690 duplicatedMap.entrySet().stream().filter(entry -> entry.getValue().size() > 1)
691 .forEach(entry -> entry.getValue().forEach(duplicatedRow -> {
692 ReefDbBeans.addWarning(duplicatedRow,
693 t("reefdb.measurement.grouped.duplicates"), duplicatedRow.getDefaultProperties().toArray(new String[0]));
694 ReefDbBeans.getPmfmIdsOfNonEmptyIndividualMeasurements(duplicatedRow).forEach(pmfmId -> ReefDbBeans.addWarning(duplicatedRow,
695 t("reefdb.measurement.grouped.duplicates"),
696 pmfmId,
697 AbstractMeasurementsGroupedRowModel.PROPERTY_INDIVIDUAL_PMFMS));
698 }));
699
700 }
701
702 protected boolean hasNoUnicityDuplicates(R row) {
703
704
705
706
707 if (getModel().getRowCount() < 2
708 || (row.getTaxonGroup() == null && row.getTaxon() == null)
709 || CollectionUtils.isEmpty(getModel().getUniquePmfms())) {
710
711 return true;
712 }
713
714 for (R otherRow : getModel().getRows()) {
715 if (otherRow == row || (otherRow.getTaxonGroup() == null && otherRow.getTaxon() == null)) continue;
716
717 if (row.isSameRow(otherRow)
718
719
720 ) {
721
722 for (PmfmDTO uniquePmfm : getModel().getUniquePmfms()) {
723
724
725 MeasurementDTO measurement = ReefDbBeans.getIndividualMeasurementByPmfmId(row, uniquePmfm.getId());
726
727 if (measurement == null) {
728 continue;
729 }
730
731
732 MeasurementDTO otherMeasurement = ReefDbBeans.getIndividualMeasurementByPmfmId(otherRow, uniquePmfm.getId());
733
734 if (otherMeasurement == null) {
735 continue;
736 }
737
738 if ((measurement.getPmfm().getParameter().isQualitative() && Objects.equals(measurement.getQualitativeValue(), otherMeasurement.getQualitativeValue()))
739 || (!measurement.getPmfm().getParameter().isQualitative() && Objects.equals(measurement.getNumericalValue(), otherMeasurement.getNumericalValue()))) {
740
741
742 List<String> properties = row.getDefaultProperties();
743 properties.add(AbstractMeasurementsGroupedRowModel.PROPERTY_INDIVIDUAL_PMFMS);
744 ReefDbBeans.addError(row,
745 t("reefdb.measurement.grouped.duplicates.taxonUnique", decorate(uniquePmfm, DecoratorService.NAME_WITH_UNIT)),
746 uniquePmfm.getId(), properties.toArray(new String[0]));
747 return false;
748
749 }
750 }
751 }
752 }
753
754 return true;
755 }
756
757 protected boolean hasNoPreconditionErrors(R row) {
758 if (row.getPreconditionErrors().isEmpty()) return true;
759
760 row.getErrors().addAll(row.getPreconditionErrors());
761 return true;
762 }
763
764
765
766 private void detectGroupedIdentifiers() {
767
768 if (CollectionUtils.isEmpty(getModel().getSurvey().getGroupedRules())) return;
769
770 for (ControlRuleDTO groupedRule : getModel().getSurvey().getGroupedRules()) {
771 boolean allIdentifiersFound = true;
772 List<Map.Entry<RuleGroupDTO, ? extends ReefDbColumnIdentifier<R>>> identifierByGroup = new ArrayList<>();
773 for (RuleGroupDTO group : groupedRule.getGroups()) {
774 ControlRuleDTO rule = group.getRule();
775 if (ControlElementValues.MEASUREMENT.equals(rule.getControlElement())) {
776
777 if (ControlFeatureMeasurementValues.TAXON_GROUP.equals(rule.getControlFeature())) {
778 ReefDbColumnIdentifier<R> identifier = findColumnIdentifierByPropertyName(AbstractMeasurementsGroupedRowModel.PROPERTY_TAXON_GROUP);
779 if (identifier != null) {
780 identifierByGroup.add(Maps.immutableEntry(group, identifier));
781 } else {
782 allIdentifiersFound = false;
783 break;
784 }
785 } else if (ControlFeatureMeasurementValues.TAXON.equals(rule.getControlFeature())) {
786 ReefDbColumnIdentifier<R> identifier = findColumnIdentifierByPropertyName(AbstractMeasurementsGroupedRowModel.PROPERTY_TAXON);
787 if (identifier != null) {
788 identifierByGroup.add(Maps.immutableEntry(group, identifier));
789 } else {
790 allIdentifiersFound = false;
791 break;
792 }
793 } else if (ControlFeatureMeasurementValues.PMFM.equals(rule.getControlFeature())) {
794 for (PmfmDTO pmfm : rule.getRulePmfms().stream().map(RulePmfmDTO::getPmfm).collect(Collectors.toList())) {
795 PmfmTableColumn column = findPmfmColumnByPmfmId(getModel().getPmfmColumns(), pmfm.getId());
796 if (column != null) {
797 identifierByGroup.add(Maps.immutableEntry(group, column.getPmfmIdentifier()));
798 } else {
799 allIdentifiersFound = false;
800 break;
801 }
802 }
803 }
804 }
805 }
806 if (allIdentifiersFound) {
807 getModel().getIdentifiersByGroupedRuleMap().putAll(groupedRule, identifierByGroup);
808 }
809 }
810 }
811
812 protected boolean hasNoGroupedRuleErrors(R row) {
813 if (getModel().getIdentifiersByGroupedRuleMap().isEmpty()) return true;
814 boolean result = true;
815
816 for (ControlRuleDTO groupedRule : getModel().getIdentifiersByGroupedRuleMap().keySet()) {
817
818 boolean groupResult = false;
819 for (Map.Entry<RuleGroupDTO, ? extends ReefDbColumnIdentifier<R>> identifierByGroup : getModel().getIdentifiersByGroupedRuleMap().get(groupedRule)) {
820 RuleGroupDTO group = identifierByGroup.getKey();
821 ReefDbColumnIdentifier<R> identifier = identifierByGroup.getValue();
822
823 if (row.getMultipleValuesOnIdentifier().contains(identifier)
824 || (identifier instanceof ReefDbPmfmColumnIdentifier && row.getMultipleValuesOnPmfmIds().contains(((ReefDbPmfmColumnIdentifier) identifier).getPmfmId()))
825 ) {
826 groupResult = true;
827 break;
828 }
829
830
831 Assert.isTrue(group.isIsOr());
832 groupResult |= getContext().getControlRuleService().controlUniqueObject(group.getRule(), identifier.getValue(row));
833 }
834
835 if (!groupResult) {
836
837 String message;
838 List<String> columnNames = new ArrayList<>();
839 Set<String> propertyNames = new HashSet<>();
840 Set<String> pmfmPropertyNames = new HashSet<>();
841 Set<Integer> pmfmIds = new HashSet<>();
842 for (RuleGroupDTO group : groupedRule.getGroups()) {
843 if (ReefDbBeans.isPmfmMandatory(group.getRule())) {
844
845 for (PmfmDTO pmfm : group.getRule().getRulePmfms().stream().map(RulePmfmDTO::getPmfm).collect(Collectors.toList())) {
846 ReefDbPmfmColumnIdentifier<R> pmfmIdentifier = findPmfmColumnByPmfmId(getModel().getPmfmColumns(), pmfm.getId()).getPmfmIdentifier();
847 columnNames.add(pmfmIdentifier.getHeaderLabel());
848 pmfmPropertyNames.add(pmfmIdentifier.getPropertyName());
849 pmfmIds.add(pmfm.getId());
850 }
851 } else {
852 ReefDbColumnIdentifier<R> identifier = getModel().getIdentifierByGroup(group);
853 columnNames.add(t(identifier.getHeaderI18nKey()));
854 propertyNames.add(identifier.getPropertyName());
855 }
856 }
857 if (StringUtils.isNotBlank(groupedRule.getMessage())) {
858 message = groupedRule.getMessage();
859 } else {
860 message = t("reefdb.measurement.grouped.invalidGroupedRule", Joiner.on(',').join(columnNames));
861 }
862
863
864 ReefDbBeans.addError(row, message, propertyNames.toArray(new String[0]));
865
866 if (!pmfmIds.isEmpty())
867 for (Integer pmfmId : pmfmIds)
868 ReefDbBeans.addError(row, message, pmfmId, pmfmPropertyNames.toArray(new String[0]));
869 }
870
871
872 result &= groupResult;
873
874 }
875
876 return result;
877 }
878
879
880
881 private void detectPreconditionedPmfms() {
882
883 if (CollectionUtils.isEmpty(getModel().getSurvey().getPreconditionedRules())) return;
884
885 for (ControlRuleDTO preconditionedRule : getModel().getSurvey().getPreconditionedRules()) {
886
887 for (PreconditionRuleDTO precondition : preconditionedRule.getPreconditions()) {
888
889 int basePmfmId = precondition.getBaseRule().getRulePmfms(0).getPmfm().getId();
890 int usedPmfmId = precondition.getUsedRule().getRulePmfms(0).getPmfm().getId();
891
892 getModel().addPreconditionRuleByPmfmId(basePmfmId, precondition);
893 if (precondition.isBidirectional())
894 getModel().addPreconditionRuleByPmfmId(usedPmfmId, precondition);
895 }
896 }
897 }
898
899
900
901
902
903
904
905
906 private void updatePmfmCellEditors(R row, Integer firstPmfmId, boolean resetValueAllowed) {
907
908 if (row == null || CollectionUtils.isEmpty(row.getIndividualPmfms())) return;
909
910
911 List<PmfmDTO> allPmfms;
912 if (firstPmfmId == null) {
913 allPmfms = row.getIndividualPmfms();
914 } else {
915 allPmfms = new ArrayList<>(row.getIndividualPmfms());
916 PmfmDTO firstPmfm = ReefDbBeans.findById(allPmfms, firstPmfmId);
917 if (firstPmfm != null) {
918 allPmfms.remove(firstPmfm);
919
920 allPmfms.add(0, firstPmfm);
921 }
922 }
923
924
925 row.getAllowedQualitativeValuesMap().clear();
926 row.getPreconditionErrors().clear();
927 List<Integer> allPmfmIds = allPmfms.stream().map(PmfmDTO::getId).collect(Collectors.toList());
928
929 for (PmfmDTO pmfm : allPmfms) {
930
931
932 if (getModel().isPmfmIdHasPreconditions(pmfm.getId())) {
933
934
935 MeasurementDTO measurement = ReefDbBeans.getIndividualMeasurementByPmfmId(row, pmfm.getId());
936 if (measurement == null) {
937
938 measurement = ReefDbBeanFactory.newMeasurementDTO();
939 measurement.setPmfm(pmfm);
940 row.getIndividualMeasurements().add(measurement);
941 }
942
943
944 getContext().getRuleListService().buildAllowedValuesByPmfmId(
945 measurement,
946 allPmfmIds,
947 getModel().getPreconditionRulesByPmfmId(pmfm.getId()),
948 row.getAllowedQualitativeValuesMap());
949
950
951 for (Integer targetPmfmId : row.getAllowedQualitativeValuesMap().getTargetIds()) {
952 updateQualitativePmfmCellEditors(row, targetPmfmId, resetValueAllowed);
953 }
954
955 }
956 }
957
958
959 for (PmfmDTO pmfm : allPmfms) {
960 if (pmfm.getParameter().isQualitative()) {
961 updateQualitativePmfmCellEditors(row, pmfm.getId(), resetValueAllowed);
962 }
963 }
964
965 }
966
967 @SuppressWarnings("unchecked")
968 private void updateQualitativePmfmCellEditors(R row, int targetPmfmId, boolean resetValueAllowed) {
969
970 PmfmDTO targetPmfm = ReefDbBeans.findById(row.getIndividualPmfms(), targetPmfmId);
971 PmfmTableColumn targetPmfmColumn = findPmfmColumnByPmfmId(getModel().getPmfmColumns(), targetPmfmId);
972
973
974 if (!(targetPmfmColumn.getCellEditor() instanceof ExtendedComboBoxCellEditor)) return;
975 ExtendedComboBoxCellEditor<QualitativeValueDTO> comboBoxCellEditor = (ExtendedComboBoxCellEditor<QualitativeValueDTO>) targetPmfmColumn.getCellEditor();
976
977 AllowedQualitativeValuesMap.AllowedValues allowedTargetIds = new AllowedQualitativeValuesMap.AllowedValues();
978
979 Collection<Integer> sourcePmfmIds = row.getAllowedQualitativeValuesMap().getExistingSourcePmfmIds(targetPmfmId);
980 Integer lastNumericalSourcePmfmId = null;
981 for (Integer sourcePmfmId : sourcePmfmIds) {
982
983
984 MeasurementDTO sourceMeasurement = ReefDbBeans.getIndividualMeasurementByPmfmId(row, sourcePmfmId);
985 boolean sourceIsNumerical = false;
986
987 if (sourceMeasurement != null) {
988 AllowedQualitativeValuesMap.AllowedValues allowedValues = null;
989 if (sourceMeasurement.getQualitativeValue() != null) {
990 allowedValues = row.getAllowedQualitativeValuesMap().getAllowedValues(targetPmfmId, sourcePmfmId, sourceMeasurement.getQualitativeValue().getId());
991 } else if (sourceMeasurement.getNumericalValue() != null) {
992 allowedValues = row.getAllowedQualitativeValuesMap().getAllowedValues(targetPmfmId, sourcePmfmId, sourceMeasurement.getNumericalValue());
993 sourceIsNumerical = true;
994 }
995 if (allowedValues != null) {
996 allowedTargetIds.addOrRetain(allowedValues);
997 if (sourceIsNumerical) lastNumericalSourcePmfmId = sourcePmfmId;
998 }
999 }
1000 }
1001
1002
1003 List<QualitativeValueDTO> allowedQualitativeValues = ReefDbBeans.filterCollection(targetPmfm.getQualitativeValues(),
1004 qualitativeValue -> allowedTargetIds.isAllowed(qualitativeValue.getId()));
1005 comboBoxCellEditor.getCombo().setData(allowedQualitativeValues);
1006
1007
1008 QualitativeValueDTO actualValue = (QualitativeValueDTO) targetPmfmColumn.getPmfmIdentifier().getValue(row);
1009 if (resetValueAllowed) {
1010 if (allowedQualitativeValues.size() == 1) {
1011 if (actualValue == null) {
1012
1013 getModel().setAdjusting(true);
1014 targetPmfmColumn.getPmfmIdentifier().setValue(row, allowedQualitativeValues.get(0));
1015 getModel().setAdjusting(false);
1016 }
1017 } else {
1018 if (actualValue != null && !allowedQualitativeValues.contains(actualValue) && lastNumericalSourcePmfmId == null) {
1019
1020 getModel().setAdjusting(true);
1021 targetPmfmColumn.getPmfmIdentifier().setValue(row, null);
1022 getModel().setAdjusting(false);
1023 }
1024 }
1025 }
1026
1027
1028 if (!allowedQualitativeValues.isEmpty() && actualValue != null && !allowedQualitativeValues.contains(actualValue) && lastNumericalSourcePmfmId != null) {
1029 PmfmDTO sourcePmfm = getContext().getReferentialService().getPmfm(lastNumericalSourcePmfmId);
1030 ErrorDTO error = ReefDbBeanFactory.newErrorDTO();
1031 error.setError(true);
1032 error.setMessage(t("reefdb.measurement.grouped.incoherentQualitativeAndNumericalValues",
1033 decorate(targetPmfm, DecoratorService.NAME_WITH_UNIT), decorate(sourcePmfm, DecoratorService.NAME_WITH_UNIT)));
1034 error.setPropertyName(ImmutableList.of(AbstractMeasurementsGroupedRowModel.PROPERTY_INDIVIDUAL_PMFMS));
1035 error.setPmfmId(lastNumericalSourcePmfmId);
1036 ReefDbBeans.addUniqueErrors(row.getPreconditionErrors(), ImmutableList.of(error));
1037 recomputeRowValidState(row);
1038 }
1039 }
1040
1041
1042
1043
1044
1045
1046 protected void editSelectedMeasurements(JDialog dialog) {
1047
1048 saveTableState();
1049
1050 Assert.isInstanceOf(ReefDbUI.class, dialog);
1051 ReefDbUI<?, ?> multiEditUI = (ReefDbUI<?, ?>) dialog;
1052 Assert.isInstanceOf(AbstractMeasurementsMultiEditUIModel.class, multiEditUI.getModel());
1053 AbstractMeasurementsMultiEditUIModel<MeasurementDTO, R, ?> multiEditModel = (AbstractMeasurementsMultiEditUIModel<MeasurementDTO, R, ?>) multiEditUI.getModel();
1054 multiEditModel.setObservationHandler(getModel().getObservationUIHandler());
1055 multiEditModel.setRowsToEdit(getModel().getSelectedRows());
1056 multiEditModel.setPmfms(getModel().getPmfms());
1057 multiEditModel.setSurvey(getModel().getSurvey());
1058
1059
1060 JComponent parent = getUI().getParentContainer(getTable(), JScrollPane.class);
1061 Insets borderInsets = parent.getBorder().getBorderInsets(parent);
1062 openDialog(dialog, new Dimension(parent.getWidth() + borderInsets.left + borderInsets.right, 160), true, parent);
1063
1064
1065 if (multiEditModel.isValid()) {
1066
1067
1068 R multiEditRow = multiEditModel.getRows().get(0);
1069
1070 for (R row : getModel().getSelectedRows()) {
1071
1072
1073 for (ReefDbColumnIdentifier<R> identifier : multiEditModel.getIdentifiersToCheck()) {
1074
1075 if (identifier.getPropertyName().equals(OperationMeasurementsGroupedRowModel.PROPERTY_SAMPLING_OPERATION))
1076 continue;
1077
1078
1079 Object multiValue = identifier.getValue(multiEditRow);
1080 Object currentValue = identifier.getValue(row);
1081 boolean isMultiple = multiEditRow.getMultipleValuesOnIdentifier().contains(identifier);
1082
1083 if ((!isMultiple || multiValue != null) && !Objects.equals(multiValue, currentValue)) {
1084 identifier.setValue(row, multiValue);
1085
1086 if (identifier.getPropertyName().equals(AbstractMeasurementsGroupedRowModel.PROPERTY_INPUT_TAXON_NAME)) {
1087
1088 row.setInputTaxonId(multiEditRow.getInputTaxonId());
1089 }
1090 }
1091 }
1092
1093
1094 for (PmfmDTO pmfm : getModel().getPmfms()) {
1095 MeasurementDTO multiMeasurement = ReefDbBeans.getIndividualMeasurementByPmfmId(multiEditRow, pmfm.getId());
1096 if (multiMeasurement == null) {
1097
1098 multiMeasurement = ReefDbBeanFactory.newMeasurementDTO();
1099 }
1100 boolean isMultiple = multiEditRow.getMultipleValuesOnPmfmIds().contains(pmfm.getId());
1101
1102 if (!isMultiple || !ReefDbBeans.isMeasurementEmpty(multiMeasurement)) {
1103 MeasurementDTO measurement = ReefDbBeans.getIndividualMeasurementByPmfmId(row, pmfm.getId());
1104 if (measurement == null) {
1105
1106 measurement = ReefDbBeanFactory.newMeasurementDTO();
1107 measurement.setPmfm(pmfm);
1108 measurement.setIndividualId(row.getIndividualId());
1109 row.getIndividualMeasurements().add(measurement);
1110 }
1111
1112 if (!ReefDbBeans.measurementValuesEquals(multiMeasurement, measurement)) {
1113 measurement.setNumericalValue(multiMeasurement.getNumericalValue());
1114 measurement.setQualitativeValue(multiMeasurement.getQualitativeValue());
1115 }
1116 }
1117 }
1118
1119 }
1120
1121
1122 getModel().setModify(true);
1123 getTable().repaint();
1124 }
1125 }
1126
1127
1128
1129
1130
1131 public void save() {
1132
1133
1134 List<? extends MeasurementAware> beansToSave = getMeasurementAwareModels();
1135 List<MeasurementDTO> existingMeasurements = beansToSave.stream()
1136 .flatMap(bean -> bean.getIndividualMeasurements().stream())
1137 .collect(Collectors.toList());
1138
1139
1140 AtomicInteger minNegativeId = new AtomicInteger(
1141 Math.min(
1142 0,
1143 existingMeasurements.stream()
1144 .filter(measurement -> measurement != null && measurement.getId() != null)
1145 .mapToInt(MeasurementDTO::getId)
1146 .min()
1147 .orElse(0)
1148 )
1149 );
1150
1151 Multimap<MeasurementAware, R> rowsByBean = ArrayListMultimap.create();
1152
1153
1154 for (int i = 0; i < getTable().getRowCount(); i++) {
1155 R row = getTableModel().getEntry(getTable().convertRowIndexToModel(i));
1156 MeasurementAware bean = getMeasurementAwareModelForRow(row);
1157 if (bean == null) {
1158 throw new ReefDbTechnicalException("The parent bean is null for a grouped measurements row");
1159 }
1160 rowsByBean.put(bean, row);
1161 }
1162
1163 rowsByBean.keySet().forEach(bean -> {
1164
1165 AtomicInteger individualId = new AtomicInteger();
1166 List<MeasurementDTO> beanMeasurements = new ArrayList<>();
1167
1168
1169 rowsByBean.get(bean).forEach(row -> {
1170
1171
1172 if (isRowToSave(row)) {
1173
1174
1175 row.setIndividualId(individualId.incrementAndGet());
1176
1177
1178 row.getIndividualMeasurements().forEach(measurement -> {
1179 if (measurement.getId() == null) {
1180
1181 measurement.setId(minNegativeId.decrementAndGet());
1182 }
1183
1184 updateMeasurementFromRow(measurement, row);
1185
1186 beanMeasurements.add(measurement);
1187 });
1188
1189 }
1190 });
1191
1192
1193 bean.getIndividualMeasurements().clear();
1194 bean.getIndividualMeasurements().addAll(beanMeasurements);
1195
1196 setDirty(bean);
1197
1198 beansToSave.remove(bean);
1199 });
1200
1201
1202 beansToSave.forEach(bean -> {
1203
1204 bean.getIndividualMeasurements().clear();
1205 setDirty(bean);
1206 });
1207 }
1208
1209
1210
1211
1212
1213
1214
1215 protected boolean isRowToSave(R row) {
1216 return
1217
1218 row != null
1219
1220 && (row.hasTaxonInformation() || row.hasNonEmptyMeasurements());
1221 }
1222 }