Project

General

Profile

Download (74.5 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.cdm.model.taxon;
11

    
12

    
13
import java.lang.reflect.Field;
14
import java.util.ArrayList;
15
import java.util.Collections;
16
import java.util.Comparator;
17
import java.util.HashMap;
18
import java.util.HashSet;
19
import java.util.Iterator;
20
import java.util.List;
21
import java.util.Map;
22
import java.util.Set;
23

    
24
import javax.persistence.Entity;
25
import javax.persistence.FetchType;
26
import javax.persistence.OneToMany;
27
import javax.persistence.Transient;
28
import javax.validation.Valid;
29
import javax.validation.constraints.NotNull;
30
import javax.xml.bind.annotation.XmlAccessType;
31
import javax.xml.bind.annotation.XmlAccessorType;
32
import javax.xml.bind.annotation.XmlAttribute;
33
import javax.xml.bind.annotation.XmlElement;
34
import javax.xml.bind.annotation.XmlElementWrapper;
35
import javax.xml.bind.annotation.XmlIDREF;
36
import javax.xml.bind.annotation.XmlRootElement;
37
import javax.xml.bind.annotation.XmlSchemaType;
38
import javax.xml.bind.annotation.XmlType;
39

    
40
import org.apache.log4j.Logger;
41
import org.hibernate.annotations.Cascade;
42
import org.hibernate.annotations.CascadeType;
43
import org.hibernate.envers.Audited;
44
import org.hibernate.search.annotations.ClassBridge;
45
import org.hibernate.search.annotations.ClassBridges;
46
import org.hibernate.search.annotations.ContainedIn;
47
import org.hibernate.search.annotations.Indexed;
48
import org.hibernate.search.annotations.IndexedEmbedded;
49
import org.springframework.beans.factory.annotation.Configurable;
50
import org.springframework.util.ReflectionUtils;
51

    
52
import eu.etaxonomy.cdm.hibernate.search.GroupByTaxonClassBridge;
53
import eu.etaxonomy.cdm.hibernate.search.TaxonRelationshipClassBridge;
54
import eu.etaxonomy.cdm.model.common.CdmBase;
55
import eu.etaxonomy.cdm.model.common.IRelated;
56
import eu.etaxonomy.cdm.model.common.RelationshipBase;
57
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
58
import eu.etaxonomy.cdm.model.description.DescriptionType;
59
import eu.etaxonomy.cdm.model.description.Feature;
60
import eu.etaxonomy.cdm.model.description.IDescribable;
61
import eu.etaxonomy.cdm.model.description.TaxonDescription;
62
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
63
import eu.etaxonomy.cdm.model.name.ITaxonNameBase;
64
import eu.etaxonomy.cdm.model.name.TaxonName;
65
import eu.etaxonomy.cdm.model.reference.ICdmTarget;
66
import eu.etaxonomy.cdm.model.reference.Reference;
67
import eu.etaxonomy.cdm.strategy.cache.taxon.ITaxonCacheStrategy;
68
import eu.etaxonomy.cdm.strategy.cache.taxon.TaxonBaseDefaultCacheStrategy;
69

    
70
/**
71
 * The class for "accepted/correct" {@link TaxonBase taxa} (only these taxa according to
72
 * the opinion of the {@link eu.etaxonomy.cdm.model.reference.Reference reference} can build a classification).
73
 * An {@link java.lang.Iterable interface} is supported to iterate through taxonomic children.<BR>
74
 * Splitting taxa in "accepted/correct" and {@link Synonym "synonyms"} makes it easier to handle
75
 * particular relationships between ("accepted/correct") taxa on the one hand
76
 * and between ("synonym") taxa and ("accepted/correct") taxa on the other.
77
 *
78
 * @author m.doering
79
 * @since 08-Nov-2007 13:06:56
80
 */
81
@XmlAccessorType(XmlAccessType.FIELD)
82
@XmlType(name = "Taxon", propOrder = {
83
    "taxonNodes",
84
    "synonyms",
85
    "relationsFromThisTaxon",
86
    "relationsToThisTaxon",
87
    "descriptions"
88
})
89
@XmlRootElement(name = "Taxon")
90
@Entity
91
@Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonBase")
92
@Audited
93
@Configurable
94
@ClassBridges({
95
    @ClassBridge(impl = GroupByTaxonClassBridge.class),
96
    @ClassBridge(impl = TaxonRelationshipClassBridge.class)
97
})
98
public class Taxon
99
            extends TaxonBase<ITaxonCacheStrategy<Taxon>>
100
            implements IRelated<RelationshipBase>, IDescribable<TaxonDescription>, ICdmTarget{
101

    
102
    private static final long serialVersionUID = -584946869762749006L;
103
    private static final Logger logger = Logger.getLogger(Taxon.class);
104

    
105
    private static final TaxonComparator defaultTaxonComparator = new TaxonComparator();
106

    
107
    @XmlElementWrapper(name = "Descriptions")
108
    @XmlElement(name = "Description")
109
    @OneToMany(mappedBy="taxon", fetch= FetchType.LAZY)
110
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
111
    @NotNull
112
    @ContainedIn
113
    private Set<TaxonDescription> descriptions = new HashSet<>();
114

    
115
    // all related synonyms
116
    @XmlElementWrapper(name = "Synonyms")
117
    @XmlElement(name = "Synonym")
118
    @XmlIDREF
119
    @XmlSchemaType(name = "IDREF")
120
    @OneToMany(mappedBy="acceptedTaxon", fetch=FetchType.LAZY, orphanRemoval=false) //we allow synonyms to stay on their own for dirty data and for intermediate states during e.g. imports
121
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
122
    @NotNull
123
    @Valid
124
    @ContainedIn
125
    private Set<Synonym> synonyms = new HashSet<>();
126

    
127
    // all taxa relations with rel.fromTaxon==this
128
    @XmlElementWrapper(name = "RelationsFromThisTaxon")
129
    @XmlElement(name = "FromThisTaxonRelationship")
130
    @OneToMany(mappedBy="relatedFrom", fetch=FetchType.LAZY, orphanRemoval=true)
131
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
132
    @NotNull
133
//    @Valid
134
    @ContainedIn
135
    private Set<TaxonRelationship> relationsFromThisTaxon = new HashSet<>();
136

    
137
    // all taxa relations with rel.toTaxon==this
138
    @XmlElementWrapper(name = "RelationsToThisTaxon")
139
    @XmlElement(name = "ToThisTaxonRelationship")
140
    @XmlIDREF
141
    @XmlSchemaType(name = "IDREF")
142
    @OneToMany(mappedBy="relatedTo", fetch=FetchType.LAZY, orphanRemoval=true)
143
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
144
//    @Valid
145
    @ContainedIn
146
    private Set<TaxonRelationship> relationsToThisTaxon = new HashSet<>();
147

    
148
    @XmlAttribute(name= "taxonStatusUnknown")
149
    private boolean taxonStatusUnknown = false;
150
    /**
151
     * The status of this taxon is unknown it could also be some kind of synonym.
152
     * @return the taxonStatusUnknown
153
     */
154
    public boolean isTaxonStatusUnknown() {return taxonStatusUnknown;}
155
     /** @see #isTaxonStatusUnknown()*/
156
    public void setTaxonStatusUnknown(boolean taxonStatusUnknown) {this.taxonStatusUnknown = taxonStatusUnknown;}
157

    
158
    @XmlElementWrapper(name = "taxonNodes")
159
    @XmlElement(name = "taxonNode")
160
    @XmlIDREF
161
    @XmlSchemaType(name = "IDREF")
162
    @OneToMany(mappedBy="taxon", fetch=FetchType.LAZY)
163
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
164
    @IndexedEmbedded
165
    private Set<TaxonNode> taxonNodes = new HashSet<>();
166

    
167
// ************************* FACTORY METHODS ********************************/
168

    
169
    /**
170
     * @see #NewInstance(TaxonName, Reference)
171
     * @param taxonName
172
     * @param sec
173
     * @return
174
     */
175
    public static Taxon NewInstance(ITaxonNameBase taxonName, Reference sec){
176
        return NewInstance(TaxonName.castAndDeproxy(taxonName), sec);
177
    }
178

    
179
    /**
180
     * Creates a new (accepted/valid) taxon instance with
181
     * the {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} used and the {@link eu.etaxonomy.cdm.model.reference.Reference reference}
182
     * using it.
183
     *
184
     * @param  taxonName	the taxon name used
185
     * @param  sec				the reference using the taxon name
186
     * @see    					#Taxon(TaxonName, Reference)
187
     */
188
    public static Taxon NewInstance(TaxonName taxonName, Reference sec){
189
        Taxon result = new Taxon(taxonName, sec);
190
        return result;
191
    }
192

    
193
    /**
194
     * Creates a new Taxon for the given name, secundum reference and secundum detail
195
     * @param taxonName
196
     * @param sec
197
     * @param secMicroReference
198
     * @see #
199
     */
200
    public static Taxon NewInstance(TaxonName taxonName, Reference sec, String secMicroReference){
201
        Taxon result = new Taxon(taxonName, sec, secMicroReference);
202
        return result;
203
    }
204

    
205
    /**
206
     * Creates a new taxon instance with an unknown status (accepted/synonym) and with
207
     * the {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} used and the {@link eu.etaxonomy.cdm.model.reference.Reference reference}
208
     * using it.
209
     *
210
     * @param  taxonName	the taxon name used
211
     * @param  sec				the reference using the taxon name
212
     * @see    					#Taxon(TaxonName, Reference)
213
     */
214
    public static Taxon NewUnknownStatusInstance(TaxonName taxonName, Reference sec){
215
        Taxon result = new Taxon(taxonName, sec);
216
        result.setTaxonStatusUnknown(true);
217
        return result;
218
    }
219
// ************* CONSTRUCTORS *************/
220

    
221
    //TODO should be private, but still produces Spring init errors
222
    @Deprecated
223
    public Taxon(){
224
        this.cacheStrategy = new TaxonBaseDefaultCacheStrategy<>();
225
    }
226

    
227
    private Taxon(TaxonName taxonName, Reference sec){
228
        super(taxonName, sec, null);
229
        this.cacheStrategy = new TaxonBaseDefaultCacheStrategy<>();
230
    }
231

    
232
    private Taxon(TaxonName taxonName, Reference sec, String secMicroReference){
233
        super(taxonName, sec, secMicroReference);
234
        this.cacheStrategy = new TaxonBaseDefaultCacheStrategy<Taxon>();
235
    }
236

    
237
//********* METHODS **************************************/
238

    
239
    /**
240
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonDescription taxon descriptions}
241
     * concerning <i>this</i> taxon.
242
     *
243
     * @see #removeDescription(TaxonDescription)
244
     * @see #addDescription(TaxonDescription)
245
     * @see eu.etaxonomy.cdm.model.description.TaxonDescription#getTaxon()
246
     */
247
    @Override
248
    public Set<TaxonDescription> getDescriptions() {
249
        if(descriptions == null) {
250
            descriptions = new HashSet<>();
251
        }
252
        return descriptions;
253
    }
254

    
255
    public Set<TaxonDescription> getDescriptions(DescriptionType type) {
256
        Set<TaxonDescription> result = new HashSet<>();
257
        for (TaxonDescription description : getDescriptions()){
258
            if (description.getTypes().contains(type)){
259
                result.add(description);
260
            }
261
        }
262
        return result;
263
    }
264

    
265
    /**
266
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonDescription taxon description} to the set
267
     * of taxon descriptions assigned to <i>this</i> (accepted/correct) taxon.
268
     * Due to bidirectionality the content of the {@link eu.etaxonomy.cdm.model.description.TaxonDescription#getTaxon() taxon attribute} of the
269
     * taxon description itself will be replaced with <i>this</i> taxon. The taxon
270
     * description will also be removed from the set of taxon descriptions
271
     * assigned to its previous taxon.
272
     *
273
     * @param  description	the taxon description to be added for <i>this</i> taxon
274
     * @see     		  	#getDescriptions()
275
     * @see     		  	#removeDescription(TaxonDescription)
276
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonDescription#getTaxon()
277
     */
278
    @Override
279
    public void addDescription(TaxonDescription description) {
280
        if (description.getTaxon() != null){
281
            description.getTaxon().removeDescription(description);
282
        }
283
        Field field = ReflectionUtils.findField(TaxonDescription.class, "taxon", Taxon.class);
284
        ReflectionUtils.makeAccessible(field);
285
        ReflectionUtils.setField(field, description, this);
286
        descriptions.add(description);
287

    
288
    }
289

    
290
    /**
291
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonDescription taxon descriptions} assigned
292
     * to <i>this</i> (accepted/correct) taxon. Due to bidirectionality the content of
293
     * the {@link eu.etaxonomy.cdm.model.description.TaxonDescription#getTaxon() taxon attribute} of the taxon description
294
     * itself will be set to "null".
295
     *
296
     * @param  description  the taxon description which should be removed
297
     * @see     		  	#getDescriptions()
298
     * @see     		  	#addDescription(TaxonDescription)
299
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonDescription#getTaxon()
300
     */
301
    @Override
302
    public void removeDescription(TaxonDescription description) {
303
        //description.setTaxon(null) for not visible method
304
        Field field = ReflectionUtils.findField(TaxonDescription.class, "taxon", Taxon.class);
305
        ReflectionUtils.makeAccessible(field);
306
        ReflectionUtils.setField(field, description, null);
307
        descriptions.remove(description);
308
    }
309

    
310
    public void removeDescription(TaxonDescription description, boolean removeElements){
311
    	if (removeElements){
312
    		Set<DescriptionElementBase> elements = new HashSet<DescriptionElementBase>(description.getElements());
313
            for (DescriptionElementBase el:elements){
314
            	description.getElements().remove(el);
315
            }
316
            removeDescription(description);
317
    	} else{
318
    		removeDescription(description);
319
    	}
320
    }
321

    
322
    /**
323
     * Returns the image gallery for a taxon. If there are multiple taxon descriptions
324
     * marked as image galleries an arbitrary one is chosen.
325
     * If no image gallery exists, a new one is created if <code>createNewIfNotExists</code>
326
     * is <code>true</code>.
327
     * @param createNewIfNotExists
328
     * @return
329
     */
330
    public TaxonDescription getImageGallery(boolean createNewIfNotExists) {
331
        TaxonDescription result = null;
332
        Set<TaxonDescription> descriptions= getDescriptions();
333
        for (TaxonDescription description : descriptions){
334
            if (description.isImageGallery()){
335
                result = description;
336
                break;
337
            }
338
        }
339
        if (result == null && createNewIfNotExists){
340
            result = TaxonDescription.NewInstance(this);
341
            result.setImageGallery(true);
342
        }
343
        return result;
344
    }
345

    
346

    
347

    
348
    public Set<TaxonNode> getTaxonNodes() {
349
        return taxonNodes;
350
    }
351
    //	protected void setTaxonNodes(Set<TaxonNode> taxonNodes) {
352
//		this.taxonNodes = taxonNodes;
353
//	}
354
    protected void addTaxonNode(TaxonNode taxonNode){
355
        taxonNodes.add(taxonNode);
356
    }
357

    
358
    public boolean removeTaxonNode(TaxonNode taxonNode){
359
        if (!taxonNodes.contains(taxonNode)){
360
            return false;
361
        }
362
        TaxonNode parent = taxonNode.getParent();
363
        if (parent != null){
364
            parent.removeChildNode(taxonNode);
365
        }
366
        taxonNode.setTaxon(null);
367
        return taxonNodes.remove(taxonNode);
368

    
369
    }
370

    
371
    public boolean removeTaxonNode(TaxonNode taxonNode, boolean deleteChildren){
372
        TaxonNode parent = taxonNode.getParent();
373
        boolean success = true;
374

    
375
        if ((!taxonNode.getChildNodes().isEmpty() && deleteChildren) || (taxonNode.getChildNodes().isEmpty()) ){
376

    
377
            taxonNode.delete();
378

    
379
        } else if (!taxonNode.isTopmostNode()){
380

    
381
            List<TaxonNode> nodes = new ArrayList<TaxonNode> (taxonNode.getChildNodes());
382
            for (TaxonNode childNode: nodes){
383
                taxonNode.getChildNodes().remove(childNode);
384
                parent.addChildNode(childNode, null, null);
385
            }
386

    
387
            taxonNode.delete();
388

    
389
        } else if (taxonNode.isTopmostNode()){
390
            success = false;
391
        }
392
        return success;
393
    }
394

    
395
    public boolean removeTaxonNodes(boolean deleteChildren){
396
        Iterator<TaxonNode> nodesIterator = taxonNodes.iterator();
397
        TaxonNode node;
398
        TaxonNode parent;
399
        boolean success = false;
400
        List<TaxonNode> removeNodes = new ArrayList<>();
401
        while (nodesIterator.hasNext()){
402
            node = nodesIterator.next();
403
            if (!deleteChildren){
404
                List<TaxonNode> children = node.getChildNodes();
405
                Iterator<TaxonNode> childrenIterator = children.iterator();
406
                parent = node.getParent();
407
                while (childrenIterator.hasNext()){
408
                    TaxonNode childNode = childrenIterator.next();
409
                    if (parent != null){
410
                        parent.addChildNode(childNode, null, null);
411
                    }else{
412
                        childNode.setParent(null);
413
                    }
414
                }
415

    
416
                for (int i = 0; i<node.getChildNodes().size(); i++){
417
                    node.removeChild(i);
418
                }
419
            }
420

    
421
            removeNodes.add(node);
422
         }
423
        for (int i = 0; i<removeNodes.size(); i++){
424
            TaxonNode removeNode = removeNodes.get(i);
425
            success = removeNode.delete(deleteChildren);
426
            removeNode.setTaxon(null);
427
            removeTaxonNode(removeNode);
428
        }
429
        return success;
430

    
431
    }
432

    
433
    public TaxonNode getTaxonNode(Classification classification) {
434
        if (classification == null){
435
            return null;
436
        }
437
        for (TaxonNode node : this.getTaxonNodes()){
438
            if (classification.equals(node.getClassification())){
439
                return node;
440
            }
441
        }
442
        return null;
443
    }
444

    
445
    /**
446
     * Returns the set of all {@link Synonym synonyms}
447
     * for which <i>this</i> ("accepted/valid") taxon is the accepted taxon.
448
     *
449
     * @see    #addSynonym(Synonym, SynonymType)
450
     * @see    #removeSynonym(Synonym)
451
     */
452
    public Set<Synonym> getSynonyms() {
453
        if(synonyms == null) {
454
            this.synonyms = new HashSet<>();
455
        }
456
        return synonyms;
457
    }
458

    
459
    /**
460
     * Returns the set of all {@link TaxonRelationship taxon relationships}
461
     * between two taxa in which <i>this</i> taxon is involved as a source.
462
     *
463
     * @see    #getRelationsToThisTaxon()
464
     * @see    #getTaxonRelations()
465
     */
466
    public Set<TaxonRelationship> getRelationsFromThisTaxon() {
467
        if(relationsFromThisTaxon == null) {
468
            this.relationsFromThisTaxon = new HashSet<>();
469
        }
470
        return relationsFromThisTaxon;
471
    }
472

    
473
    /**
474
     * Returns the set of all {@link TaxonRelationship taxon relationships}
475
     * between two taxa in which <i>this</i> taxon is involved as a target.
476
     *
477
     * @see    #getRelationsFromThisTaxon()
478
     * @see    #getTaxonRelations()
479
     */
480
    public Set<TaxonRelationship> getRelationsToThisTaxon() {
481
        if(relationsToThisTaxon == null) {
482
            this.relationsToThisTaxon = new HashSet<>();
483
        }
484
        return relationsToThisTaxon;
485
    }
486

    
487
    /**
488
     * Returns the set of all {@link TaxonRelationship taxon relationships}
489
     * between two taxa in which <i>this</i> taxon is involved either as a source or
490
     * as a target.
491
     *
492
     * @see    #getRelationsFromThisTaxon()
493
     * @see    #getRelationsToThisTaxon()
494
     */
495
    @Transient
496
    public Set<TaxonRelationship> getTaxonRelations() {
497
        Set<TaxonRelationship> rels = new HashSet<>();
498
        rels.addAll(getRelationsToThisTaxon());
499
        rels.addAll(getRelationsFromThisTaxon());
500
        return rels;
501
    }
502

    
503
    /**
504
     * @see    #getRelationsToThisTaxon()
505
     */
506
    protected void setRelationsToThisTaxon(Set<TaxonRelationship> relationsToThisTaxon) {
507
        this.relationsToThisTaxon = relationsToThisTaxon;
508
    }
509

    
510
    /**
511
     * @see    #getRelationsFromThisTaxon()
512
     */
513
    protected void setRelationsFromThisTaxon(Set<TaxonRelationship> relationsFromThisTaxon) {
514
        this.relationsFromThisTaxon = relationsFromThisTaxon;
515
    }
516

    
517
    /**
518
     * If a relationships between <i>this</i> and the given taxon exists they will be returned.
519
     * <i>This</i> taxon is involved either as a source or as a target in the relationships.
520
     * The method will return <code>null</code> if no relations exist between the two taxa.
521
     *
522
     * @param possiblyRelatedTaxon
523
     * 			a taxon to check for a relationship
524
     * @return
525
     * 			a set of <code>TaxonRelationship</code>s or <code>null</null> if none exists.
526
     */
527
    public Set<TaxonRelationship> getTaxonRelations(Taxon possiblyRelatedTaxon){
528
        Set<TaxonRelationship> relations = new HashSet<>();
529

    
530
        for(TaxonRelationship relationship : getTaxonRelations()){
531
            if(relationship.getFromTaxon().equals(possiblyRelatedTaxon)) {
532
                relations.add(relationship);
533
            }
534
            if(relationship.getToTaxon().equals(possiblyRelatedTaxon)) {
535
                relations.add(relationship);
536
            }
537
        }
538

    
539
        return relations.size() > 0 ? relations : null;
540
    }
541

    
542
    /**
543
     * Removes one {@link TaxonRelationship taxon relationship} from one of both sets of
544
     * {@link #getTaxonRelations() taxon relationships} in which <i>this</i> taxon is involved
545
     * either as a {@link #getRelationsFromThisTaxon() source} or as a {@link #getRelationsToThisTaxon() target}.
546
     * The taxon relationship will also be removed from one of both sets
547
     * belonging to the second taxon involved. Furthermore the inherited RelatedFrom and
548
     * RelatedTo attributes of the given taxon relationship will be nullified.<P>
549
     *
550
     * @param  rel  the taxon relationship which should be removed from one
551
     * 				of both sets
552
     * @see    		#getTaxonRelations()
553
     * @see    		eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedFrom()
554
     * @see    		eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedTo()
555
     *
556
     */
557
    public void removeTaxonRelation(TaxonRelationship rel) {
558
        this.relationsToThisTaxon.remove(rel);
559
        this.relationsFromThisTaxon.remove(rel);
560
        Taxon fromTaxon = rel.getFromTaxon();
561
        Taxon toTaxon = rel.getToTaxon();
562

    
563
        //delete Relationship from other related Taxon
564
        if (fromTaxon != this){
565
            rel.setToTaxon(null);  //remove this Taxon from relationship
566
            if (fromTaxon != null){
567
                if (fromTaxon.getTaxonRelations().contains(rel)){
568
                    fromTaxon.removeTaxonRelation(rel);
569
                }
570
            }
571
        }
572
        if (toTaxon != this ){
573
            rel.setFromTaxon(null); //remove this Taxon from relationship
574
           if (toTaxon != null){
575
               if (toTaxon.getTaxonRelations().contains(rel)) {
576
                   toTaxon.removeTaxonRelation(rel);
577
               }
578
           }
579
        }
580
    }
581

    
582
    /**
583
     * Adds an existing {@link TaxonRelationship taxon relationship} either to the set of
584
     * {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon} or to the set of
585
     * {@link #getRelationsFromThisTaxon() taxon relationships from <i>this</i> taxon}. If neither the
586
     * source nor the target of the taxon relationship match with <i>this</i> taxon
587
     * no addition will be carried out. The taxon relationship will also be
588
     * added to the second taxon involved in the given relationship.<P>
589
     *
590
     * @param rel  the taxon relationship to be added to one of <i>this</i> taxon's taxon relationships sets
591
     * @see    	   #addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
592
     * @see    	   #getTaxonRelations()
593
     * @see    	   #getRelationsFromThisTaxon()
594
     * @see    	   #getRelationsToThisTaxon()
595
     */
596
    public void addTaxonRelation(TaxonRelationship rel) {
597
        if (rel!=null && rel.getType()!=null && !getTaxonRelations().contains(rel) ){
598
            Taxon toTaxon=rel.getToTaxon();
599
            Taxon fromTaxon=rel.getFromTaxon();
600
            if ( this.equals(toTaxon) || this.equals(fromTaxon) ){
601
                if (this.equals(fromTaxon)){
602
                    relationsFromThisTaxon.add(rel);
603
                    // also add relation to other taxon object
604
                    if (toTaxon!=null){
605
                        toTaxon.addTaxonRelation(rel);
606
                    }
607
                }else if (this.equals(toTaxon)){
608
                    relationsToThisTaxon.add(rel);
609
                    // also add relation to other taxon object
610
                    if (fromTaxon!=null){
611
                        fromTaxon.addTaxonRelation(rel);
612
                    }
613
                }
614
            }else if (toTaxon == null || fromTaxon == null){
615
                if (toTaxon == null){
616
                    toTaxon = this;
617
                    relationsToThisTaxon.add(rel);
618
                    if (fromTaxon!= null){
619
                        fromTaxon.addTaxonRelation(rel);
620
                    }
621
                }else if (fromTaxon == null && toTaxon != null){
622
                    fromTaxon = this;
623
                    relationsFromThisTaxon.add(rel);
624
                    toTaxon.addTaxonRelation(rel);
625
                }
626
            }
627
        }
628
    }
629

    
630
    @Override
631
    @Deprecated //for inner use by RelationshipBase only
632
    public void addRelationship(RelationshipBase rel){
633
        if (rel instanceof TaxonRelationship){
634
            addTaxonRelation((TaxonRelationship)rel);
635
        }else{
636
            throw new ClassCastException("Wrong Relationsship type for Taxon.addRelationship");
637
        }
638
    }
639

    
640
    /**
641
     * Creates a new {@link TaxonRelationship taxon relationship} instance where <i>this</i> taxon
642
     * plays the source role and adds it to the set of
643
     * {@link #getRelationsFromThisTaxon() "taxon relationships from"} belonging to <i>this</i> taxon.
644
     * The taxon relationship will also be added to the set of taxon
645
     * relationships to the second taxon involved in the created relationship.<P>
646
     *
647
     * @param toTaxon		the taxon which plays the target role in the new taxon relationship
648
     * @param type			the taxon relationship type for the new taxon relationship
649
     * @param citation		the reference source for the new taxon relationship
650
     * @param microcitation	the string with the details describing the exact localisation within the reference
651
     * @return
652
     * @see    	   			#addTaxonRelation(TaxonRelationship)
653
     * @see    	   			#getTaxonRelations()
654
     * @see    	   			#getRelationsFromThisTaxon()
655
     * @see    	   			#getRelationsToThisTaxon()
656
     */
657
    public TaxonRelationship addTaxonRelation(Taxon toTaxon, TaxonRelationshipType type, Reference citation, String microcitation) {
658
        return new TaxonRelationship(this, toTaxon, type, citation, microcitation);
659
    }
660

    
661
    /**
662
     * Creates a new {@link TaxonRelationship taxon relationship} (with {@link TaxonRelationshipType taxon relationship type}
663
     * "misapplied name for") instance where <i>this</i> taxon plays the target role
664
     * and adds it to the set of {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon}.
665
     * The taxon relationship will also be added to the set of taxon
666
     * relationships to the other (misapplied name) taxon involved in the created relationship.
667
     *
668
     * @param misappliedNameTaxon	the taxon which plays the source role in the new taxon relationship
669
     * @param citation				the reference source for the new taxon relationship
670
     * @param microcitation			the string with the details describing the exact localisation within the reference
671
     * @return
672
     * @see    	   					#getMisappliedNames()
673
     * @see                         #addProParteMisappliedName(Taxon, Reference, String)
674
     * @see    	   					#addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
675
     * @see    	   					#addTaxonRelation(TaxonRelationship)
676
     * @see    	   					#getTaxonRelations()
677
     * @see    	   					#getRelationsFromThisTaxon()
678
     * @see    	   					#getRelationsToThisTaxon()
679
     */
680
    public TaxonRelationship addMisappliedName(Taxon misappliedNameTaxon, Reference citation, String microcitation) {
681
        return misappliedNameTaxon.addTaxonRelation(this, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), citation, microcitation);
682
    }
683

    
684
//	public void removeMisappliedName(Taxon misappliedNameTaxon){
685
//		Set<TaxonRelationship> taxRels = this.getTaxonRelations();
686
//		for (TaxonRelationship taxRel : taxRels ){
687
//			if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())
688
//				&& taxRel.getFromTaxon().equals(misappliedNameTaxon)){
689
//				this.removeTaxonRelation(taxRel);
690
//			}
691
//		}
692
//	}
693

    
694
    /**
695
     * Creates a new {@link TaxonRelationship taxon relationship} (with {@link TaxonRelationshipType taxon relationship type}
696
     * "pro parte misapplied name for") instance where <i>this</i> taxon plays the target role
697
     * and adds it to the set of {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon}.
698
     * The taxon relationship will also be added to the set of taxon
699
     * relationships to the other (pro parte misapplied name) taxon involved in the created relationship.
700
     *
701
     * @param proParteMisappliedNameTaxon   the taxon which plays the source role in the new taxon relationship
702
     * @param citation              the reference source for the new taxon relationship
703
     * @param microcitation         the string with the details describing the exact localisation within the reference
704
     * @return
705
     * @see                         #addMisappliedName(Taxon, Reference, String)
706
     * @see                         #getMisappliedNames()
707
     * @see                         #addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
708
     * @see                         #addTaxonRelation(TaxonRelationship)
709
     * @see                         #getTaxonRelations()
710
     * @see                         #getRelationsFromThisTaxon()
711
     * @see                         #getRelationsToThisTaxon()
712
     */
713
    public TaxonRelationship addProParteMisappliedName(Taxon proParteMisappliedNameTaxon, Reference citation, String microcitation) {
714
        return proParteMisappliedNameTaxon.addTaxonRelation(this, TaxonRelationshipType.PRO_PARTE_MISAPPLIED_NAME_FOR(), citation, microcitation);
715
    }
716

    
717
    /**
718
     * Creates a new {@link TaxonRelationship taxon relationship} (with {@link TaxonRelationshipType taxon relationship type}
719
     * "partial misapplied name for") instance where <i>this</i> taxon plays the target role
720
     * and adds it to the set of {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon}.
721
     * The taxon relationship will also be added to the set of taxon
722
     * relationships to the other (pro parte misapplied name) taxon involved in the created relationship.
723
     *
724
     * @param partialMisappliedNameTaxon   the taxon which plays the source role in the new taxon relationship
725
     * @param citation              the reference source for the new taxon relationship
726
     * @param microcitation         the string with the details describing the exact localization within the reference
727
     * @return
728
     * @see                         #addMisappliedName(Taxon, Reference, String)
729
     * @see                         #addProParteMisappliedName(Taxon, Reference, String)
730
     * @see                         #getMisappliedNames()
731
     * @see                         #addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
732
     * @see                         #addTaxonRelation(TaxonRelationship)
733
     * @see                         #getTaxonRelations()
734
     * @see                         #getRelationsFromThisTaxon()
735
     * @see                         #getRelationsToThisTaxon()
736
     */
737
    public TaxonRelationship addPartialMisappliedName(Taxon partialMisappliedNameTaxon, Reference citation, String microcitation) {
738
        return partialMisappliedNameTaxon.addTaxonRelation(this, TaxonRelationshipType.PARTIAL_MISAPPLIED_NAME_FOR(), citation, microcitation);
739
    }
740

    
741
    /**
742
     * Creates a new {@link TaxonRelationship taxon relationship} (with {@link TaxonRelationshipType taxon relationship type}
743
     * "pro parte synonym for") instance where <i>this</i> taxon plays the target role
744
     * and adds it to the set of {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon}.
745
     * The taxon relationship will also be added to the set of taxon
746
     * relationships to the other (pro parte synonym) taxon involved in the created relationship.
747
     *
748
     * @param proParteTaxon         the taxon which plays the source role in the new taxon relationship
749
     * @param citation              the reference source for the new taxon relationship
750
     * @param microcitation         the string with the details describing the exact localisation within the reference
751
     * @return
752
     * @see                         #getMisappliedNames()
753
     * @see                         #addProParteMisappliedName(Taxon, Reference, String)
754
     * @see                         #addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
755
     * @see                         #addTaxonRelation(TaxonRelationship)
756
     * @see                         #getTaxonRelations()
757
     * @see                         #getRelationsFromThisTaxon()
758
     * @see                         #getRelationsToThisTaxon()
759
     */
760
    public TaxonRelationship addProparteSynonym(Taxon proParteTaxon, Reference citation, String microcitation) {
761
        return proParteTaxon.addTaxonRelation(this, TaxonRelationshipType.PRO_PARTE_SYNONYM_FOR(), citation, microcitation);
762
    }
763

    
764
    /**
765
     * Creates a new {@link TaxonRelationship taxon relationship} instance with
766
     * {@link TaxonRelationshipType taxon relationship type} {@link TaxonRelationshipType#PARTIAL_SYNONYM_FOR()
767
     * partial synonym for} where <i>this</i> taxon plays the target role
768
     * and adds it to the set of {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon}.
769
     * The taxon relationship will also be added to the set of taxon
770
     * relationships to the other (partial synonym) taxon involved in the created relationship.
771
     *
772
     * @param partialTaxon         the taxon which plays the source role in the new taxon relationship
773
     * @param citation             the reference source for the new taxon relationship
774
     * @param microcitation        the string with the details describing the exact localisation within the reference
775
     * @return
776
     * @see                         #addProparteSynonym(Taxon, Reference, String)
777
     * @see                         #addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
778
     * @see                         #addTaxonRelation(TaxonRelationship)
779
     * @see                         #getTaxonRelations()
780
     * @see                         #getRelationsFromThisTaxon()
781
     * @see                         #getRelationsToThisTaxon()
782
     */
783
    public TaxonRelationship addPartialSynonym(Taxon partialTaxon, Reference citation, String microcitation) {
784
        return partialTaxon.addTaxonRelation(this, TaxonRelationshipType.PARTIAL_SYNONYM_FOR(), citation, microcitation);
785
    }
786

    
787
    /**
788
     * TODO update documentation
789
     * Removes one {@link TaxonRelationship taxon relationship} with {@link TaxonRelationshipType taxon relationship type}
790
     * taxonRelType and with the given child taxon playing the
791
     * source role from the set of {@link #getRelationsToThisTaxon() "taxon relationships to"} belonging
792
     * to <i>this</i> taxon. The taxon relationship will also be removed from the set
793
     * of {@link #getRelationsFromThisTaxon() "taxon relationships from"} belonging to the other side taxon.
794
     * Furthermore, the inherited RelatedFrom and RelatedTo attributes of the
795
     * taxon relationship will be nullified.<P>
796
     *
797
     * @param taxon			the taxon which plays the source role in the taxon relationship
798
     * @param taxonRelType	the taxon relationship type
799
     */
800
    public void removeTaxon(Taxon taxon, TaxonRelationshipType taxonRelType){
801
        Set<TaxonRelationship> taxRels = this.getTaxonRelations();
802
        for (TaxonRelationship taxRel : taxRels ){
803
            if (taxRel.getType().equals(taxonRelType)
804
                && taxRel.getFromTaxon().equals(taxon)){
805
                this.removeTaxonRelation(taxRel);
806
            }
807
        }
808
    }
809

    
810
    /**
811
     * Returns the boolean value indicating whether <i>this</i> taxon is a misapplication
812
     * (misapplied name) for at least one other taxon.
813
     */
814
    // TODO cache as for #hasTaxonomicChildren
815
    @Transient
816
    public boolean isMisapplication(){
817
        return computeMisapliedNameRelations() > 0;
818
    }
819

    
820
    /**
821
     * Counts the number of misapplied name relationships (including pro parte and partial
822
     * misapplied names) where this taxon represents the
823
     * misapplied name for another taxon.
824
     * @return
825
     */
826
    private int computeMisapliedNameRelations(){
827
        int count = 0;
828
        for (TaxonRelationship rel: this.getRelationsFromThisTaxon()){
829
            if (rel.getType().isAnyMisappliedName()){
830
                count++;
831
            }
832
        }
833
        return count;
834
    }
835

    
836
    /**
837
     * Returns the boolean value indicating whether <i>this</i> taxon is a misapplication
838
     * (misapplied name) for at least one other taxon.
839
     */
840
    // TODO cache as for #hasTaxonomicChildren
841
    @Transient
842
    public boolean isProparteSynonym(){
843
        return computeProparteSynonymRelations() > 0;
844
    }
845

    
846
    /**
847
     * Counts the number of misapplied name relationships (including pro parte misapplied
848
     * names) where this taxon represents the
849
     * misapplied name for another taxon.
850
     * @return
851
     */
852
    private int computeProparteSynonymRelations(){
853
        int count = 0;
854
        for (TaxonRelationship rel: this.getRelationsFromThisTaxon()){
855
            if (rel.getType().isAnySynonym()){
856
                count++;
857
            }
858
        }
859
        return count;
860
    }
861

    
862
    /**
863
     * Returns the boolean value indicating whether <i>this</i> taxon is a related
864
     * concept for at least one other taxon.
865
     */
866
    @Transient
867
    public boolean isRelatedConcept(){
868
        return computeConceptRelations() > 0;
869
    }
870

    
871
    /**
872
     * Counts the number of concept relationships where this taxon represents the
873
     * related concept for another taxon.
874
     * @return
875
     */
876
    private int computeConceptRelations(){
877
        int count = 0;
878
        for (TaxonRelationship rel: this.getRelationsFromThisTaxon()){
879
            TaxonRelationshipType type = rel.getType();
880
            if (type.isConceptRelationship()){
881
                count++;
882
            }
883
        }
884
        return count;
885
    }
886

    
887
    /**
888
     * Returns the boolean value indicating whether <i>this</i> taxon has at least one
889
     * {@link Synonym synonym} (true) or not (false). If true the {@link #getSynonyms() set of synonyms}
890
     * belonging to <i>this</i> ("accepted/valid") taxon is not empty .
891
     *
892
     * @see  #getSynonyms()
893
     * @see  #getSynonymNames()
894
     * @see  #removeSynonym(Synonym)
895
     */
896
    @Transient
897
    public boolean hasSynonyms(){
898
        return this.getSynonyms().size() > 0;
899
    }
900

    
901
    /**
902
     * Returns the boolean value indicating whether <i>this</i> taxon is at least
903
     * involved in one {@link #getTaxonRelations() taxon relationship} between
904
     * two taxa (true), either as a source or as a target, or not (false).
905
     *
906
     * @see  #getTaxonRelations()
907
     * @see  #getRelationsToThisTaxon()
908
     * @see  #getRelationsFromThisTaxon()
909
     * @see  #removeTaxonRelation(TaxonRelationship)
910
     * @see  TaxonRelationship
911
     */
912
    public boolean hasTaxonRelationships(){
913
        return this.getTaxonRelations().size() > 0;
914
    }
915

    
916
    /*
917
     * MISAPPLIED NAMES
918
     */
919
    /**
920
     * Returns the set of taxa playing the source role in {@link TaxonRelationship taxon relationships}
921
     * (with {@link TaxonRelationshipType taxon relationship type} "misapplied name for") where
922
     * <i>this</i> taxon plays the target role. A misapplied name is a taxon the
923
     * {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} of which has been erroneously used
924
     * by its {@link TaxonBase#getSec() taxon reference} to denominate the same real taxon
925
     * as the one meant by <i>this</i> ("accepted/correct") taxon.
926
     *
927
     * @see  #getTaxonRelations()
928
     * @see  #getRelationsToThisTaxon()
929
     * @see  #addMisappliedName(Taxon, Reference, String)
930
     * @param includeNonCongruent if <code>true</code> also those taxa are returned that are related
931
     * via a non congruent relationship like {@link TaxonRelationshipType#PRO_PARTE_MISAPPLIED_NAME_FOR()
932
     * pro parte misapplied name}
933
     */
934
    @Transient
935
    public Set<Taxon> getMisappliedNames(boolean includeNonCongruent){
936
        Set<Taxon> taxa = new HashSet<>();
937
        Set<TaxonRelationship> rels = this.getRelationsToThisTaxon();
938
        for (TaxonRelationship rel: rels){
939
            TaxonRelationshipType relType = rel.getType();
940
            if ( (includeNonCongruent && relType.isAnyMisappliedName())
941
                    || relType.equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())){
942
                taxa.add(rel.getFromTaxon());
943
            }
944
        }
945
        return taxa;
946
    }
