Project

General

Profile

Download (84.9 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.EnumSet;
18
import java.util.HashMap;
19
import java.util.HashSet;
20
import java.util.Iterator;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.Set;
24

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

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

    
55
import eu.etaxonomy.cdm.compare.taxon.HomotypicGroupTaxonComparator;
56
import eu.etaxonomy.cdm.compare.taxon.TaxonComparator;
57
import eu.etaxonomy.cdm.hibernate.search.GroupByTaxonClassBridge;
58
import eu.etaxonomy.cdm.hibernate.search.TaxonRelationshipClassBridge;
59
import eu.etaxonomy.cdm.model.common.CdmBase;
60
import eu.etaxonomy.cdm.model.common.CdmClass;
61
import eu.etaxonomy.cdm.model.common.IRelated;
62
import eu.etaxonomy.cdm.model.common.RelationshipBase;
63
import eu.etaxonomy.cdm.model.common.TimePeriod;
64
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
65
import eu.etaxonomy.cdm.model.description.DescriptionType;
66
import eu.etaxonomy.cdm.model.description.Feature;
67
import eu.etaxonomy.cdm.model.description.IDescribable;
68
import eu.etaxonomy.cdm.model.description.TaxonDescription;
69
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
70
import eu.etaxonomy.cdm.model.name.ITaxonNameBase;
71
import eu.etaxonomy.cdm.model.name.TaxonName;
72
import eu.etaxonomy.cdm.model.reference.ICdmTarget;
73
import eu.etaxonomy.cdm.model.reference.Reference;
74
import eu.etaxonomy.cdm.strategy.cache.taxon.ITaxonCacheStrategy;
75

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

    
113
    private static final long serialVersionUID = -584946869762749006L;
114
    private static final Logger logger = LogManager.getLogger(Taxon.class);
115

    
116
    private static final TaxonComparator defaultTaxonComparator = new TaxonComparator();
117

    
118
    @XmlElementWrapper(name = "Descriptions")
119
    @XmlElement(name = "Description")
120
    @OneToMany(mappedBy="taxon", fetch= FetchType.LAZY)
121
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
122
    @NotNull
123
    @ContainedIn
124
    private Set<TaxonDescription> descriptions = new HashSet<>();
125

    
126
    // all related synonyms
127
    @XmlElementWrapper(name = "Synonyms")
128
    @XmlElement(name = "Synonym")
129
    @XmlIDREF
130
    @XmlSchemaType(name = "IDREF")
131
    @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
132
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
133
    @NotNull
134
    @Valid
135
    @ContainedIn
136
    private Set<Synonym> synonyms = new HashSet<>();
137

    
138
    // all taxa relations with rel.fromTaxon==this
139
    @XmlElementWrapper(name = "RelationsFromThisTaxon")
140
    @XmlElement(name = "FromThisTaxonRelationship")
141
    @OneToMany(mappedBy="relatedFrom", fetch=FetchType.LAZY, orphanRemoval=true)
142
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
143
    @NotNull
144
//    @Valid
145
    @ContainedIn
146
    private Set<TaxonRelationship> relationsFromThisTaxon = new HashSet<>();
147

    
148
    // all taxa relations with rel.toTaxon==this
149
    @XmlElementWrapper(name = "RelationsToThisTaxon")
150
    @XmlElement(name = "ToThisTaxonRelationship")
151
    @XmlIDREF
152
    @XmlSchemaType(name = "IDREF")
153
    @OneToMany(mappedBy="relatedTo", fetch=FetchType.LAZY, orphanRemoval=true)
154
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
155
//    @Valid
156
    @ContainedIn
157
    private Set<TaxonRelationship> relationsToThisTaxon = new HashSet<>();
158

    
159
    @XmlAttribute(name= "taxonStatusUnknown")
160
    private boolean taxonStatusUnknown = false;
161
    /**
162
     * The status of this taxon is unknown it could also be some kind of synonym.
163
     * @return the taxonStatusUnknown
164
     */
165
    public boolean isTaxonStatusUnknown() {return taxonStatusUnknown;}
166
     /** @see #isTaxonStatusUnknown()*/
167
    public void setTaxonStatusUnknown(boolean taxonStatusUnknown) {this.taxonStatusUnknown = taxonStatusUnknown;}
168

    
169
    @XmlElementWrapper(name = "taxonNodes")
170
    @XmlElement(name = "taxonNode")
171
    @XmlIDREF
172
    @XmlSchemaType(name = "IDREF")
173
    @OneToMany(mappedBy="taxon", fetch=FetchType.LAZY)
174
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
175
    @IndexedEmbedded
176
    private Set<TaxonNode> taxonNodes = new HashSet<>();
177

    
178
    private String conceptId;
179

    
180
    @XmlAttribute(name ="TaxonType")
181
    @NotNull
182
    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumSetUserType",
183
        parameters = {@org.hibernate.annotations.Parameter(name="enumClass", value="eu.etaxonomy.cdm.model.taxon.TaxonType")}
184
    )
185
    @Audited
186
    private EnumSet<TaxonType> taxonTypes = EnumSet.noneOf(TaxonType.class);
187

    
188
    @XmlAttribute(name ="ConceptDefinition")
189
    @NotNull
190
    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumSetUserType",
191
        parameters = {@org.hibernate.annotations.Parameter(name="enumClass", value="eu.etaxonomy.cdm.model.taxon.ConceptDefinition")}
192
    )
193
    @Audited
194
    private EnumSet<ConceptDefinition> conceptDefinitions = EnumSet.noneOf(ConceptDefinition.class);
195

    
196
    @XmlAttribute(name ="ConceptStatus")
197
    @NotNull
198
    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumSetUserType",
199
        parameters = {@org.hibernate.annotations.Parameter(name="enumClass", value="eu.etaxonomy.cdm.model.taxon.ConceptStatus")}
200
    )
201
    @Audited
202
    private EnumSet<ConceptStatus> conceptStatus = EnumSet.noneOf(ConceptStatus.class);
203

    
204
    private TimePeriod currentConceptPeriod = TimePeriod.NewInstance();
205

    
206
// ************************* FACTORY METHODS ********************************/
207

    
208
    /**
209
     * @see #NewInstance(TaxonName, Reference)
210
     * @param taxonName
211
     * @param sec
212
     * @return
213
     */
214
    public static Taxon NewInstance(ITaxonNameBase taxonName, Reference sec){
215
        return NewInstance(TaxonName.castAndDeproxy(taxonName), sec);
216
    }
217

    
218
    /**
219
     * Creates a new (accepted/valid) taxon instance with
220
     * the {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} used and the {@link eu.etaxonomy.cdm.model.reference.Reference reference}
221
     * using it.
222
     *
223
     * @param  taxonName	the taxon name used
224
     * @param  sec				the reference using the taxon name
225
     * @see    					#Taxon(TaxonName, Reference)
226
     */
227
    public static Taxon NewInstance(TaxonName taxonName, Reference sec){
228
        Taxon result = new Taxon(taxonName, sec);
229
        return result;
230
    }
231

    
232
    /**
233
     * Creates a new Taxon for the given name, secundum reference and secundum detail
234
     * @param taxonName
235
     * @param sec
236
     * @param secMicroReference
237
     * @see #
238
     */
239
    public static Taxon NewInstance(TaxonName taxonName, Reference sec, String secMicroReference){
240
        Taxon result = new Taxon(taxonName, sec, secMicroReference);
241
        return result;
242
    }
243

    
244
    /**
245
     * Creates a new taxon instance with an unknown status (accepted/synonym) and with
246
     * the {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} used and the {@link eu.etaxonomy.cdm.model.reference.Reference reference}
247
     * using it.
248
     *
249
     * @param  taxonName	the taxon name used
250
     * @param  sec				the reference using the taxon name
251
     * @see    					#Taxon(TaxonName, Reference)
252
     */
253
    public static Taxon NewUnknownStatusInstance(TaxonName taxonName, Reference sec){
254
        Taxon result = new Taxon(taxonName, sec);
255
        result.setTaxonStatusUnknown(true);
256
        return result;
257
    }
258
// ************* CONSTRUCTORS *************/
259

    
260
    //for hibernate use only, *packet* private required by bytebuddy
261
    //TODO should be private, but still produces Spring init errors
262
    @Deprecated
263
    Taxon(){}
264

    
265
    private Taxon(TaxonName taxonName, Reference sec){
266
        super(taxonName, sec, null);
267
    }
268

    
269
    private Taxon(TaxonName taxonName, Reference sec, String secMicroReference){
270
        super(taxonName, sec, secMicroReference);
271
    }
272

    
273
//********* METHODS **************************************/
274

    
275
    public String getConceptId() {
276
        return conceptId;
277
    }
278
    public void setConceptId(String conceptId) {
279
        this.conceptId = conceptId;
280
    }
281

    
282
    public TimePeriod getCurrentConceptPeriod() {
283
        return currentConceptPeriod;
284
    }
285
    public void setCurrentConceptPeriod(TimePeriod currentConceptPeriod) {
286
        this.currentConceptPeriod = currentConceptPeriod;
287
    }
288

    
289
// ************************ Concept Defintion ************************/
290

    
291
    public boolean isHomotypicGroups() {
292
        return ConceptDefinition.includesType(conceptDefinitions, ConceptDefinition.HOMOTYPIC_GROUP);
293
    }
294
    public void setHomotypicGroups(boolean isHomotypicGroups) {
295
        setConceptDefinition(ConceptDefinition.HOMOTYPIC_GROUP, isHomotypicGroups);
296
    }
297

    
298
    //calling it descriptionConcept not Descriptions to avoid name clash with setter for TaxonDescriptions
299
    public boolean isDescriptionConcept() {
300
        return ConceptDefinition.includesType(conceptDefinitions, ConceptDefinition.DESCRIPTION);
301
    }
302
    public void setDescriptionConcept(boolean isDescriptions) {
303
        setConceptDefinition(ConceptDefinition.DESCRIPTION, isDescriptions);
304
    }
305

    
306
    protected EnumSet<ConceptDefinition> getConceptDefinition() {
307
        return conceptDefinitions;
308
    }
309

    
310
    /**
311
     * for know it is private and the boolean getters and setters should be used instead.
312
     * If you make it public make sure to guarantee that any change to the enum set results
313
     * in a new enum set (see also {@link #newEnumSet(EnumSet, ConceptDefinition, ConceptDefinition)}
314
     * and that the client is aware of the enum set being immutable.
315
     */
316
    private void setConceptDefinitions(EnumSet<ConceptDefinition> conceptDefinitions){
317
        this.conceptDefinitions = conceptDefinitions;
318
    }
319

    
320
    /**
321
     * Sets the value for taxon concept definitions
322
     * @param conceptDefinition the concept definition
323
     * @param value the value if this taxon has this concept definition (<code>true</code>) or not (<code>false</code>)
324
     */
325
    protected void setConceptDefinition(ConceptDefinition conceptDefinition, boolean value) {
326
        if (value && !this.conceptDefinitions.contains(conceptDefinition)){
327
            setConceptDefinitions(newEnumSet(this.conceptDefinitions, conceptDefinition, null));
328
        }else if (!value && this.conceptDefinitions.contains(conceptDefinition)){
329
            setConceptDefinitions(newEnumSet(this.conceptDefinitions, null, conceptDefinition));
330
        }else{
331
            return;
332
        }
333
    }
