View Javadoc
1   package fr.ifremer.tutti.ui.swing.content.operation.catches.species.edit;
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 com.google.common.collect.Lists;
27  import com.google.common.collect.Sets;
28  import fr.ifremer.adagio.core.dao.referential.pmfm.QualitativeValueId;
29  import fr.ifremer.tutti.persistence.entities.TuttiEntities;
30  import fr.ifremer.tutti.persistence.entities.data.Attachment;
31  import fr.ifremer.tutti.persistence.entities.data.BatchContainer;
32  import fr.ifremer.tutti.persistence.entities.data.FishingOperation;
33  import fr.ifremer.tutti.persistence.entities.data.IndividualObservationBatch;
34  import fr.ifremer.tutti.persistence.entities.data.SampleCategory;
35  import fr.ifremer.tutti.persistence.entities.data.SampleCategoryModel;
36  import fr.ifremer.tutti.persistence.entities.data.SampleCategoryModelEntry;
37  import fr.ifremer.tutti.persistence.entities.data.SpeciesBatch;
38  import fr.ifremer.tutti.persistence.entities.data.SpeciesBatchFrequency;
39  import fr.ifremer.tutti.persistence.entities.referential.CaracteristicQualitativeValue;
40  import fr.ifremer.tutti.persistence.entities.referential.CaracteristicQualitativeValues;
41  import fr.ifremer.tutti.persistence.entities.referential.Species;
42  import fr.ifremer.tutti.service.ValidationService;
43  import fr.ifremer.tutti.type.WeightUnit;
44  import fr.ifremer.tutti.ui.swing.content.operation.catches.AbstractTuttiBatchTableUIHandler;
45  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.IndividualObservationBatchRowModel;
46  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.SpeciesOrBenthosBatchUISupport;
47  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.create.CreateSpeciesBatchUIModel;
48  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.SpeciesFrequencyCellComponent;
49  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.SpeciesFrequencyRowModel;
50  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.split.SplitSpeciesBatchRowModel;
51  import fr.ifremer.tutti.ui.swing.util.TuttiBeanMonitor;
52  import fr.ifremer.tutti.ui.swing.util.TuttiUIUtil;
53  import fr.ifremer.tutti.ui.swing.util.attachment.AttachmentCellEditor;
54  import fr.ifremer.tutti.ui.swing.util.attachment.AttachmentCellRenderer;
55  import fr.ifremer.tutti.ui.swing.util.comment.CommentCellEditor;
56  import fr.ifremer.tutti.ui.swing.util.comment.CommentCellRenderer;
57  import fr.ifremer.tutti.ui.swing.util.computable.ComputableDataTableCell;
58  import jaxx.runtime.SwingUtil;
59  import jaxx.runtime.swing.JTables;
60  import jaxx.runtime.validator.swing.SwingValidator;
61  import org.apache.commons.collections4.CollectionUtils;
62  import org.apache.commons.lang3.ObjectUtils;
63  import org.apache.commons.logging.Log;
64  import org.apache.commons.logging.LogFactory;
65  import org.jdesktop.swingx.JXTable;
66  import org.jdesktop.swingx.decorator.HighlightPredicate;
67  import org.jdesktop.swingx.decorator.Highlighter;
68  import org.jdesktop.swingx.table.DefaultTableColumnModelExt;
69  import org.jdesktop.swingx.table.TableColumnExt;
70  import org.jdesktop.swingx.table.TableColumnModelExt;
71  import org.nuiton.decorator.Decorator;
72  import org.nuiton.jaxx.application.swing.table.ColumnIdentifier;
73  import org.nuiton.validator.NuitonValidatorResult;
74  
75  import javax.swing.JComponent;
76  import javax.swing.RowFilter;
77  import javax.swing.event.TableModelEvent;
78  import javax.swing.table.TableCellRenderer;
79  import javax.swing.table.TableColumnModel;
80  import java.awt.Color;
81  import java.io.File;
82  import java.io.Serializable;
83  import java.util.ArrayList;
84  import java.util.Collections;
85  import java.util.EnumMap;
86  import java.util.List;
87  import java.util.Set;
88  import java.util.stream.Collectors;
89  
90  import static org.nuiton.i18n.I18n.n;
91  import static org.nuiton.i18n.I18n.t;
92  
93  /**
94   * @author Tony Chemit - chemit@codelutin.com
95   * @since 0.1
96   */
97  public class SpeciesBatchUIHandler extends AbstractTuttiBatchTableUIHandler<SpeciesBatchRowModel, SpeciesBatchUIModel, SpeciesBatchTableModel, SpeciesBatchUI> {
98  
99      /** Logger. */
100     private static final Log log =
101             LogFactory.getLog(SpeciesBatchUIHandler.class);
102 
103     private EnumMap<TableViewMode, RowFilter<SpeciesBatchTableModel, Integer>> tableFilters;
104 
105     /**
106      * Sample categories model.
107      *
108      * @since 2.4
109      */
110     protected SampleCategoryModel sampleCategoryModel;
111 
112 //    /**
113 //     * Weight unit.
114 //     *
115 //     * @since 2.5
116 //     */
117 //    protected WeightUnit weightUnit;
118 
119     /**
120      * id of the unsorted qualitative value to remove of V/HV universe.
121      *
122      * @since 2.6
123      */
124     protected Integer qualitative_unsorted_id;
125 
126     protected SpeciesOrBenthosBatchUISupport speciesOrBenthosBatchUISupport;
127 
128     public SpeciesBatchUIHandler() {
129         super(SpeciesBatchRowModel.PROPERTY_SPECIES_TO_CONFIRM,
130               SpeciesBatchRowModel.PROPERTY_SPECIES,
131               SpeciesBatchRowModel.PROPERTY_WEIGHT,
132               SpeciesBatchRowModel.PROPERTY_NUMBER,
133               SpeciesBatchRowModel.PROPERTY_COMMENT,
134               SpeciesBatchRowModel.PROPERTY_ATTACHMENT,
135               SpeciesBatchRowModel.PROPERTY_SAMPLE_CATEGORY_WEIGHT,
136               SpeciesBatchRowModel.PROPERTY_FREQUENCY);
137     }
138 
139     //------------------------------------------------------------------------//
140     //-- AbstractTuttiBatchTableUIHandler methods                           --//
141     //------------------------------------------------------------------------//
142 
143     @Override
144     protected ColumnIdentifier<SpeciesBatchRowModel> getCommentIdentifier() {
145         return SpeciesBatchTableModel.COMMENT;
146     }
147 
148     @Override
149     protected ColumnIdentifier<SpeciesBatchRowModel> getAttachementIdentifier() {
150         return SpeciesBatchTableModel.ATTACHMENT;
151     }
152 
153     @Override
154     public void selectFishingOperation(FishingOperation bean) {
155 
156         boolean empty = bean == null;
157 
158         SpeciesBatchUIModel model = getModel();
159 
160         List<SpeciesBatchRowModel> rows;
161 
162         if (empty) {
163             rows = null;
164         } else {
165 
166             if (log.isDebugEnabled()) {
167                 log.debug("Get species batch for fishingOperation: " +
168                                   bean.getId());
169             }
170             rows = Lists.newArrayList();
171 
172             if (!TuttiEntities.isNew(bean)) {
173 
174                 // get all batch species root (says the one with only a species sample category)
175                 BatchContainer<SpeciesBatch> rootSpeciesBatch = speciesOrBenthosBatchUISupport.getRootSpeciesBatch(bean.getIdAsInt());
176 //                        getPersistenceService().getRootSpeciesBatch(bean.getIdAsInt(), true);
177 
178                 List<SpeciesBatch> catches = rootSpeciesBatch.getChildren();
179 
180                 // use first category from configuration
181                 Integer firstCategoryId = sampleCategoryModel.getFirstCategoryId();
182 
183                 for (SpeciesBatch aBatch : catches) {
184 
185                     // root batch sample category is species
186 
187                     Preconditions.checkState(
188                             firstCategoryId.equals(aBatch.getSampleCategoryId()),
189                             "Root species batch must be a sortedUnsorted sample " +
190                                     "category but was:" + aBatch.getSampleCategoryId());
191 
192 //                    SpeciesBatchRowModel rootRow =
193                     loadBatch(aBatch, null, rows);
194 
195                     //FIXME kmorin 20140902 NPE decorator does not exist
196 //                    if (log.isDebugEnabled()) {
197 //                        log.debug("Loaded root batch " +
198 //                                  decorate(rootRow.getSpecies(), DecoratorService.FROM_PROTOCOL) + " - " +
199 //                                  decorate(rootRow.getSampleCategoryById(firstCategoryId)));
200 //                    }
201                 }
202             }
203 
204 //            SpeciesBatchDecorator decorator = getSpeciesColumnDecorator();
205 //
206 //            SpeciesSortMode speciesSortMode = model.getSpeciesSortMode();
207 //
208 //            SpeciesBatchRowHelper.sortSpeciesRows(getTable(),
209 //                                                  decorator,
210 //                                                  rows,
211 //                                                  speciesSortMode);
212         }
213 
214 //        model.setRows(rows);
215         sortAndSetRow(model, rows);
216     }
217 
218     public void sortAndSetRow(SpeciesBatchUIModel model, List<SpeciesBatchRowModel> rows) {
219         SpeciesSortMode order = model.getSpeciesSortMode();
220         if (order != SpeciesSortMode.NONE && rows != null && rows.size() > 0) {
221             SpeciesBatchDecorator decorator = getSpeciesColumnDecorator();
222             SpeciesBatchRowHelper.sortSpeciesRows(getTable(),
223                                                   decorator,
224                                                   rows,
225                                                   order);
226         }
227         model.setRows(rows);
228     }
229 
230     //------------------------------------------------------------------------//
231     //-- AbstractTuttiTableUIHandler methods                                --//
232     //------------------------------------------------------------------------//
233 
234     @Override
235     public SpeciesBatchTableModel getTableModel() {
236         return (SpeciesBatchTableModel) getTable().getModel();
237     }
238 
239     @Override
240     public JXTable getTable() {
241         return ui.getTable();
242     }
243 
244     @Override
245     protected boolean isRowValid(SpeciesBatchRowModel row) {
246         SpeciesBatch batch = convertRowToEntity(row, true);
247         NuitonValidatorResult validator =
248                 getValidationService().validateEditSpeciesBatch(batch);
249         boolean result = !validator.hasErrorMessagess();
250 
251         if (result
252                 && ValidationService.VALIDATION_CONTEXT_VALIDATE.equals(
253                 getContext().getValidationContext())
254                 && row.isBatchLeaf()) {
255 
256             List<SpeciesBatchFrequency> frequencies =
257                     SpeciesFrequencyRowModel.toEntity(
258                             row.getFrequency(),
259                             batch);
260             result = getValidateCruiseOperationsService().isSpeciesBatchValid(
261                     batch,
262                     frequencies);
263         }
264 
265         return result;
266     }
267 
268     @Override
269     protected void onRowModified(int rowIndex,
270                                  SpeciesBatchRowModel row,
271                                  String propertyName,
272                                  Object oldValue,
273                                  Object newValue) {
274 
275         recomputeRowValidState(row);
276 
277         SpeciesBatchTableModel tableModel = getTableModel();
278 
279         if (SpeciesBatchRowModel.PROPERTY_SAMPLE_CATEGORY_WEIGHT.equals(propertyName)) {
280 
281             // sampling category weight has changed, must then save the top
282             // ancestor row
283 
284             // new value is the sample category
285             SampleCategory<?> sampleCategory = (SampleCategory<?>) newValue;
286             Integer sampleCategoryId = sampleCategory.getCategoryId();
287 
288             SpeciesBatchRowModel firstAncestorRow = row.getFirstAncestor(sampleCategory);
289             int firstAncestorIndex = tableModel.getRowIndex(firstAncestorRow);
290             if (rowIndex != firstAncestorIndex) {
291 
292                 // ancestor is not this row
293                 // then only save ancestor
294 
295                 if (log.isDebugEnabled()) {
296                     log.debug("Sample category " + sampleCategoryId +
297                                       " weight was modified, First ancestor row: " +
298                                       firstAncestorIndex + " will save it");
299                 }
300                 saveRow(firstAncestorRow);
301 
302                 cleanrRowMonitor();
303 
304                 return;
305             }
306 
307             // modified sample weight is a leaf
308             // will save it after
309         } else if (SpeciesBatchRowModel.PROPERTY_SPECIES_TO_CONFIRM.equals(propertyName)) {
310 
311             // update his shell
312 
313             Set<SpeciesBatchRowModel> shell = Sets.newHashSet();
314             row.collectShell(shell);
315 
316             boolean newVal = newValue == null ? false : (Boolean) newValue;
317 
318             for (SpeciesBatchRowModel rowToupdate : shell) {
319                 rowToupdate.setSpeciesToConfirm(newVal);
320             }
321 
322             tableModel.fireTableRowUpdatedShell(shell);
323         }
324 
325         saveSelectedRowIfNeeded();
326     }
327 
328     @Override
329     protected void saveSelectedRowIfRequired(TuttiBeanMonitor<SpeciesBatchRowModel> rowMonitor,
330                                              SpeciesBatchRowModel row) {
331         // there is a valid bean attached to the monitor
332         if (rowMonitor.wasModified()) {
333 
334             // monitored bean was modified, save it
335             if (log.isDebugEnabled()) {
336                 log.debug("Row " + row + " was modified, will save it");
337             }
338 
339             String title = buildReminderLabelTitle(row.getSpecies(),
340                                                    row,
341                                                    "Sauvegarde des modifications du lot Capture - Espèces : ",
342                                                    "Ligne :" + (getTableModel().getRowIndex(row) + 1));
343 
344             showInformationMessage(title);
345 
346             rowMonitor.setBean(null);
347             saveRow(row);
348             rowMonitor.setBean(row);
349 
350             // clear modified flag on the monitor
351             rowMonitor.clearModified();
352         }
353     }
354 
355     @Override
356     protected void onModelRowsChanged(List<SpeciesBatchRowModel> rows) {
357         super.onModelRowsChanged(rows);
358 
359         SpeciesBatchUIModel model = getModel();
360         model.setRootNumber(0);
361         model.setDistinctSortedSpeciesCount(0);
362         model.setDistinctUnsortedSpeciesCount(0);
363 
364         for (SpeciesBatchRowModel row : rows) {
365             updateTotalFromFrequencies(row);
366             if (row.isBatchRoot()) {
367 
368                 // update speciesUsed
369                 addToSpeciesUsed(row);
370             }
371         }
372 
373         getTable().clearSelection();
374     }
375 
376     @Override
377     protected void addHighlighters(JXTable table) {
378 
379         // use white color for not editable even row (to make sure white color is apply at least one)
380         Highlighter evenHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
381                 new HighlightPredicate.AndHighlightPredicate(
382                         HighlightPredicate.EVEN,
383                         HighlightPredicate.EDITABLE),
384                 Color.WHITE);
385         table.addHighlighter(evenHighlighter);
386 
387         super.addHighlighters(table);
388 
389         Color toConfirmColor = getConfig().getColorRowToConfirm();
390 
391         // paint the cell in orange if the row is to confirm
392         Highlighter confirmHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
393                 new HighlightPredicate.AndHighlightPredicate(
394                         new HighlightPredicate.NotHighlightPredicate(HighlightPredicate.IS_SELECTED),
395                         HighlightPredicate.EDITABLE,
396                         (renderer, adapter) -> {
397                             SpeciesBatchRowModel row = getTableModel().getEntry(adapter.convertRowIndexToModel(adapter.row));
398                             return row.isSpeciesToConfirm();
399                         }), toConfirmColor);
400         table.addHighlighter(confirmHighlighter);
401 
402         // paint the cell in dark orange if the row is to confirm and the cell is not editable
403         Highlighter confirmNotEditableHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
404                 new HighlightPredicate.AndHighlightPredicate(
405                         new HighlightPredicate.NotHighlightPredicate(HighlightPredicate.IS_SELECTED),
406                         HighlightPredicate.READ_ONLY,
407                         (renderer, adapter) -> {
408                             SpeciesBatchRowModel row = getTableModel().getEntry(adapter.convertRowIndexToModel(adapter.row));
409                             return row.isSpeciesToConfirm() && !adapter.isEditable();
410                         }), toConfirmColor.darker());
411         table.addHighlighter(confirmNotEditableHighlighter);
412     }
413 
414     @Override
415     protected void beforeOpenPopup(int rowIndex, int columnIndex) {
416         super.beforeOpenPopup(rowIndex, columnIndex);
417 
418         boolean enableRename = false;
419         boolean enableSplit = false;
420         boolean enableChangeSampleCategory = false;
421         boolean enableAddSampleCategory = false;
422         boolean enableRemove = false;
423         boolean enableRemoveSub = false;
424         boolean enableCreateMelag = false;
425         boolean enableEditFrequencies = false;
426 
427         if (rowIndex != -1) {
428 
429             // there is a selected row
430 
431 
432             //TODO If there is some sub-batch, can remove them
433             //TODO If there is no sub-batch, can split current batch
434 
435             SpeciesBatchTableModel tableModel = getTableModel();
436             SpeciesBatchRowModel row = tableModel.getEntry(rowIndex);
437             int selectedRowCount = getTable().getSelectedRowCount();
438 
439             // can edit frequencies on a single selected leaf row
440 
441             enableSplit = true;
442 
443             // action with single selection
444             enableRemove = true;
445             enableRemoveSub = selectedRowCount == 1;
446             enableRename = selectedRowCount == 1;
447             enableEditFrequencies = selectedRowCount == 1;
448             enableChangeSampleCategory = selectedRowCount == 1 && tableModel.isCellEditable(rowIndex, columnIndex);
449             enableAddSampleCategory = selectedRowCount == 1 && tableModel.isCellEditable(rowIndex, columnIndex);
450 
451             // action with multi-selection
452             enableCreateMelag = selectedRowCount > 1;
453 
454             if (enableSplit) {
455 
456                 // can split if selected batch is a leaf
457 
458                 Integer lastSamplingId = sampleCategoryModel.getLastCategoryId();
459 
460                 enableSplit = row.isBatchLeaf()
461                         && selectedRowCount == 1
462                         && ObjectUtils.notEqual(lastSamplingId, row.getFinestCategory().getCategoryId())
463                         && row.getNumber() == null
464                         && (row.getComputedNumber() == null
465                         || row.getComputedNumber() == 0);
466             }
467 
468             boolean firstCategory = false;
469             Integer sampleCategoryId =
470                     tableModel.getSampleCategoryId(columnIndex);
471 
472             SampleCategoryModelEntry category;
473 
474             List<CaracteristicQualitativeValue> available = null;
475 
476             if (sampleCategoryId != null) {
477 
478                 // is first category ?
479                 firstCategory = sampleCategoryModel.getFirstCategoryId().equals(sampleCategoryId);
480 
481                 // get category
482                 category = sampleCategoryModel.getCategoryById(sampleCategoryId);
483 
484                 if (category.getCaracteristic().isNumericType()) {
485 
486                     // no category available
487                     available = Collections.emptyList();
488                 } else {
489 
490                     // get the first ancestor row using this category
491                     SpeciesBatchRowModel firstAncestorRow = row.getFirstAncestor(sampleCategoryId);
492 
493                     // get all used values for this category
494                     Set<Serializable> used = getSampleUsedValues(
495                             firstAncestorRow, sampleCategoryId);
496 
497                     // get all possible values
498                     available = Lists.newArrayList(category.getCaracteristic().getQualitativeValue());
499                     available.removeAll(used);
500 
501                     if (firstCategory) {
502 
503                         // remove the unsorted qualitative value
504                         CaracteristicQualitativeValues.removeQualitativeValue(available, qualitative_unsorted_id);
505                     }
506                 }
507             }
508 
509             if (enableChangeSampleCategory) {
510 
511                 // can change category if on a sample column and
512                 // there is still a brother category free
513 
514                 if (sampleCategoryId == null) {
515 
516                     // not on a sample category column
517                     enableChangeSampleCategory = false;
518                 } else {
519 
520                     // action possible only if there is still some available values
521                     enableChangeSampleCategory = CollectionUtils.isNotEmpty(available);
522                 }
523             }
524 
525             if (enableAddSampleCategory) {
526 
527                 // can change category if on a sample column and
528                 // there is still a brother category free
529                 // and sample category is not the first
530 
531                 if (sampleCategoryId == null || firstCategory) {
532 
533                     // not on a sample category column
534                     // or using the first sample category (V/HV)
535                     enableAddSampleCategory = false;
536                 } else {
537 
538                     // action possible only if there is still some available values
539                     enableAddSampleCategory = CollectionUtils.isNotEmpty(available);
540                 }
541             }
542 
543             if (enableEditFrequencies) {
544 
545                 // can edit frequencies only on a leaf
546                 enableEditFrequencies = row.isBatchLeaf();
547             }
548 
549             if (enableRename) {
550 
551                 // can rename if selected batch is a parent and none of his shell has individual observations
552                 enableRename = row.isBatchRoot();
553 
554                 if (enableRename) {
555 
556                     enableRename = !row.containsIndividualObservations(true);
557 
558                 }
559             }
560 
561             if (enableRemove) {
562 
563                 // can always remove the batch
564 
565                 // update the text of the remove batch menu
566                 String text, tip;
567 
568                 if (selectedRowCount == 1) {
569                     text = t("tutti.editSpeciesBatch.action.removeBatch");
570                     tip = t("tutti.editBenthosBatch.action.removeBatch.tip");
571 
572                 } else {
573                     text = t("tutti.editSpeciesBatch.action.removeBatches");
574                     tip = t("tutti.editSpeciesBatch.action.removeBatches.tip");
575                 }
576 
577                 ui.getRemoveSpeciesBatchMenu().setText(text);
578                 ui.getRemoveSpeciesBatchMenu().setToolTipText(tip);
579             }
580 
581             if (enableRemoveSub) {
582 
583                 // can remove sub batch if selected batch is not a leaf
584                 enableRemoveSub = !row.isBatchLeaf();
585             }
586 
587         }
588         SpeciesBatchUIModel model = getModel();
589         model.setSplitBatchEnabled(enableSplit);
590         model.setChangeSampleCategoryEnabled(enableChangeSampleCategory);
591         model.setAddSampleCategoryEnabled(enableAddSampleCategory);
592         model.setRemoveBatchEnabled(enableRemove);
593         model.setRemoveSubBatchEnabled(enableRemoveSub);
594         model.setRenameBatchEnabled(enableRename);
595         model.setCreateMelagEnabled(enableCreateMelag);
596         model.setEditFrequenciesEnabled(enableEditFrequencies);
597 
598         if (log.isDebugEnabled()) {
599             StringBuilder builder = new StringBuilder("actions for (" + rowIndex + "," + columnIndex + "):");
600             builder.append("\nenableSplit:                ").append(enableSplit);
601             builder.append("\nenableChangeSampleCategory: ").append(enableChangeSampleCategory);
602             builder.append("\nenableAddSampleCategory:    ").append(enableAddSampleCategory);
603             builder.append("\nenableRemove:               ").append(enableRemove);
604             builder.append("\nenableRemoveSub:            ").append(enableRemoveSub);
605             builder.append("\nenableRename:               ").append(enableRename);
606             builder.append("\nenableCreateMelag:          ").append(enableCreateMelag);
607             builder.append("\nenableEditFrequencies:      ").append(enableEditFrequencies);
608             log.debug(builder.toString());
609         }
610     }
611 
612     //------------------------------------------------------------------------//
613     //-- AbstractTuttiUIHandler methods                                     --//
614     //------------------------------------------------------------------------//
615 
616     @Override
617     public SwingValidator<SpeciesBatchUIModel> getValidator() {
618         return ui.getValidator();
619     }
620 
621     @Override
622     public void beforeInit(SpeciesBatchUI ui) {
623 
624         super.beforeInit(ui);
625         if (log.isDebugEnabled()) {
626             log.debug("beforeInit: " + ui);
627         }
628 
629         qualitative_unsorted_id = QualitativeValueId.UNSORTED.getValue();
630 
631 //        weightUnit = getConfig().getSpeciesWeightUnit();
632 
633         sampleCategoryModel = getDataContext().getSampleCategoryModel();
634 
635         tableFilters = new EnumMap<>(TableViewMode.class);
636 
637         tableFilters.put(TableViewMode.ALL, new RowFilter<SpeciesBatchTableModel, Integer>() {
638 
639             @Override
640             public boolean include(Entry<? extends SpeciesBatchTableModel, ? extends Integer> entry) {
641                 return true;
642             }
643         });
644 
645         tableFilters.put(TableViewMode.ROOT, new RowFilter<SpeciesBatchTableModel, Integer>() {
646 
647             @Override
648             public boolean include(Entry<? extends SpeciesBatchTableModel, ? extends Integer> entry) {
649                 boolean result = false;
650                 Integer rowIndex = entry.getIdentifier();
651                 if (rowIndex != null) {
652                     SpeciesBatchTableModel model = entry.getModel();
653                     SpeciesBatchRowModel row = model.getEntry(rowIndex);
654                     result = row != null && row.isBatchRoot();
655                 }
656                 return result;
657             }
658         });
659 
660         tableFilters.put(TableViewMode.LEAF, new RowFilter<SpeciesBatchTableModel, Integer>() {
661 
662             @Override
663             public boolean include(Entry<? extends SpeciesBatchTableModel, ? extends Integer> entry) {
664                 boolean result = false;
665                 Integer rowIndex = entry.getIdentifier();
666                 if (rowIndex != null) {
667                     SpeciesBatchTableModel model = entry.getModel();
668                     SpeciesBatchRowModel row = model.getEntry(rowIndex);
669                     result = row != null && row.isBatchLeaf();
670                 }
671                 return result;
672             }
673         });
674 
675         speciesOrBenthosBatchUISupport = ui.getContextValue(SpeciesOrBenthosBatchUISupport.class, ui.getSpeciesOrBenthosContext());
676 
677         SpeciesBatchUIModel model = new SpeciesBatchUIModel(speciesOrBenthosBatchUISupport);
678         model.setTableViewMode(TableViewMode.ALL);
679         model.setSpeciesSortMode(SpeciesSortMode.NONE);
680         ui.setContextValue(model);
681     }
682 
683     @Override
684     public void afterInit(SpeciesBatchUI ui) {
685 
686         if (log.isDebugEnabled()) {
687             log.debug("afterInit: " + ui);
688         }
689 
690         WeightUnit weightUnit = getWeightUnit();
691 
692         initUI(ui);
693 
694         List<Integer> samplingOrder = sampleCategoryModel.getSamplingOrder();
695 
696         if (log.isDebugEnabled()) {
697             log.debug("Will use sampling order: " + samplingOrder);
698         }
699 
700         JXTable table = getTable();
701 
702         // can show / hide some columns in model
703         table.setColumnControlVisible(true);
704 
705         // create table column model
706         TableCellRenderer defaultRenderer =
707                 table.getDefaultRenderer(Object.class);
708 
709         DefaultTableColumnModelExt columnModel =
710                 new DefaultTableColumnModelExt();
711 
712         Decorator<CaracteristicQualitativeValue> caracteristicDecorator =
713                 getDecorator(CaracteristicQualitativeValue.class, null);
714 
715         Decorator<Number> numberDecorator =
716                 getDecorator(Number.class, null);
717 
718         Color computedDataColor = getConfig().getColorComputedWeights();
719 
720         { // Species to confirm column
721 
722             addBooleanColumnToModel(columnModel,
723                                     SpeciesBatchTableModel.SPECIES_TO_CONFIRM,
724                                     getTable());
725         }
726 
727         {
728             // Id column
729 
730             addIdColumnToModel(columnModel, SpeciesBatchTableModel.ID, table);
731 
732         }
733 
734         { // Species column
735 
736             TableColumnExt speciesColumn = addColumnToModel(
737                     columnModel,
738                     null,
739                     null,
740                     SpeciesBatchTableModel.SPECIES);
741             speciesColumn.setSortable(true);
742             SpeciesBatchDecorator<SpeciesBatchRowModel> speciesDecorator =
743                     SpeciesBatchDecorator.newDecorator();
744             speciesColumn.putClientProperty(SpeciesBatchRowHelper.SPECIES_DECORATOR, speciesDecorator);
745             speciesColumn.setCellRenderer(newTableCellRender(speciesDecorator));
746         }
747 
748         // Sample category columns
749 
750         for (SampleCategoryModelEntry sampleCategoryDef : sampleCategoryModel.getCategory()) {
751 
752             SampleCategoryColumnIdentifier columnIdentifier = SampleCategoryColumnIdentifier.newId(
753                     sampleCategoryDef.getLabel(),
754                     sampleCategoryDef.getCategoryId(),
755                     n(sampleCategoryDef.getLabel()),
756                     n(sampleCategoryDef.getLabel()));
757 
758             Decorator<? extends Serializable> decorator =
759                     sampleCategoryDef.getCaracteristic().isNumericType() ?
760                             numberDecorator : caracteristicDecorator;
761 
762             addSampleCategoryColumnToModel(columnModel,
763                                            columnIdentifier,
764                                            decorator,
765                                            defaultRenderer,
766                                            weightUnit);
767         }
768 
769         { // Weight column
770 
771             addColumnToModel(columnModel,
772                              ComputableDataTableCell.newEditor(weightUnit, computedDataColor),
773                              ComputableDataTableCell.newRender(defaultRenderer, weightUnit, computedDataColor),
774                              SpeciesBatchTableModel.WEIGHT,
775                              weightUnit);
776         }
777 
778         { // Number column (from frequencies)
779 
780             addColumnToModel(columnModel,
781                              SpeciesFrequencyCellComponent.newEditor(ui, computedDataColor),
782                              SpeciesFrequencyCellComponent.newRender(computedDataColor),
783                              SpeciesBatchTableModel.COMPUTED_NUMBER);
784         }
785 
786         { // Comment column
787 
788             addColumnToModel(columnModel,
789                              CommentCellEditor.newEditor(ui),
790                              CommentCellRenderer.newRender(),
791                              SpeciesBatchTableModel.COMMENT);
792         }
793 
794         { // File column
795 
796             addColumnToModel(columnModel,
797                              AttachmentCellEditor.newEditor(ui),
798                              AttachmentCellRenderer.newRender(getDecorator(Attachment.class, null)),
799                              SpeciesBatchTableModel.ATTACHMENT);
800         }
801 
802         // create table model
803         SpeciesBatchTableModel tableModel =
804                 new SpeciesBatchTableModel(weightUnit,
805                                            sampleCategoryModel,
806                                            columnModel);
807 
808         table.setModel(tableModel);
809         table.setColumnModel(columnModel);
810 
811         initBatchTable(table, columnModel, tableModel);
812 
813         getModel().addPropertyChangeListener(SpeciesBatchUIModel.PROPERTY_TABLE_VIEW_MODE, evt -> {
814             TableViewMode tableViewMode = (TableViewMode) evt.getNewValue();
815 
816             if (tableViewMode == null) {
817                 tableViewMode = TableViewMode.ALL;
818             }
819 
820             if (log.isDebugEnabled()) {
821                 log.debug("Will use rowfilter for viewMode: " + tableViewMode);
822             }
823             RowFilter<SpeciesBatchTableModel, Integer> filter = tableFilters.get(tableViewMode);
824             getTable().setRowFilter(filter);
825         });
826 
827         // when species sort mode change, must reload the firshing operation
828         // and applying the sort on model
829         getModel().addPropertyChangeListener(SpeciesBatchUIModel.PROPERTY_SPECIES_SORT_MODE, evt -> {
830             SpeciesSortMode newValue = (SpeciesSortMode) evt.getNewValue();
831             if (log.isInfoEnabled()) {
832                 log.info("New species sort mode: " + newValue);
833             }
834 
835             // no reload to fixes bug https://forge.codelutin.com/issues/8334)
836             // must reload fishing operation
837 //            selectFishingOperation(getModel().getFishingOperation());
838 
839             if (newValue == SpeciesSortMode.NONE) {
840                 // need to reload data for NONE, because default element order is loose
841                 // after sorting by alphabetic order.
842                 // This maintains same behavior before fixes bug #8334
843                 // (elevation computation disapear when sorting change)
844                 selectFishingOperation(getModel().getFishingOperation());
845             } else {
846                 SpeciesBatchUIModel model = getModel();
847                 sortAndSetRow(model, model.getRows());
848             }
849         });
850 
851         // when species sort mode change, must reload the firshing operation
852         // and applying the sort on model
853         getModel().addPropertyChangeListener(SpeciesBatchUIModel.PROPERTY_SPECIES_DECORATOR_CONTEXT_INDEX, evt -> {
854             int newValue = (int) evt.getNewValue();
855             if (log.isInfoEnabled()) {
856                 log.info("New species decorator context index: " + newValue);
857             }
858 
859             // update the decorator context index
860             getSpeciesColumnDecorator().setContextIndex(newValue);
861 
862             // reload fishing operation
863             selectFishingOperation(getModel().getFishingOperation());
864         });
865     }
866 
867     @Override
868     protected void initBatchTable(final JXTable table,
869                                   TableColumnModelExt columnModel,
870                                   SpeciesBatchTableModel tableModel) {
871         super.initBatchTable(table,
872                              columnModel,
873                              tableModel);
874 
875         // by default do not authorize to change column orders
876         table.getTableHeader().setReorderingAllowed(false);
877 
878         // get the species column
879         final TableColumnExt speciesColumn = table.getColumnExt(SpeciesBatchTableModel.SPECIES);
880 
881         // when model change, then rebuild the species comparator + set model as modified
882 
883         tableModel.addTableModelListener(e -> {
884 
885             SpeciesBatchTableModel tableModel1 =
886                     (SpeciesBatchTableModel) e.getSource();
887             int type = e.getType();
888             if (type == TableModelEvent.DELETE ||
889                     type == TableModelEvent.INSERT ||
890                     e.getLastRow() == Integer.MAX_VALUE) {
891 
892                 // get column comparator
893                 SpeciesBatchDecoratorComparator<SpeciesBatchRowModel> comparator =
894                         getSpeciesRowComparator();
895 
896                 // get column decorator
897                 SpeciesBatchDecorator<SpeciesBatchRowModel> decorator =
898                         getSpeciesColumnDecorator();
899 
900                 // init comparator with model species list
901                 comparator.init((SpeciesBatchDecorator) decorator, tableModel1.getRows());
902             }
903         });
904 
905         // create popup to change species decorator
906         SpeciesBatchRowHelper.installSpeciesColumnComparatorPopup(
907                 table,
908                 speciesColumn,
909                 getModel(),
910                 t("tutti.species.surveyCode.tip"),
911                 t("tutti.species.name.tip")
912         );
913     }
914 
915     @Override
916     protected JComponent getComponentToFocus() {
917         return getUI().getTable();
918     }
919 
920     @Override
921     public void onCloseUI() {
922         if (log.isDebugEnabled()) {
923             log.debug("Closing: " + ui);
924         }
925         ui.getSpeciesBatchAttachmentsButton().onCloseUI();
926         clearValidators();
927     }
928 
929     //------------------------------------------------------------------------//
930     //-- Public methods                                                     --//
931     //------------------------------------------------------------------------//
932 
933     public SampleCategoryModel getSampleCategoryModel() {
934         return sampleCategoryModel;
935     }
936 
937     public Integer getQualitative_unsorted_id() {
938         return qualitative_unsorted_id;
939     }
940 
941     public SpeciesBatchRowModel addBatch(CreateSpeciesBatchUIModel batchRootRowModel) {
942 
943         SpeciesBatchRowModel result = null;
944 
945         if (batchRootRowModel.isValid()) {
946 
947             SpeciesBatchTableModel tableModel = getTableModel();
948 
949             SpeciesBatchRowModel newRow = tableModel.createNewRow();
950             Species species = batchRootRowModel.getSpecies();
951             newRow.setSpecies(species);
952             newRow.setNumber(batchRootRowModel.getBatchCount());
953             CaracteristicQualitativeValue sampleCategory = batchRootRowModel.getSampleCategory();
954             SampleCategory category = newRow.getFirstSampleCategory();
955             category.setCategoryValue(sampleCategory);
956             category.setCategoryWeight(batchRootRowModel.getBatchSampleCategoryWeight());
957             newRow.setSampleCategory(category);
958             newRow.setWeight(batchRootRowModel.getBatchWeight());
959 
960             recomputeRowValidState(newRow);
961 
962             saveRow(newRow);
963 
964             int insertIndex = SpeciesBatchRowHelper.getIndexToInsert(
965                     tableModel.getRows(),
966                     newRow,
967                     getModel().getSpeciesSortMode(),
968                     getSpeciesColumnDecorator());
969 
970             if (log.isDebugEnabled()) {
971                 log.debug("Will insert at index: " + insertIndex);
972             }
973             tableModel.addNewRow(insertIndex, newRow);
974             JTables.doSelectCell(getTable(), insertIndex, 0);
975 
976             // update speciesUsed
977             addToSpeciesUsed(newRow);
978 
979             if (batchRootRowModel.getBatchCount() == null &&
980                     batchRootRowModel.getSelectedCategory() != null) {
981 
982                 // add first category
983                 splitBatch(batchRootRowModel.getSelectedCategory(),
984                            batchRootRowModel.getRows(),
985                            batchRootRowModel.getSampleWeight()
986                 );
987             }
988 
989             batchRootRowModel.setLastSampleCategoryUsed(batchRootRowModel.getSampleCategory());
990             result = newRow;
991         }
992 
993         return result;
994     }
995 
996     public void splitBatch(SampleCategoryModelEntry sampleCategoryDef,
997                            List<SplitSpeciesBatchRowModel> rows,
998                            Float totalRowWeight) {
999         JXTable table = getTable();
1000 
1001         // get selected row
1002         int insertRow = SwingUtil.getSelectedModelRow(table);
1003 
1004         SpeciesBatchTableModel tableModel = getTableModel();
1005         SpeciesBatchRowModel parentBatch = tableModel.getEntry(insertRow);
1006 
1007         // Create rows in batch table model
1008 
1009         //FIXME Weight check!!!
1010         Float parentWeight = parentBatch.getFinestCategory().getNotNullWeight();
1011         boolean subSample = parentWeight != null
1012                 && totalRowWeight != null
1013                 && getWeightUnit().isGreaterThan(parentWeight, totalRowWeight);
1014         List<SpeciesBatchRowModel> newBatches = Lists.newArrayList();
1015         for (SplitSpeciesBatchRowModel row : rows) {
1016             if (row.isValid()) {
1017 
1018                 // can keep this row
1019                 SpeciesBatchRowModel newBatch = tableModel.createNewRow();
1020 
1021                 loadBatchRow(parentBatch,
1022                              newBatch,
1023                              sampleCategoryDef.getCategoryId(),
1024                              row.getCategoryValue(),
1025                              row.getWeight(),
1026                              null);
1027 
1028                 newBatch.getFinestCategory().setSubSample(subSample);
1029 
1030                 recomputeRowValidState(newBatch);
1031                 newBatches.add(newBatch);
1032 
1033                 tableModel.addNewRow(++insertRow, newBatch);
1034                 JTables.selectFirstCellOnRow(getTable(), insertRow, false);
1035             }
1036         }
1037 
1038         // add new batches to his parent
1039         parentBatch.setChildBatch(newBatches);
1040 
1041         // save new batches
1042         saveRows(newBatches);
1043 
1044         SpeciesBatchUIModel model = getModel();
1045         model.setLeafNumber(model.getLeafNumber() + newBatches.size() - 1);
1046     }
1047 
1048     public void addSampleCategoryBatch(SpeciesBatchRowModel parentBatch,
1049                                        SampleCategoryModelEntry sampleCategoryDef,
1050                                        List<SplitSpeciesBatchRowModel> rows,
1051                                        Float totalRowWeight) {
1052 
1053         // get table model
1054         SpeciesBatchTableModel tableModel = getTableModel();
1055 
1056         // get insert row index
1057         int insertRow = tableModel.getNextChildRowIndex(parentBatch);
1058 
1059         // Create rows in batch table model
1060         List<SpeciesBatchRowModel> newBatches = Lists.newArrayList();
1061         for (SplitSpeciesBatchRowModel row : rows) {
1062             if (row.isEditable() && row.isValid()) {
1063 
1064                 // can keep this row
1065                 SpeciesBatchRowModel newBatch = tableModel.createNewRow();
1066 
1067                 loadBatchRow(parentBatch,
1068                              newBatch,
1069                              sampleCategoryDef.getCategoryId(),
1070                              row.getCategoryValue(),
1071                              row.getWeight(),
1072                              null);
1073 
1074                 recomputeRowValidState(newBatch);
1075                 newBatches.add(newBatch);
1076 
1077                 tableModel.addNewRow(insertRow++, newBatch);
1078             }
1079         }
1080 
1081         // add new batches to his parent
1082         List<SpeciesBatchRowModel> childBatch = parentBatch.getChildBatch();
1083         childBatch.addAll(newBatches);
1084         parentBatch.setChildBatch(childBatch);
1085 
1086         // re compute the sub sample property for all childs
1087         int categoryIndex = sampleCategoryModel.indexOf(sampleCategoryDef);
1088 
1089         //FIXME Weight check!!!
1090         Float parentWeight = parentBatch.getSampleCategoryByIndex(categoryIndex - 1).getNotNullWeight();
1091         boolean subSample = parentWeight != null && totalRowWeight != null
1092                 && getWeightUnit().isGreaterThan(parentWeight, totalRowWeight);
1093 
1094         Set<SpeciesBatchRowModel> shell = Sets.newHashSet();
1095         parentBatch.collectShell(shell);
1096 
1097         for (SpeciesBatchRowModel rowModel : shell) {
1098             rowModel.getSampleCategoryByIndex(categoryIndex).setSubSample(subSample);
1099         }
1100 
1101         // save new batches
1102         saveRows(newBatches);
1103 
1104         // update model number of leaf
1105         SpeciesBatchUIModel model = getModel();
1106         model.setLeafNumber(model.getLeafNumber() + newBatches.size() - 1);
1107 
1108         // update columns for the parent shell
1109         tableModel.updateShell(shell, SwingUtil.getSelectedModelColumn(getTable()));
1110     }
1111 
1112     public void updateTotalFromFrequencies(SpeciesBatchRowModel row) {
1113         List<SpeciesFrequencyRowModel> frequency = row.getFrequency();
1114 
1115         Integer totalNumber = 0;
1116         boolean onlyOneFrequency = false;
1117         if (CollectionUtils.isNotEmpty(frequency)) {
1118             for (SpeciesFrequencyRowModel frequencyModel : frequency) {
1119                 if (frequencyModel.getNumber() != null) {
1120                     totalNumber += frequencyModel.getNumber();
1121                 }
1122             }
1123             onlyOneFrequency = frequency.size() == 1;
1124         }
1125         row.setComputedNumber(totalNumber);
1126         row.getFinestCategory().setOnlyOneFrequency(onlyOneFrequency);
1127     }
1128 
1129     public void saveRows(Iterable<SpeciesBatchRowModel> rows) {
1130         for (SpeciesBatchRowModel row : rows) {
1131             recomputeRowValidState(row);
1132             saveRow(row);
1133         }
1134     }
1135 
1136     public String getFilterSpeciesBatchRootButtonText(int rootNumber) {
1137         return t("tutti.editSpeciesBatch.filterBatch.mode.root", rootNumber);
1138     }
1139 
1140     public void collectChildren(SpeciesBatchRowModel row,
1141                                 Set<SpeciesBatchRowModel> collectedRows) {
1142 
1143         if (!row.isBatchLeaf()) {
1144 
1145             for (SpeciesBatchRowModel batchChild : row.getChildBatch()) {
1146                 collectedRows.add(batchChild);
1147                 collectChildren(batchChild, collectedRows);
1148             }
1149         }
1150     }
1151 
1152     public SpeciesBatchRowModel loadBatch(SpeciesBatch aBatch,
1153                                           SpeciesBatchRowModel parentRow,
1154                                           List<SpeciesBatchRowModel> rows) {
1155 
1156         WeightUnit weightUnit = getWeightUnit();
1157 
1158         Integer id = aBatch.getIdAsInt();
1159 
1160         List<SpeciesBatchFrequency> frequencies = getPersistenceService().getAllSpeciesBatchFrequency(id);
1161         List<IndividualObservationBatch> individualObservations = getPersistenceService().getAllIndividualObservationBatchsForBatch(id);
1162 
1163         SpeciesBatchRowModel newRow =
1164                 new SpeciesBatchRowModel(weightUnit,
1165                                          getConfig().getIndividualObservationWeightUnit(),
1166                                          sampleCategoryModel,
1167                                          aBatch,
1168                                          frequencies,
1169                                          individualObservations,
1170                                          getDataContext().getDefaultIndividualObservationCaracteristics());
1171 
1172         for (IndividualObservationBatchRowModel obsRow : newRow.getIndividualObservation()) {
1173             List<Attachment> attachments = getPersistenceService().getAllAttachments(obsRow.getObjectType(), obsRow.getObjectId());
1174             obsRow.addAllAttachment(attachments);
1175         }
1176 
1177         List<Attachment> attachments = getPersistenceService().getAllAttachments(newRow.getObjectType(), newRow.getObjectId());
1178         newRow.addAllAttachment(attachments);
1179 
1180 
1181         // set the surveycode, do it only on the parent,
1182         // the species of the parent is set to the children in loadBatchRow
1183         if (parentRow == null && getContext().isProtocolFilled()) {
1184             // get the surveycode from the species list of the model
1185             List<Species> speciesList = speciesOrBenthosBatchUISupport.getReferentSpeciesWithSurveyCode();
1186             int i = speciesList.indexOf(newRow.getSpecies());
1187             if (i > -1) {
1188                 newRow.setSpecies(speciesList.get(i));
1189             }
1190         }
1191 
1192         Integer sampleCategoryId = aBatch.getSampleCategoryId();
1193 
1194         Preconditions.checkNotNull(
1195                 sampleCategoryId,
1196                 "Can't have a batch with no sample category, but was: " + aBatch);
1197 
1198         loadBatchRow(parentRow,
1199                      newRow,
1200                      sampleCategoryId,
1201                      aBatch.getSampleCategoryValue(),
1202                      weightUnit.fromEntity(aBatch.getSampleCategoryWeight()),
1203                      weightUnit.fromEntity(aBatch.getSampleCategoryComputedWeight()));
1204 
1205         rows.add(newRow);
1206 
1207         if (!aBatch.isChildBatchsEmpty()) {
1208 
1209             // create batch childs rows
1210 
1211             List<SpeciesBatchRowModel> batchChilds = Lists.newArrayListWithCapacity(aBatch.sizeChildBatchs());
1212 
1213             Float childrenWeights = 0f;
1214             for (SpeciesBatch childBatch : aBatch.getChildBatchs()) {
1215                 SpeciesBatchRowModel childRow = loadBatch(childBatch, newRow, rows);
1216                 if (childrenWeights != null) {
1217                     Float weight = childRow.getFinestCategory().getNotNullWeight();
1218                     if (weight == null) {
1219                         childrenWeights = null;
1220                     } else {
1221                         childrenWeights += weight;
1222                     }
1223                 }
1224 
1225                 batchChilds.add(childRow);
1226             }
1227 
1228             //FIXME Weight check!!!
1229             Float rowWeight = newRow.getFinestCategory().getNotNullWeight();
1230             boolean subSample = rowWeight != null
1231                     && childrenWeights != null
1232                     && weightUnit.isSmallerThan(childrenWeights, rowWeight);
1233             for (SpeciesBatchRowModel childRow : batchChilds) {
1234                 childRow.getFinestCategory().setSubSample(subSample);
1235             }
1236 
1237             newRow.setChildBatch(batchChilds);
1238         }
1239 
1240         return newRow;
1241     }
1242 
1243     protected WeightUnit getWeightUnit() {
1244         return speciesOrBenthosBatchUISupport.getWeightUnit();
1245     }
1246 
1247     public void removeFromSpeciesUsed(SpeciesBatchRowModel row) {
1248         Preconditions.checkNotNull(row);
1249         Preconditions.checkNotNull(row.getSpecies());
1250         SampleCategory<?> firstSampleCategory = row.getFirstSampleCategory();
1251         CaracteristicQualitativeValue categoryValue = (CaracteristicQualitativeValue) firstSampleCategory.getCategoryValue();
1252         Preconditions.checkNotNull(firstSampleCategory);
1253         if (log.isDebugEnabled()) {
1254             log.debug("Remove from speciesUsed: " + decorate(categoryValue) + " - " + decorate(row.getSpecies()));
1255         }
1256         SpeciesBatchUIModel model = getModel();
1257         model.getSpeciesUsed().remove(categoryValue, row.getSpecies());
1258 
1259         if (row.isBatchRoot()) {
1260             model.setRootNumber(model.getRootNumber() - 1);
1261 
1262             if (QualitativeValueId.SORTED_VRAC.getValue().equals(categoryValue.getIdAsInt())) {
1263                 model.decDistinctSortedSpeciesCount();
1264             } else if (QualitativeValueId.SORTED_HORS_VRAC.getValue().equals(categoryValue.getIdAsInt())) {
1265                 model.decDistinctUnsortedSpeciesCount();
1266             }
1267         }
1268     }
1269 
1270     //------------------------------------------------------------------------//
1271     //-- Internal methods                                                   --//
1272     //------------------------------------------------------------------------//
1273 
1274     public void saveRow(SpeciesBatchRowModel row) {
1275 
1276         Preconditions.checkNotNull(row);
1277         Preconditions.checkNotNull(row.getSpecies());
1278 
1279         SpeciesBatch catchBean = row.toEntity();
1280 
1281         FishingOperation fishingOperation = getModel().getFishingOperation();
1282         Preconditions.checkNotNull(fishingOperation);
1283         catchBean.setFishingOperation(fishingOperation);
1284 
1285         SpeciesBatchRowModel parent = row.getParentBatch();
1286         if (parent != null) {
1287             catchBean.setParentBatch(parent.toEntity());
1288         }
1289 
1290         if (TuttiEntities.isNew(catchBean)) {
1291 
1292             Integer parentBatchId = null;
1293             if (parent != null) {
1294                 parentBatchId = parent.getIdAsInt();
1295             }
1296 
1297             if (log.isDebugEnabled()) {
1298                 log.debug("Persist new species batch with parentId: " + parentBatchId);
1299             }
1300             catchBean = speciesOrBenthosBatchUISupport.createBatch(catchBean, parentBatchId);
1301             row.setId(catchBean.getId());
1302         } else {
1303             if (log.isDebugEnabled()) {
1304                 log.debug("Persist existing species batch: " + catchBean.getId() + " (parent : " + catchBean.getParentBatch() + ")");
1305             }
1306             speciesOrBenthosBatchUISupport.saveBatch(catchBean);
1307         }
1308 
1309         fireBatchSaved(row);
1310 
1311         List<SpeciesFrequencyRowModel> frequencyRows = row.getFrequency();
1312 
1313         List<SpeciesBatchFrequency> frequency = SpeciesFrequencyRowModel.toEntity(frequencyRows, catchBean);
1314 
1315         if (log.isDebugEnabled()) {
1316             log.debug("Will save " + frequency.size() + " frequencies.");
1317         }
1318         frequency = speciesOrBenthosBatchUISupport.saveBatchFrequencies(catchBean.getIdAsInt(), frequency);
1319 
1320         // push it back to row model
1321         frequencyRows = SpeciesFrequencyRowModel.fromEntity(getWeightUnit(), frequency);
1322         row.setFrequency(frequencyRows);
1323 
1324         List<IndividualObservationBatchRowModel> obsRows = row.getIndividualObservation()
1325                                                               .stream()
1326                                                               .filter(obsRow -> !obsRow.isEmpty())
1327                                                               .collect(Collectors.toList());
1328 
1329         List<IndividualObservationBatch> obs = IndividualObservationBatchRowModel.toEntity(obsRows, catchBean);
1330 
1331         if (log.isDebugEnabled()) {
1332             log.debug("Will save " + obs.size() + " observations.");
1333         }
1334         obs = getPersistenceService().saveBatchIndividualObservation(catchBean.getIdAsInt(), obs);
1335 
1336         // push it back to row model
1337         List<IndividualObservationBatchRowModel> savedObsRows =
1338                 IndividualObservationBatchRowModel.fromEntity(getConfig().getIndividualObservationWeightUnit(),
1339                                                               getDataContext().getDefaultIndividualObservationCaracteristics(),
1340                                                               obs);
1341 
1342         // save the not saved attachments
1343         for (int i = 0, n = obsRows.size(); i < n; i++) {
1344 
1345             IndividualObservationBatchRowModel obsRow = obsRows.get(i);
1346             IndividualObservationBatchRowModel savedObsRow = savedObsRows.get(i);
1347 
1348             List<Attachment> attachments = new ArrayList<>();
1349 
1350             for (Attachment attachment : obsRow.getAttachment()) {
1351 
1352                 if (TuttiEntities.isNew(attachment)) {
1353                     File file = new File(attachment.getPath());
1354                     attachment.setObjectId(savedObsRow.getObjectId());
1355                     attachment = getPersistenceService().createAttachment(attachment, file);
1356                 }
1357 
1358                 attachments.add(attachment);
1359 
1360             }
1361 
1362             savedObsRow.addAllAttachment(attachments);
1363         }
1364 
1365         row.setIndividualObservation(savedObsRows);
1366 
1367     }
1368 
1369     protected void loadBatchRow(SpeciesBatchRowModel parentRow,
1370                                 SpeciesBatchRowModel newRow,
1371                                 Integer sampleCategoryId,
1372                                 Serializable categoryValue,
1373                                 Float categoryWeight,
1374                                 Float categoryComputedWeight) {
1375 
1376         // get sample category from his type
1377         SampleCategory sampleCategory =
1378                 newRow.getSampleCategoryById(sampleCategoryId);
1379 
1380         // fill it
1381         sampleCategory.setCategoryValue(categoryValue);
1382         sampleCategory.setCategoryWeight(categoryWeight);
1383         sampleCategory.setComputedWeight(categoryComputedWeight);
1384 
1385         // push it back to row as his *main* sample category
1386         newRow.setSampleCategory(sampleCategory);
1387 
1388         if (parentRow != null) {
1389 
1390             // copy back parent data (mainly other sample categories)
1391 
1392             newRow.setSpecies(parentRow.getSpecies());
1393             if (parentRow.isSpeciesToConfirm()) {
1394 
1395                 // only set parent speciesToConfirm only if true in parent
1396                 newRow.setSpeciesToConfirm(true);
1397             }
1398             newRow.setParentBatch(parentRow);
1399 
1400             newRow.setSpecies(parentRow.getSpecies());
1401 
1402             for (Integer id : sampleCategoryModel.getSamplingOrder()) {
1403                 if (!id.equals(sampleCategoryId)) {
1404                     newRow.setSampleCategory(parentRow.getSampleCategoryById(id));
1405                 }
1406             }
1407         }
1408     }
1409 
1410     protected <C extends Serializable> void addSampleCategoryColumnToModel(TableColumnModel columnModel,
1411                                                                            ColumnIdentifier<SpeciesBatchRowModel> columnIdentifier,
1412                                                                            Decorator<C> decorator,
1413                                                                            TableCellRenderer defaultRenderer,
1414                                                                            WeightUnit weightUnit) {
1415         addColumnToModel(
1416                 columnModel,
1417                 SampleCategoryComponent.newEditor(decorator, weightUnit),
1418                 SampleCategoryComponent.newRender(defaultRenderer,
1419                                                   decorator,
1420                                                   getConfig().getColorComputedWeights(),
1421                                                   weightUnit),
1422                 columnIdentifier,
1423                 weightUnit);
1424     }
1425 
1426     protected void addToSpeciesUsed(SpeciesBatchRowModel row) {
1427         Preconditions.checkNotNull(row);
1428         Preconditions.checkNotNull(row.getSpecies());
1429         SampleCategory<?> firstSampleCategory = row.getFirstSampleCategory();
1430         Preconditions.checkNotNull(firstSampleCategory);
1431         CaracteristicQualitativeValue categoryValue = (CaracteristicQualitativeValue) firstSampleCategory.getCategoryValue();
1432         if (log.isDebugEnabled()) {
1433             log.debug("Add to speciesUsed: " +
1434                               decorate(categoryValue) +
1435                               " - " + decorate(row.getSpecies()));
1436         }
1437         SpeciesBatchUIModel model = getModel();
1438         model.getSpeciesUsed().put(categoryValue, row.getSpecies());
1439 
1440         if (QualitativeValueId.SORTED_VRAC.getValue().equals(categoryValue.getIdAsInt())) {
1441             model.incDistinctSortedSpeciesCount();
1442         } else if (QualitativeValueId.SORTED_HORS_VRAC.getValue().equals(categoryValue.getIdAsInt())) {
1443             model.incDistinctUnsortedSpeciesCount();
1444         }
1445 
1446         model.setRootNumber(model.getRootNumber() + 1);
1447     }
1448 
1449     protected SpeciesBatch convertRowToEntity(SpeciesBatchRowModel row,
1450                                               boolean convertParent) {
1451         Preconditions.checkNotNull(row.getFinestCategory());
1452         Preconditions.checkNotNull(row.getFinestCategory().getCategoryId());
1453         Preconditions.checkNotNull(row.getFinestCategory().getCategoryValue());
1454 
1455         SpeciesBatch catchBean = row.toEntity();
1456 
1457         if (convertParent && row.getParentBatch() != null) {
1458             SpeciesBatch parent = convertRowToEntity(row.getParentBatch(), true);
1459             catchBean.setParentBatch(parent);
1460         }
1461 
1462         return catchBean;
1463     }
1464 
1465     /**
1466      * Return all the sample category values (of the given
1467      * {@code sampleCategoryId}) for all brothers of the given {@code row}.
1468      *
1469      * @param row              the row
1470      * @param sampleCategoryId id of the sample category to seek in brothers of the given row
1471      * @return all the sample category values (of the given
1472      * {@code sampleCategoryId}) for all brothers of the given {@code row}.
1473      */
1474     public Set<Serializable> getSampleUsedValues(SpeciesBatchRowModel row, int sampleCategoryId) {
1475 
1476         Set<Serializable> usedValues = Sets.newHashSet();
1477         List<SpeciesBatchRowModel> childs;
1478         if (row.isBatchRoot()) {
1479 
1480             // on a root must take all his brothers (but have no common ancestor...)
1481             Species species = row.getSpecies();
1482             childs = Lists.newArrayList();
1483             for (SpeciesBatchRowModel rowToScan : getModel().getRows()) {
1484                 if (rowToScan.isBatchRoot() && species.equals(rowToScan.getSpecies())) {
1485                     childs.add(rowToScan);
1486                 }
1487             }
1488         } else {
1489             // on a son, must take all the brother directly from his father
1490             SpeciesBatchRowModel parentBatch = row.getParentBatch();
1491             childs = parentBatch.getChildBatch();
1492         }
1493 
1494         for (SpeciesBatchRowModel child : childs) {
1495             SampleCategory<?> category = child.getSampleCategoryById(sampleCategoryId);
1496             usedValues.add(category.getCategoryValue());
1497         }
1498         return usedValues;
1499     }
1500 
1501     protected SpeciesBatchDecoratorComparator<SpeciesBatchRowModel> getSpeciesRowComparator() {
1502         TableColumnExt speciesColumn = getTable().getColumnExt(SpeciesBatchTableModel.SPECIES);
1503 
1504         SpeciesBatchDecoratorComparator<SpeciesBatchRowModel> comparator =
1505                 (SpeciesBatchDecoratorComparator<SpeciesBatchRowModel>) speciesColumn.getComparator();
1506 
1507         SpeciesBatchDecorator<SpeciesBatchRowModel> decorator = getSpeciesColumnDecorator();
1508 
1509         boolean comparatorNull = comparator == null;
1510         if (comparatorNull) {
1511 
1512             // first time coming here, add the comparator
1513             comparator = (SpeciesBatchDecoratorComparator) decorator.getCurrentComparator();
1514         }
1515 
1516         if (comparatorNull) {
1517 
1518             // affect it to colum
1519             speciesColumn.setComparator(comparator);
1520         }
1521         return comparator;
1522     }
1523 
1524     protected SpeciesBatchDecorator<SpeciesBatchRowModel> getSpeciesColumnDecorator() {
1525         TableColumnExt speciesColumn = getTable().getColumnExt(SpeciesBatchTableModel.SPECIES);
1526         return (SpeciesBatchDecorator<SpeciesBatchRowModel>) SpeciesBatchRowHelper.getSpeciesColumnDecorator(speciesColumn);
1527     }
1528 
1529 }