Project

General

Profile

Download (26 KB) Statistics
| Branch: | Revision:
1
/**
2
* Copyright (C) 2020 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
package eu.etaxonomy.cdm.app.pesi.merging;
10

    
11
import java.util.HashSet;
12
import java.util.List;
13
import java.util.Optional;
14
import java.util.Set;
15
import java.util.UUID;
16
import java.util.stream.Collectors;
17

    
18
import org.apache.log4j.Logger;
19
import org.springframework.transaction.TransactionStatus;
20

    
21
import eu.etaxonomy.cdm.api.service.DeleteResult;
22
import eu.etaxonomy.cdm.api.service.config.SynonymDeletionConfigurator;
23
import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
24
import eu.etaxonomy.cdm.app.common.CdmDestinations;
25
import eu.etaxonomy.cdm.common.CdmRegEx;
26
import eu.etaxonomy.cdm.common.CdmUtils;
27
import eu.etaxonomy.cdm.database.DbSchemaValidation;
28
import eu.etaxonomy.cdm.database.ICdmDataSource;
29
import eu.etaxonomy.cdm.io.api.application.CdmIoApplicationController;
30
import eu.etaxonomy.cdm.io.common.mapping.out.DbLastActionMapper;
31
import eu.etaxonomy.cdm.io.pesi.erms.ErmsTransformer;
32
import eu.etaxonomy.cdm.io.pesi.out.PesiTransformer;
33
import eu.etaxonomy.cdm.model.common.Annotation;
34
import eu.etaxonomy.cdm.model.common.CdmBase;
35
import eu.etaxonomy.cdm.model.common.Credit;
36
import eu.etaxonomy.cdm.model.common.Extension;
37
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
38
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
39
import eu.etaxonomy.cdm.model.common.Marker;
40
import eu.etaxonomy.cdm.model.description.TaxonDescription;
41
import eu.etaxonomy.cdm.model.name.TaxonName;
42
import eu.etaxonomy.cdm.model.taxon.Synonym;
43
import eu.etaxonomy.cdm.model.taxon.Taxon;
44
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
45
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
46
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
47
import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
48

    
49
/**
50
 * @author a.mueller
51
 * @since 20.01.2020
52
 */