334

    
335
// ************************ TaxonType ********************************/
336

    
337
    public boolean isConcept() {
338
        return TaxonType.includesType(taxonTypes, TaxonType.CONCEPT);
339
    }
340
    public void setConcept(boolean isConcept) {
341
        setTaxonType(TaxonType.CONCEPT, isConcept);
342
    }
343

    
344
    public boolean isNameUsage() {
345
        return TaxonType.includesType(taxonTypes, TaxonType.NAME_USAGE);
346
    }
347
    public void setNameUsage(boolean isNameUsage) {
348
        setTaxonType(TaxonType.NAME_USAGE, isNameUsage);
349
    }
350

    
351
    protected EnumSet<TaxonType> getTaxonTypes() {
352
        return taxonTypes;
353
    }
354

    
355
    /**
356
     * for know it is private and the boolean getters and setters should be used instead.
357
     * If you make it public make sure to guarantee that any change to the enum set results
358
     * in a new enum set (see also {@link #newEnumSet(EnumSet, TaxonType, TaxonType)}
359
     * and that the client is aware of the enum set being immutable.
360
     */
361
    private void setTaxonTypes(EnumSet<TaxonType> taxonTypes){
362
        this.taxonTypes = taxonTypes;
363
    }
364

    
365
    /**
366
     * Sets the value for taxon types
367
     * @param type the taxon type
368
     * @param value the value if this taxon has this type (<code>true</code>) or not (<code>false</code>)
369
     */
370
    protected void setTaxonType(TaxonType type, boolean value) {
371
        if (value && !this.taxonTypes.contains(type)){
372
            setTaxonTypes(newEnumSet(this.taxonTypes, type, null));
373
        }else if (!value && this.taxonTypes.contains(type)){
374
            setTaxonTypes(newEnumSet(this.taxonTypes, null, type));
375
        }else{
376
            return;
377
        }
378
    }
379

    
380
// ************************ Concept Status ********************************/
381

    
382
    public boolean isPersistent() {
383
        return ConceptStatus.includesType(conceptStatus, ConceptStatus.PERSISTENT);
384
    }
385
    public void setPersistent(boolean isPersistent) {
386
        setConceptStatus(ConceptStatus.PERSISTENT, isPersistent);
387
    }
388
    public boolean isSupportsProvenance() {
389
        return ConceptStatus.includesType(conceptStatus, ConceptStatus.SUPPORTS_PROVENANCE);
390
    }
391
    public void setSupportsProvenance(boolean isSupportsProvenance) {
392
        setConceptStatus(ConceptStatus.SUPPORTS_PROVENANCE, isSupportsProvenance);
393
    }
394
    public boolean isCurrentConcept() {
395
        return ConceptStatus.includesType(conceptStatus, ConceptStatus.CURRENT);
396
    }
397
    public void setCurrentConcept(boolean isCurrentConcept) {
398
        setConceptStatus(ConceptStatus.CURRENT, isCurrentConcept);
399
    }
400

    
401
    protected EnumSet<ConceptStatus> getConceptStatus() {
402
        return conceptStatus;
403
    }
404

    
405
    /**
406
     * for know it is private and the boolean getters and setters should be used instead.
407
     * If you make it public make sure to guarantee that any change to the enum set results
408
     * in a new enum set (see also {@link #newEnumSet(EnumSet, CdmClass, CdmClass)}
409
     * and that the client is aware of the enum set being immutable.
410
     */
411
    private void setConceptStatus(EnumSet<ConceptStatus> conceptStatus){
412
        this.conceptStatus = conceptStatus;
413
    }
414

    
415
    /**
416
     * Sets the value for concept status
417
     * @param conceptStatus the concept status
418
     * @param value the value if this concept has this status (<code>true</code>) or not (<code>false</code>)
419
     */
420
    protected void setConceptStatus(ConceptStatus conceptStatus, boolean value) {
421
        if (value && !this.conceptStatus.contains(conceptStatus)){
422
            setConceptStatus(newEnumSet(this.conceptStatus, conceptStatus, null));
423
        }else if (!value && this.conceptStatus.contains(conceptStatus)){
424
            setConceptStatus(newEnumSet(this.conceptStatus, null, conceptStatus));
425
        }else{
426
            return;
427
        }
428
    }
429

    
430
// ****************************************************************/
431

    
432
    /**
433
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonDescription taxon descriptions}
434
     * concerning <i>this</i> taxon.
435
     *
436
     * @see #removeDescription(TaxonDescription)
437
     * @see #addDescription(TaxonDescription)
438
     * @see eu.etaxonomy.cdm.model.description.TaxonDescription#getTaxon()
439
     */
440
    @Override
441
    public Set<TaxonDescription> getDescriptions() {
442
        if(descriptions == null) {
443
            descriptions = new HashSet<>();
444
        }
445
        return descriptions;
446
    }
447

    
448
    public Set<TaxonDescription> getDescriptions(DescriptionType type) {
449
        Set<TaxonDescription> result = new HashSet<>();
450
        for (TaxonDescription description : getDescriptions()){
451
            if (description.getTypes().contains(type)){
452
                result.add(description);
453
            }
454
        }
455
        return result;
456
    }
457

    
458
    /**
459
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonDescription taxon description} to the set
460
     * of taxon descriptions assigned to <i>this</i> (accepted/correct) taxon.
461
     * Due to bidirectionality the content of the {@link eu.etaxonomy.cdm.model.description.TaxonDescription#getTaxon() taxon attribute} of the
462
     * taxon description itself will be replaced with <i>this</i> taxon. The taxon
463
     * description will also be removed from the set of taxon descriptions
464
     * assigned to its previous taxon.
465
     *
466
     * @param  description	the taxon description to be added for <i>this</i> taxon
467
     * @see     		  	#getDescriptions()
468
     * @see     		  	#removeDescription(TaxonDescription)
469
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonDescription#getTaxon()
470
     */
471
    @Override
472
    public void addDescription(TaxonDescription description) {
473
        if (description.getTaxon() != null){
474
            description.getTaxon().removeDescription(description);
475
        }
476
        Field field = ReflectionUtils.findField(TaxonDescription.class, "taxon", Taxon.class);
477
        ReflectionUtils.makeAccessible(field);
478
        ReflectionUtils.setField(field, description, this);
479
        descriptions.add(description);
480
    }
481

    
482
    /**
483
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonDescription taxon descriptions} assigned
484
     * to <i>this</i> (accepted/correct) taxon. Due to bidirectionality the content of
485
     * the {@link eu.etaxonomy.cdm.model.description.TaxonDescription#getTaxon() taxon attribute} of the taxon description
486
     * itself will be set to "null".
487
     *
488
     * @param  description  the taxon description which should be removed
489
     * @see     		  	#getDescriptions()
490
     * @see     		  	#addDescription(TaxonDescription)
491
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonDescription#getTaxon()
492
     */
493
    @Override
494
    public void removeDescription(TaxonDescription description) {
495
        //description.setTaxon(null) for not visible method
496
        Field field = ReflectionUtils.findField(TaxonDescription.class, "taxon", Taxon.class);
497
        ReflectionUtils.makeAccessible(field);
498
        ReflectionUtils.setField(field, description, null);
499
        descriptions.remove(description);
500
    }
501

    
502
    public void removeDescription(TaxonDescription description, boolean removeElements){
503
    	if (removeElements){
504
    		Set<DescriptionElementBase> elements = new HashSet<DescriptionElementBase>(description.getElements());
505
            for (DescriptionElementBase el:elements){
506
            	description.getElements().remove(el);
507
            }
508
            removeDescription(description);
509
    	} else{
510
    		removeDescription(description);
511
    	}
512
    }
513

    
514
    /**
515
     * Returns the image gallery for a taxon. If there are multiple taxon descriptions
516
     * marked as image galleries an arbitrary one is chosen.
517
     * If no image gallery exists, a new one is created if <code>createNewIfNotExists</code>
518
     * is <code>true</code>.
519
     * @param createNewIfNotExists
520
     * @return
521
     */
522
    public TaxonDescription getImageGallery(boolean createNewIfNotExists) {
523
        TaxonDescription result = null;
524
        Set<TaxonDescription> descriptions= getDescriptions();
525
        for (TaxonDescription description : descriptions){
526
            if (description.isImageGallery()){
527
                result = description;
528
                break;
529
            }
530
        }
531
        if (result == null && createNewIfNotExists){
532
            result = TaxonDescription.NewInstance(this);
533
            result.setImageGallery(true);
534
        }
535
        return result;
536
    }
537

    
538

    
539

    
540
    public Set<TaxonNode> getTaxonNodes() {
541
        return taxonNodes;
542
    }
543
    //	protected void setTaxonNodes(Set<TaxonNode> taxonNodes) {
544
//		this.taxonNodes = taxonNodes;
545
//	}
546
    protected void addTaxonNode(TaxonNode taxonNode){
547
        taxonNodes.add(taxonNode);
548
    }
549

    
550
    public boolean removeTaxonNode(TaxonNode taxonNode){
551
        if (!taxonNodes.contains(taxonNode)){
552
            return false;
553
        }
554
        TaxonNode parent = taxonNode.getParent();
555
        if (parent != null){
556
            parent.removeChildNode(taxonNode);
557
        }
558
        taxonNode.setTaxon(null);
559
        return taxonNodes.remove(taxonNode);
560

    
561
    }
562

    
563
    public boolean removeTaxonNode(TaxonNode taxonNode, boolean deleteChildren){
564
        TaxonNode parent = taxonNode.getParent();
565
        boolean success = true;
566

    
567
        if ((!taxonNode.getChildNodes().isEmpty() && deleteChildren) || (taxonNode.getChildNodes().isEmpty()) ){
568

    
569
            taxonNode.delete();
570

    
571
        } else if (!taxonNode.isTopmostNode()){
572

    
573
            List<TaxonNode> nodes = new ArrayList<TaxonNode> (taxonNode.getChildNodes());
574
            for (TaxonNode childNode: nodes){
575
                taxonNode.getChildNodes().remove(childNode);
576
                parent.addChildNode(childNode, null, null);
577
            }
578

    
579
            taxonNode.delete();
580

    
581
        } else if (taxonNode.isTopmostNode()){
582
            success = false;
583
        }
584
        return success;
585
    }
586

    
587
    public boolean removeTaxonNodes(boolean deleteChildren){
588
        Iterator<TaxonNode> nodesIterator = taxonNodes.iterator();
589
        TaxonNode node;
590
        TaxonNode parent;
591
        boolean success = false;
592
        List<TaxonNode> removeNodes = new ArrayList<>();
593
        while (nodesIterator.hasNext()){
594
            node = nodesIterator.next();
595
            if (!deleteChildren){
596
                List<TaxonNode> children = node.getChildNodes();
597
                Iterator<TaxonNode> childrenIterator = children.iterator();
598
                parent = node.getParent();
599
                while (childrenIterator.hasNext()){
600
                    TaxonNode childNode = childrenIterator.next();
601
                    if (parent != null){
602
                        parent.addChildNode(childNode, null, null);
603
                    }else{
604
                        childNode.setParent(null);
605
                    }
606
                }
607

    
608
                for (int i = 0; i<node.getChildNodes().size(); i++){
609
                    node.removeChild(i);
610
                }
611
            }
612

    
613
            removeNodes.add(node);
614
         }
615
        for (int i = 0; i<removeNodes.size(); i++){
616
            TaxonNode removeNode = removeNodes.get(i);
617
            success = removeNode.delete(deleteChildren);
618
            removeNode.setTaxon(null);
619
            removeTaxonNode(removeNode);
620
        }
621
        return success;
622

    
623
    }