947

    
948
    /**
949
     * Returns the set of misapplied name relationships in which this taxon
950
     * plays the role of the correctly accepted taxon (target). A misapplied name is a taxon the
951
     * {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} of which has been erroneously used
952
     * by its {@link TaxonBase#getSec() taxon reference} to denominate the same real taxon
953
     * as the one meant by <i>this</i> ("accepted/correct") taxon.
954
     */
955
    @Transient
956
    public Set<TaxonRelationship> getMisappliedNameRelations(){
957
        Set<TaxonRelationship> result = new HashSet<>();
958
        Set<TaxonRelationship> rels = this.getRelationsToThisTaxon();
959
        for (TaxonRelationship rel: rels){
960
            TaxonRelationshipType relType = rel.getType();
961
            if (relType.isAnyMisappliedName()){
962
                result.add(rel);
963
            }
964
        }
965
        return result;
966
    }
967

    
968
    /**
969
     * Returns the set of taxa playing the target role in {@link TaxonRelationship taxon relationships}
970
     * (with {@link TaxonRelationshipType taxon relationship type} "misapplied name for"
971
     * or "pro parte misapplied name for") where
972
     * <i>this</i> taxon plays the source role. A misapplied name is a taxon the
973
     * {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} of which has been erroneously used
974
     * by its {@link TaxonBase#getSec() taxon reference} to denominate the same real taxon
975
     * as the one meant by <i>this</i> ("accepted/correct") taxon.
976

    
977
     * @param includeNonCongruent if <code>true</code> also those taxa are returned that are related
978
     * via a non congruent relationship like {@link TaxonRelationshipType#PRO_PARTE_MISAPPLIED_NAME_FOR()
979
     * pro parte misapplied name}
980
     *
981
     * @see  #getTaxonRelations()
982
     * @see  #getRelationsToThisTaxon()
983
     * @see  #addMisappliedName(Taxon, Reference, String)
984
     * @see  #addProParteMisappliedName(Taxon, Reference, String)
985
     */
