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