624

    
625
    public TaxonNode getTaxonNode(Classification classification) {
626
        if (classification == null){
627
            return null;
628
        }
629
        for (TaxonNode node : this.getTaxonNodes()){
630
            if (classification.equals(node.getClassification())){
631
                return node;
632
            }
633
        }
634
        return null;
635
    }
636

    
637
    /**
638
     * Returns the set of all {@link Synonym synonyms}
639
     * for which <i>this</i> ("accepted/valid") taxon is the accepted taxon.
640
     *
641
     * @see    #addSynonym(Synonym, SynonymType)
642
     * @see    #removeSynonym(Synonym)
643
     */
644
    public Set<Synonym> getSynonyms() {
645
        if(synonyms == null) {
646
            this.synonyms = new HashSet<>();
647
        }
648
        return synonyms;
649
    }
650

    
651
    /**
652
     * Returns the set of all {@link TaxonRelationship taxon relationships}
653
     * between two taxa in which <i>this</i> taxon is involved as a source.
654
     *
655
     * @see    #getRelationsToThisTaxon()
656
     * @see    #getTaxonRelations()
657
     */
658
    public Set<TaxonRelationship> getRelationsFromThisTaxon() {
659
        if(relationsFromThisTaxon == null) {
660
            this.relationsFromThisTaxon = new HashSet<>();
661
        }
662
        return relationsFromThisTaxon;
663
    }
664

    
665
    /**
666
     * Returns the set of all {@link TaxonRelationship taxon relationships}
667
     * between two taxa in which <i>this</i> taxon is involved as a target.
668
     *
669
     * @see    #getRelationsFromThisTaxon()
670
     * @see    #getTaxonRelations()
671
     */
672
    public Set<TaxonRelationship> getRelationsToThisTaxon() {
673
        if(relationsToThisTaxon == null) {
674
            this.relationsToThisTaxon = new HashSet<>();
675
        }
676
        return relationsToThisTaxon;
677
    }
678

    
679
    /**
680
     * Returns the set of all {@link TaxonRelationship taxon relationships}
681
     * between two taxa in which <i>this</i> taxon is involved either as a source or
682
     * as a target.
683
     *
684
     * @see    #getRelationsFromThisTaxon()
685
     * @see    #getRelationsToThisTaxon()
686
     */
687
    @Transient
688
    public Set<TaxonRelationship> getTaxonRelations() {
689
        Set<TaxonRelationship> rels = new HashSet<>();
690
        rels.addAll(getRelationsToThisTaxon());
691
        rels.addAll(getRelationsFromThisTaxon());
692
        return rels;
693
    }
694

    
695
    /**
696
     * @see    #getRelationsToThisTaxon()
697
     */
698
    protected void setRelationsToThisTaxon(Set<TaxonRelationship> relationsToThisTaxon) {
699
        this.relationsToThisTaxon = relationsToThisTaxon;
700
    }
701

    
702
    /**
703
     * @see    #getRelationsFromThisTaxon()
704
     */
705
    protected void setRelationsFromThisTaxon(Set<TaxonRelationship> relationsFromThisTaxon) {
706
        this.relationsFromThisTaxon = relationsFromThisTaxon;
707
    }
708

    
709
    /**
710
     * If a relationships between <i>this</i> and the given taxon exists they will be returned.
711
     * <i>This</i> taxon is involved either as a source or as a target in the relationships.
712
     * The method will return <code>null</code> if no relations exist between the two taxa.
713
     *
714
     * @param possiblyRelatedTaxon
715
     * 			a taxon to check for a relationship
716
     * @return
717
     * 			a set of <code>TaxonRelationship</code>s or <code>null</null> if none exists.
718
     */
719
    public Set<TaxonRelationship> getTaxonRelations(Taxon possiblyRelatedTaxon){
720
        Set<TaxonRelationship> relations = new HashSet<>();
721

    
722
        for(TaxonRelationship relationship : getTaxonRelations()){
723
            if(relationship.getFromTaxon().equals(possiblyRelatedTaxon)) {
724
                relations.add(relationship);
725
            }
726
            if(relationship.getToTaxon().equals(possiblyRelatedTaxon)) {
727
                relations.add(relationship);
728
            }
729
        }
730

    
731
        return relations.size() > 0 ? relations : null;
732
    }
733

    
734
    /**
735
     * Removes one {@link TaxonRelationship taxon relationship} from one of both sets of
736
     * {@link #getTaxonRelations() taxon relationships} in which <i>this</i> taxon is involved
737
     * either as a {@link #getRelationsFromThisTaxon() source} or as a {@link #getRelationsToThisTaxon() target}.
738
     * The taxon relationship will also be removed from one of both sets
739
     * belonging to the second taxon involved. Furthermore the inherited RelatedFrom and
740
     * RelatedTo attributes of the given taxon relationship will be nullified.<P>
741
     *
742
     * @param  rel  the taxon relationship which should be removed from one
743
     * 				of both sets
744
     * @see    		#getTaxonRelations()
745
     * @see    		eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedFrom()
746
     * @see    		eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedTo()
747
     *
748
     */
749
    public void removeTaxonRelation(TaxonRelationship rel) {
750
        this.relationsToThisTaxon.remove(rel);
751
        this.relationsFromThisTaxon.remove(rel);
752
        Taxon fromTaxon = rel.getFromTaxon();
753
        Taxon toTaxon = rel.getToTaxon();
754

    
755
        //delete Relationship from other related Taxon
756
        if (fromTaxon != this){
757
            rel.setToTaxon(null);  //remove this Taxon from relationship
758
            if (fromTaxon != null){
759
                if (fromTaxon.getTaxonRelations().contains(rel)){
760
                    fromTaxon.removeTaxonRelation(rel);
761
                }
762
            }
763
        }
764
        if (toTaxon != this ){
765
            rel.setFromTaxon(null); //remove this Taxon from relationship
766
           if (toTaxon != null){
767
               if (toTaxon.getTaxonRelations().contains(rel)) {
768
                   toTaxon.removeTaxonRelation(rel);
769
               }
770
           }
771
        }
772
    }
773

    
774
    /**
775
     * Adds an existing {@link TaxonRelationship taxon relationship} either to the set of
776
     * {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon} or to the set of
777
     * {@link #getRelationsFromThisTaxon() taxon relationships from <i>this</i> taxon}. If neither the
778
     * source nor the target of the taxon relationship match with <i>this</i> taxon
779
     * no addition will be carried out. The taxon relationship will also be
780
     * added to the second taxon involved in the given relationship.<P>
781
     *
782
     * @param rel  the taxon relationship to be added to one of <i>this</i> taxon's taxon relationships sets
783
     * @see    	   #addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
784
     * @see    	   #getTaxonRelations()
785
     * @see    	   #getRelationsFromThisTaxon()
786
     * @see    	   #getRelationsToThisTaxon()
787
     */
788
    public void addTaxonRelation(TaxonRelationship rel) {
789
        if (rel!=null && rel.getType()!=null && !getTaxonRelations().contains(rel) ){
790
            Taxon toTaxon=rel.getToTaxon();
791
            Taxon fromTaxon=rel.getFromTaxon();
792
            if ( this.equals(toTaxon) || this.equals(fromTaxon) ){
793
                if (this.equals(fromTaxon)){
794
                    relationsFromThisTaxon.add(rel);
795
                    // also add relation to other taxon object
796
                    if (toTaxon!=null){
797
                        toTaxon.addTaxonRelation(rel);
798
                    }
799
                }else if (this.equals(toTaxon)){
800
                    relationsToThisTaxon.add(rel);
801
                    // also add relation to other taxon object
802
                    if (fromTaxon!=null){
803
                        fromTaxon.addTaxonRelation(rel);
804
                    }
805
                }
806
            }else if (toTaxon == null || fromTaxon == null){
807
                if (toTaxon == null){
808
                    toTaxon = this;
809
                    relationsToThisTaxon.add(rel);
810
                    if (fromTaxon!= null){
811
                        fromTaxon.addTaxonRelation(rel);
812
                    }
813
                }else if (fromTaxon == null && toTaxon != null){
814
                    fromTaxon = this;
815
                    relationsFromThisTaxon.add(rel);
816
                    toTaxon.addTaxonRelation(rel);
817
                }
818
            }
819
        }
820
    }
821

    
822
    @Override
823
    @Deprecated //for inner use by RelationshipBase only
824
    public void addRelationship(RelationshipBase rel){
825
        if (rel instanceof TaxonRelationship){
826
            addTaxonRelation((TaxonRelationship)rel);
827
        }else{
828
            throw new ClassCastException("Wrong Relationsship type for Taxon.addRelationship");
829
        }
830
    }
831

    
832
    /**
833
     * Creates a new {@link TaxonRelationship taxon relationship} instance where <i>this</i> taxon
834
     * plays the source role and adds it to the set of
835
     * {@link #getRelationsFromThisTaxon() "taxon relationships from"} belonging to <i>this</i> taxon.
836
     * The taxon relationship will also be added to the set of taxon
837
     * relationships to the second taxon involved in the created relationship.<P>
838
     *
839
     * @param toTaxon		the taxon which plays the target role in the new taxon relationship
840
     * @param type			the taxon relationship type for the new taxon relationship
841
     * @param citation		the reference source for the new taxon relationship
842
     * @param microcitation	the string with the details describing the exact localisation within the reference
843
     * @return
844
     * @see    	   			#addTaxonRelation(TaxonRelationship)
845
     * @see    	   			#getTaxonRelations()
846
     * @see    	   			#getRelationsFromThisTaxon()
847
     * @see    	   			#getRelationsToThisTaxon()
848
     */
849
    public TaxonRelationship addTaxonRelation(Taxon toTaxon, TaxonRelationshipType type, Reference citation, String microcitation) {
850
        return new TaxonRelationship(this, toTaxon, type, citation, microcitation);
851
    }
852

    
853
    /**
854
     * Creates a new {@link TaxonRelationship taxon relationship} (with {@link TaxonRelationshipType taxon relationship type}
855
     * "misapplied name for") instance where <i>this</i> taxon plays the target role
856
     * and adds it to the set of {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon}.
857
     * The taxon relationship will also be added to the set of taxon
858
     * relationships to the other (misapplied name) taxon involved in the created relationship.
859
     *
860
     * @param misappliedNameTaxon	the taxon which plays the source role in the new taxon relationship
861
     * @param citation				the reference source for the new taxon relationship
862
     * @param microcitation			the string with the details describing the exact localisation within the reference
863
     * @return
864
     * @see    	   					#getMisappliedNames()
865
     * @see                         #addProParteMisappliedName(Taxon, Reference, String)
866
     * @see    	   					#addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
867
     * @see    	   					#addTaxonRelation(TaxonRelationship)
868
     * @see    	   					#getTaxonRelations()
869
     * @see    	   					#getRelationsFromThisTaxon()
870
     * @see    	   					#getRelationsToThisTaxon()
871
     */