986
    @Transient
987
    public Set<Taxon> getTaxaForMisappliedName(boolean includeNonCongruent){
988
        Set<Taxon> taxa = new HashSet<>();
989
        Set<TaxonRelationship> rels = this.getRelationsFromThisTaxon();
990
        for (TaxonRelationship rel: rels){
991
            TaxonRelationshipType relType = rel.getType();
992
            if ( (includeNonCongruent && relType.isAnyMisappliedName())
993
                    || relType.equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())){
994
                taxa.add(rel.getToTaxon());
995
            }
996
        }
997
        return taxa;
998
    }
999

    
1000
    /**
1001
     * Returns the set of pro parte or partial synonym relationships in which this taxon
1002
     * plays the role of the "correctly" accepted taxon (target).
1003
     *
1004
     * @see #getProParteAndPartialSynonyms()
1005
     * @see #getMisappliedNameRelations()
1006
     */
1007
    @Transient
1008
    public Set<TaxonRelationship> getProParteAndPartialSynonymRelations(){
1009
        Set<TaxonRelationship> result = new HashSet<>();
1010
        Set<TaxonRelationship> rels = this.getRelationsToThisTaxon();
1011
        for (TaxonRelationship rel: rels){
1012
            TaxonRelationshipType relType = rel.getType();
1013
            if (relType.isAnySynonym()){
1014
                result.add(rel);
1015
            }
1016
        }
1017
        return result;
1018
    }
