Project

General

Profile

Download (21 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9

    
10
package eu.etaxonomy.taxeditor.bulkeditor.e4;
11

    
12
import java.io.Serializable;
13
import java.util.ArrayList;
14
import java.util.HashMap;
15
import java.util.List;
16
import java.util.Map;
17

    
18
import javax.annotation.PostConstruct;
19
import javax.annotation.PreDestroy;
20
import javax.inject.Inject;
21

    
22
import org.eclipse.core.runtime.IProgressMonitor;
23
import org.eclipse.core.runtime.NullProgressMonitor;
24
import org.eclipse.e4.core.contexts.IEclipseContext;
25
import org.eclipse.e4.core.di.annotations.Optional;
26
import org.eclipse.e4.core.services.events.IEventBroker;
27
import org.eclipse.e4.ui.di.Focus;
28
import org.eclipse.e4.ui.di.Persist;
29
import org.eclipse.e4.ui.di.UIEventTopic;
30
import org.eclipse.e4.ui.model.application.ui.MDirtyable;
31
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
32
import org.eclipse.e4.ui.services.EMenuService;
33
import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
34
import org.eclipse.jface.dialogs.MessageDialog;
35
import org.eclipse.jface.layout.GridDataFactory;
36
import org.eclipse.jface.viewers.ISelectionChangedListener;
37
import org.eclipse.jface.viewers.IStructuredSelection;
38
import org.eclipse.jface.viewers.StructuredSelection;
39
import org.eclipse.nebula.widgets.nattable.NatTable;
40
import org.eclipse.nebula.widgets.nattable.command.VisualRefreshCommand;
41
import org.eclipse.nebula.widgets.nattable.command.VisualRefreshCommandHandler;
42
import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration;
43
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
44
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
45
import org.eclipse.nebula.widgets.nattable.data.IRowIdAccessor;
46
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
47
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
48
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
49
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
50
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
51
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
52
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
53
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
54
import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
55
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
56
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
57
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
58
import org.eclipse.nebula.widgets.nattable.layer.stack.DefaultBodyLayerStack;
59
import org.eclipse.nebula.widgets.nattable.selection.RowSelectionModel;
60
import org.eclipse.nebula.widgets.nattable.selection.RowSelectionProvider;
61
import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
62
import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
63
import org.eclipse.nebula.widgets.nattable.style.theme.ModernNatTableThemeConfiguration;
64
import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;
65
import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;
66
import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuAction;
67
import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
68
import org.eclipse.swt.SWT;
69
import org.eclipse.swt.dnd.Clipboard;
70
import org.eclipse.swt.dnd.TextTransfer;
71
import org.eclipse.swt.dnd.Transfer;
72
import org.eclipse.swt.layout.GridData;
73
import org.eclipse.swt.layout.GridLayout;
74
import org.eclipse.swt.widgets.Composite;
75
import org.eclipse.swt.widgets.Display;
76
import org.eclipse.swt.widgets.Menu;
77

    
78
import ca.odell.glazedlists.SortedList;
79
import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
80
import eu.etaxonomy.cdm.api.conversation.IConversationEnabled;
81
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
82
import eu.etaxonomy.cdm.model.common.CdmBase;
83
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
84
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
85
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
86
import eu.etaxonomy.cdm.model.taxon.Taxon;
87
import eu.etaxonomy.cdm.persistence.hibernate.CdmDataChangeMap;
88
import eu.etaxonomy.taxeditor.bulkeditor.BulkEditorQuery;
89
import eu.etaxonomy.taxeditor.bulkeditor.input.AbstractBulkEditorInput;
90
import eu.etaxonomy.taxeditor.editor.ITaxonEditor;
91
import eu.etaxonomy.taxeditor.event.WorkbenchEventConstants;
92
import eu.etaxonomy.taxeditor.l10n.Messages;
93
import eu.etaxonomy.taxeditor.model.IDerivedUnitFacadePart;
94
import eu.etaxonomy.taxeditor.model.IDirtyMarkable;
95
import eu.etaxonomy.taxeditor.model.IPartContentHasDetails;
96
import eu.etaxonomy.taxeditor.model.IPartContentHasFactualData;
97
import eu.etaxonomy.taxeditor.model.IPartContentHasMedia;
98
import eu.etaxonomy.taxeditor.model.IPartContentHasSupplementalData;
99
import eu.etaxonomy.taxeditor.operation.IPostOperationEnabled;
100
import eu.etaxonomy.taxeditor.workbench.part.IE4SavablePart;
101

    
102
/**
103
 *
104
 * @author pplitzner
105
 * @since Sep 8, 2017
106
 *
107
 */
108
public class BulkEditorE4 implements IPartContentHasDetails, IConversationEnabled, IPostOperationEnabled,
109
        IDirtyMarkable, IDerivedUnitFacadePart, IPartContentHasFactualData,
110
        IPartContentHasSupplementalData, IPartContentHasMedia, IE4SavablePart, ITaxonEditor {
111

    
112
    public static final String TYPE_PROPERTY = Messages.BulkEditorE4_TYPE;
113

    
114
    @Inject
115
	private MDirtyable dirty;
116

    
117
    private AbstractBulkEditorInput input;
118

    
119
    private Composite topComposite;
120

    
121
    private ConversationHolder conversation;
122

    
123
    @Inject
124
    private ESelectionService selService;
125

    
126
    @Inject
127
    private IEventBroker eventBroker;
128

    
129
    private ISelectionChangedListener selectionChangedListener;
130

    
131
    @Inject
132
    IEclipseContext context;
133

    
134
    @Inject
135
    private MPart thisPart;
136

    
137
    private BulkEditorQuery lastQuery = null;
138

    
139
    private Composite bottomComposite;
140

    
141
    @Inject
142
    private EMenuService menuService;
143

    
144
    private NatTable natTable;
145

    
146
    private DefaultBodyLayerStack bodyLayer;
147

    
148
    private ListDataProvider<CdmBase> bodyDataProvider;
149

    
150
    @Inject
151
    public BulkEditorE4() {
152
	}
153

    
154
	@SuppressWarnings("unused")
155
    public void init(AbstractBulkEditorInput<?> input){
156
	    this.input = input;
157
	    this.conversation = input.getConversation();
158

    
159
	    new BulkEditorSearchE4(this, topComposite, SWT.NONE);
160
	    //layout needed because the search bar is added after @PostConstuct method
161
	    topComposite.getParent().layout();
162

    
163
	    thisPart.setLabel(input.getEditorName());
164

    
165
        if(input.getEntityUuid()!=null){
166
            performSearch(new BulkEditorQuery(input.getEntityUuid().toString()));
167
        }
168

    
169
        createTable();
170

    
171
        configureTable();
172

    
173
        styleTable();
174

    
175
        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
176

    
177
        //propagate selection
178
        selectionChangedListener = (event -> selService.setSelection(getSelection()));
179
        RowSelectionProvider<CdmBase> selectionProvider = new RowSelectionProvider<CdmBase>(bodyLayer.getSelectionLayer(), bodyDataProvider, true);
180
        selectionProvider.addSelectionChangedListener(selectionChangedListener);
181

    
182
        bottomComposite.layout();
183
	}
184

    
185
	private void createTable(){
186
	    ConfigRegistry configRegistry = new ConfigRegistry();
187
	    //property map
188
        Map<String, String> propertyToLabels = new HashMap<>();
189
        propertyToLabels.put(getEditorInput().getName(), getEditorInput().getName());
190
        propertyToLabels.put(TYPE_PROPERTY, TYPE_PROPERTY);
191
        String[] propertyNames = new String[] { input.getName(), TYPE_PROPERTY };
192
        //sorted list
193
        SortedList<CdmBase> sortedList = new SortedList<>(input.getModel(), input.getTitleComparator());
194
        //data provider
195
        BulkEditorPropertyAccessor columnPropertyAccessor = new BulkEditorPropertyAccessor(input);
196
        bodyDataProvider = new ListDataProvider<CdmBase>(sortedList,
197
                columnPropertyAccessor);
198
        DefaultColumnHeaderDataProvider colHeaderDataProvider = new DefaultColumnHeaderDataProvider(
199
                propertyNames, propertyToLabels);
200
        DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(
201
                bodyDataProvider);
202
        //body
203
        DataLayer dataLayer = new DataLayer(bodyDataProvider);
204
        dataLayer.registerCommandHandler(new VisualRefreshCommandHandler());
205
        GlazedListsEventLayer<CdmBase> eventLayer = new GlazedListsEventLayer<>(dataLayer, input.getModel());
206
        bodyLayer = new DefaultBodyLayerStack(eventLayer);
207
        //column
208
        DataLayer columnHeaderDataLayer = new DataLayer(colHeaderDataProvider);
209
        ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(
210
                columnHeaderDataLayer,
211
                bodyLayer, bodyLayer.getSelectionLayer());
212
        // add the SortHeaderLayer to the column header layer stack
213
        // as we use GlazedLists, we use the GlazedListsSortModel which
214
        // delegates the sorting to the SortedList
215
        final SortHeaderLayer<SpecimenDescription> sortHeaderLayer = new SortHeaderLayer<>(
216
                columnHeaderLayer,
217
                new GlazedListsSortModel<>(
218
                        sortedList,
219
                        columnPropertyAccessor,
220
                        configRegistry,
221
                        columnHeaderDataLayer));
222
        //row
223
        RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(new DataLayer(rowHeaderDataProvider, 50, 50),
224
//        DataLayer rowHeaderDataLayer = new DataLayer(rowHeaderDataProvider);
225
//        RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer,
226
                bodyLayer, bodyLayer.getSelectionLayer());
227
//        rowHeaderDataLayer.setDefaultRowHeight(20);
228
//        rowHeaderDataLayer.setColumnWidthByPosition(0, 50);
229
//        rowHeaderDataLayer.setColumnWidthByPosition(1, 200);
230
//        rowHeaderDataLayer.setColumnWidthByPosition(2, 100);
231

    
232
        //corner
233
        DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(
234
                colHeaderDataProvider, rowHeaderDataProvider);
235
        CornerLayer cornerLayer = new CornerLayer(new DataLayer(
236
                cornerDataProvider), rowHeaderLayer, sortHeaderLayer);
237
        //grid
238
        GridLayer gridLayer = new GridLayer(bodyLayer, sortHeaderLayer,
239
                rowHeaderLayer, cornerLayer);
240
        dataLayer.setColumnPercentageSizing(true);
241
        natTable = new NatTable(bottomComposite, gridLayer, false);
242
        natTable.setConfigRegistry(configRegistry);
243

    
244
        //full row selection
245
        bodyLayer.getSelectionLayer().setSelectionModel(new RowSelectionModel<CdmBase>(bodyLayer.getSelectionLayer(), bodyDataProvider, new IRowIdAccessor<CdmBase>() {
246
            @Override
247
            public Serializable getRowId(CdmBase rowObject) {
248
                return input.getModel().indexOf(rowObject);
249
            }
250
        }));
251
	}
252

    
253
	private void configureTable(){
254
        //+++CONTEXT MENU+++
255
        menuService.registerContextMenu(natTable, "eu.etaxonomy.taxeditor.bulkeditor.popupmenu.bulkeditor"); //$NON-NLS-1$
256
        // get the menu registered by EMenuService
257
        final Menu e4Menu = natTable.getMenu();
258
        // remove the menu reference from NatTable instance
259
        natTable.setMenu(null);
260
        natTable.addConfiguration(
261
                new AbstractUiBindingConfiguration() {
262
            @Override
263
            public void configureUiBindings(
264
                    UiBindingRegistry uiBindingRegistry) {
265
                // add e4 menu to NatTable
266
                new PopupMenuBuilder(natTable, e4Menu)
267
                    .build();
268

    
269
                // register the UI binding for header, corner and body region
270
                uiBindingRegistry.registerMouseDownBinding(
271
                        new MouseEventMatcher(
272
                                SWT.NONE,
273
                                GridRegion.BODY,
274
                                MouseEventMatcher.RIGHT_BUTTON),
275
                        new PopupMenuAction(e4Menu));
276
                uiBindingRegistry.registerMouseDownBinding(
277
                        new MouseEventMatcher(
278
                                SWT.NONE,
279
                                GridRegion.COLUMN_HEADER,
280
                                MouseEventMatcher.RIGHT_BUTTON),
281
                        new PopupMenuAction(e4Menu));
282
                uiBindingRegistry.registerMouseDownBinding(
283
                        new MouseEventMatcher(
284
                                SWT.NONE,
285
                                GridRegion.CORNER,
286
                                MouseEventMatcher.RIGHT_BUTTON),
287
                        new PopupMenuAction(e4Menu));
288
            }
289
        });
290

    
291
        //make cells editable to allow selecting the text
292
//        natTable.addConfiguration(new AbstractRegistryConfiguration() {
293
//            @Override
294
//            public void configureRegistry(IConfigRegistry configRegistry) {
295
//              //make cell editable
296
//                configRegistry.registerConfigAttribute(
297
//                        EditConfigAttributes.CELL_EDITABLE_RULE,
298
//                        IEditableRule.ALWAYS_EDITABLE,
299
//                        DisplayMode.EDIT);
300
//                //register editor
301
//                NullTextEditor nullTextEditor = new NullTextEditor();
302
//                ContextInjectionFactory.inject(nullTextEditor, context);
303
//                configRegistry.registerConfigAttribute(
304
//                        EditConfigAttributes.CELL_EDITOR,
305
//                        nullTextEditor,
306
//                        DisplayMode.EDIT);
307
//            }
308
//        });
309

    
310
        //enable sorting
311
        natTable.addConfiguration(new SingleClickSortConfiguration());
312
        //add default configuration because autoconfigure is set to false in constructor
313
        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
314
        natTable.configure();
315
	}
316

    
317
	private void styleTable(){
318
	    natTable.setTheme(new ModernNatTableThemeConfiguration());
319
	}
320

    
321
	/** {@inheritDoc} */
322
	@PostConstruct
323
	public void createPartControl(Composite parent) {
324
		parent.setLayout(new GridLayout());
325

    
326
		topComposite = new Composite(parent, SWT.NONE);
327
		topComposite.setLayout(new GridLayout());
328

    
329
		GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
330
		topComposite.setLayoutData(gridData);
331

    
332
		bottomComposite = new Composite(parent, SWT.NONE);
333
		bottomComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
334
		bottomComposite.setLayout(new GridLayout());
335
	}
336

    
337
	@Override
338
	@Persist
339
	public void save(IProgressMonitor monitor) {
340
	    save(monitor, true);
341
	}
342

    
343
	@Focus
344
	public void setFocus() {
345
        //make sure to bind again if maybe in another view the conversation was unbound
346
        if(conversation!=null && !conversation.isBound()){
347
            conversation.bind();
348
        }
349
        if(input!=null && input.getCdmEntitySession()!= null) {
350
            input.getCdmEntitySession().bind();
351
        }
352

    
353
	    //make sure to bind again if maybe in another view the conversation was unbound
354
	    eventBroker.post(WorkbenchEventConstants.CURRENT_ACTIVE_EDITOR, this);
355
	}
356

    
357
	@PreDestroy
358
	public void dispose() {
359
	    if(conversation!=null){
360
	        conversation.unregisterForDataStoreChanges(this);
361
	        conversation.close();
362
	    }
363
	    if(input!=null){
364
	        input.dispose();
365
	    }
366
	    dirty.setDirty(false);
367
	}
368

    
369
	public void save(IProgressMonitor monitor, boolean resetMerge) {
370
	    if (!input.getCdmEntitySession().isActive()){
371
            input.getCdmEntitySession().bind();
372
        }
373
	    input.saveModel(resetMerge);
374

    
375
	    IStructuredSelection selection = getSelection();
376

    
377
        dirty.setDirty(false);
378
        input.dispose();
379
        input.bind();
380
        conversation.commit(true);
381

    
382
        if (lastQuery != null){
383
            performSearch(lastQuery, selection);
384
        }
385
	}
386

    
387
	public void performSearch(BulkEditorQuery query) {
388
	    performSearch(query, null);
389
	}
390

    
391
	/** {@inheritDoc}
392
	 * @param selection */
393
    public void performSearch(BulkEditorQuery query, IStructuredSelection selection) {
394
        if (query != null) {
395
            // TODO check if dirty, prompt save
396
            if (isDirty()) {
397
                String[] labels = {Messages.BulkEditorE4_SAVE_AND_SEARCH, Messages.BulkEditorE4_DONT_SAVE,Messages.BulkEditorE4_CANCEL};
398
                MessageDialog dialog =new MessageDialog(topComposite.getShell(), Messages.BulkEditorE4_SAVE_CHANGES_TITLE, null, Messages.BulkEditorE4_SAVE_CHANGES_MESSAGE, MessageDialog.QUESTION,labels, 0);
399
                int result = dialog.open();
400
                if (result == 0) {
401
                    save(new NullProgressMonitor());
402
                } else if (result == 2){
403
                    return;
404
                }
405
            }
406
            dirty.setDirty(false);
407
            input.performSearch(query, selection);
408
            lastQuery = query;
409
        }
410
    }
411

    
412
    @Optional
413
    @Inject
414
    private void updateAfterSearch(@UIEventTopic(WorkbenchEventConstants.BULK_EDITOR_SEARCH_FINISHED)IStructuredSelection selection){
415
        if(selection!=null){
416
            setSelection(selection);
417
        }
418
//        //auto resize columns
419
//        InitializeAutoResizeColumnsCommand command = new InitializeAutoResizeColumnsCommand(
420
//                natTable, 2, natTable.getConfigRegistry(), new GCFactory(
421
//                        natTable));
422
//        natTable.doCommand(command);
423
//        command = new InitializeAutoResizeColumnsCommand(
424
//                natTable, 1, natTable.getConfigRegistry(), new GCFactory(
425
//                        natTable));
426
//        natTable.doCommand(command);
427
    }
428

    
429
    public void refresh(){
430
        natTable.doCommand(new VisualRefreshCommand());
431
    }
432

    
433
    public IStructuredSelection getSelection(){
434
        List<CdmBase> selection = new ArrayList<>();
435
        int[] fullySelectedRowPositions = bodyLayer.getSelectionLayer().getFullySelectedRowPositions();
436
        for (int i : fullySelectedRowPositions) {
437
            Object rowObject = bodyDataProvider.getRowObject(i);
438
            if(rowObject instanceof CdmBase){
439
                selection.add((CdmBase) rowObject);
440
            }
441
        }
442
        return new StructuredSelection(selection);
443
    }
444

    
445
    public void setSelection(IStructuredSelection selection){
446
        Object[] objects = selection.toArray();
447
        for (Object object : objects) {
448
            if(object instanceof CdmBase){
449
                bodyLayer.getSelectionLayer().selectRow(0, bodyDataProvider.indexOfRowObject((CdmBase) object), false, false);
450
            }
451
        }
452
    }
453

    
454
    public void setDirty(){
455
        dirty.setDirty(true);
456
    }
457

    
458
    public boolean isDirty() {
459
        return dirty.isDirty();
460
    }
461

    
462
    public AbstractBulkEditorInput getEditorInput() {
463
        return input;
464
    }
465

    
466
    public void copyDataToClipboard() {
467
        String textData = "";
468
        IStructuredSelection selection = getSelection();
469
        Object[] objects = selection.toArray();
470
        for (Object object : objects) {
471
            if(object instanceof CdmBase){
472
                textData += getEditorInput().getText((CdmBase)object);
473
            }
474
        }
475
        final TextTransfer textTransfer = TextTransfer.getInstance();
476
        final Clipboard clipboard = new Clipboard(Display.getDefault());
477
        try {
478
            clipboard.setContents(new Object[] { textData.toString() },
479
                    new Transfer[] { textTransfer });
480
        } finally {
481
            clipboard.dispose();
482
        }
483
    }
484

    
485
    @Override
486
    public void update(CdmDataChangeMap arg0) {
487
    }
488

    
489
    @Override
490
    public boolean canAttachMedia() {
491
        return true;
492
    }
493

    
494
    @Override
495
    public void changed(Object element) {
496
        if(element instanceof DerivedUnitFacade){
497
            DerivedUnit derivedUnit = ((DerivedUnitFacade) element).innerDerivedUnit();
498
            if(derivedUnit!=null){
499
                getEditorInput().addSaveCandidate(derivedUnit);
500
            }
501
            FieldUnit fieldUnit = ((DerivedUnitFacade) element).innerFieldUnit();
502
            if(fieldUnit!=null){
503
                getEditorInput().addSaveCandidate(fieldUnit);
504
            }
505
        }
506
        else if (element instanceof CdmBase) {
507
            getEditorInput().addSaveCandidate((CdmBase)element);
508
            input.replaceInModel((CdmBase) element);
509
        }
510
        dirty.setDirty(true);
511
        setSelection(new StructuredSelection(element));
512
    }
513

    
514
    @Override
515
    public void forceDirty() {
516
        dirty.setDirty(true);
517
    }
518

    
519
    @Override
520
    public boolean postOperation(CdmBase objectAffectedByOperation) {
521
        return false;
522
    }
523

    
524
    @Override
525
    public boolean onComplete() {
526
        return false;
527
    }
528

    
529
    @Override
530
    public ConversationHolder getConversationHolder() {
531
        return conversation;
532
    }
533

    
534
    public BulkEditorQuery getLastQuery() {
535
       return lastQuery;
536
    }
537

    
538
    public void setLastQuery(BulkEditorQuery lastQuery) {
539
       this.lastQuery = lastQuery;
540

    
541
    }
542

    
543
    @Override
544
    public Taxon getTaxon() {
545
        IStructuredSelection selection = getSelection();
546
        if(selection.size()==1) {
547
            Object object = selection.iterator().next();
548
            if(object instanceof Taxon){
549
                return (Taxon) object;
550
            }
551
        }
552
        return null;
553
    }
554

    
555
}
(1-1/5)