872
    public TaxonRelationship addMisappliedName(Taxon misappliedNameTaxon, Reference citation, String microcitation) {
873
        return misappliedNameTaxon.addTaxonRelation(this, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), citation, microcitation);
874
    }
875

    
876
//	public void removeMisappliedName(Taxon misappliedNameTaxon){
877
//		Set<TaxonRelationship> taxRels = this.getTaxonRelations();
878
//		for (TaxonRelationship taxRel : taxRels ){
879
//			if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())
880
//				&& taxRel.getFromTaxon().equals(misappliedNameTaxon)){
881
//				this.removeTaxonRelation(taxRel);
882
//			}
883
//		}
884
//	}
885

    
886
    /**
887
     * Creates a new {@link TaxonRelationship taxon relationship} (with {@link TaxonRelationshipType taxon relationship type}
888
     * "pro parte misapplied name for") instance where <i>this</i> taxon plays the target role
889
     * and adds it to the set of {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon}.
890
     * The taxon relationship will also be added to the set of taxon
891
     * relationships to the other (pro parte misapplied name) taxon involved in the created relationship.
892
     *
893
     * @param proParteMisappliedNameTaxon   the taxon which plays the source role in the new taxon relationship
894
     * @param citation              the reference source for the new taxon relationship
895
     * @param microcitation         the string with the details describing the exact localisation within the reference
896
     * @return
897
     * @see                         #addMisappliedName(Taxon, Reference, String)
898
     * @see                         #getMisappliedNames()
899
     * @see                         #addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
900
     * @see                         #addTaxonRelation(TaxonRelationship)
901
     * @see                         #getTaxonRelations()
902
     * @see                         #getRelationsFromThisTaxon()
903
     * @see                         #getRelationsToThisTaxon()
904
     */
905
    public TaxonRelationship addProParteMisappliedName(Taxon proParteMisappliedNameTaxon, Reference citation, String microcitation) {
906
        return proParteMisappliedNameTaxon.addTaxonRelation(this, TaxonRelationshipType.PRO_PARTE_MISAPPLIED_NAME_FOR(), citation, microcitation);
907
    }
908

    
909
    /**
910
     * Creates a new {@link TaxonRelationship taxon relationship} (with {@link TaxonRelationshipType taxon relationship type}
911
     * "partial misapplied name for") instance where <i>this</i> taxon plays the target role
912
     * and adds it to the set of {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon}.
913
     * The taxon relationship will also be added to the set of taxon
914
     * relationships to the other (pro parte misapplied name) taxon involved in the created relationship.
915
     *
916
     * @param partialMisappliedNameTaxon   the taxon which plays the source role in the new taxon relationship
917
     * @param citation              the reference source for the new taxon relationship
918
     * @param microcitation         the string with the details describing the exact localization within the reference
919
     * @return
920
     * @see                         #addMisappliedName(Taxon, Reference, String)
921
     * @see                         #addProParteMisappliedName(Taxon, Reference, String)
922
     * @see                         #getMisappliedNames()
923
     * @see                         #addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
924
     * @see                         #addTaxonRelation(TaxonRelationship)
925
     * @see                         #getTaxonRelations()
926
     * @see                         #getRelationsFromThisTaxon()
927
     * @see                         #getRelationsToThisTaxon()
928
     */
929
    public TaxonRelationship addPartialMisappliedName(Taxon partialMisappliedNameTaxon, Reference citation, String microcitation) {
930
        return partialMisappliedNameTaxon.addTaxonRelation(this, TaxonRelationshipType.PARTIAL_MISAPPLIED_NAME_FOR(), citation, microcitation);
931
    }
932

    
933
    /**
934
     * Creates a new {@link TaxonRelationship taxon relationship} (with {@link TaxonRelationshipType taxon relationship type}
935
     * "pro parte synonym for") instance where <i>this</i> taxon plays the target role
936
     * and adds it to the set of {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon}.
937
     * The taxon relationship will also be added to the set of taxon
938
     * relationships to the other (pro parte synonym) taxon involved in the created relationship.
939
     *
940
     * @param proParteTaxon         the taxon which plays the source role in the new taxon relationship
941
     * @param citation              the reference source for the new taxon relationship
942
     * @param microcitation         the string with the details describing the exact localisation within the reference
943
     * @return
944
     * @see                         #getMisappliedNames()
945
     * @see                         #addProParteMisappliedName(Taxon, Reference, String)
946
     * @see                         #addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
947
     * @see                         #addTaxonRelation(TaxonRelationship)
948
     * @see                         #getTaxonRelations()
949
     * @see                         #getRelationsFromThisTaxon()
950
     * @see                         #getRelationsToThisTaxon()
951
     */
952
    public TaxonRelationship addProparteSynonym(Taxon proParteTaxon, Reference citation, String microcitation) {
953
        return proParteTaxon.addTaxonRelation(this, TaxonRelationshipType.PRO_PARTE_SYNONYM_FOR(), citation, microcitation);
954
    }
955

    
956
    /**
957
     * Creates a new {@link TaxonRelationship taxon relationship} instance with
958
     * {@link TaxonRelationshipType taxon relationship type} {@link TaxonRelationshipType#PARTIAL_SYNONYM_FOR()
959
     * partial synonym for} where <i>this</i> taxon plays the target role
960
     * and adds it to the set of {@link #getRelationsToThisTaxon() taxon relationships to <i>this</i> taxon}.
961
     * The taxon relationship will also be added to the set of taxon
962
     * relationships to the other (partial synonym) taxon involved in the created relationship.
963
     *
964
     * @param partialTaxon         the taxon which plays the source role in the new taxon relationship
965
     * @param citation             the reference source for the new taxon relationship
966
     * @param microcitation        the string with the details describing the exact localisation within the reference
967
     * @return
968
     * @see                         #addProparteSynonym(Taxon, Reference, String)
969
     * @see                         #addTaxonRelation(Taxon, TaxonRelationshipType, Reference, String)
970
     * @see                         #addTaxonRelation(TaxonRelationship)
971
     * @see                         #getTaxonRelations()
972
     * @see                         #getRelationsFromThisTaxon()
973
     * @see                         #getRelationsToThisTaxon()
974
     */
975
    public TaxonRelationship addPartialSynonym(Taxon partialTaxon, Reference citation, String microcitation) {
976
        return partialTaxon.addTaxonRelation(this, TaxonRelationshipType.PARTIAL_SYNONYM_FOR(), citation, microcitation);
977
    }
978

    
979
    /**
980
     * TODO update documentation
981
     * Removes one {@link TaxonRelationship taxon relationship} with {@link TaxonRelationshipType taxon relationship type}
982
     * taxonRelType and with the given child taxon playing the
983
     * source role from the set of {@link #getRelationsToThisTaxon() "taxon relationships to"} belonging
984
     * to <i>this</i> taxon. The taxon relationship will also be removed from the set
985
     * of {@link #getRelationsFromThisTaxon() "taxon relationships from"} belonging to the other side taxon.
986
     * Furthermore, the inherited RelatedFrom and RelatedTo attributes of the
987
     * taxon relationship will be nullified.<P>
988
     *
989
     * @param taxon			the taxon which plays the source role in the taxon relationship
990
     * @param taxonRelType	the taxon relationship type
991
     */
992
    public void removeTaxon(Taxon taxon, TaxonRelationshipType taxonRelType){
993
        Set<TaxonRelationship> taxRels = this.getTaxonRelations();
994
        for (TaxonRelationship taxRel : taxRels ){
995
            if (taxRel.getType().equals(taxonRelType)
996
                && taxRel.getFromTaxon().equals(taxon)){
997
                this.removeTaxonRelation(taxRel);
998
            }
999
        }
1000
    }
1001

    
1002
    @Transient
1003
    public boolean isMisapplicationOnly() {
1004
        if (!getTaxonNodes().isEmpty()){
1005
            return false;
1006
        }
1007
        int nMan = computeMisapliedNameRelations();
1008
        if (nMan > 0 && nMan == this.relationsFromThisTaxon.size() + this.relationsToThisTaxon.size()){
1009
            return true;
1010
        }else{
1011
            return false;
1012
        }
1013
    }
1014

    
1015
    /**
1016
     * Returns the boolean value indicating whether <i>this</i> taxon is a misapplication
1017
     * (misapplied name) for at least one other taxon.
1018
     */
1019
    // TODO cache as for #hasTaxonomicChildren
1020
    @Transient
1021
    public boolean isMisapplication(){
1022
        return computeMisapliedNameRelations() > 0;
1023
    }
1024

    
1025
    /**
1026
     * Counts the number of misapplied name relationships (including pro parte and partial
1027
     * misapplied names) where this taxon represents the
1028
     * misapplied name for another taxon.
1029
     * @return
1030
     */
1031
    private int computeMisapliedNameRelations(){
1032
        int count = 0;
1033
        for (TaxonRelationship rel: this.getRelationsFromThisTaxon()){
1034
            if (rel.getType().isAnyMisappliedName()){
1035
                count++;
1036
            }
1037
        }
1038
        return count;
1039
    }
1040

    
1041
    /**
1042
     * Returns the boolean value indicating whether <i>this</i> taxon is a misapplication
1043
     * (misapplied name) for at least one other taxon.
1044
     */
1045
    // TODO cache as for #hasTaxonomicChildren
1046
    @Transient
1047
    public boolean isProparteSynonym(){
1048
        return computeProparteSynonymRelations() > 0;
1049
    }
1050

    
1051
    /**
1052
     * Counts the number of misapplied name relationships (including pro parte misapplied
1053
     * names) where this taxon represents the
1054
     * misapplied name for another taxon.
1055
     * @return
1056
     */
1057
    private int computeProparteSynonymRelations(){
1058
        int count = 0;
1059
        for (TaxonRelationship rel: this.getRelationsFromThisTaxon()){
1060
            if (rel.getType().isAnySynonym()){
1061
                count++;
1062
            }
1063
        }
1064
        return count;
1065
    }
1066

    
1067
    /**
1068
     * Returns the boolean value indicating whether <i>this</i> taxon is a related
1069
     * concept for at least one other taxon.
1070
     */
1071
    @Transient
1072
    public boolean isRelatedConcept(){
1073
        return computeConceptRelations() > 0;
1074
    }
1075

    
1076
    /**
1077
     * Counts the number of concept relationships where this taxon represents the
1078
     * related concept for another taxon.
1079
     * @return
1080
     */
1081
    private int computeConceptRelations(){
1082
        int count = 0;
1083
        for (TaxonRelationship rel: this.getRelationsFromThisTaxon()){
1084
            TaxonRelationshipType type = rel.getType();
1085
            if (type.isConceptRelationship()){
1086
                count++;
1087
            }
1088
        }
1089
        return count;
1090
    }
1091

    
1092
    /**
1093
     * Returns the boolean value indicating whether <i>this</i> taxon has at least one
1094
     * {@link Synonym synonym} (true) or not (false). If true the {@link #getSynonyms() set of synonyms}
1095
     * belonging to <i>this</i> ("accepted/valid") taxon is not empty .
1096
     *
1097
     * @see  #getSynonyms()
1098
     * @see  #getSynonymNames()
1099
     * @see  #removeSynonym(Synonym)
1100
     */