1019

    
1020
    /**
1021
     * Returns the set of pro parte or partial synonyms in which this taxon
1022
     * plays the role of the "correctly" accepted taxon (target).
1023
     *
1024
     * @see #getProParteAndPartialSynonymRelations()
1025
     * @see #getMisappliedNames(boolean)
1026
     */
1027
    @Transient
1028
    public Set<Taxon> getProParteAndPartialSynonyms(){
1029
        Set<Taxon> synonyms = new HashSet<>();
1030
        Set<TaxonRelationship> rels = this.getProParteAndPartialSynonymRelations();
1031
        for (TaxonRelationship rel: rels){
1032
            synonyms.add(rel.getFromTaxon());
1033
        }
1034
        return synonyms;
1035
    }
1036

    
1037
    /**
1038
     * Returns the set of all {@link TaxonName taxon names} used as {@link Synonym synonyms}
1039
     * of <i>this</i> ("accepted/valid") taxon.
1040
     *
1041
     * @see    #getSynonyms()
1042
     * @see    #getSynonymsSortedByType()
1043
     * @see    #addSynonymName(TaxonName, SynonymType)
1044
     * @see    #addSynonym(Synonym, SynonymType, Reference, String)
1045
     * @see    #removeSynonym(Synonym)
1046
     */