53
public class PesiCommandLineMerge extends PesiMergeBase {
54

    
55
    private static final Logger logger = Logger.getLogger(PesiCommandLineMerge.class);
56

    
57
    static final ICdmDataSource pesiSource = CdmDestinations.cdm_pesi2019_final();
58

    
59
    private CdmIoApplicationController app;
60

    
61
    private void invoke(ICdmDataSource source){
62
        app = CdmIoApplicationController.NewInstance(source, DbSchemaValidation.VALIDATE, false);
63
        doInvoke();
64
    }
65

    
66
    private void doInvoke(){
67
        List<List<String>> fileData = null;
68
        String next = nextMerge(fileData);
69
        while(next.equalsIgnoreCase("m")|| next.equals("f")){
70
            TransactionStatus tx = app.startTransaction();
71
            TaxonInformation taxonInformation;
72
            if (next.equalsIgnoreCase("f")){
73
                if (fileData == null){
74
                    fileData = getFileData();
75
                }else if (fileData.isEmpty()){
76
                    fileData = null;
77
                    next = nextMerge(fileData);
78
                    continue;
79
                }
80
                taxonInformation = readLineFromFile(fileData);
81
                if (taxonInformation == null){
82
                    app.rollbackTransaction(tx);
83
                    nextMerge(fileData);
84
                    continue;
85
                }
86
            }else{
87
                TaxonBase<?>[] taxa = null;
88
                while (taxa == null) {
89
                    taxa = readTaxa();
90
                }
91
                taxonInformation = new TaxonInformation();
92
                taxonInformation.taxon2 = taxa[0];
93
                taxonInformation.taxon1 = taxa[1];
94
            }
95

    
96
            try {
97
                mergeTaxa(tx, taxonInformation);
98
            } catch (Exception e) {
99
                e.printStackTrace();
100
                app.rollbackTransaction(tx);
101
                continue;
102
            }
103
            next = nextMerge(fileData);
104
        }
105
    }
106

    
107
    private boolean mergeTaxa(TransactionStatus tx, TaxonInformation taxonInformation) {
108
        boolean commit = compareTaxa(taxonInformation);
109
        if (commit){
110
            moveTaxonInformation(taxonInformation);
111
        }
112
        if (commit){
113
            app.commitTransaction(tx);
114
            if (isAutomatedAnswer(taxonInformation)){
115
                removeTaxon(taxonInformation.taxon2);
116
            }else if (booleanAnswer("Information moved. Delete old taxon")){
117
                removeTaxon(taxonInformation.taxonToUse == 2 ? taxonInformation.taxon1 : taxonInformation.taxon2);
118
            }
119
        }else{
120
            app.rollbackTransaction(tx);
121
        }
122
        return commit;
123
    }
124

    
125
    private boolean isAutomatedAnswer(TaxonInformation taxonInformation) {
126
        return taxonInformation.taxonToUse == 2 && taxonInformation.nameToUse == 2 && false;
127
    }
128

    
129
    private class TaxonInformation{
130
        TaxonBase<?> taxon1;
131
        TaxonBase<?> taxon2;
132
        int taxonToUse = 1;   //
133
        int nameToUse = 1;
134
    }
135

    
136
    /**
137
     * Reads a line from the file, returns it's taxon information and removes
138
     * the line from the input list.
139
     */
140
    private TaxonInformation readLineFromFile(List<List<String>> fileData) {
141
        List<String> line = fileData.get(0);
142
        TaxonInformation taxonInformation = new TaxonInformation();
143
        taxonInformation.taxon1 = taxonByString(line.get(0));
144
        taxonInformation.taxon2 = taxonByString(line.get(1));
145

    
146
        if (taxonInformation.taxon1 == null || taxonInformation.taxon2 == null){
147
            boolean cancel = booleanAnswer("Taxon1 or Taxon2 could not be read from DB! Cancel record");
148
            if (cancel){
149
                fileData.remove(0);
150
            }
151
            return null;
152
        }
153

    
154
        try {
155
            Integer taxonToUse = Integer.valueOf(line.get(2));
156
            if (1 != taxonInformation.taxonToUse){
157
                booleanAnswer("Stay taxon is not '1'");
158
            }
159
            Integer nameToUse = "".equals(line.get(3))? taxonToUse: Integer.valueOf(line.get(3));
160
            if (taxonToUse != 1 && taxonToUse != 2 && taxonToUse != 0){
161
                boolean cancel = booleanAnswer("taxonToUse is not 0, 1 or 2. Cancel record");
162
                if (cancel){
163
                    fileData.remove(0);
164
                }
165
                return null;
166
            }else if (taxonToUse == 0){
167
                logger.warn("Record marked as homonym. No merge: " + taxonInformation.taxon1.getName().getNameCache());
168
                fileData.remove(0);
169
                return null;
170
            }else{
171
                taxonInformation.taxonToUse = taxonToUse;
172
            }
173
            if (nameToUse == null){
174
                nameToUse = taxonToUse;
175
            }
176
            if (nameToUse != 1 && nameToUse != 2){
177
                logger.warn("Name to use has incorrect value: " +  nameToUse);
178
            }else{
179
                taxonInformation.nameToUse = nameToUse;
180
            }
181
        } catch (NumberFormatException e) {
182
            e.printStackTrace();
183
            taxonInformation = null;
184
        }
185
        fileData.remove(0);
186
        return taxonInformation;
187
    }
188

    
189
    private List<List<String>> getFileData() {
190
        List<List<String>> result = null;
191
        while(result == null){
192
            String input = CdmUtils.readInputLine("Path and filename: ");
193
            result = readCsvFile(input);
194
        }
195
        return result;
196
    }
197

    
198
    private String nextMerge(List<List<String>> fileData) {
199
        if (fileData != null){
200
            return "f";
201
        }
202
        do{
203
            String input = CdmUtils.readInputLine("Next input: manual[m], file[f], quit[q]: ");
204
            if (input.matches("[mMfFqQ]")){
205
                return input;
206
            }
207
        }while (true);
208
    }
209

    
210
    private boolean compareTaxa(TaxonInformation taxonInformation) {
211
        TaxonBase<?> removeTaxon = taxonInformation.taxon2;
212
        TaxonBase<?> stayTaxon = taxonInformation.taxon1;
213
        if(removeTaxon.getId() == stayTaxon.getId()){
214
            logger.warn("Same taxon: "+  removeTaxon.getTitleCache());
215
            return false;
216
        }
217
        String nc1 = removeTaxon.getName().getNameCache();
218
        String nc2 = stayTaxon.getName().getNameCache();
219

    
220
        String ft1 = removeTaxon.getName().getFullTitleCache();
221
        String ft2 = stayTaxon.getName().getFullTitleCache();
222
        System.out.println("Remove " + getStatusStr(removeTaxon) + ft1);
223
        System.out.println("Stay   " + getStatusStr(stayTaxon) + ft2);
224
        boolean isStandard = isAutomatedAnswer(taxonInformation);
225
        if (!nc1.equals(nc2)){
226
            return booleanAnswer("Name Cache differs!!! Do you really want to merge???");
227
        }else if (!ft1.equals(ft2)){
228
            return isStandard || booleanAnswer("Full title cache differs! Do you really want to merge anyway");
229
        }else{
230
            return isStandard || booleanAnswer("Same title. Merge");
231
        }
232
    }
233

    
234
    private String getStatusStr(TaxonBase<?> taxon) {
235
        //TODO MAN and Taxon Synonyms
236
        if (taxon.isInstanceOf(Synonym.class)){
237
            return "Syn: ";
238
        }else{
239
            return "Acc: ";
240
        }
241
    }
242

    
243
    private void removeTaxon(TaxonBase<?> taxonBase) {
244
        DeleteResult result;
245
        if (taxonBase.isInstanceOf(Taxon.class)){
246
            Taxon taxonToRemove = CdmBase.deproxy(taxonBase, Taxon.class);
247
            TaxonDeletionConfigurator config = new TaxonDeletionConfigurator();
248
            if (isTaxonSynonym(taxonToRemove)){
249
                result = app.getTaxonService().deleteTaxon(taxonToRemove.getUuid(), config, null);
250
            }else{
251
                TaxonNode nodeToRemove = taxonToRemove.getTaxonNodes().iterator().next();
252
                result = app.getTaxonNodeService().deleteTaxonNode(nodeToRemove.getUuid(), config);
253
            }
254
        }else{
255
            Synonym syn = CdmBase.deproxy(taxonBase, Synonym.class);
256
            SynonymDeletionConfigurator config = new SynonymDeletionConfigurator();
257
            result = app.getTaxonService().deleteSynonym(syn.getUuid(), config);
258
        }
259
        if (!result.isOk()){
260
            System.out.println("Remove taxon was not successful.");
261
        }
262
    }
263

    
264
    private boolean booleanAnswer(String message) {
265
        String answer = "";
266
        while (!(answer.equalsIgnoreCase("y") || answer.equalsIgnoreCase("n"))){
267
            answer = CdmUtils.readInputLine(message + " (y/n)? ");
268
        }
269
        return answer.equalsIgnoreCase("y");
270
    }
271

    
272
    private boolean moveTaxonInformation(TaxonInformation taxonInformation) {
273
        try {
274

    
275
            TaxonBase<?> removeTaxon = CdmBase.deproxy(taxonInformation.taxonToUse == 2 ? taxonInformation.taxon1: taxonInformation.taxon2);
276
            TaxonBase<?> stayTaxon = CdmBase.deproxy(taxonInformation.taxonToUse == 2 ? taxonInformation.taxon2 : taxonInformation.taxon1);
277

    
278
            //mergeTaxa;
279
            mergeSources(removeTaxon, stayTaxon);
280
            mergeAnnotations(removeTaxon, stayTaxon);
281
            mergeMarkers(removeTaxon, stayTaxon);
282
            //TODO for
283
            mergeExtensions(removeTaxon, stayTaxon);
284
            mergeCredits(removeTaxon, stayTaxon);
285
            if (removeTaxon.isInstanceOf(Taxon.class)){
286
                Taxon removeAccTaxon = CdmBase.deproxy(removeTaxon, Taxon.class);
287

    
288
                Taxon stayAccTaxon = accTaxon(stayTaxon);
289
                mergeDescriptions(removeAccTaxon, accTaxon(stayTaxon));
290
                boolean isTaxonSynonym = isTaxonSynonym(removeAccTaxon);
291
                mergeSynonyms(removeAccTaxon, stayAccTaxon, isTaxonSynonym);
292
                mergeChildren(removeAccTaxon, stayAccTaxon, isTaxonSynonym);
293
                //TODO taxon synonym relations
294
                mergeTaxonRelations(removeAccTaxon, stayAccTaxon, isTaxonSynonym);
295
            }
296

    
297
            //mergeNames;
298
            TaxonName removeName;
299
            TaxonName stayName;
300
            if (taxonInformation.nameToUse == taxonInformation.taxonToUse){
301
                removeName = CdmBase.deproxy(removeTaxon.getName());
302
                stayName = CdmBase.deproxy(stayTaxon.getName());
303
            }else{
304
                removeName = CdmBase.deproxy(stayTaxon.getName());
305
                stayName = CdmBase.deproxy(removeTaxon.getName());
306
                stayTaxon.setName(stayName);
307
            }
308
            //TODO unclear if name information should be merged at all
309
            mergeSources(removeName, stayName);
310
            mergeAnnotations(removeName, stayName);
311
            mergeMarkers(removeName, stayName);
312
            mergeExtensions(removeName, stayName);
313
            mergeCredits(removeName, stayName);
314
            mergeNameRelationships(removeName, stayName);
315
            mergeHybridRelationships(removeName, stayName);
316
            mergeNameDescriptions(removeName, stayName);
317

    
318
            if(isAutomatedAnswer(taxonInformation)){
319
                return true;
320
            }else{
321
                return booleanAnswer("Commit moved information");
322
            }
323
        } catch (CloneNotSupportedException e) {
324
            e.printStackTrace();
325
            return false;
326
        }
327
    }
328

    
329
    private boolean isTaxonSynonym(Taxon removeAccTaxon) {
330
        for (TaxonRelationship rel:  removeAccTaxon.getRelationsFromThisTaxon()){
331
            boolean isPseudo = TaxonRelationshipType.pseudoTaxonUuids().contains(rel.getType().getUuid());
332
            if (isPseudo){
333
                return true;
334
            }
335
        }
336
        return false;
337
    }
338

    
339
    private Taxon accTaxon(TaxonBase<?> stayTaxon) {
340
        if (stayTaxon.isInstanceOf(Synonym.class)){
341
            return CdmBase.deproxy(stayTaxon, Synonym.class).getAcceptedTaxon();
342
        }else{
343
            return CdmBase.deproxy(stayTaxon, Taxon.class);
344
        }
345
    }
346

    
347
    private boolean mergeTaxonRelations(Taxon removeTaxon, Taxon stayTaxon, boolean isTaxonSynonym) {
348
        if (isTaxonSynonym){
349
            if (!removeTaxon.getRelationsToThisTaxon().isEmpty()){
350
                logger.warn("taxon synonym has taxon relations to itself. This should not happen. Handle manually.");
351
                return false;
352
            }else{
353
                return true;
354
            }
355
        }
356
        for (TaxonRelationship rel : removeTaxon.getRelationsToThisTaxon()){
357
            System.out.println("Move taxon relationship: " + rel.getType().getTitleCache() + ": " + rel.getFromTaxon().getTitleCache());
358

    
359
            rel.setToTaxon(stayTaxon);
360
//            if (!synonymExists()){
361
//                //TODO homotypical group
362
//                stayTaxon.addSynonym(synonym, synonym.getType());
363
//            }else{
364
//                //TODO merge synonym names
365
//            }
366
        }
367
        if(!removeTaxon.getRelationsFromThisTaxon().isEmpty()){
368
            logger.warn("Taxon-from-relations not yet implemented");
369
        }
370
        return true;
371
    }
372

    
373
    private void mergeNameDescriptions(TaxonName removeName, @SuppressWarnings("unused") TaxonName stayName) {
374
        if(!removeName.getDescriptions().isEmpty()){
375
            logger.warn("Name description exist but merge not yet implemented");
376
        }
377
    }
378

    
379
    private void mergeHybridRelationships(TaxonName removeName, @SuppressWarnings("unused") TaxonName stayName) {
380
        if(!removeName.getHybridChildRelations().isEmpty()){
381
            logger.warn("Hybrid child relation exist but merge not yet implemented");
382
        }
383
        if(!removeName.getHybridParentRelations().isEmpty()){
384
            logger.warn("Hybrid parent relation exist but merge not yet implemented");
385
        }
386
    }
387

    
388
    private void mergeNameRelationships(TaxonName removeName, @SuppressWarnings("unused") TaxonName stayName) {
389
        if(!removeName.getNameRelations().isEmpty()){
390
            logger.warn("Name relations exist but merge not yet implemented");
391
        }
392
    }
393

    
394
    private boolean mergeChildren(Taxon removeTaxon, Taxon stayTaxon, boolean isTaxonSynonym) {
395
        if (isTaxonSynonym){
396
            if (!removeTaxon.getTaxonNodes().isEmpty()){
397
                logger.warn("taxon synonym has taxon node itself. This should not happen. Handle manually.");
398
                return false;
399
            }else{
400
                return true;
401
            }
402
        }
403
        TaxonNode removeNode = removeTaxon.getTaxonNodes().iterator().next();
404
        if(removeNode.getChildNodes().isEmpty()){
405
            return true;
406
        }
407

    
408
        stayTaxon = reallyAccTaxon(stayTaxon);
409
        TaxonNode stayNode = stayTaxon.getTaxonNodes().iterator().next();
410
        Set<UUID> removeNodeChildrenUuids = removeNode.getChildNodes()
411
                .stream().map(tn->tn.getUuid()).collect(Collectors.toSet());
412

    
413
        if(!removeNodeChildrenUuids.isEmpty()){
414
            app.getTaxonNodeService().moveTaxonNodes(removeNodeChildrenUuids,
415
                    stayNode.getUuid(), 0, null);
416
            System.out.println("Child nodes moved: " + removeNodeChildrenUuids.size());
417
        }
418
        return true;
419
    }
420

    
421
    private Taxon reallyAccTaxon(Taxon stayTaxon) {
422
        if (isTaxonSynonym(stayTaxon)){
423
            for (TaxonRelationship rel: stayTaxon.getRelationsFromThisTaxon()){
424
                boolean isPseudo = TaxonRelationshipType.pseudoTaxonUuids().contains(rel.getType().getUuid());
425
                if (isPseudo){
426
                    return rel.getToTaxon();
427
                }
428
            }
429
        }
430
        return stayTaxon;
431
    }
432
    private boolean mergeSynonyms(Taxon removeTaxon, Taxon stayTaxon, boolean isTaxonSynonym) {
433
        if (isTaxonSynonym){
434
            if (!removeTaxon.getSynonyms().isEmpty()){
435
                logger.warn("taxon synonym has synonyms itself. This should not happen. Handle manually.");
436
                return false;
437
            }else{
438
                return true;
439
            }
440
        }
441
        Set<Synonym> synonymsToAdd = new HashSet<>();
442
        for (Synonym synonym : removeTaxon.getSynonyms()){
443
            if (!synonymExists()){
444
                //TODO homotypical group
445
                synonymsToAdd.add(synonym);
446
            }else{
447
                //TODO merge synonym names
448
            }
449
        }
450
        for (Synonym synonym: synonymsToAdd){
451
            stayTaxon.addSynonym(synonym, synonym.getType());
452
        }
453
        return true;
454
    }
455

    
456
    private boolean synonymExists() {
457
        logger.warn("Synonym dulicate check - not yet implemented");
458
        return false;
459
    }
460

    
461
    private void mergeDescriptions(Taxon remove, Taxon stay) {
462
        //TODO handle duplicates for taxon descriptions
463
        for (TaxonDescription description: remove.getDescriptions()){
464
            System.out.println("Move taxon description: " + description.getTitleCache());
465
            stay.addDescription((TaxonDescription)description.clone());
466
        }
467
    }
468

    
469
    private void mergeCredits(IdentifiableEntity<?> removeEntity,
470
            IdentifiableEntity<?> stayEntity) throws CloneNotSupportedException {
471
        String className = removeEntity.getClass().getSimpleName();
472
        for (Credit credit: removeEntity.getCredits()){
473
            System.out.println("Move "+className+" credit: " + credit.toString());
474
            stayEntity.addCredit((Credit)credit.clone());
475
        }
476
    }
477

    
478
    private void mergeExtensions(IdentifiableEntity<?> removeEntity,
479
            IdentifiableEntity<?> stayEntity) throws CloneNotSupportedException {
480

    
481
        String className = removeEntity.getClass().getSimpleName();
482
        for (Extension extension: removeEntity.getExtensions()){
483
            if (!filterExtension(extension, removeEntity, stayEntity)){
484
                System.out.println("Move "+className+" extension: " + extension.getType().getTitleCache() + ": " + extension.getValue());
485

    
486
                IdentifiableEntity<?> thisStayEntity = selectStay(removeEntity, stayEntity, "Extension");
487
                if (thisStayEntity != null){
488
                    thisStayEntity.addExtension((Extension)extension.clone());
489
                }
490
            }
491
        }
492
    }
493

    
494
    private <T extends IdentifiableEntity<?>> T selectStay(T removeEntity, T stayEntity, String type) {
495
        if(removeEntity.isInstanceOf(Taxon.class) && stayEntity.isInstanceOf(Synonym.class)){
496
            String answer = "";
497
            while(!(answer.matches("[sSaAcC]"))){
498
                answer = CdmUtils.readInputLine(type + ": Stay is Synonym. Move information to [s]ynonym, to [a]ccepted or [c]ancel extension merge?: ");
499
            }
500
            if (answer.equalsIgnoreCase("c")){
501
                return null;
502
            }else if (answer.equalsIgnoreCase("a")){
503
               return (T)accTaxon(CdmBase.deproxy(stayEntity, Synonym.class));
504
            }else{
505
                return stayEntity;
506
            }
507
        }
508
        return stayEntity;
509
    }
510

    
511
    private boolean filterExtension(Extension extension,
512
            @SuppressWarnings("unused") IdentifiableEntity<?> removeEntity,
513
            @SuppressWarnings("unused") IdentifiableEntity<?> stayEntity) {
514
        if (extension.getType().getUuid().equals(ErmsTransformer.uuidExtDisplayName)){
515
            //for merged taxa display name information is not relevant because name is formatted according to "stay" taxon.
516
            return true;
517
        }
518
        return false;
519
    }
520

    
521
    private void mergeMarkers(IdentifiableEntity<?> removeEntity,
522
            IdentifiableEntity<?> stayEntity) throws CloneNotSupportedException {
523
        String className = removeEntity.getClass().getSimpleName();
524
        for (Marker marker: removeEntity.getMarkers()){
525
            if (!filterMarker(marker, removeEntity, stayEntity)){
526
                System.out.println("Move "+className+" marker: " + marker.getMarkerType().getTitleCache() + ": " + marker.getValue());
527
                IdentifiableEntity<?> thisStayEntity = selectStay(removeEntity, stayEntity, "Marker");
528
                if (thisStayEntity != null){
529
                    thisStayEntity.addMarker((Marker)marker.clone());
530
                }
531
            }
532
        }
533
    }
534

    
535
    private void mergeAnnotations(IdentifiableEntity<?> removeEntity,
536
            IdentifiableEntity<?> stayEntity) throws CloneNotSupportedException {
537
        String className = removeEntity.getClass().getSimpleName();
538
        for (Annotation annotation: removeEntity.getAnnotations()){
539
            if (!filterAnnotation(annotation, removeEntity, stayEntity)){
540
                String type = annotation.getAnnotationType() == null? "no type" : annotation.getAnnotationType().getTitleCache();
541
                System.out.println("Move "+className+" note: " + type + ": " + annotation.getText());
542
                handleRemoveAnnotation(annotation, removeEntity, stayEntity);
543
                stayEntity.addAnnotation((Annotation)annotation.clone());
544
            }
545
        }
546
    }
547

    
548
    private void mergeSources(IdentifiableEntity<?> removeEntity,
549
            IdentifiableEntity<?> stayEntity) throws CloneNotSupportedException {
550
        String className = removeEntity.getClass().getSimpleName();
551
        for (IdentifiableSource source: removeEntity.getSources()){
552
            System.out.println("Move "+className+" source: " + source.getType().getLabel() + ": " + source.getCitation().getTitleCache() + "; " + source.getIdInSource() + "/" + source.getIdNamespace());
553
            stayEntity.addSource((IdentifiableSource)source.clone());
554
        }
555
    }
556

    
557
    private boolean filterMarker(Marker marker, @SuppressWarnings("unused") IdentifiableEntity<?> removeEntity,
558
            IdentifiableEntity<?> stayEntity) {
559
        if (isNoLastActionMarker(marker)){
560
            for (Annotation annotation : stayEntity.getAnnotations()){
561
                if (isLastActionDateAnnotation(annotation)){
562
                        return true;
563
                }
564
            }
565
        }
566
        return false;
567
    }
568

    
569
    private boolean isLastActionDateAnnotation(Annotation annotation) {
570
        return annotation.getAnnotationType()!= null
571
                && annotation.getAnnotationType().getUuid().equals(DbLastActionMapper.uuidAnnotationTypeLastActionDate)
572
                && !isBlank(annotation.getText());
573
    }
574

    
575
    private void handleRemoveAnnotation(Annotation annotation,
576
            @SuppressWarnings("unused") IdentifiableEntity<?> removeEntity,
577
            IdentifiableEntity<?> stayEntity) {
578
        if (isLastActionDateAnnotation(annotation)){
579
            Optional<Marker> noLastActionMarker = stayEntity.getMarkers().stream().filter(m->isNoLastActionMarker(m)).findFirst();
580
            if (noLastActionMarker.isPresent()){
581
                stayEntity.removeMarker(noLastActionMarker.get());
582
                System.out.println("  NoLastActionDate annotation removed from 'stay' " + stayEntity.getClass().getSimpleName());
583
            }
584
        }
585
    }
586

    
587
    private boolean isNoLastActionMarker(Marker marker) {
588
        return marker.getMarkerType().getUuid().equals(PesiTransformer.uuidMarkerTypeHasNoLastAction)
589
                && marker.getValue() == true;
590
    }
591

    
592
    @SuppressWarnings("unused")
593
    private boolean filterAnnotation(Annotation annotation, IdentifiableEntity<?> removeEntity, IdentifiableEntity<?> stayEntity) {
594
        return false;
595
    }
596

    
597
    private TaxonBase<?>[] readTaxa() {
598
        TaxonBase<?> taxon1 = readTaxon("Taxon to be removed");
599
        TaxonBase<?> taxon2 = readTaxon("Taxon to stay");
600
        if (taxon1 == null || taxon2 == null){
601
            return null;
602
        }else{
603
            return new TaxonBase<?>[]{taxon1, taxon2};
604
        }
605
    }
606

    
607
    private TaxonBase<?> readTaxon(String message) {
608
        TaxonBase<?> taxon = null;
609
        boolean quit = false;
610
        while (taxon == null && quit == false){
611
            String strTaxon = CdmUtils.readInputLine(message + ": ");
612
            if (strTaxon.equalsIgnoreCase("q")){
613
                quit = true;
614
            }else{
615
                taxon = taxonByString(strTaxon);
616
            }
617
        }
618
        if (taxon == null){
619
            return null;
620
        }else if (taxon.isInstanceOf(Synonym.class)){
621
            return CdmBase.deproxy(taxon, Synonym.class);
622
        }else{
623
            return CdmBase.deproxy(taxon, Taxon.class);
624
        }
625
    }
626

    
627
    /**
628
     * Reads a taxon from database using it's id or uuid as String
629
     */
630
    private TaxonBase<?> taxonByString(String strTaxon) {
631
        TaxonBase<?> taxon = null;
632
        if (strTaxon.matches("\\d{1,10}")){
633
            taxon = app.getTaxonService().find(Integer.valueOf(strTaxon));
634
        }else if (strTaxon.matches(CdmRegEx.UUID_RE)){
635
            taxon = app.getTaxonService().find(UUID.fromString(strTaxon));
636
        }
637
        return taxon;
638
    }
639

    
640

    
641

    
642
    public static void main(String[] args) {
643
        PesiCommandLineMerge merger = new PesiCommandLineMerge();
644
        merger.invoke(pesiSource);
645
        System.exit(0);
646
    }
647
}
(2-2/6)