1101
    @Transient
1102
    public boolean hasSynonyms(){
1103
        return this.getSynonyms().size() > 0;
1104
    }
1105

    
1106
    /**
1107
     * Returns the boolean value indicating whether <i>this</i> taxon is at least
1108
     * involved in one {@link #getTaxonRelations() taxon relationship} between
1109
     * two taxa (true), either as a source or as a target, or not (false).
1110
     *
1111
     * @see  #getTaxonRelations()
1112
     * @see  #getRelationsToThisTaxon()
1113
     * @see  #getRelationsFromThisTaxon()
1114
     * @see  #removeTaxonRelation(TaxonRelationship)
1115
     * @see  TaxonRelationship
1116
     */
1117
    public boolean hasTaxonRelationships(){
1118
        return this.getTaxonRelations().size() > 0;
1119
    }
1120

    
1121
    /*
1122
     * MISAPPLIED NAMES
1123
     */
1124
    /**
1125
     * Returns the set of taxa playing the source role in {@link TaxonRelationship taxon relationships}
1126
     * (with {@link TaxonRelationshipType taxon relationship type} "misapplied name for") where
1127
     * <i>this</i> taxon plays the target role. A misapplied name is a taxon the
1128
     * {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} of which has been erroneously used
1129
     * by its {@link TaxonBase#getSec() taxon reference} to denominate the same real taxon
1130
     * as the one meant by <i>this</i> ("accepted/correct") taxon.
1131
     *
1132
     * @see  #getTaxonRelations()
1133
     * @see  #getRelationsToThisTaxon()
1134
     * @see  #addMisappliedName(Taxon, Reference, String)
1135
     * @param includeNonCongruent if <code>true</code> also those taxa are returned that are related
1136
     * via a non congruent relationship like {@link TaxonRelationshipType#PRO_PARTE_MISAPPLIED_NAME_FOR()
1137
     * pro parte misapplied name}
1138
     */
1139
    @Transient
1140
    public Set<Taxon> getMisappliedNames(boolean includeNonCongruent){
1141
        Set<Taxon> taxa = new HashSet<>();
1142
        Set<TaxonRelationship> rels = this.getRelationsToThisTaxon();
1143
        for (TaxonRelationship rel: rels){
1144
            TaxonRelationshipType relType = rel.getType();
1145
            if ( (includeNonCongruent && relType.isAnyMisappliedName())
1146
                    || relType.equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())){
1147
                taxa.add(rel.getFromTaxon());
1148
            }
1149
        }
1150
        return taxa;
1151
    }
1152

    
1153
    /**
1154
     * Returns the set of misapplied name relationships in which this taxon
1155
     * plays the role of the correctly accepted taxon (target). A misapplied name is a taxon the
1156
     * {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} of which has been erroneously used
1157
     * by its {@link TaxonBase#getSec() taxon reference} to denominate the same real taxon
1158
     * as the one meant by <i>this</i> ("accepted/correct") taxon.
1159
     */
1160
    @Transient
1161
    public Set<TaxonRelationship> getMisappliedNameRelations(){
1162
        Set<TaxonRelationship> result = new HashSet<>();
1163
        Set<TaxonRelationship> rels = this.getRelationsToThisTaxon();
1164
        for (TaxonRelationship rel: rels){
1165
            TaxonRelationshipType relType = rel.getType();
1166
            if (relType.isAnyMisappliedName()){
1167
                result.add(rel);
1168
            }
1169
        }
1170
        return result;
1171
    }
1172

    
1173
    /**
1174
     * Returns the set of taxa playing the target role in {@link TaxonRelationship taxon relationships}
1175
     * (with {@link TaxonRelationshipType taxon relationship type} "misapplied name for"
1176
     * or "pro parte misapplied name for") where
1177
     * <i>this</i> taxon plays the source role. A misapplied name is a taxon the
1178
     * {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} of which has been erroneously used
1179
     * by its {@link TaxonBase#getSec() taxon reference} to denominate the same real taxon
1180
     * as the one meant by <i>this</i> ("accepted/correct") taxon.
1181

    
1182
     * @param includeNonCongruent if <code>true</code> also those taxa are returned that are related
1183
     * via a non congruent relationship like {@link TaxonRelationshipType#PRO_PARTE_MISAPPLIED_NAME_FOR()
1184
     * pro parte misapplied name}
1185
     *
1186
     * @see  #getTaxonRelations()
1187
     * @see  #getRelationsToThisTaxon()
1188
     * @see  #addMisappliedName(Taxon, Reference, String)
1189
     * @see  #addProParteMisappliedName(Taxon, Reference, String)
1190
     */
1191
    @Transient
1192
    public Set<Taxon> getTaxaForMisappliedName(boolean includeNonCongruent){
1193
        Set<Taxon> taxa = new HashSet<>();
1194
        Set<TaxonRelationship> rels = this.getRelationsFromThisTaxon();
1195
        for (TaxonRelationship rel: rels){
1196
            TaxonRelationshipType relType = rel.getType();
1197
            if ( (includeNonCongruent && relType.isAnyMisappliedName())
1198
                    || relType.equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())){
1199
                taxa.add(rel.getToTaxon());
1200
            }
1201
        }
1202
        return taxa;
1203
    }
1204

    
1205
    /**
1206
     * Returns the set of pro parte or partial synonym relationships in which this taxon
1207
     * plays the role of the "correctly" accepted taxon (target).
1208
     *
1209
     * @see #getProParteAndPartialSynonyms()
1210
     * @see #getMisappliedNameRelations()
1211
     */
1212
    @Transient
1213
    public Set<TaxonRelationship> getProParteAndPartialSynonymRelations(){
1214
        Set<TaxonRelationship> result = new HashSet<>();
1215
        Set<TaxonRelationship> rels = this.getRelationsToThisTaxon();
1216
        for (TaxonRelationship rel: rels){
1217
            TaxonRelationshipType relType = rel.getType();
1218
            if (relType.isAnySynonym()){
1219
                result.add(rel);
1220
            }
1221
        }
1222
        return result;
1223
    }
1224

    
1225
    /**
1226
     * Returns the set of pro parte or partial synonyms in which this taxon
1227
     * plays the role of the "correctly" accepted taxon (target).
1228
     *
1229
     * @see #getProParteAndPartialSynonymRelations()
1230
     * @see #getMisappliedNames(boolean)
1231
     */
1232
    @Transient
1233
    public Set<Taxon> getProParteAndPartialSynonyms(){
1234
        Set<Taxon> synonyms = new HashSet<>();
1235
        Set<TaxonRelationship> rels = this.getProParteAndPartialSynonymRelations();
1236
        for (TaxonRelationship rel: rels){
1237
            synonyms.add(rel.getFromTaxon());
1238
        }
1239
        return synonyms;
1240
    }
1241

    
1242
    /**
1243
     * Returns the set of all {@link TaxonName taxon names} used as {@link Synonym synonyms}
1244
     * of <i>this</i> ("accepted/valid") taxon.
1245
     *
1246
     * @see    #getSynonyms()
1247
     * @see    #getSynonymsSortedByType()
1248
     * @see    #addSynonymName(TaxonName, SynonymType)
1249
     * @see    #addSynonym(Synonym, SynonymType, Reference, String)
1250
     * @see    #removeSynonym(Synonym)
1251
     */
1252
    @Transient
1253
    public Set<TaxonName> getSynonymNames(){
1254
        Set<TaxonName> names = new HashSet<>();
1255
        for (Synonym syn: this.getSynonyms()){
1256
            names.add(syn.getName());
1257
        }
1258
        return names;
1259
    }
1260

    
1261
    /**
1262
     * Might be public in future. For the moment protected to ensure that
1263
     * synonym type is always set after refactoring.
1264
     *
1265
     * @param synonym
1266
     */
1267
    protected void addSynonym(Synonym synonym){
1268
        if (! this.equals(synonym.getAcceptedTaxon())){
1269
            synonym.setAcceptedTaxon(this);
1270
        }
1271
        if (!synonyms.contains(synonym)){
1272
            synonyms.add(synonym);
1273
        }
1274
    }
1275

    
1276
    /**
1277
     * Adds the given {@link Synonym synonym} to <code>this</code> taxon
1278
     * and changes the {@link SynonymType
1279
     * synonym type} before.
1280
     *
1281
     * @param synonym       the synonym to be added
1282
     * @param synonymType   the synonym type of the synonym to be added. If not <code>null</code>
1283
     *                      and if the synonym already has a type the existing type will be overwritten.<BR>
1284
     *                      If synonymType is {@link SynonymType#HOMOTYPIC_SYNONYM_OF()}
1285
     *                      the homotypic group of the synonym is changed to that of <code>this</code> taxon.<BR>
1286
     *                      To explicitly set the type to <code>null</code> use {@link Synonym#setType(SynonymType)}
1287
     * @see                 #addSynonym(Synonym)
1288
     * @see                 #addSynonym(Synonym, SynonymType, Reference, String)
1289
     * @see                 #addSynonymName(TaxonName, SynonymType)
1290
     * @see                 #addSynonymName(TaxonName, SynonymType, Reference, String)
1291
     * @see                 #addHomotypicSynonymName(TaxonName, Reference, String)
1292
     * @see                 #addHeterotypicSynonymName(TaxonName)
1293
     * @see                 #addHeterotypicSynonymName(TaxonName, Reference, String, HomotypicalGroup)
1294
     * @see                 #getSynonyms()
1295
     * @see                 #removeSynonym(Synonym)
1296
     * @see                 Synonym#getAcceptedTaxon()
1297
     */
1298
    public void addSynonym(Synonym synonym, SynonymType synonymType){
1299
        synonym.setType(synonymType); //must be set before as otherwise merging of homotypical groups may not work correctly in Synonym.checkHomotypic()
1300
        addSynonym(synonym);
1301
    }
1302

    
1303
    /**
1304
     * Adds the given {@link Synonym synonym} with the given {@link SynonymType
1305
     * synonym relationship type}
1306
     *
1307
     * @param synonym		the synonym to be added
1308
     * @param synonymType	the synonym  type of the synonym to be added. If not null
1309
     *                      and if the synonym already has a type the existing type will be overwritten.
1310
//     * @param citation		the reference source for the new synonym relationship
1311
//     * @param microcitation	the string with the details describing the exact localization within the reference
1312
     * @see    	   			#addSynonym(Synonym)
1313
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1314
     * @see    	   			#addSynonymName(TaxonName, SynonymType)
1315
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1316
     * @see    	   			#addHomotypicSynonymName(TaxonName, Reference, String)
1317
     * @see    	   			#addHeterotypicSynonymName(TaxonName)
1318
     * @see    	   			#addHeterotypicSynonymName(TaxonName, HomotypicalGroup, Reference, String)
1319
     * @see    	   			#getSynonyms()
1320
     * @see    				#removeSynonym(Synonym)
1321
     * @see    	   			Synonym#getAcceptedTaxon()
1322
     */
1323
    private void addSynonym(Synonym synonym, SynonymType synonymType, Reference newSecReference, String newSecMicroReference){
1324
        if (newSecReference != null){
1325
            synonym.setSec(newSecReference);
1326
        }
1327
        if (newSecMicroReference != null){
1328
            synonym.setSecMicroReference(newSecMicroReference);
1329
        }
1330
        addSynonym(synonym, synonymType);
1331
        return;
1332
    }
