View Javadoc
1   package fr.ifremer.tutti.ui.swing.content.protocol.calcifiedpiecessampling;
2   
3   /*
4    * #%L
5    * Tutti :: UI
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2012 - 2016 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU General Public License as
13   * published by the Free Software Foundation, either version 3 of the
14   * License, or (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU General Public
22   * License along with this program.  If not, see
23   * <http://www.gnu.org/licenses/gpl-3.0.html>.
24   * #L%
25   */
26  
27  import fr.ifremer.tutti.persistence.entities.referential.Caracteristic;
28  import fr.ifremer.tutti.persistence.entities.referential.Species;
29  import fr.ifremer.tutti.service.DecoratorService;
30  import fr.ifremer.tutti.ui.swing.content.protocol.EditProtocolSpeciesRowModel;
31  import fr.ifremer.tutti.ui.swing.content.protocol.EditProtocolUIModel;
32  import fr.ifremer.tutti.ui.swing.util.AbstractTuttiBeanUIModel;
33  import fr.ifremer.tutti.ui.swing.util.AbstractTuttiUIHandler;
34  import fr.ifremer.tutti.ui.swing.util.TuttiUI;
35  import fr.ifremer.tutti.ui.swing.util.TuttiUIUtil;
36  import jaxx.runtime.SwingUtil;
37  import jaxx.runtime.swing.JAXXWidgetUtil;
38  import jaxx.runtime.swing.editor.cell.NumberCellEditor;
39  import jaxx.runtime.validator.swing.SwingValidator;
40  import org.jdesktop.swingx.JXTable;
41  import org.jdesktop.swingx.decorator.FontHighlighter;
42  import org.jdesktop.swingx.decorator.HighlightPredicate;
43  import org.jdesktop.swingx.decorator.Highlighter;
44  import org.jdesktop.swingx.table.DefaultTableColumnModelExt;
45  import org.jdesktop.swingx.table.TableColumnExt;
46  
47  import javax.swing.JComponent;
48  import javax.swing.JLabel;
49  import javax.swing.JMenuItem;
50  import javax.swing.SwingConstants;
51  import javax.swing.border.LineBorder;
52  import javax.swing.event.CellEditorListener;
53  import javax.swing.event.ChangeEvent;
54  import javax.swing.table.JTableHeader;
55  import javax.swing.table.TableCellRenderer;
56  import java.awt.Color;
57  import java.awt.Component;
58  import java.awt.Font;
59  import java.beans.PropertyChangeEvent;
60  import java.beans.PropertyChangeListener;
61  import java.util.ArrayList;
62  import java.util.List;
63  import java.util.Optional;
64  import java.util.TreeSet;
65  import java.util.stream.Collectors;
66  
67  import static org.nuiton.i18n.I18n.t;
68  
69  /**
70   * @author Kevin Morin (Code Lutin)
71   * @since 4.5
72   */
73  public class CalcifiedPiecesSamplingEditorUIHandler extends AbstractTuttiUIHandler<EditProtocolUIModel, CalcifiedPiecesSamplingEditorUI> {
74  
75      protected Caracteristic sexCaracteristic;
76  
77      protected final PropertyChangeListener rowChangeListener = new PropertyChangeListener() {
78  
79          @Override
80          public void propertyChange(PropertyChangeEvent evt) {
81              getModel().setModify(true);
82  
83              String propertyName = evt.getPropertyName();
84              CalcifiedPiecesSamplingEditorRowModel row = (CalcifiedPiecesSamplingEditorRowModel) evt.getSource();
85  
86              if (CalcifiedPiecesSamplingEditorRowModel.PROPERTY_MIN_SIZE.equals(propertyName)) {
87  
88                  List<CalcifiedPiecesSamplingEditorRowModel> cpsRows = getModel().getCpsRows();
89  
90                  int newRowIndex = cpsRows.indexOf(row);
91                  int previousRowIndex = newRowIndex - 1;
92                  CalcifiedPiecesSamplingEditorRowModel previousRow = cpsRows.get(previousRowIndex);
93  
94                  Integer newValue = (Integer) evt.getNewValue();
95  
96                  if (newValue == null) {
97                      row.setMinSize(0);
98  
99                  } else if (newValue <= previousRow.getMinSize() + 1
100                            || row.getMaxSize() != null && newValue >= row.getMaxSize()) {
101                     // si la nouvelle valeur sort de l'intervalle des deux lignes
102                     row.setMinSize((Integer) evt.getOldValue());
103 
104                 } else {
105                     previousRow.setMaxSize(newValue - 1);
106                     CalcifiedPiecesSamplingEditorTableModel model =
107                             (CalcifiedPiecesSamplingEditorTableModel) getUI().getCpsTable().getModel();
108                     model.fireTableRowsUpdated(previousRowIndex, newRowIndex);
109                 }
110 
111             } else if (CalcifiedPiecesSamplingEditorRowModel.PROPERTY_SAMPLING_INTERVAL.equals(propertyName)) {
112                 if (evt.getNewValue() == null) {
113                     row.setSamplingInterval(0);
114                 }
115             }
116         }
117     };
118 
119     @Override
120     public void beforeInit(CalcifiedPiecesSamplingEditorUI ui) {
121         super.beforeInit(ui);
122         sexCaracteristic = getPersistenceService().getSexCaracteristic();
123     }
124 
125     @Override
126     public void afterInit(CalcifiedPiecesSamplingEditorUI calcifiedPiecesSamplingEditorUI) {
127         initUI(calcifiedPiecesSamplingEditorUI);
128 
129         initBeanFilterableComboBox(ui.getSpeciesComboBox(), new ArrayList<>(), null, DecoratorService.WITH_SURVEY_CODE);
130 
131         ui.getSpeciesComboBox().getComboBoxModel().addWillChangeSelectedItemListener(evt -> {
132             Species species = (Species) evt.getNextSelectedItem();
133             if (species != null) {
134                 Optional<EditProtocolSpeciesRowModel> protocolSpecies = getModel().getProtocolSpeciesRowForSpecies(species);
135                 if (protocolSpecies.isPresent()) {
136                     getUI().getMaturityCheckBox().setSelected(protocolSpecies.get().getMaturityPmfm() != null);
137                 }
138             }
139         });
140 
141         JXTable cpsTable = ui.getCpsTable();
142 
143         DefaultTableColumnModelExt columnModel = initTableColumnModel();
144 
145         final CalcifiedPiecesSamplingEditorTableModel tableModel =
146                 new CalcifiedPiecesSamplingEditorTableModel(columnModel);
147         cpsTable.setModel(tableModel);
148         cpsTable.setColumnModel(columnModel);
149 
150         // when model change, then rebuild the species comparator + set model as modified
151         tableModel.addTableModelListener(e -> getModel().setModify(true));
152 
153         JTableHeader tableHeader = cpsTable.getTableHeader();
154 
155         // by default do not authorize to change column orders
156         tableHeader.setReorderingAllowed(false);
157 
158         // always scroll to selected row
159         SwingUtil.scrollToTableSelection(cpsTable);
160 
161         addHighlighters(cpsTable);
162 
163         // at the very end, set rows to model
164         getModel().addPropertyChangeListener(EditProtocolUIModel.PROPERTY_CPS_ROWS,
165                                              evt -> {
166                                                  List<CalcifiedPiecesSamplingEditorRowModel> rows =
167                                                          (List<CalcifiedPiecesSamplingEditorRowModel>) evt.getNewValue();
168                                                  for (CalcifiedPiecesSamplingEditorRowModel row : rows) {
169                                                      row.removePropertyChangeListener(rowChangeListener);
170                                                      row.addPropertyChangeListener(rowChangeListener);
171                                                  }
172                                                  tableModel.setRows(rows);
173                                                  tableModel.fireTableDataChanged();
174 
175                                                  getUI().getSpeciesComboBox().removeItems(rows.stream()
176                                                                                               .map(CalcifiedPiecesSamplingEditorRowModel::getProtocolSpecies)
177                                                                                               .map(EditProtocolSpeciesRowModel::getSpecies)
178                                                                                               .collect(Collectors.toList()));
179                                              });
180 
181     }
182 
183     @Override
184     protected void addHighlighters(JXTable cpsTable) {
185         CalcifiedPiecesSamplingEditorTableModel tableModel = (CalcifiedPiecesSamplingEditorTableModel) cpsTable.getModel();
186 
187         HighlightPredicate notSelectedPredicate = new HighlightPredicate.NotHighlightPredicate(HighlightPredicate.IS_SELECTED);
188         HighlightPredicate rowIsInvalidPredicate = (renderer, adapter) -> {
189 
190             boolean result = false;
191             if (adapter.isEditable()) {
192 
193                 int viewRow = adapter.row;
194                 int modelRow = adapter.convertRowIndexToModel(viewRow);
195                 AbstractTuttiBeanUIModel row = tableModel.getEntry(modelRow);
196                 result = !row.isValid();
197             }
198             return result;
199         };
200         HighlightPredicate rowIsValidPredicate =
201                 new HighlightPredicate.NotHighlightPredicate(rowIsInvalidPredicate);
202         Highlighter selectedHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
203                 HighlightPredicate.IS_SELECTED,
204                 getConfig().getColorSelectedRow());
205         cpsTable.addHighlighter(selectedHighlighter);
206 
207         // paint in a special color for read only cells (not selected)
208         Highlighter readOnlyHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
209                 new HighlightPredicate.AndHighlightPredicate(
210                         HighlightPredicate.READ_ONLY,
211                         notSelectedPredicate),
212                 getConfig().getColorRowReadOnly());
213         cpsTable.addHighlighter(readOnlyHighlighter);
214 
215         // paint in a special color for read only cells (selected)
216         Highlighter readOnlySelectedHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
217                 new HighlightPredicate.AndHighlightPredicate(
218                         HighlightPredicate.READ_ONLY,
219                         HighlightPredicate.IS_SELECTED),
220                 getConfig().getColorRowReadOnly().darker());
221         cpsTable.addHighlighter(readOnlySelectedHighlighter);
222 
223         // paint in a special color inValid rows (not selected)
224         Highlighter validHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
225                 new HighlightPredicate.AndHighlightPredicate(
226                         HighlightPredicate.EDITABLE,
227                         notSelectedPredicate,
228                         rowIsInvalidPredicate),
229                 getConfig().getColorRowInvalid());
230         cpsTable.addHighlighter(validHighlighter);
231 
232         // paint in a special color inValid rows (selected)
233         Highlighter validSelectedHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
234                 new HighlightPredicate.AndHighlightPredicate(
235                         HighlightPredicate.EDITABLE,
236                         HighlightPredicate.IS_SELECTED,
237                         rowIsInvalidPredicate),
238                 getConfig().getColorRowInvalid().darker());
239         cpsTable.addHighlighter(validSelectedHighlighter);
240 
241         // use configured color odd row (not for selected)
242 
243         HighlightPredicate speciesOrderEven = (renderer, adapter) -> {
244             int rowIndex = adapter.convertRowIndexToModel(adapter.row);
245             return tableModel.isSpeciesOrderEven(rowIndex);
246         };
247 
248         Highlighter evenHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
249                 new HighlightPredicate.AndHighlightPredicate(
250                         new HighlightPredicate.NotHighlightPredicate(speciesOrderEven),
251                         notSelectedPredicate,
252                         rowIsValidPredicate,
253                         HighlightPredicate.READ_ONLY),
254                 getConfig().getColorAlternateRow().darker());
255         cpsTable.addHighlighter(evenHighlighter);
256 
257         Highlighter oddHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
258                 new HighlightPredicate.AndHighlightPredicate(
259                         speciesOrderEven,
260                         notSelectedPredicate,
261                         rowIsValidPredicate,
262                         HighlightPredicate.READ_ONLY),
263                 Color.WHITE.darker());
264         cpsTable.addHighlighter(oddHighlighter);
265 
266         Highlighter evenNotReadOnlyHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
267                 new HighlightPredicate.AndHighlightPredicate(
268                         new HighlightPredicate.NotHighlightPredicate(speciesOrderEven),
269                         notSelectedPredicate,
270                         rowIsValidPredicate,
271                         HighlightPredicate.EDITABLE),
272                 getConfig().getColorAlternateRow());
273         cpsTable.addHighlighter(evenNotReadOnlyHighlighter);
274 
275         Highlighter oddNotReadOnlyHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
276                 new HighlightPredicate.AndHighlightPredicate(
277                         speciesOrderEven,
278                         notSelectedPredicate,
279                         rowIsValidPredicate,
280                         HighlightPredicate.EDITABLE),
281                 Color.WHITE);
282         cpsTable.addHighlighter(oddNotReadOnlyHighlighter);
283 
284         // use configured color odd row (for selected)
285         Highlighter evenSelectedHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
286                 new HighlightPredicate.AndHighlightPredicate(
287                         new HighlightPredicate.NotHighlightPredicate(speciesOrderEven),
288                         HighlightPredicate.IS_SELECTED,
289                         rowIsValidPredicate,
290                         HighlightPredicate.EDITABLE),
291                 getConfig().getColorSelectedRow());
292         cpsTable.addHighlighter(evenSelectedHighlighter);
293 
294 
295         // paint in a special color inValid rows
296         Font font = cpsTable.getFont().deriveFont(Font.BOLD);
297         Highlighter selectHighlighter = new FontHighlighter(HighlightPredicate.IS_SELECTED, font);
298         cpsTable.addHighlighter(selectHighlighter);
299 
300         HighlightPredicate rowWithoutValuePredicate = (renderer, adapter) -> {
301             int rowIndex = adapter.convertRowIndexToModel(adapter.row);
302             CalcifiedPiecesSamplingEditorRowModel entry = tableModel.getEntry(rowIndex);
303             Integer maxByLenghtStep = entry.getMaxByLenghtStep();
304             return maxByLenghtStep == null || maxByLenghtStep == 0;
305         };
306 
307         font = cpsTable.getFont().deriveFont(Font.ITALIC);
308         cpsTable.addHighlighter(new FontHighlighter(rowWithoutValuePredicate, font));
309     }
310 
311     protected DefaultTableColumnModelExt initTableColumnModel() {
312 
313         NumberCellEditor numberCellEditor = JAXXWidgetUtil.newNumberTableCellEditor(Integer.class, false);
314         numberCellEditor.getNumberEditor().setSelectAllTextOnError(true);
315         numberCellEditor.getNumberEditor().getTextField().setBorder(new LineBorder(Color.GRAY, 2));
316         numberCellEditor.getNumberEditor().setNumberPattern(TuttiUI.INT_6_DIGITS_PATTERN);
317 
318         // renderer to display the infinite symbol instead of null
319         TableCellRenderer infiniteRenderer = (table, value, isSelected, hasFocus, row, column) -> {
320 
321             Component result = table.getDefaultRenderer(Integer.class)
322                     .getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
323             if (result instanceof JLabel) {
324                 JLabel jLabel = (JLabel) result;
325                 String decoratedValue = getDecorator(Integer.class, DecoratorService.NULL_INFINITE).toString(value);
326                 jLabel.setText(decoratedValue);
327                 jLabel.setHorizontalTextPosition(SwingConstants.RIGHT);
328             }
329             return result;
330         };
331 
332         JXTable cpsTable = ui.getCpsTable();
333 
334         DefaultTableColumnModelExt columnModel = new DefaultTableColumnModelExt();
335 
336         // renderer to display the species
337         TableCellRenderer speciesRenderer = (table, value, isSelected, hasFocus, row, column) -> {
338 
339             Component result = table.getDefaultRenderer(String.class)
340                     .getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
341             if (result instanceof JLabel) {
342                 JLabel jLabel = (JLabel) result;
343                 EditProtocolSpeciesRowModel species = (EditProtocolSpeciesRowModel) value;
344                 jLabel.setText(decorate(species.getSpecies(), DecoratorService.WITH_SURVEY_CODE));
345             }
346             return result;
347         };
348         addColumnToModel(columnModel,
349                          null,
350                          speciesRenderer,
351                          CalcifiedPiecesSamplingEditorTableModel.SPECIES);
352 
353         // renderer to display infinite instead of null
354 
355         addColumnToModel(columnModel,
356                          null,
357                          newTableCellRender(Boolean.class, DecoratorService.MATURITY),
358                          CalcifiedPiecesSamplingEditorTableModel.MATURITY);
359 
360         TableColumnExt sexColumn = addBooleanColumnToModel(columnModel, CalcifiedPiecesSamplingEditorTableModel.SEX, cpsTable);
361 
362         // sex cell listener to update the sex value of other rows with the same protocolSpecies
363         sexColumn.getCellEditor().addCellEditorListener(new CellEditorListener() {
364 
365             @Override
366             public void editingStopped(ChangeEvent e) {
367 
368                 int selectedRow = getUI().getCpsTable().getSelectedRow();
369 
370                 List<CalcifiedPiecesSamplingEditorRowModel> cpsRows = getModel().getCpsRows();
371                 CalcifiedPiecesSamplingEditorRowModel row = cpsRows.get(selectedRow);
372 
373                 List<CalcifiedPiecesSamplingEditorRowModel> rowsToChangeSex =
374                         cpsRows.stream().filter(r -> r.getProtocolSpecies().equals(row.getProtocolSpecies())).collect(Collectors.toList());
375 
376                 rowsToChangeSex.forEach(r -> r.setSex(row.isSex()));
377 
378                 TreeSet<Integer> indexesToUpdate =
379                         new TreeSet<>(rowsToChangeSex.stream().map(cpsRows::indexOf).collect(Collectors.toSet()));
380 
381                 CalcifiedPiecesSamplingEditorTableModel tableModel =
382                         (CalcifiedPiecesSamplingEditorTableModel) getUI().getCpsTable().getModel();
383                 tableModel.fireTableRowsUpdated(indexesToUpdate.first(), indexesToUpdate.last());
384             }
385 
386             @Override
387             public void editingCanceled(ChangeEvent e) {
388                 // do nothing
389             }
390         });
391 
392         addIntegerColumnToModel(columnModel, CalcifiedPiecesSamplingEditorTableModel.MIN_SIZE, TuttiUI.INT_6_DIGITS_PATTERN, cpsTable);
393 
394         addColumnToModel(columnModel, null, infiniteRenderer, CalcifiedPiecesSamplingEditorTableModel.MAX_SIZE);
395 
396         addColumnToModel(columnModel, numberCellEditor, infiniteRenderer, CalcifiedPiecesSamplingEditorTableModel.MAX_BY_LENGHT_STEP);
397 
398         addIntegerColumnToModel(columnModel, CalcifiedPiecesSamplingEditorTableModel.SAMPLING_INTERVAL, TuttiUI.INT_6_DIGITS_PATTERN, cpsTable);
399 
400         addColumnToModel(columnModel, numberCellEditor, infiniteRenderer, CalcifiedPiecesSamplingEditorTableModel.OPERATION_LIMITATION);
401 
402         addColumnToModel(columnModel, numberCellEditor, infiniteRenderer, CalcifiedPiecesSamplingEditorTableModel.ZONE_LIMITATION);
403 
404         return columnModel;
405     }
406 
407     @Override
408     protected void beforeOpenPopup(int modelRowIndex, int modelColumnIndex) {
409         super.beforeOpenPopup(modelRowIndex, modelColumnIndex);
410 
411         boolean speciesDeletable;
412         boolean splitEnabled;
413         boolean rowDeletable;
414 
415         JMenuItem deleteSpeciesMenu = getUI().getDeleteSpeciesMenu();
416 
417         int selectedRowCount = getUI().getCpsTable().getSelectedRowCount();
418 
419         if (selectedRowCount > 1) {
420 
421             // multi sélection
422             speciesDeletable = true;
423             splitEnabled = false;
424             rowDeletable = false;
425 
426             deleteSpeciesMenu.setText(t("tutti.editCps.deleteMoreThanOneSpecies"));
427             deleteSpeciesMenu.setToolTipText(t("tutti.editCps.deleteMoreThanOneSpecies.tip"));
428 
429         } else {
430 
431             speciesDeletable = modelRowIndex >= 0 && modelRowIndex < getModel().getCpsRows().size();
432             splitEnabled = speciesDeletable;
433             rowDeletable = speciesDeletable;
434 
435             if (speciesDeletable) {
436 
437                 CalcifiedPiecesSamplingEditorRowModel selectedRow = getModel().getCpsRows().get(modelRowIndex);
438 
439                 Integer minSize = selectedRow.getMinSize();
440 
441                 splitEnabled = selectedRow.getMaxSize() == null
442                         || selectedRow.getMaxSize() - minSize > 1;
443                 rowDeletable = minSize > 0;
444 
445             }
446 
447             deleteSpeciesMenu.setText(t("tutti.editCps.deleteOneSpecies"));
448             deleteSpeciesMenu.setToolTipText(t("tutti.editCps.deleteOneSpecies.tip"));
449 
450         }
451 
452         getUI().getSplitCpsRowMenu().setEnabled(splitEnabled);
453 
454         getUI().getDeleteCpsRowMenu().setEnabled(rowDeletable);
455 
456         deleteSpeciesMenu.setEnabled(speciesDeletable);
457 
458     }
459 
460     @Override
461     protected JComponent getComponentToFocus() {
462         return null;
463     }
464 
465     @Override
466     public void onCloseUI() {
467 
468     }
469 
470     @Override
471     public SwingValidator<EditProtocolUIModel> getValidator() {
472         return null;
473     }
474 
475 
476     public CalcifiedPiecesSamplingEditorRowModel createNewRow(Species species,
477                                                               Boolean maturity) {
478 
479         Optional<EditProtocolSpeciesRowModel> speciesProtocolRow = getEditProtocolSpeciesRowModel(species);
480         boolean sex = speciesProtocolRow.isPresent()
481                       && speciesProtocolRow.get().containsMandatorySampleCategoryId(sexCaracteristic.getIdAsInt());
482 
483         return createNewRow(speciesProtocolRow.orElse(null), maturity, sex, 0, null);
484     }
485 
486     public CalcifiedPiecesSamplingEditorRowModel createNewRow(Species species,
487                                                               Boolean maturity,
488                                                               boolean sex,
489                                                               Integer minSize,
490                                                               Integer maxSize) {
491 
492         Optional<EditProtocolSpeciesRowModel> speciesProtocolRow = getEditProtocolSpeciesRowModel(species);
493 
494         return createNewRow(speciesProtocolRow.orElse(null), maturity, sex, minSize, maxSize);
495     }
496 
497     public CalcifiedPiecesSamplingEditorRowModel createNewRow(EditProtocolSpeciesRowModel species,
498                                                               Boolean maturity,
499                                                               boolean sex,
500                                                               Integer minSize,
501                                                               Integer maxSize) {
502 
503         JXTable cpsTable = getUI().getCpsTable();
504         CalcifiedPiecesSamplingEditorTableModel tableModel = (CalcifiedPiecesSamplingEditorTableModel) cpsTable.getModel();
505 
506         CalcifiedPiecesSamplingEditorRowModel newRow = tableModel.createNewRow(species, maturity, sex, minSize, maxSize);
507         newRow.removePropertyChangeListener(rowChangeListener);
508         newRow.addPropertyChangeListener(rowChangeListener);
509 
510         return newRow;
511     }
512 
513     protected Optional<EditProtocolSpeciesRowModel> getEditProtocolSpeciesRowModel(Species species) {
514 
515         Optional<EditProtocolSpeciesRowModel> speciesProtocolRow = getModel().getSpeciesRow()
516                 .stream()
517                 .filter(sp -> species.equals(sp.getSpecies()))
518                 .findFirst();
519 
520         if (!speciesProtocolRow.isPresent()) {
521             speciesProtocolRow = getModel().getBenthosRow()
522                     .stream()
523                     .filter(sp -> species.equals(sp.getSpecies()))
524                     .findFirst();
525         }
526         return speciesProtocolRow;
527     }
528 }