1047
    @Transient
1048
    public Set<TaxonName> getSynonymNames(){
1049
        Set<TaxonName> names = new HashSet<>();
1050
        for (Synonym syn: this.getSynonyms()){
1051
            names.add(syn.getName());
1052
        }
1053
        return names;
1054
    }
1055

    
1056
    /**
1057
     * Might be public in future. For the moment protected to ensure that
1058
     * synonym type is always set after refactoring.
1059
     *
1060
     * @param synonym
1061
     */
1062
    protected void addSynonym(Synonym synonym){
1063
        if (! this.equals(synonym.getAcceptedTaxon())){
1064
            synonym.setAcceptedTaxon(this);
1065
        }
1066
        if (!synonyms.contains(synonym)){
1067
            synonyms.add(synonym);
1068
        }
1069
    }
1070

    
1071
    /**
1072
     * Adds the given {@link Synonym synonym} to <code>this</code> taxon
1073
     * and changes the {@link SynonymType
1074
     * synonym type} before.
1075
     *
1076
     * @param synonym       the synonym to be added
1077
     * @param synonymType   the synonym type of the synonym to be added. If not <code>null</code>
1078
     *                      and if the synonym already has a type the existing type will be overwritten.<BR>
1079
     *                      If synonymType is {@link SynonymType#HOMOTYPIC_SYNONYM_OF()}
1080
     *                      the homotypic group of the synonym is changed to that of <code>this</code> taxon.<BR>
1081
     *                      To explicitly set the type to <code>null</code> use {@link Synonym#setType(SynonymType)}
1082
     * @see                 #addSynonym(Synonym)
1083
     * @see                 #addSynonym(Synonym, SynonymType, Reference, String)
1084
     * @see                 #addSynonymName(TaxonName, SynonymType)
1085
     * @see                 #addSynonymName(TaxonName, SynonymType, Reference, String)
1086
     * @see                 #addHomotypicSynonymName(TaxonName, Reference, String)
1087
     * @see                 #addHeterotypicSynonymName(TaxonName)
1088
     * @see                 #addHeterotypicSynonymName(TaxonName, Reference, String, HomotypicalGroup)
1089
     * @see                 #getSynonyms()
1090
     * @see                 #removeSynonym(Synonym)
1091
     * @see                 Synonym#getAcceptedTaxon()
1092
     */
1093
    public void addSynonym(Synonym synonym, SynonymType synonymType){
1094
        synonym.setType(synonymType); //must be set before as otherwise merging of homotypical groups may not work correctly in Synonym.checkHomotypic()
1095
        addSynonym(synonym);
1096
    }
1097

    
1098
    /**
1099
     * Adds the given {@link Synonym synonym} with the given {@link SynonymType
1100
     * synonym relationship type}
1101
     *
1102
     * @param synonym		the synonym to be added
1103
     * @param synonymType	the synonym  type of the synonym to be added. If not null
1104
     *                      and if the synonym already has a type the existing type will be overwritten.
1105
//     * @param citation		the reference source for the new synonym relationship
1106
//     * @param microcitation	the string with the details describing the exact localization within the reference
1107
     * @see    	   			#addSynonym(Synonym)
1108
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1109
     * @see    	   			#addSynonymName(TaxonName, SynonymType)
1110
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1111
     * @see    	   			#addHomotypicSynonymName(TaxonName, Reference, String)
1112
     * @see    	   			#addHeterotypicSynonymName(TaxonName)
1113
     * @see    	   			#addHeterotypicSynonymName(TaxonName, HomotypicalGroup, Reference, String)
1114
     * @see    	   			#getSynonyms()
1115
     * @see    				#removeSynonym(Synonym)
1116
     * @see    	   			Synonym#getAcceptedTaxon()
1117
     */
1118
    private void addSynonym(Synonym synonym, SynonymType synonymType, Reference newSecReference, String newSecMicroReference){
1119
        if (newSecReference != null){
1120
            synonym.setSec(newSecReference);
1121
        }
1122
        if (newSecMicroReference != null){
1123
            synonym.setSecMicroReference(newSecMicroReference);
1124
        }
1125
        addSynonym(synonym, synonymType);
1126
        return;
1127
    }