1333

    
1334
    /**
1335
     * Creates a new {@link Synonym synonym} to <code>this</code> {@link Taxon taxon}) using the
1336
     * given {@link TaxonName synonym name} and with the given
1337
     * {@link SynonymType synonym type}. If the later is
1338
     * {@link SynonymType#HOMOTYPIC_SYNONYM_OF() homotypic synonym}
1339
     * the name will be added to the same {@link HomotypicalGroup homotypical group}
1340
     * as the <code>this</code> accepted taxon.<BR>
1341
     * The secundum reference of the new synonym is taken from <code>this</code> taxon.
1342
     * A secundum detail is not set.
1343
     *
1344
     * @param synonymName	the taxon name to be used as a synonym to be added
1345
     * 						to <i>this</i> taxon's set of synonyms
1346
     * @param synonymType	the synonym  type of the synonym
1347
     * 						relationship to be added
1348
     * @return 				the created synonym
1349
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1350
     * @see    	   			#addSynonym(Synonym, SynonymType)
1351
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1352
     * @see    	   			#addHomotypicSynonym(Synonym, Reference, String)
1353
     * @see    	   			#addHomotypicSynonymName(TaxonName, Reference, String)
1354
     * @see    	   			#addHeterotypicSynonymName(TaxonName)
1355
     * @see    	   			#addHeterotypicSynonymName(TaxonName, HomotypicalGroup, Reference, String)
1356
     * @see    	   			#getSynonyms()
1357
     * @see    				#removeSynonym(Synonym)
1358
     */
1359
    public Synonym addSynonymName(TaxonName synonymName, SynonymType synonymType){
1360
        return addSynonymName(synonymName, null, null, synonymType);
1361
    }
1362

    
1363
    /**
1364
     * Creates a new {@link Synonym synonym} to <code>this</code> {@link Taxon taxon}) using the
1365
     * given {@link TaxonName synonym name} and with the given
1366
     * {@link SynonymType synonym type}. If the later is
1367
     * {@link SynonymType#HOMOTYPIC_SYNONYM_OF() homotypic synonym}
1368
     * the name will be added to the same {@link HomotypicalGroup homotypical group}
1369
     * as the <code>this</code> accepted taxon.<BR>
1370
     *
1371
     * If secReference is not <code>null</code>, the new synonym will have this as
1372
     * secundum reference. Otherwise <code>this</code> taxons sec reference is taken
1373
     * as secundum reference for the synonym. SecDetail will be the secMicroReference of the
1374
     * new synonym.<BR>
1375
     *
1376
     * @param synonymName	the taxon name to be used as a synonym to be added
1377
     * 						to <i>this</i> taxon's set of synonyms
1378
     * @param secReference	the secundum reference for the new synonym (if <code>null</code>
1379
     *                      <code>this</code> taxon's secundum reference is taken.
1380
     * @param secMicroReference the secundum micro reference of the new synonym
1381
     * @param synonymType	the synonym type of the synonym to be added
1382
     *
1383
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1384
     * @see    	   			#addSynonym(Synonym, SynonymType)
1385
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1386
     * @see    	   			#addHomotypicSynonym(Synonym, Reference, String)
1387
     * @see    	   			#addHomotypicSynonymName(TaxonName, Reference, String)
1388
     * @see    	   			#addHeterotypicSynonymName(TaxonName)
1389
     * @see    	   			#addHeterotypicSynonymName(TaxonName, HomotypicalGroup, Reference, String)
1390
     * @see    	   			#getSynonyms()
1391
     * @see    				#removeSynonym(Synonym)
1392
     */
1393
    public Synonym addSynonymName(TaxonName synonymName, Reference secReference, String secMicroReference, SynonymType synonymType){
1394
        Synonym synonym = Synonym.NewInstance(synonymName, this.getSec()); //default sec
1395
        synonym.setPublish(this.isPublish());
1396
        addSynonym(synonym, synonymType, secReference, secMicroReference);
1397
        return synonym;
1398
    }
1399

    
1400
    /**
1401
     * Creates a new {@link Synonym synonym} to <code>this</code> {@link Taxon taxon}) using the given
1402
     * {@link TaxonName synonym name}. The synonym will have the synonym type
1403
     * {@link SynonymType#HETEROTYPIC_SYNONYM_OF() "is heterotypic synonym of"}.<BR>
1404
     * The secundum reference is taken from <code>this</code> taxon.
1405
     * No secMicroReference will be set for the new synonym.<BR>
1406
     * The synonym will keep it's old homotypical group.<BR>
1407
     *
1408
     * @param synonymName	the taxon name to be used as an heterotypic synonym
1409
     * 						to be added to <i>this</i> taxon's set of synonyms
1410
     * @return 				the created synonym
1411
     * @see    	   			#addHeterotypicSynonymName(TaxonName, Reference, String, HomotypicalGroup)
1412
     * @see    	   			#addSynonymName(TaxonName, SynonymType)
1413
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1414
     * @see    	   			#addSynonym(Synonym, SynonymType)
1415
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1416
     * @see    	   			#addHomotypicSynonym(Synonym, Reference, String)
1417
     * @see    	   			#addHomotypicSynonymName(TaxonName, Reference, String)
1418
     * @see    	   			#getSynonyms()
1419
     * @see    				#removeSynonym(Synonym)
1420
     */
1421
    public Synonym addHeterotypicSynonymName(TaxonName synonymName){
1422
        return addHeterotypicSynonymName(synonymName, null, null, null);
1423
    }
1424

    
1425
    /**
1426
     * Creates a new {@link Synonym synonym} to <code>this</code> {@link Taxon taxon}) using the given
1427
     * {@link TaxonName synonym name}. The synonym will have the synonym type
1428
     * {@link SynonymType#HETEROTYPIC_SYNONYM_OF() "is heterotypic synonym of"}.<BR>
1429
     *
1430
     * If secReference is not <code>null</code>, the new synonym will have this as
1431
     * secundum reference. Otherwise <code>this</code> taxons sec reference is taken
1432
     * as secundum reference for the synonym. SecDetail will be the secMicroReference of the
1433
     * new synonym.<BR>
1434
     * Furthermore the taxon name used as synonym will be added
1435
     * to the given {@link name.HomotypicalGroup homotypical group} (if not <code>null</code>).<BR>
1436
     *
1437
     * @param synonymName		the taxon name to be used as an heterotypic synonym
1438
     * 							to be added to <i>this</i> taxon's set of synonyms
1439
     * @param secReference		the secundum reference for the new synonym
1440
     * @param secDetail		    the secundum detil for the new synonym
1441
     * @param homotypicalGroup	the homotypical group to which the taxon name
1442
     * 							of the synonym will be added. If <code>null</code>
1443
     *                          the homotypical group of synonymName is not changed
1444
     * @return 					the created synonym
1445
     * @see    	   				#addHeterotypicSynonymName(TaxonName)
1446
     * @see    	   				#addSynonymName(TaxonName, SynonymType, Reference, String)
1447
     * @see    	   				#addSynonymName(TaxonName, SynonymType)
1448
     * @see    	   				#addSynonym(Synonym, SynonymType)
1449
     * @see    	   				#addSynonym(Synonym, SynonymType, Reference, String)
1450
     * @see    	   				#addHomotypicSynonym(Synonym, Reference, String)
1451
     * @see    	   				#addHomotypicSynonymName(TaxonName, Reference, String)
1452
     * @see    	   				#getSynonyms()
1453
     * @see    					#removeSynonym(Synonym)
1454
     */
1455
    public Synonym addHeterotypicSynonymName(TaxonName synonymName, Reference secReference, String secDetail, HomotypicalGroup homotypicalGroup){
1456
        Synonym synonym = Synonym.NewInstance(synonymName, this.getSec());
1457
        if (homotypicalGroup != null){
1458
            homotypicalGroup.addTypifiedName(synonymName);
1459
        }
1460
        synonym.setPublish(this.isPublish());
1461

    
1462
        addSynonym(synonym, SynonymType.HETEROTYPIC_SYNONYM_OF(), secReference, secDetail);
1463
        return synonym;
1464
    }
1465

    
1466
    /**
1467
    * Creates a new {@link Synonym synonym} to <code>this</code> {@link Taxon taxon}) using the given
1468
     * {@link TaxonName synonym name}. The synonym will have the synonym type
1469
     * {@link SynonymType#HOMOTYPIC_SYNONYM_OF() "is homotypic synonym of"}.<BR>
1470
     * The secundum reference is taken from <code>this</code> taxon.
1471
     * No secMicroReference will be set for the new synonym.<BR>
1472
     * The synonym's homotypic group will be changed to <code>this</code> taxon's group.<BR>
1473
     *
1474
     * @param synonymName	the taxon name to be used as an homotypic synonym
1475
     * 						to be added to <i>this</i> taxon's set of synonyms
1476
     * @return 				the created synonym
1477
     * @see    	   			#addHomotypicSynonym(Synonym, Reference, String)
1478
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1479
     * @see    	   			#addSynonymName(TaxonName, SynonymType)
1480
     * @see    	   			#addSynonym(Synonym, SynonymType)
1481
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1482
     * @see    	   			#addHeterotypicSynonymName(TaxonName)
1483
     * @see    	   			#addHeterotypicSynonymName(TaxonName, Reference, String, HomotypicalGroup)
1484
     * @see    	   			#getSynonyms()
1485
     * @see    				#removeSynonym(Synonym)
1486
     */
1487
    public Synonym addHomotypicSynonymName(TaxonName synonymName){
1488
        Synonym synonym = Synonym.NewInstance(synonymName, this.getSec());
1489
        synonym.setPublish(this.isPublish());
1490
        addHomotypicSynonym(synonym);
1491
        return synonym;
1492
    }
1493

    
1494
    /**
1495
     * Adds the given {@link Synonym synonym} to <code>this</code> taxon,
1496
     * with the {@link SynonymType#HOMOTYPIC_SYNONYM_OF() "is homotypic synonym of"
1497
     * relationship type} and returns it.
1498
     * Furthermore the {@link TaxonName taxon name}
1499
     * used as synonym will be added to the same {@link HomotypicalGroup homotypic group}
1500
     * to which the taxon name of <i>this</i> taxon belongs.<BR>
1501
     *
1502
     * @param synonym		the synonym added to <i>this</i> taxon's synonym set
1503
     * @see    	   			#addHomotypicSynonymName(TaxonName, Reference, String)
1504
     * @see    	   			#addSynonym(Synonym, SynonymType)
1505
     * @see    	   			#addSynonym(Synonym, SynonymType, Reference, String)
1506
     * @see    	   			#addSynonymName(TaxonName, SynonymType, Reference, String)
1507
     * @see    	   			#addSynonymName(TaxonName, SynonymType)
1508
     * @see    	   			#addHeterotypicSynonymName(TaxonName)
1509
     * @see    	   			#addHeterotypicSynonymName(TaxonName, Reference, String, HomotypicalGroup)
1510
     * @see    	   			#getSynonyms()
1511
     * @see    				#removeSynonym(Synonym)
1512
     */