1128

    
1129
    /**
1130
     * Creates a new {@link Synonym synonym} to <code>this</code> {@link Taxon taxon}) using the
1131
     * given {@link TaxonName synonym name} and with the given
1132
     * {@link SynonymType synonym type}. If the later is
1133
     * {@link SynonymType#HOMOTYPIC_SYNONYM_OF() homotypic synonym}
1134
     * the name will be added to the same {@link HomotypicalGroup homotypical group}
1135
     * as the <code>this</code> accepted taxon.<BR>
1136
     * The secundum reference of the new synonym is taken from <code>this</code> taxon.
1137
     * A secundum detail is not set.
1138
     *
1139
     * @param synonymName	the taxon name to be used as a synonym to be added
1140
     * 						to <i>this</i> taxon's set of synonyms
1141
     * @param synonymType	the synonym  type of the synonym
1142
     * 						relationship to be added
1143
     * @return 				the created synonym
1144
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1145
     * @see    	   			#addSynonym(Synonym, SynonymType)
1146
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1147
     * @see    	   			#addHomotypicSynonym(Synonym, Reference, String)
1148
     * @see    	   			#addHomotypicSynonymName(TaxonName, Reference, String)
1149
     * @see    	   			#addHeterotypicSynonymName(TaxonName)
1150
     * @see    	   			#addHeterotypicSynonymName(TaxonName, HomotypicalGroup, Reference, String)
1151
     * @see    	   			#getSynonyms()
1152
     * @see    				#removeSynonym(Synonym)
1153
     */
1154
    public Synonym addSynonymName(TaxonName synonymName, SynonymType synonymType){
1155
        return addSynonymName(synonymName, null, null, synonymType);
1156
    }
1157

    
1158
    /**
1159
     * Creates a new {@link Synonym synonym} to <code>this</code> {@link Taxon taxon}) using the
1160
     * given {@link TaxonName synonym name} and with the given
1161
     * {@link SynonymType synonym type}. If the later is
1162
     * {@link SynonymType#HOMOTYPIC_SYNONYM_OF() homotypic synonym}
1163
     * the name will be added to the same {@link HomotypicalGroup homotypical group}
1164
     * as the <code>this</code> accepted taxon.<BR>
1165
     *
1166
     * If secReference is not <code>null</code>, the new synonym will have this as
1167
     * secundum reference. Otherwise <code>this</code> taxons sec reference is taken
1168
     * as secundum reference for the synonym. SecDetail will be the secMicroReference of the
1169
     * new synonym.<BR>
1170
     *
1171
     * @param synonymName	the taxon name to be used as a synonym to be added
1172
     * 						to <i>this</i> taxon's set of synonyms
1173
     * @param secReference	the secundum reference for the new synonym (if <code>null</code>
1174
     *                      <code>this</code> taxon's secundum reference is taken.
1175
     * @param secMicroReference the secundum micro reference of the new synonym
1176
     * @param synonymType	the synonym type of the synonym to be added
1177
     *
1178
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1179
     * @see    	   			#addSynonym(Synonym, SynonymType)
1180
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1181
     * @see    	   			#addHomotypicSynonym(Synonym, Reference, String)
1182
     * @see    	   			#addHomotypicSynonymName(TaxonName, Reference, String)
1183
     * @see    	   			#addHeterotypicSynonymName(TaxonName)
1184
     * @see    	   			#addHeterotypicSynonymName(TaxonName, HomotypicalGroup, Reference, String)
1185
     * @see    	   			#getSynonyms()
1186
     * @see    				#removeSynonym(Synonym)
1187
     */
1188
    public Synonym addSynonymName(TaxonName synonymName, Reference secReference, String secMicroReference, SynonymType synonymType){
1189
        Synonym synonym = Synonym.NewInstance(synonymName, this.getSec()); //default sec
1190
        addSynonym(synonym, synonymType, secReference, secMicroReference);
1191
        return synonym;
1192
    }
1193

    
1194
    /**
1195
     * Creates a new {@link Synonym synonym} to <code>this</code> {@link Taxon taxon}) using the given
1196
     * {@link TaxonName synonym name}. The synonym will have the synonym type
1197
     * {@link SynonymType#HETEROTYPIC_SYNONYM_OF() "is heterotypic synonym of"}.<BR>
1198
     * The secundum reference is taken from <code>this</code> taxon.
1199
     * No secMicroReference will be set for the new synonym.<BR>
1200
     * The synonym will keep it's old homotypical group.<BR>
1201
     *
1202
     * @param synonymName	the taxon name to be used as an heterotypic synonym
1203
     * 						to be added to <i>this</i> taxon's set of synonyms
1204
     * @return 				the created synonym
1205
     * @see    	   			#addHeterotypicSynonymName(TaxonName, Reference, String, HomotypicalGroup)
1206
     * @see    	   			#addSynonymName(TaxonName, SynonymType)
1207
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1208
     * @see    	   			#addSynonym(Synonym, SynonymType)
1209
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1210
     * @see    	   			#addHomotypicSynonym(Synonym, Reference, String)
1211
     * @see    	   			#addHomotypicSynonymName(TaxonName, Reference, String)
1212
     * @see    	   			#getSynonyms()
1213
     * @see    				#removeSynonym(Synonym)
1214
     */
1215
    public Synonym addHeterotypicSynonymName(TaxonName synonymName){
1216
        return addHeterotypicSynonymName(synonymName, null, null, null);
1217
    }
1218

    
1219
    /**
1220
     * Creates a new {@link Synonym synonym} to <code>this</code> {@link Taxon taxon}) using the given
1221
     * {@link TaxonName synonym name}. The synonym will have the synonym type
1222
     * {@link SynonymType#HETEROTYPIC_SYNONYM_OF() "is heterotypic synonym of"}.<BR>
1223
     *
1224
     * If secReference is not <code>null</code>, the new synonym will have this as
1225
     * secundum reference. Otherwise <code>this</code> taxons sec reference is taken
1226
     * as secundum reference for the synonym. SecDetail will be the secMicroReference of the
1227
     * new synonym.<BR>
1228
     * Furthermore the taxon name used as synonym will be added
1229
     * to the given {@link name.HomotypicalGroup homotypical group} (if not <code>null</code>).<BR>
1230
     *
1231
     * @param synonymName		the taxon name to be used as an heterotypic synonym
1232
     * 							to be added to <i>this</i> taxon's set of synonyms
1233
     * @param secReference		the secundum reference for the new synonym
1234
     * @param secDetail		    the secundum detil for the new synonym
1235
     * @param homotypicalGroup	the homotypical group to which the taxon name
1236
     * 							of the synonym will be added. If <code>null</code>
1237
     *                          the homotypical group of synonymName is not changed
1238
     * @return 					the created synonym
1239
     * @see    	   				#addHeterotypicSynonymName(TaxonName)
1240
     * @see    	   				#addSynonymName(TaxonName, SynonymType, Reference, String)
1241
     * @see    	   				#addSynonymName(TaxonName, SynonymType)
1242
     * @see    	   				#addSynonym(Synonym, SynonymType)
1243
     * @see    	   				#addSynonym(Synonym, SynonymType, Reference, String)
1244
     * @see    	   				#addHomotypicSynonym(Synonym, Reference, String)
1245
     * @see    	   				#addHomotypicSynonymName(TaxonName, Reference, String)
1246
     * @see    	   				#getSynonyms()
1247
     * @see    					#removeSynonym(Synonym)
1248
     */
1249
    public Synonym addHeterotypicSynonymName(TaxonName synonymName, Reference secReference, String secDetail, HomotypicalGroup homotypicalGroup){
1250
        Synonym synonym = Synonym.NewInstance(synonymName, this.getSec());
1251
        if (homotypicalGroup != null){
1252
            homotypicalGroup.addTypifiedName(synonymName);
1253
        }
1254
        addSynonym(synonym, SynonymType.HETEROTYPIC_SYNONYM_OF(), secReference, secDetail);
1255
        return synonym;
1256
    }
1257

    
1258
    /**
1259
    * Creates a new {@link Synonym synonym} to <code>this</code> {@link Taxon taxon}) using the given
1260
     * {@link TaxonName synonym name}. The synonym will have the synonym type
1261
     * {@link SynonymType#HOMOTYPIC_SYNONYM_OF() "is homotypic synonym of"}.<BR>
1262
     * The secundum reference is taken from <code>this</code> taxon.
1263
     * No secMicroReference will be set for the new synonym.<BR>
1264
     * The synonym's homotypic group will be changed to <code>this</code> taxon's group.<BR>
1265
     *
1266
     * @param synonymName	the taxon name to be used as an homotypic synonym
1267
     * 						to be added to <i>this</i> taxon's set of synonyms
1268
     * @return 				the created synonym
1269
     * @see    	   			#addHomotypicSynonym(Synonym, Reference, String)
1270
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1271
     * @see    	   			#addSynonymName(TaxonName, SynonymType)
1272
     * @see    	   			#addSynonym(Synonym, SynonymType)
1273
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1274
     * @see    	   			#addHeterotypicSynonymName(TaxonName)
1275
     * @see    	   			#addHeterotypicSynonymName(TaxonName, Reference, String, HomotypicalGroup)
1276
     * @see    	   			#getSynonyms()
1277
     * @see    				#removeSynonym(Synonym)
1278
     */
1279
    public Synonym addHomotypicSynonymName(TaxonName synonymName){
1280
        Synonym synonym = Synonym.NewInstance(synonymName, this.getSec());
1281
        addHomotypicSynonym(synonym);
1282
        return synonym;
1283
    }
1284

    
1285
    /**
1286
     * Adds the given {@link Synonym synonym} to <code>this</code> taxon,
1287
     * with the {@link SynonymType#HOMOTYPIC_SYNONYM_OF() "is homotypic synonym of"
1288
     * relationship type} and returns it.
1289
     * Furthermore the {@link TaxonName taxon name}
1290
     * used as synonym will be added to the same {@link HomotypicalGroup homotypic group}
1291
     * to which the taxon name of <i>this</i> taxon belongs.<BR>
1292
     *
1293
     * @param synonym		the synonym added to <i>this</i> taxon's synonym set
1294
     * @see    	   			#addHomotypicSynonymName(TaxonName, Reference, String)
1295
     * @see    	   			#addSynonym(Synonym, SynonymType)
1296
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1297
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1298
     * @see    	   			#addSynonymName(TaxonName, SynonymType)
1299
     * @see    	   			#addHeterotypicSynonymName(TaxonName)
1300
     * @see    	   			#addHeterotypicSynonymName(TaxonName, Reference, String, HomotypicalGroup)
1301
     * @see    	   			#getSynonyms()
1302
     * @see    				#removeSynonym(Synonym)
1303
     */
1304
    public void addHomotypicSynonym(Synonym synonym){
1305
    	if (!this.getSynonyms().contains(synonym)){
1306
    		addSynonym(synonym, SynonymType.HOMOTYPIC_SYNONYM_OF());
1307
    	} else{
1308
    		logger.warn("Tried to add a synonym to an accepted taxon that already is a synonym of this taxon.");
1309
    	}
1310
        return;
1311
    }
1312

    
1313
    /**
1314
     * Like {@link #removeSynonym(Synonym, boolean)} with <code>removeSynonymNameFromHomotypicalGroup</code> set to true.
1315
     * @see #removeSynonym(Synonym, boolean)
1316
     */
1317
    public void removeSynonym(Synonym synonym){
1318
        removeSynonym(synonym, true);
1319
    }
1320

    
1321

    
1322
    /**
1323
     * Removes one element from the set of {@link Synonym synonyms} assigned
1324
     * to <i>this</i> (accepted/valid) taxon.
1325
     *
1326
     * @param synonym  the synonym to be removed
1327
     * @param removeSynonymNameFromHomotypicalGroup
1328
     *              if <code>true</code> the synonym name will also be deleted from its homotypical group if the
1329
     *              group contains other names
1330
     * @see     #getSynonyms()
1331
     * @see     #removeSynonym(Synonym)
1332
     */
1333
    public void removeSynonym(Synonym synonym, boolean removeSynonymNameFromHomotypicalGroup) {
1334
        if (synonym != null && this.equals(synonym.getAcceptedTaxon())){
1335
            if(removeSynonymNameFromHomotypicalGroup){
1336
                HomotypicalGroup synHG = synonym.getName().getHomotypicalGroup();
1337
                if (synHG.getTypifiedNames().size() > 1){
1338
                    synHG.removeTypifiedName(synonym.getName(), false);
1339
                }
1340
            }
1341
            this.synonyms.remove(synonym);
1342
            synonym.setAcceptedTaxon(null);
1343
        }
1344
    }
1345

    
1346
    /**
1347
     * @see #getHomotypicSynonymsByHomotypicGroup(TaxonComparator)
1348
     */
1349
    @Transient
1350
    public List<Synonym> getHomotypicSynonymsByHomotypicGroup(){
1351
        return getHomotypicSynonymsByHomotypicGroup(null);
1352
    }
1353

    
1354
    /**
1355
     * Retrieves the ordered list (depending on the date of publication) of
1356
     * homotypic {@link Synonym synonyms} (according to the same {@link eu.etaxonomy.cdm.model.reference.Reference reference}
1357
     * as for <i>this</i> taxon) under the condition that the {@link eu.etaxonomy.cdm.model.name.TaxonName taxon names}
1358
     * of these synonyms and the taxon name of <i>this</i> taxon belong to the
1359
     * same {@link eu.etaxonomy.cdm.model.name.HomotypicalGroup homotypical group}.
1360
     *
1361
     * @param       comparator the taxon comparator to use, if <code>null</code> the default comparator is taken.
1362
     * @return      the ordered list of homotypic synonyms
1363
     * @see         #getHomotypicSynonymsByHomotypicSynonymType()
1364
     * @see         #getSynonyms()
1365
     * @see         #getHomotypicSynonymyGroups()
1366
     * @see         eu.etaxonomy.cdm.model.name.HomotypicalGroup
1367
     * @see         eu.etaxonomy.cdm.model.name.HomotypicalGroup#getSynonymsInGroup(Reference)
1368
     */
1369
    @Transient
1370
    public List<Synonym> getHomotypicSynonymsByHomotypicGroup(TaxonComparator comparator){
1371
        if (this.getHomotypicGroup() == null){
1372
            return null;
1373
        }else if (comparator == null){
1374
            return this.getSynonymsInGroup(this.getHomotypicGroup());
1375
        }else{
1376
            return this.getSynonymsInGroup(this.getHomotypicGroup(), comparator);
1377
        }
1378
    }
1379

    
1380
    /**
1381
     * Retrieves the list of homotypic {@link Synonym synonyms}
1382
     * (according to the same {@link eu.etaxonomy.cdm.model.reference.Reference reference}
1383
     * as for <i>this</i> taxon) under the condition that these synonyms and
1384
     * <i>this</i> taxon are involved in {@link SynonymRelationship synonym relationships} with an
1385
     * "is homotypic synonym of" {@link SynonymType#HOMOTYPIC_SYNONYM_OF() synonym relationship type}.
1386
     *
1387
     * @return		the ordered list of homotypic synonyms
1388
     * @see			#getHomotypicSynonymsByHomotypicGroup()
1389
     * @see			#getSynonyms()
1390
     * @see			#getHomotypicSynonymyGroups()
1391
     * @see			SynonymType
1392
     * @deprecated as the method currently returns data not matching the original description of the method
1393
     * as an ordered list (according to date of publication) of synonyms with same secundum as <i>this</i> taxon.
1394
     * In future this method will either be removed or semantics may change.
1395
     */
1396
    @Deprecated
1397
    @Transient
1398
    public List<Synonym> getHomotypicSynonymsByHomotypicSynonymType(){
1399
        Set<Synonym> synonyms = this.getSynonyms();
1400
        List<Synonym> result = new ArrayList<>();
1401
        for(Synonym synonym : synonyms) {
1402
            if(synonym.getType().equals(SynonymType.HOMOTYPIC_SYNONYM_OF())){
1403
                result.add(synonym);
1404
            }
1405
        }
1406
        return result;
1407
    }
1408

    
1409
    /**
1410
     * Returns the ordered list of all {@link eu.etaxonomy.cdm.model.name.HomotypicalGroup homotypical groups} {@link Synonym synonyms} of
1411
     * <i>this</i> taxon belong to. {@link eu.etaxonomy.cdm.model.name.TaxonName Taxon names} of homotypic synonyms
1412
     * belong to the same homotypical group as the taxon name of <i>this</i>
1413
     * taxon. Taxon names of heterotypic synonyms belong to at least one other
1414
     * homotypical group. <BR>
1415
     * The list returned is ordered according to the date of publication of the
1416
     * first published name within each homotypical group.
1417
     *
1418
     * @see			#getHeterotypicSynonymyGroups()
1419
     * @see			#getSynonyms()
1420
     * @see			eu.etaxonomy.cdm.model.name.HomotypicalGroup
1421
     */
1422
    @Transient
1423
    public List<HomotypicalGroup> getHomotypicSynonymyGroups(){
1424
        List<HomotypicalGroup> result = new ArrayList<>();
1425
        HomotypicalGroup myGroup = this.getHomotypicGroup();
1426
        if (myGroup != null){  //if taxon has no name HG might be null
1427
            result.add(myGroup);
1428
        }
1429
        for (TaxonName taxonName :this.getSynonymNames()){
1430
            if (taxonName != null) {
1431
                if (!result.contains(taxonName.getHomotypicalGroup())){
1432
                    result.add(taxonName.getHomotypicalGroup());
1433
                }
1434
            }
1435
        }
1436
        return result;
1437
    }
1438

    
1439
    /**
1440
     * {@inheritDoc}.
1441
     * <BR>Also returns <code>false</code> if it is a misapplied name or has a similar concept relationship that
1442
     * is similar to synonym relationship (shows up in the synonymy of applications)
1443
     */
1444
    @Override
1445
    @Transient
1446
    public boolean isOrphaned() {
1447

    
1448
        if(taxonNodes == null || taxonNodes.isEmpty()) {
1449
            if(getRelationsFromThisTaxon().isEmpty()) {
1450
                return true;
1451
            }else{
1452
                for (TaxonRelationship rel : getRelationsFromThisTaxon()){
1453
                    if (rel.getType() != null && ! rel.getType().isConceptRelationship()){
1454
                        return false;  //a synonym relationship type similar relationship exists => not orphaned
1455
                    }
1456
                }
1457
                return true;  //all relations are real concept relations and therefore not relevant
1458
            }
1459
        }else{
1460
            return false;
1461
        }
1462
    }
1463

    
1464
    /**
1465
     * Returns the ordered list of all
1466
     * {@link eu.etaxonomy.cdm.model.name.HomotypicalGroup homotypical groups}
1467
     * that contain {@link Synonym synonyms} that are heterotypic to <i>this</i> taxon.<BR>
1468
     *
1469
     * {@link eu.etaxonomy.cdm.model.name.TaxonName Taxon names} of heterotypic synonyms
1470
     * belong to a homotypical group which cannot be the homotypical group to which the
1471
     * taxon name of <i>this</i> taxon belongs.
1472
     * This method returns the same
1473
     * list as the {@link #getHomotypicSynonymyGroups() getHomotypicSynonymyGroups} method
1474
     * but without the homotypical group to which the taxon name of <i>this</i> taxon
1475
     * belongs.<BR>
1476
     * The list returned is <B>ordered</B> according to the rules defined for
1477
     * the {@link HomotypicGroupTaxonComparator} which includes 1) grouping of
1478
     * basionym groups, 2) replaced synonym relationships, 3) publication date,
1479
     * 4) ranks and 5) alphabetical order.
1480
     *
1481
     * @see			#getHeterotypicSynonymyGroups()
1482
     * @see			#getSynonyms()
1483
     * @see			SynonymType#HETEROTYPIC_SYNONYM_OF()
1484
     * @see			eu.etaxonomy.cdm.model.name.HomotypicalGroup
1485
     */
1486
    @Transient