1513
    public void addHomotypicSynonym(Synonym synonym){
1514
    	if (!this.getSynonyms().contains(synonym)){
1515
    		addSynonym(synonym, SynonymType.HOMOTYPIC_SYNONYM_OF());
1516
    	} else{
1517
    		logger.warn("Tried to add a synonym to an accepted taxon that already is a synonym of this taxon.");
1518
    	}
1519
        return;
1520
    }
1521

    
1522
    /**
1523
     * Like {@link #removeSynonym(Synonym, boolean)} with <code>removeSynonymNameFromHomotypicalGroup</code> set to true.
1524
     * @see #removeSynonym(Synonym, boolean)
1525
     */
1526
    public void removeSynonym(Synonym synonym){
1527
        removeSynonym(synonym, true);
1528
    }
1529

    
1530

    
1531
    /**
1532
     * Removes one element from the set of {@link Synonym synonyms} assigned
1533
     * to <i>this</i> (accepted/valid) taxon.
1534
     *
1535
     * @param synonym  the synonym to be removed
1536
     * @param removeSynonymNameFromHomotypicalGroup
1537
     *              if <code>true</code> the synonym name will also be deleted from its homotypical group if the
1538
     *              group contains other names
1539
     * @see     #getSynonyms()
1540
     * @see     #removeSynonym(Synonym)
1541
     */
1542
    public void removeSynonym(Synonym synonym, boolean removeSynonymNameFromHomotypicalGroup) {
1543
        if (synonym != null && this.equals(synonym.getAcceptedTaxon())){
1544
            if(removeSynonymNameFromHomotypicalGroup){
1545
                HomotypicalGroup synHG = synonym.getName().getHomotypicalGroup();
1546
                if (synHG.getTypifiedNames().size() > 1){
1547
                    synHG.removeTypifiedName(synonym.getName(), false);
1548
                }
1549
            }
1550
            this.synonyms.remove(synonym);
1551
            synonym.setAcceptedTaxon(null);
1552
        }
1553
    }
1554

    
1555
    /**
1556
     * @see #getHomotypicSynonymsByHomotypicGroup(TaxonComparator)
1557
     */
1558
    @Transient
1559
    public List<Synonym> getHomotypicSynonymsByHomotypicGroup(){
1560
        return getHomotypicSynonymsByHomotypicGroup(null);
1561
    }
1562

    
1563
    /**
1564
     * Retrieves the ordered list (depending on the date of publication) of
1565
     * homotypic {@link Synonym synonyms} (according to the same {@link eu.etaxonomy.cdm.model.reference.Reference reference}
1566
     * as for <i>this</i> taxon) under the condition that the {@link eu.etaxonomy.cdm.model.name.TaxonName taxon names}
1567
     * of these synonyms and the taxon name of <i>this</i> taxon belong to the
1568
     * same {@link eu.etaxonomy.cdm.model.name.HomotypicalGroup homotypical group}.
1569
     *
1570
     * @param       comparator the taxon comparator to use, if <code>null</code> the default comparator is taken.
1571
     * @return      the ordered list of homotypic synonyms
1572
     * @see         #getHomotypicSynonymsByHomotypicSynonymType()
1573
     * @see         #getSynonyms()
1574
     * @see         #getHomotypicSynonymyGroups()
1575
     * @see         eu.etaxonomy.cdm.model.name.HomotypicalGroup
1576
     * @see         eu.etaxonomy.cdm.model.name.HomotypicalGroup#getSynonymsInGroup(Reference)
1577
     */
1578
    @Transient
1579
    public List<Synonym> getHomotypicSynonymsByHomotypicGroup(TaxonComparator comparator){
1580
        if (this.getHomotypicGroup() == null){
1581
            return null;
1582
        }else if (comparator == null){
1583
            return this.getSynonymsInGroup(this.getHomotypicGroup());
1584
        }else{
1585
            return this.getSynonymsInGroup(this.getHomotypicGroup(), comparator);
1586
        }
1587
    }
1588

    
1589
    /**
1590
     * Retrieves the list of homotypic {@link Synonym synonyms}
1591
     * (according to the same {@link eu.etaxonomy.cdm.model.reference.Reference reference}
1592
     * as for <i>this</i> taxon) under the condition that these synonyms and
1593
     * <i>this</i> taxon are involved in {@link SynonymRelationship synonym relationships} with an
1594
     * "is homotypic synonym of" {@link SynonymType#HOMOTYPIC_SYNONYM_OF() synonym relationship type}.
1595
     *
1596
     * @return		the ordered list of homotypic synonyms
1597
     * @see			#getHomotypicSynonymsByHomotypicGroup()
1598
     * @see			#getSynonyms()
1599
     * @see			#getHomotypicSynonymyGroups()
1600
     * @see			SynonymType
1601
     * @deprecated as the method currently returns data not matching the original description of the method
1602
     *    as an ordered list (according to date of publication) of synonyms with same secundum as <i>this</i> taxon.<BR>
1603
     *    In future this method will either be removed or semantics may change.
1604
     */
1605
    @Deprecated
1606
    @Transient
1607
    public List<Synonym> getHomotypicSynonymsByHomotypicSynonymType(){
1608
        Set<Synonym> synonyms = this.getSynonyms();
1609
        List<Synonym> result = new ArrayList<>();
1610
        for(Synonym synonym : synonyms) {
1611
            if(synonym.getType().equals(SynonymType.HOMOTYPIC_SYNONYM_OF())){
1612
                result.add(synonym);
1613
            }
1614
        }
1615
        return result;
1616
    }
1617

    
1618
    /**
1619
     * Returns the ordered list of all {@link eu.etaxonomy.cdm.model.name.HomotypicalGroup homotypical groups} {@link Synonym synonyms} of
1620
     * <i>this</i> taxon belong to. {@link eu.etaxonomy.cdm.model.name.TaxonName Taxon names} of homotypic synonyms
1621
     * belong to the same homotypical group as the taxon name of <i>this</i>
1622
     * taxon. Taxon names of heterotypic synonyms belong to at least one other
1623
     * homotypical group. <BR>
1624
     * The list returned is ordered according to the date of publication of the
1625
     * first published name within each homotypical group.
1626
     *
1627
     * @see			#getHeterotypicSynonymyGroups()
1628
     * @see			#getSynonyms()
1629
     * @see			eu.etaxonomy.cdm.model.name.HomotypicalGroup
1630
     */
1631
    @Transient
1632
    public List<HomotypicalGroup> getHomotypicSynonymyGroups(){
1633
        List<HomotypicalGroup> result = new ArrayList<>();
1634
        HomotypicalGroup myGroup = this.getHomotypicGroup();
1635
        if (myGroup != null){  //if taxon has no name HG might be null
1636
            result.add(myGroup);
1637
        }
1638
        for (TaxonName taxonName :this.getSynonymNames()){
1639
            if (taxonName != null) {
1640
                if (!result.contains(taxonName.getHomotypicalGroup())){
1641
                    result.add(taxonName.getHomotypicalGroup());
1642
                }
1643
            }
1644
        }
1645
        return result;
1646
    }
1647

    
1648
    /**
1649
     * {@inheritDoc}.
1650
     *
1651
     * <BR>Also returns <code>false</code> if it is a misapplied name or has a similar concept relationship that
1652
     * is similar to synonym relationship (shows up in the synonymy of applications)
1653
     */
1654
    @Override
1655
    @Transient
1656
    public boolean isOrphaned() {
1657

    
1658
        if(taxonNodes == null || taxonNodes.isEmpty()) {
1659
            if(getRelationsFromThisTaxon().isEmpty()) {
1660
                return true;
1661
            }else{
1662
                for (TaxonRelationship rel : getRelationsFromThisTaxon()){
1663
                    if (rel.getType() != null && ! rel.getType().isConceptRelationship()){
1664
                        return false;  //a synonym relationship type similar relationship exists => not orphaned
1665
                    }
1666
                }
1667
                return true;  //all relations are real concept relations and therefore not relevant
1668
            }
1669
        }else{
1670
            return false;
1671
        }
1672
    }
1673

    
1674
    /**
1675
     * Returns the ordered list of all
1676
     * {@link eu.etaxonomy.cdm.model.name.HomotypicalGroup homotypical groups}
1677
     * that contain {@link Synonym synonyms} that are heterotypic to <i>this</i> taxon.<BR>
1678
     *
1679
     * {@link eu.etaxonomy.cdm.model.name.TaxonName Taxon names} of heterotypic synonyms
1680
     * belong to a homotypical group which cannot be the homotypical group to which the
1681
     * taxon name of <i>this</i> taxon belongs.
1682
     * This method returns the same
1683
     * list as the {@link #getHomotypicSynonymyGroups() getHomotypicSynonymyGroups} method
1684
     * but without the homotypical group to which the taxon name of <i>this</i> taxon
1685
     * belongs.<BR>
1686
     * The list returned is <B>ordered</B> according to the rules defined for
1687
     * the {@link HomotypicGroupTaxonComparator} which includes 1) grouping of
1688
     * basionym groups, 2) replaced synonym relationships, 3) publication date,
1689
     * 4) ranks and 5) alphabetical order.
1690
     *
1691
     * @see			#getHeterotypicSynonymyGroups()
1692
     * @see			#getSynonyms()
1693
     * @see			SynonymType#HETEROTYPIC_SYNONYM_OF()
1694
     * @see			eu.etaxonomy.cdm.model.name.HomotypicalGroup
1695
     */
1696
    @Transient
1697
    public List<HomotypicalGroup> getHeterotypicSynonymyGroups(){
1698
        List<HomotypicalGroup> list = getHomotypicSynonymyGroups();
1699
        //remove homotypic group
1700
        list.remove(this.getHomotypicGroup());
1701
        //sort
1702
        Map<Synonym, HomotypicalGroup> map = new HashMap<>();
1703
        for (HomotypicalGroup homotypicalGroup: list){
1704
            List<Synonym> synonymList = getSynonymsInGroup(homotypicalGroup);
1705
            if (synonymList.size() > 0){
1706
                //select the first synonym in the group
1707
                map.put(synonymList.get(0), homotypicalGroup);
1708
            }
1709
        }
1710
        List<Synonym> keyList = new ArrayList<>();
1711
        keyList.addAll(map.keySet());
1712
        //order by first synonym
1713
        Collections.sort(keyList, defaultTaxonComparator);
1714

    
1715
        List<HomotypicalGroup> result = new ArrayList<>();
1716
        for(Synonym synonym: keyList){
1717
            //"replace" synonyms by homotypic groups
1718
            result.add(map.get(synonym));
1719
        }
1720
        //sort end
1721
        return result;
1722
    }
1723

    
1724
    /**
1725
     * Retrieves the ordered list (depending on the rules defined for
1726
     * the {@link HomotypicGroupTaxonComparator}) of
1727
     * {@link taxon.Synonym synonyms} (according to a given reference)
1728
     * the {@link TaxonName taxon names} of which belong to the homotypical group.
1729
     * If other names are part of the group that are not considered synonyms of
1730
     * <i>this</i> taxon, then they will not be included in
1731
     * the result set.
1732
     *
1733
     * @param homotypicGroup
1734
     * @see          #getHeterotypicSynonymyGroups()
1735
     * @see			TaxonName#getSynonyms()
1736
     * @see			TaxonName#getTaxa()
1737
     * @see			taxon.Synonym
1738
     */