1487
    public List<HomotypicalGroup> getHeterotypicSynonymyGroups(){
1488
        List<HomotypicalGroup> list = getHomotypicSynonymyGroups();
1489
        //remove homotypic group
1490
        list.remove(this.getHomotypicGroup());
1491
        //sort
1492
        Map<Synonym, HomotypicalGroup> map = new HashMap<>();
1493
        for (HomotypicalGroup homotypicalGroup: list){
1494
            List<Synonym> synonymList = getSynonymsInGroup(homotypicalGroup);
1495
            if (synonymList.size() > 0){
1496
                //select the first synonym in the group
1497
                map.put(synonymList.get(0), homotypicalGroup);
1498
            }
1499
        }
1500
        List<Synonym> keyList = new ArrayList<>();
1501
        keyList.addAll(map.keySet());
1502
        //order by first synonym
1503
        Collections.sort(keyList, defaultTaxonComparator);
1504

    
1505
        List<HomotypicalGroup> result = new ArrayList<>();
1506
        for(Synonym synonym: keyList){
1507
            //"replace" synonyms by homotypic groups
1508
            result.add(map.get(synonym));
1509
        }
1510
        //sort end
1511
        return result;
1512
    }
1513

    
1514
    /**
1515
     * Retrieves the ordered list (depending on the rules defined for
1516
     * the {@link HomotypicGroupTaxonComparator}) of
1517
     * {@link taxon.Synonym synonyms} (according to a given reference)
1518
     * the {@link TaxonName taxon names} of which belong to the homotypical group.
1519
     * If other names are part of the group that are not considered synonyms of
1520
     * <i>this</i> taxon, then they will not be included in
1521
     * the result set.
1522
     *
1523
     * @param homotypicGroup
1524
     * @see          #getHeterotypicSynonymyGroups()
1525
     * @see			TaxonName#getSynonyms()
1526
     * @see			TaxonName#getTaxa()
1527
     * @see			taxon.Synonym
1528
     */
1529
    @Transient
1530
    public List<Synonym> getSynonymsInGroup(HomotypicalGroup homotypicGroup){
1531
        return getSynonymsInGroup(homotypicGroup, new HomotypicGroupTaxonComparator(this));
1532
    }
1533

    
1534
    /**
1535
     * @param homotypicGroup
1536
     * @param comparator
1537
     * @return
1538
     * @see     #getSynonymsInGroup(HomotypicalGroup)
1539
     * @see     #getHeterotypicSynonymyGroups()
1540
     */
1541
    @Transient
1542
    public List<Synonym> getSynonymsInGroup(HomotypicalGroup homotypicGroup, TaxonComparator comparator){
1543
        List<Synonym> result = new ArrayList<>();
1544
        if (homotypicGroup == null){
1545
            return result;  //always empty
1546
        }
1547

    
1548
        for (Synonym synonym : this.getSynonyms()){
1549
            if (homotypicGroup.equals(synonym.getHomotypicGroup())){
1550
                result.add(synonym);
1551
            }
1552
        }
1553

    
1554
        Collections.sort(result, comparator);
1555
        return result;
1556
    }
1557

    
1558
    /**
1559
     * @see     #getSynonymsGroups()
1560
     */
1561
    @Transient
1562
    public List<Taxon> getAllMisappliedNames(){
1563
        List<Taxon> result = new ArrayList<>();
1564

    
1565
        for (TaxonRelationship rel : this.getRelationsToThisTaxon()){
1566
            if (rel.getType().isAnyMisappliedName() ){
1567
                result.add(rel.getFromTaxon());
1568
            }
1569
        }
1570
        sortBySimpleTitleCacheComparator(result);
1571
        return result;
1572
    }
1573

    
1574

    
1575
    /**
1576
     * @see     #getSynonymsGroups()
1577
     */
1578
    @Transient
1579
    public List<Taxon> getAllProParteSynonyms(){
1580
        List<Taxon> result = new ArrayList<>();
1581

    
1582
        for (TaxonRelationship rel : this.getRelationsToThisTaxon()){
1583
            if (rel.getType().isAnySynonym()){
1584
                result.add(rel.getFromTaxon());
1585
            }
1586
        }
1587
        sortBySimpleTitleCacheComparator(result);
1588
        return result;
1589
    }
1590

    
1591
    private void sortBySimpleTitleCacheComparator(List<Taxon> result) {
1592

    
1593
        Comparator<Taxon> taxonComparator = new Comparator<Taxon>(){
1594

    
1595
            @Override
1596
            public int compare(Taxon o1, Taxon o2) {
1597

    
1598
                if (o1.getTitleCache() == o2.getTitleCache()){
1599
                    return 0;
1600
                }
1601
                if (o1.getTitleCache() == null){
1602
                    return -1;
1603
                }
1604
                if (o2.getTitleCache() == null){
1605
                    return 1;
1606
                }
1607
                return o1.getTitleCache().compareTo(o2.getTitleCache());
1608

    
1609
            }
1610
        };
1611
        Collections.sort(result, taxonComparator);
1612
    }
1613

    
1614
    /**
1615
     * Returns the image gallery description. If no image gallery exists, a new one is created using the
1616
     * defined title and adds the string "-Image Gallery" to the title.</BR>
1617
     * If multiple image galleries exist an arbitrary one is choosen.
1618
     * @param title
1619
     * @return
1620
     */
1621
    public TaxonDescription getOrCreateImageGallery(String title){
1622
        return getOrCreateImageGallery(title, true, false);
1623
    }
1624

    
1625
    /**
1626
     * Returns the image gallery description. If no image gallery exists, a new one is created using the
1627
     * defined title.</BR>
1628
     * If onlyTitle == true we look only for an image gallery with this title, create a new one otherwise.
1629
     * If multiple image galleries exist that match the conditions an arbitrary one is choosen.
1630
     * @param title
1631
     * @param onlyTitle
1632
     * @param if true, the String "Image Gallery
1633
     * @return
1634
     */
1635
    @Transient
1636
    public TaxonDescription getOrCreateImageGallery(String title, boolean addImageGalleryToTitle, boolean onlyTitle){
1637
        TaxonDescription result = null;
1638
        String titleCache = (title == null) ? "Image Gallery" : title;
1639
        if (title != null && addImageGalleryToTitle){
1640
            titleCache = titleCache+ "-Image Gallery";
1641
        }
1642
        Set<TaxonDescription> descriptionSet = this.getDescriptions();
1643
        for (TaxonDescription desc: descriptionSet){
1644
            if (desc.isImageGallery()){
1645
                if (onlyTitle && ! titleCache.equals(desc.getTitleCache())){
1646
                    continue;
1647
                }
1648
                result = desc;
1649
                if (onlyTitle && titleCache.equals(desc.getTitleCache())){
1650
                    break;
1651
                }
1652
            }
1653
        }
1654
        if (result == null){
1655
            result = TaxonDescription.NewInstance();
1656
            result.setTitleCache(titleCache, true);
1657
            this.addDescription(result);
1658
            result.setImageGallery(true);
1659
        }
1660
        return result;
1661
    }
1662

    
1663
    public void clearDescriptions() {
1664
        this.descriptions = new HashSet<>();
1665
    }
1666

    
1667
    /**
1668
     * Compiles all description items attached to this taxon having the given feature
1669
     * and being of the given class. If feature or clazz is null no according filter
1670
     * is applied.
1671
     */
1672
    public <T extends DescriptionElementBase> Set<T> getDescriptionItems(Feature feature, Class<T> clazz) {
1673
        Set<T> result = new HashSet<>();
1674
        Set<TaxonDescription> descriptions = this.getDescriptions();
1675
        for (TaxonDescription description : descriptions) {
1676
            for (DescriptionElementBase deb : description.getElements()) {
1677
                if (clazz == null || deb.isInstanceOf(clazz)) {
1678
                    if (feature == null || feature.equals(deb.getFeature())) {
1679
                        T matchingDeb = CdmBase.deproxy(deb, clazz);
1680
                        result.add(matchingDeb);
1681
                    }
1682
                }
1683
            }
1684
        }
1685
        return result;
1686
    }
1687

    
1688
    //*********************** CLONE ********************************************************/
1689

    
1690
    /**
1691
     * Clones <i>this</i> taxon. This is a shortcut that enables to create
1692
     * a new instance that differs only slightly from <i>this</i> taxon by
1693
     * modifying only some of the attributes.<BR><BR>
1694
     * The TaxonNodes are not cloned, the list is empty.<BR>
1695
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
1696
     * The taxon relationships and synonym relationships are cloned <BR>
1697
     *
1698
     * @see eu.etaxonomy.cdm.model.taxon.TaxonBase#clone()
1699
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
1700
     * @see java.lang.Object#clone()
1701
     */
1702
    @Override
1703
    public Taxon clone() {
1704
        Taxon result;
1705
        result = (Taxon)super.clone();
1706

    
1707
        result.setRelationsFromThisTaxon(new HashSet<>());
1708

    
1709
        for (TaxonRelationship fromRelationship : this.getRelationsFromThisTaxon()){
1710
            TaxonRelationship newRelationship = (TaxonRelationship)fromRelationship.clone();
1711
            newRelationship.setRelatedFrom(result);
1712
            result.relationsFromThisTaxon.add(newRelationship);
1713
        }
1714

    
1715
        result.setRelationsToThisTaxon(new HashSet<>());
1716
        for (TaxonRelationship toRelationship : this.getRelationsToThisTaxon()){
1717
            TaxonRelationship newRelationship = (TaxonRelationship)toRelationship.clone();
1718
            newRelationship.setRelatedTo(result);
1719
            result.relationsToThisTaxon.add(newRelationship);
1720
        }
1721

    
1722
        //clone synonyms (is this wanted or should we remove synonyms
1723
        result.synonyms = new HashSet<>();
1724
        for (Synonym synonym : this.getSynonyms()){
1725
            Synonym newSyn = synonym.clone();
1726
            newSyn.setAcceptedTaxon(result);
1727
        }
1728

    
1729
        result.descriptions = new HashSet<>();
1730
        for (TaxonDescription description : this.getDescriptions()){
1731
            TaxonDescription newDescription = description.clone();
1732
            result.addDescription(newDescription);
1733
        }
1734

    
1735
        result.taxonNodes = new HashSet<>();
1736

    
1737
        /*for (TaxonNode taxonNode : this.getTaxonNodes()){
1738
            TaxonNode newTaxonNode = (TaxonNode)taxonNode.clone();
1739
            newTaxonNode.setTaxon(result);
1740
            result.addTaxonNode(newTaxonNode);
1741
        }*/
1742

    
1743
        return result;
1744
    }
1745
}
(9-9/21)