1739
    @Transient
1740
    public List<Synonym> getSynonymsInGroup(HomotypicalGroup homotypicGroup){
1741
        return getSynonymsInGroup(homotypicGroup, new HomotypicGroupTaxonComparator(this));
1742
    }
1743

    
1744
    /**
1745
     * @param homotypicGroup
1746
     * @param comparator
1747
     * @return
1748
     * @see     #getSynonymsInGroup(HomotypicalGroup)
1749
     * @see     #getHeterotypicSynonymyGroups()
1750
     */
1751
    @Transient
1752
    public List<Synonym> getSynonymsInGroup(HomotypicalGroup homotypicGroup, TaxonComparator comparator){
1753
        List<Synonym> result = new ArrayList<>();
1754
        if (homotypicGroup == null){
1755
            return result;  //always empty
1756
        }
1757

    
1758
        for (Synonym synonym : this.getSynonyms()){
1759
            if (homotypicGroup.equals(synonym.getHomotypicGroup())){
1760
                result.add(synonym);
1761
            }
1762
        }
1763

    
1764
        Collections.sort(result, comparator);
1765
        return result;
1766
    }
1767

    
1768
    /**
1769
     * @see     #getSynonymsGroups()
1770
     */
1771
    @Transient
1772
    public List<Taxon> getAllMisappliedNames(){
1773
        List<Taxon> result = new ArrayList<>();
1774

    
1775
        for (TaxonRelationship rel : this.getRelationsToThisTaxon()){
1776
            if (rel.getType().isAnyMisappliedName() ){
1777
                result.add(rel.getFromTaxon());
1778
            }
1779
        }
1780
        sortBySimpleTitleCacheComparator(result);
1781
        return result;
1782
    }
1783

    
1784

    
1785
    /**
1786
     * @see     #getSynonymsGroups()
1787
     */
1788
    @Transient
1789
    public List<Taxon> getAllProParteSynonyms(){
1790
        List<Taxon> result = new ArrayList<>();
1791

    
1792
        for (TaxonRelationship rel : this.getRelationsToThisTaxon()){
1793
            if (rel.getType().isAnySynonym()){
1794
                result.add(rel.getFromTaxon());
1795
            }
1796
        }
1797
        sortBySimpleTitleCacheComparator(result);
1798
        return result;
1799
    }
1800

    
1801
//    /**
1802
//     * @see     #getSynonymsGroups()
1803
//     */
1804
//    @Transient
1805
//    public List<Taxon> getProParteSynonyms(){
1806
//        List<Taxon> result = new ArrayList<>();
1807
//
1808
//        for (TaxonRelationship rel : this.getRelationsToThisTaxon()){
1809
//            if (rel.getType().isProParte()){
1810
//                result.add(rel.getFromTaxon());
1811
//            }
1812
//        }
1813
//        sortBySimpleTitleCacheComparator(result);
1814
//        return result;
1815
//    }
1816
//
1817
//    /**
1818
//     * @see     #getSynonymsGroups()
1819
//     */
1820
//    @Transient
1821
//    public List<Taxon> getPartialSynonyms(){
1822
//        List<Taxon> result = new ArrayList<>();
1823
//
1824
//        for (TaxonRelationship rel : this.getRelationsToThisTaxon()){
1825
//            if (rel.getType().isPartial()){
1826
//                result.add(rel.getFromTaxon());
1827
//            }
1828
//        }
1829
//        sortBySimpleTitleCacheComparator(result);
1830
//        return result;
1831
//    }
1832
    private void sortBySimpleTitleCacheComparator(List<Taxon> result) {
1833

    
1834
        Comparator<Taxon> taxonComparator = new Comparator<Taxon>(){
1835

    
1836
            @Override
1837
            public int compare(Taxon o1, Taxon o2) {
1838

    
1839
                if (o1.getTitleCache() == o2.getTitleCache()){
1840
                    return 0;
1841
                }
1842
                if (o1.getTitleCache() == null){
1843
                    return -1;
1844
                }
1845
                if (o2.getTitleCache() == null){
1846
                    return 1;
1847
                }
1848
                return o1.getTitleCache().compareTo(o2.getTitleCache());
1849

    
1850
            }
1851
        };
1852
        Collections.sort(result, taxonComparator);
1853
    }
1854

    
1855
    /**
1856
     * Returns the image gallery description. If no image gallery exists, a new one is created using the
1857
     * defined title and adds the string "-Image Gallery" to the title.</BR>
1858
     * If multiple image galleries exist an arbitrary one is choosen.
1859
     * @param title
1860
     * @return
1861
     */
1862
    public TaxonDescription getOrCreateImageGallery(String title){
1863
        return getOrCreateImageGallery(title, true, false);
1864
    }
1865

    
1866
    /**
1867
     * Returns the image gallery description. If no image gallery exists, a new one is created using the
1868
     * defined title.</BR>
1869
     * If onlyTitle == true we look only for an image gallery with this title, create a new one otherwise.
1870
     * If multiple image galleries exist that match the conditions an arbitrary one is choosen.
1871
     * @param title
1872
     * @param onlyTitle
1873
     * @param if true, the String "Image Gallery
1874
     * @return
1875
     */
1876
    @Transient
1877
    public TaxonDescription getOrCreateImageGallery(String title, boolean addImageGalleryToTitle, boolean onlyTitle){
1878
        TaxonDescription result = null;
1879
        String titleCache = (title == null) ? "Image Gallery" : title;
1880
        if (title != null && addImageGalleryToTitle){
1881
            titleCache = titleCache+ "-Image Gallery";
1882
        }
1883
        Set<TaxonDescription> descriptionSet = this.getDescriptions();
1884
        for (TaxonDescription desc: descriptionSet){
1885
            if (desc.isImageGallery()){
1886
                if (onlyTitle && ! titleCache.equals(desc.getTitleCache())){
1887
                    continue;
1888
                }
1889
                result = desc;
1890
                if (onlyTitle && titleCache.equals(desc.getTitleCache())){
1891
                    break;
1892
                }
1893
            }
1894
        }
1895
        if (result == null){
1896
            result = TaxonDescription.NewInstance();
1897
            result.setTitleCache(titleCache, true);
1898
            this.addDescription(result);
1899
            result.setImageGallery(true);
1900
        }
1901
        return result;
1902
    }
1903

    
1904
    public void clearDescriptions() {
1905
        this.descriptions = new HashSet<>();
1906
    }
1907

    
1908
    /**
1909
     * Compiles all description items attached to this taxon having the given feature
1910
     * and being of the given class. If feature or clazz is null no according filter
1911
     * is applied.
1912
     */
1913
    public <T extends DescriptionElementBase> Set<T> getDescriptionItems(Feature feature, Class<T> clazz) {
1914
        Set<T> result = new HashSet<>();
1915
        Set<TaxonDescription> descriptions = this.getDescriptions();
1916
        for (TaxonDescription description : descriptions) {
1917
            for (DescriptionElementBase deb : description.getElements()) {
1918
                if (clazz == null || deb.isInstanceOf(clazz)) {
1919
                    if (feature == null || feature.equals(deb.getFeature())) {
1920
                        T matchingDeb = CdmBase.deproxy(deb, clazz);
1921
                        result.add(matchingDeb);
1922
                    }
1923
                }
1924
            }
1925
        }
1926
        return result;
1927
    }
1928

    
1929
    //*********************** CLONE ********************************************************/
1930

    
1931
    /**
1932
     * Clones <i>this</i> taxon. This is a shortcut that enables to create
1933
     * a new instance that differs only slightly from <i>this</i> taxon by
1934
     * modifying only some of the attributes.<BR><BR>
1935
     * The TaxonNodes are not cloned, the list is empty.<BR>
1936
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
1937
     * The taxon relationships and synonym relationships are cloned <BR>
1938
     *
1939
     * @see eu.etaxonomy.cdm.model.taxon.TaxonBase#clone()
1940
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
1941
     * @see java.lang.Object#clone()
1942
     */
1943
    @Override
1944
    public Taxon clone() {
1945
        return clone(true, true, true, true);
1946
    }
1947

    
1948
    public Taxon clone(boolean withSynonyms, boolean withTaxonRelations, boolean withDescriptions,
1949
            boolean withMedia) {
1950

    
1951
        Taxon result;
1952
        result = (Taxon)super.clone();
1953

    
1954
        result.setRelationsFromThisTaxon(new HashSet<>());
1955
        result.setRelationsToThisTaxon(new HashSet<>());
1956

    
1957
        if (withTaxonRelations || withSynonyms){
1958
            for (TaxonRelationship fromRelationship : this.getRelationsFromThisTaxon()){
1959
                boolean isSynonymRelation = fromRelationship.getType() != null &&
1960
                        fromRelationship.getType().isAnySynonymOrMisappliedName();
1961
                if (isSynonymRelation && withSynonyms || !isSynonymRelation && withTaxonRelations){
1962
                    TaxonRelationship newRelationship = fromRelationship.clone();
1963
                    newRelationship.setRelatedFrom(result);
1964
                    result.relationsFromThisTaxon.add(newRelationship);
1965
                }
1966
            }
1967

    
1968
            for (TaxonRelationship toRelationship : this.getRelationsToThisTaxon()){
1969
                boolean isSynonymRelation = toRelationship.getType() != null &&
1970
                        toRelationship.getType().isAnySynonymOrMisappliedName();
1971
                if (isSynonymRelation && withSynonyms || !isSynonymRelation && withTaxonRelations){
1972
                    TaxonRelationship newRelationship = toRelationship.clone();
1973
                    newRelationship.setRelatedTo(result);
1974
                    result.relationsToThisTaxon.add(newRelationship);
1975
                }
1976
            }
1977
        }
1978

    
1979
        //clone synonyms (is this wanted or should we remove synonyms
1980
        result.synonyms = new HashSet<>();
1981
        if(withSynonyms){
1982
            for (Synonym synonym : this.getSynonyms()){
1983
                Synonym newSyn = synonym.clone();
1984
                newSyn.setAcceptedTaxon(result);
1985
            }
1986
        }
1987

    
1988
        result.descriptions = new HashSet<>();
1989
        for (TaxonDescription description : this.getDescriptions()){
1990
            if (description.isImageGallery() && withMedia ||
1991
                    !description.isImageGallery() && withDescriptions){
1992
                TaxonDescription newDescription = description.clone();
1993
                result.addDescription(newDescription);
1994
            }
1995
        }
1996

    
1997
        result.taxonNodes = new HashSet<>();
1998

    
1999
        /*for (TaxonNode taxonNode : this.getTaxonNodes()){
2000
            TaxonNode newTaxonNode = (TaxonNode)taxonNode.clone();
2001
            newTaxonNode.setTaxon(result);
2002
            result.addTaxonNode(newTaxonNode);
2003
        }*/
2004

    
2005

    
2006
        //concept related attributes
2007
        result.taxonTypes = this.taxonTypes.clone();
2008
        result.conceptDefinitions = this.conceptDefinitions.clone();
2009
        result.conceptStatus = this.conceptStatus.clone();
2010

    
2011
        if (this.currentConceptPeriod != null){
2012
            result.currentConceptPeriod = this.currentConceptPeriod.clone();
2013
        }
2014

    
2015
        return result;
2016
    }
2017

    
2018
}
(9-9/20)