Project

General

Profile

Download (141 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
package eu.etaxonomy.cdm.model.name;
10

    
11
import java.beans.PropertyChangeListener;
12
import java.lang.reflect.Method;
13
import java.util.ArrayList;
14
import java.util.Collections;
15
import java.util.HashSet;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.Objects;
19
import java.util.Set;
20

    
21
import javax.persistence.Column;
22
import javax.persistence.Entity;
23
import javax.persistence.FetchType;
24
import javax.persistence.Inheritance;
25
import javax.persistence.InheritanceType;
26
import javax.persistence.JoinTable;
27
import javax.persistence.ManyToMany;
28
import javax.persistence.ManyToOne;
29
import javax.persistence.OneToMany;
30
import javax.persistence.OneToOne;
31
import javax.persistence.Table;
32
import javax.persistence.Transient;
33
import javax.validation.Valid;
34
import javax.validation.constraints.Min;
35
import javax.validation.constraints.NotEmpty;
36
import javax.validation.constraints.NotNull;
37
import javax.validation.constraints.Pattern;
38
import javax.xml.bind.annotation.XmlAccessType;
39
import javax.xml.bind.annotation.XmlAccessorType;
40
import javax.xml.bind.annotation.XmlAttribute;
41
import javax.xml.bind.annotation.XmlElement;
42
import javax.xml.bind.annotation.XmlElementWrapper;
43
import javax.xml.bind.annotation.XmlIDREF;
44
import javax.xml.bind.annotation.XmlRootElement;
45
import javax.xml.bind.annotation.XmlSchemaType;
46
import javax.xml.bind.annotation.XmlType;
47

    
48
import org.apache.logging.log4j.LogManager;
49
import org.apache.logging.log4j.Logger;
50
import org.hibernate.annotations.Cascade;
51
import org.hibernate.annotations.CascadeType;
52
import org.hibernate.annotations.Type;
53
import org.hibernate.envers.Audited;
54
import org.hibernate.search.annotations.Analyze;
55
import org.hibernate.search.annotations.Analyzer;
56
import org.hibernate.search.annotations.Field;
57
import org.hibernate.search.annotations.Fields;
58
import org.hibernate.search.annotations.Index;
59
import org.hibernate.search.annotations.Indexed;
60
import org.hibernate.search.annotations.IndexedEmbedded;
61
import org.hibernate.search.annotations.Store;
62
import org.springframework.util.ReflectionUtils;
63

    
64
import eu.etaxonomy.cdm.common.CdmUtils;
65
import eu.etaxonomy.cdm.common.URI;
66
import eu.etaxonomy.cdm.common.UTF8;
67
import eu.etaxonomy.cdm.format.reference.NomenclaturalSourceFormatter;
68
import eu.etaxonomy.cdm.model.EntityCollectionSetterAdapter;
69
import eu.etaxonomy.cdm.model.EntityCollectionSetterAdapter.SetterAdapterException;
70
import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
71
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
72
import eu.etaxonomy.cdm.model.common.CdmBase;
73
import eu.etaxonomy.cdm.model.common.IIntextReferenceTarget;
74
import eu.etaxonomy.cdm.model.common.IParsable;
75
import eu.etaxonomy.cdm.model.common.IRelated;
76
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
77
import eu.etaxonomy.cdm.model.common.Language;
78
import eu.etaxonomy.cdm.model.common.RelationshipBase;
79
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
80
import eu.etaxonomy.cdm.model.description.IDescribable;
81
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
82
import eu.etaxonomy.cdm.model.media.ExternalLink;
83
import eu.etaxonomy.cdm.model.media.ExternalLinkType;
84
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
85
import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
86
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
87
import eu.etaxonomy.cdm.model.reference.Reference;
88
import eu.etaxonomy.cdm.model.taxon.Synonym;
89
import eu.etaxonomy.cdm.model.taxon.Taxon;
90
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
91
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
92
import eu.etaxonomy.cdm.strategy.cache.name.CacheUpdate;
93
import eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy;
94
import eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy;
95
import eu.etaxonomy.cdm.strategy.match.IMatchable;
96
import eu.etaxonomy.cdm.strategy.match.Match;
97
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
98
import eu.etaxonomy.cdm.strategy.match.MatchMode;
99
import eu.etaxonomy.cdm.strategy.merge.Merge;
100
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
101
import eu.etaxonomy.cdm.strategy.parser.ParserProblem;
102
import eu.etaxonomy.cdm.validation.Level2;
103
import eu.etaxonomy.cdm.validation.Level3;
104
import eu.etaxonomy.cdm.validation.annotation.CorrectEpithetsForRank;
105
import eu.etaxonomy.cdm.validation.annotation.CorrectRanksForCode;
106
import eu.etaxonomy.cdm.validation.annotation.NameMustFollowCode;
107
import eu.etaxonomy.cdm.validation.annotation.NameMustHaveAuthority;
108
import eu.etaxonomy.cdm.validation.annotation.NoDuplicateNames;
109
import eu.etaxonomy.cdm.validation.annotation.NullOrNotEmpty;
110
import eu.etaxonomy.cdm.validation.annotation.ValidTaxonomicYear;
111

    
112
/**
113
 * The upmost (abstract) class for scientific taxon names regardless of any
114
 * particular {@link NomenclaturalCode nomenclature code}. The scientific taxon name does not depend
115
 * on the use made of it in a publication or a treatment
116
 * ({@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon concept respectively potential taxon})
117
 * as an {@link eu.etaxonomy.cdm.model.taxon.Taxon "accepted" respectively "correct" (taxon) name}
118
 * or as a {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
119
 * <P>
120
 * This class corresponds partially to: <ul>
121
 * <li> TaxonName according to the TDWG ontology
122
 * <li> ScientificName and CanonicalName according to the TCS
123
 * <li> ScientificName according to the ABCD schema
124
 * </ul>
125
 *
126
 * @author m.doering
127
 * @since 08-Nov-2007 13:06:57
128
 */
129
@XmlAccessorType(XmlAccessType.FIELD)
130
@XmlType(name = "TaxonName", propOrder = {
131
    "nameType",
132
    "appendedPhrase",
133
    "nomenclaturalSource",
134
    "rank",
135
    "fullTitleCache",
136
    "protectedFullTitleCache",
137
    "homotypicalGroup",
138
    "typeDesignations",
139
    "relationsFromThisName",
140
    "relationsToThisName",
141
    "status",
142
    "descriptions",
143
    "taxonBases",
144
    "registrations",
145
    //non-viral names
146
    "nameCache",
147
    "genusOrUninomial",
148
    "infraGenericEpithet",
149
    "specificEpithet",
150
    "infraSpecificEpithet",
151
    "combinationAuthorship",
152
    "exCombinationAuthorship",
153
    "basionymAuthorship",
154
    "exBasionymAuthorship",
155
    "authorshipCache",
156
    "protectedAuthorshipCache",
157
    "protectedNameCache",
158
    "hybridParentRelations",
159
    "hybridChildRelations",
160
    "hybridFormula",
161
    "monomHybrid",
162
    "binomHybrid",
163
    "trinomHybrid",
164
    //viral name
165
    "acronym",
166
    //bacterial names
167
    "subGenusAuthorship",
168
    "nameApprobation",
169
    //zoological name
170
    "breed",
171
    "publicationYear",
172
    "originalPublicationYear",
173
    "inCombinationAuthorship",
174
    "inBasionymAuthorship",
175
    //fungi
176
    "anamorphic",
177
    //cultivar plant names
178
    "cultivarGroupEpithet",
179
    "cultivarEpithet"
180
})
181
@XmlRootElement(name = "TaxonName")
182
@Entity
183
@Audited
184
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
185
@Table(name="TaxonName", indexes = {
186
        @javax.persistence.Index(name = "taxonNameBaseTitleCacheIndex", columnList = "titleCache"),
187
        @javax.persistence.Index(name = "taxonNameBaseNameCacheIndex", columnList = "nameCache") })
188
@NameMustFollowCode
189
@CorrectEpithetsForRank(groups = Level2.class)
190
@CorrectRanksForCode(groups = Level2.class)
191
@NameMustHaveAuthority(groups = Level2.class)
192
@NoDuplicateNames(groups = Level3.class)
193
@Indexed(index = "eu.etaxonomy.cdm.model.name.TaxonName")
194
public class TaxonName
195
            extends IdentifiableEntity<INameCacheStrategy>
196
            implements ITaxonNameBase, INonViralName, IViralName, IBacterialName, IZoologicalName,
197
                IBotanicalName, ICultivarPlantName, IFungusName,
198
                IParsable, IRelated, IMatchable, IIntextReferenceTarget,
199
                IDescribable<TaxonNameDescription>,
200
                INomenclaturalStanding {
201

    
202
    private static final long serialVersionUID = -791164269603409712L;
203
    private static final Logger logger = LogManager.getLogger(TaxonName.class);
204

    
205
    /**
206
     * The {@link NomenclaturalCode nomenclatural code} this taxon name is ruled by.
207
     */
208
    @XmlAttribute(name ="NameType")
209
    @Column(name="nameType", length=15)
210
    @NotNull
211
    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
212
        parameters = {@org.hibernate.annotations.Parameter(name = "enumClass", value = "eu.etaxonomy.cdm.model.name.NomenclaturalCode")}
213
    )
214
    @Audited //needed ?
215
    private NomenclaturalCode nameType;
216

    
217
    @XmlElement(name = "FullTitleCache")
218
    @Column(length=800, name="fullTitleCache")  //see #1592
219
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
220
    @CacheUpdate(noUpdate ="titleCache")
221
    @NotEmpty(groups = Level2.class)
222
    protected String fullTitleCache;
223

    
224
    //if true titleCache will not be automatically generated/updated
225
    @XmlElement(name = "ProtectedFullTitleCache")
226
    @CacheUpdate(value ="fullTitleCache", noUpdate ="titleCache")
227
    private boolean protectedFullTitleCache;
228

    
229
    @XmlElementWrapper(name = "Descriptions")
230
    @XmlElement(name = "Description")
231
    @OneToMany(mappedBy="taxonName", fetch= FetchType.LAZY, orphanRemoval=true)
232
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
233
    @NotNull
234
    private Set<TaxonNameDescription> descriptions = new HashSet<>();
235

    
236
    @XmlElement(name = "AppendedPhrase")
237
    @Field
238
    @CacheUpdate(value ="nameCache")
239
    //TODO Val #3379
240
//    @NullOrNotEmpty
241
    @Column(length=255)
242
    private String appendedPhrase;
243

    
244
    @XmlAttribute
245
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
246
    @Match(value=MatchMode.IGNORE)
247
    private int parsingProblem = 0;
248

    
249
    @XmlAttribute
250
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
251
    @Match(value=MatchMode.IGNORE)
252
    private int problemStarts = -1;
253

    
254
    @XmlAttribute
255
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
256
    @Match(value=MatchMode.IGNORE)
257
    private int problemEnds = -1;
258

    
259
    @XmlElementWrapper(name = "TypeDesignations")
260
    @XmlElement(name = "TypeDesignation")
261
    @XmlIDREF
262
    @XmlSchemaType(name = "IDREF")
263
    @ManyToMany(fetch = FetchType.LAZY)
264
    @JoinTable(
265
        name="TaxonName_TypeDesignationBase",
266
        joinColumns=@javax.persistence.JoinColumn(name="TaxonName_id"),
267
        inverseJoinColumns=@javax.persistence.JoinColumn(name="typedesignations_id")
268
    )
269
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
270
    @NotNull
271
    private Set<TypeDesignationBase> typeDesignations = new HashSet<>();
272

    
273
    @XmlElement(name = "HomotypicalGroup")
274
    @XmlIDREF
275
    @XmlSchemaType(name = "IDREF")
276
    @ManyToOne(fetch = FetchType.LAZY)
277
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
278
    @Match(MatchMode.IGNORE)
279
    @CacheUpdate(noUpdate ="titleCache")
280
    //TODO Val #3379
281
//    @NotNull
282
    private HomotypicalGroup homotypicalGroup;
283

    
284
    @XmlElementWrapper(name = "RelationsFromThisName")
285
    @XmlElement(name = "RelationFromThisName")
286
    @OneToMany(mappedBy="relatedFrom", fetch= FetchType.LAZY, orphanRemoval=true)
287
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
288
    @Merge(MergeMode.RELATION)
289
    @NotNull
290
    @Valid
291
    private Set<NameRelationship> relationsFromThisName = new HashSet<>();
292

    
293
    @XmlElementWrapper(name = "RelationsToThisName")
294
    @XmlElement(name = "RelationToThisName")
295
    @XmlIDREF
296
    @XmlSchemaType(name = "IDREF")
297
    @OneToMany(mappedBy="relatedTo", fetch= FetchType.LAZY, orphanRemoval=true)
298
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
299
    @Merge(MergeMode.RELATION)
300
    @NotNull
301
    @Valid
302
    private Set<NameRelationship> relationsToThisName = new HashSet<>();
303

    
304
    @XmlElementWrapper(name = "NomenclaturalStatuses")
305
    @XmlElement(name = "NomenclaturalStatus")
306
    @OneToMany(fetch= FetchType.LAZY, mappedBy = "name", orphanRemoval=true)
307
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE,CascadeType.DELETE})
308
    @NotNull
309
    @IndexedEmbedded(depth=1)
310
    private Set<NomenclaturalStatus> status = new HashSet<>();
311

    
312
    @XmlElementWrapper(name = "TaxonBases")
313
    @XmlElement(name = "TaxonBase")
314
    @XmlIDREF
315
    @XmlSchemaType(name = "IDREF")
316
    @OneToMany(mappedBy="name", fetch= FetchType.LAZY)
317
    @NotNull
318
    @IndexedEmbedded(depth=1)
319
    private Set<TaxonBase> taxonBases = new HashSet<>();
320

    
321
    @XmlElement(name = "Rank")
322
    @XmlIDREF
323
    @XmlSchemaType(name = "IDREF")
324
    @ManyToOne(fetch = FetchType.EAGER)
325
    @CacheUpdate(value ="nameCache")
326
    //TODO Val #3379, handle maybe as groups = Level2.class ??
327
//    @NotNull
328
    @IndexedEmbedded(depth=1)
329
    private Rank rank;
330

    
331
    //#6581
332
    @XmlElement(name = "NomenclaturalSource")
333
    @XmlIDREF
334
    @XmlSchemaType(name = "IDREF")
335
    @OneToOne(fetch = FetchType.LAZY, orphanRemoval=true, mappedBy="sourcedName")
336
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
337
    @CacheUpdate(noUpdate ="titleCache")
338
    @IndexedEmbedded
339
    private NomenclaturalSource nomenclaturalSource;
340

    
341
    @XmlElementWrapper(name = "Registrations")
342
    @XmlElement(name = "Registration")
343
    @XmlIDREF
344
    @XmlSchemaType(name = "IDREF")
345
    @OneToMany(mappedBy="name", fetch= FetchType.LAZY)
346
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
347
    @NotNull
348
    @IndexedEmbedded(depth=1)
349
    private Set<Registration> registrations = new HashSet<>();
350

    
351
//****** Non-ViralName attributes ***************************************/
352

    
353
    @XmlElement(name = "NameCache")
354
    @Fields({
355
        @Field(name = "nameCache_tokenized"),
356
        @Field(store = Store.YES, index = Index.YES, analyzer = @Analyzer(impl = org.apache.lucene.analysis.core.KeywordAnalyzer.class))
357
    })
358
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
359
            cacheReplacedProperties={"genusOrUninomial", "infraGenericEpithet", "specificEpithet", "infraSpecificEpithet"} )
360
    @NotEmpty(groups = Level2.class) // implicitly NotNull
361
    @Column(length=255)
362
    private String nameCache;
363

    
364
    @XmlElement(name = "ProtectedNameCache")
365
    @CacheUpdate(value="nameCache")
366
    protected boolean protectedNameCache;
367

    
368
    @XmlElement(name = "GenusOrUninomial")
369
    @Field(analyze = Analyze.YES, indexNullAs=Field.DEFAULT_NULL_TOKEN)
370
    @Match(MatchMode.EQUAL_REQUIRED)
371
    @CacheUpdate("nameCache")
372
    @Column(length=255)
373
    @Pattern(regexp = "[A-Z][a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForUninomial.message}")
374
    @NullOrNotEmpty
375
    @NotNull(groups = Level2.class)
376
    private String genusOrUninomial;
377

    
378
    @XmlElement(name = "InfraGenericEpithet")
379
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
380
    @CacheUpdate("nameCache")
381
    //TODO Val #3379
382
//    @NullOrNotEmpty
383
    @Column(length=255)
384
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class,message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
385
    private String infraGenericEpithet;
386

    
387
    @XmlElement(name = "SpecificEpithet")
388
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
389
    @CacheUpdate("nameCache")
390
    //TODO Val #3379
391
//    @NullOrNotEmpty
392
    @Column(length=255)
393
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
394
    private String specificEpithet;
395

    
396
    @XmlElement(name = "InfraSpecificEpithet")
397
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
398
    @CacheUpdate("nameCache")
399
    //TODO Val #3379
400
//    @NullOrNotEmpty
401
    @Column(length=255)
402
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
403
    private String infraSpecificEpithet;
404

    
405
    @XmlElement(name = "CombinationAuthorship")
406
    @XmlIDREF
407
    @XmlSchemaType(name = "IDREF")
408
    @ManyToOne(fetch = FetchType.LAZY)
409
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
410
    @CacheUpdate("authorshipCache")
411
    @IndexedEmbedded
412
    private TeamOrPersonBase<?> combinationAuthorship;
413

    
414
    @XmlElement(name = "ExCombinationAuthorship")
415
    @XmlIDREF
416
    @XmlSchemaType(name = "IDREF")
417
    @ManyToOne(fetch = FetchType.LAZY)
418
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
419
    @CacheUpdate("authorshipCache")
420
    @IndexedEmbedded
421
    private TeamOrPersonBase<?> exCombinationAuthorship;
422

    
423
    //#6943
424
    @XmlElement(name = "InCombinationAuthorship")
425
    @XmlIDREF
426
    @XmlSchemaType(name = "IDREF")
427
    @ManyToOne(fetch = FetchType.LAZY)
428
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
429
    @CacheUpdate("authorshipCache")
430
    @IndexedEmbedded
431
    private TeamOrPersonBase<?> inCombinationAuthorship;
432

    
433
    @XmlElement(name = "BasionymAuthorship")
434
    @XmlIDREF
435
    @XmlSchemaType(name = "IDREF")
436
    @ManyToOne(fetch = FetchType.LAZY)
437
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
438
    @CacheUpdate("authorshipCache")
439
    @IndexedEmbedded
440
    private TeamOrPersonBase<?> basionymAuthorship;
441

    
442
    @XmlElement(name = "ExBasionymAuthorship")
443
    @XmlIDREF
444
    @XmlSchemaType(name = "IDREF")
445
    @ManyToOne(fetch = FetchType.LAZY)
446
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
447
    @CacheUpdate("authorshipCache")
448
    @IndexedEmbedded
449
    private TeamOrPersonBase<?> exBasionymAuthorship;
450

    
451
    //#6943
452
    @XmlElement(name = "InBasionymAuthorship")
453
    @XmlIDREF
454
    @XmlSchemaType(name = "IDREF")
455
    @ManyToOne(fetch = FetchType.LAZY)
456
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
457
    @CacheUpdate("authorshipCache")
458
    @IndexedEmbedded
459
    private TeamOrPersonBase<?> inBasionymAuthorship;
460

    
461
    @XmlElement(name = "AuthorshipCache")
462
    @Fields({
463
        @Field(name = "authorshipCache_tokenized"),
464
        @Field(analyze = Analyze.NO)
465
    })
466
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
467
            cacheReplacedProperties={"combinationAuthorship", "basionymAuthorship", "exCombinationAuthorship", "exBasionymAuthorship"} )
468
    //TODO Val #3379
469
//    @NotNull
470
    @Column(length=255)
471
    @Pattern(regexp = "^[A-Za-z0-9 \\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-\\&\\,\\(\\)\\.]+$", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForAuthority.message}")
472
    private String authorshipCache;
473

    
474
    @XmlElement(name = "ProtectedAuthorshipCache")
475
    @CacheUpdate("authorshipCache")
476
    protected boolean protectedAuthorshipCache;
477

    
478
    @XmlElementWrapper(name = "HybridRelationsFromThisName")
479
    @XmlElement(name = "HybridRelationsFromThisName")
480
    @OneToMany(mappedBy="relatedFrom", fetch = FetchType.LAZY)
481
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
482
    @Merge(MergeMode.RELATION)
483
    @NotNull
484
    private Set<HybridRelationship> hybridParentRelations = new HashSet<>();
485

    
486
    @XmlElementWrapper(name = "HybridRelationsToThisName")
487
    @XmlElement(name = "HybridRelationsToThisName")
488
    @OneToMany(mappedBy="relatedTo", fetch = FetchType.LAZY, orphanRemoval=true) //a hybrid relation can be deleted automatically if the child is deleted.
489
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
490
    @Merge(MergeMode.RELATION)
491
    @NotNull
492
    private Set<HybridRelationship> hybridChildRelations = new HashSet<>();
493

    
494

    
495
    //if set: this name is a hybrid formula (a hybrid that does not have an own name) and no
496
    //other hybrid flags may be set. A
497
    //hybrid name  may not have either an authorteam nor other name components.
498
    @XmlElement(name ="IsHybridFormula")
499
    @CacheUpdate("nameCache")
500
    private boolean hybridFormula = false;
501

    
502
    @XmlElement(name ="IsMonomHybrid")
503
    @CacheUpdate("nameCache")
504
    private boolean monomHybrid = false;
505

    
506
    @XmlElement(name ="IsBinomHybrid")
507
    @CacheUpdate("nameCache")
508
    private boolean binomHybrid = false;
509

    
510
    @XmlElement(name ="IsTrinomHybrid")
511
    @CacheUpdate("nameCache")
512
    private boolean trinomHybrid = false;
513

    
514
// ViralName attributes ************************* /
515

    
516
    @XmlElement(name = "Acronym")
517
    @Field
518
    //TODO Val #3379
519
//  @NullOrNotEmpty
520
    @Column(length=255)
521
    private String acronym;
522

    
523
// BacterialName attributes ***********************/
524

    
525
    //Author team and year of the subgenus name
526
    @XmlElement(name = "SubGenusAuthorship")
527
    @Field
528
    private String subGenusAuthorship;
529

    
530
    //Approbation of name according to approved list, validation list, or validly published, paper in IJSB after 1980
531
    @XmlElement(name = "NameApprobation")
532
    @Field
533
    private String nameApprobation;
534

    
535
    //ZOOLOGICAL NAME
536

    
537
    //Name of the breed of an animal
538
    @XmlElement(name = "Breed")
539
    @Field
540
    @NullOrNotEmpty
541
    @Column(length=255)
542
    private String breed;
543

    
544
    @XmlElement(name = "PublicationYear")
545
    @Field(analyze = Analyze.NO)
546
    @CacheUpdate(value ="authorshipCache")
547
    @Min(0)
548
    private Integer publicationYear;
549

    
550
    @XmlElement(name = "OriginalPublicationYear")
551
    @Field(analyze = Analyze.NO)
552
    @CacheUpdate(value ="authorshipCache")
553
    @Min(0)
554
    private Integer originalPublicationYear;
555

    
556
    //Cultivar attribute(s)
557

    
558
    //the characteristical name of the cultivar
559
    @XmlElement(name = "CultivarEpithet")
560
    //TODO Val #3379
561
    //@NullOrNotEmpty
562
    @CacheUpdate("nameCache")
563
    @Column(length=255)
564
    private String cultivarEpithet;
565

    
566
    //#9761
567
    @XmlElement(name = "CultivarGroupEpithet")
568
    @NullOrNotEmpty
569
    @CacheUpdate("nameCache")
570
    @Column(length=255)
571
    private String cultivarGroupEpithet;
572

    
573
    // ************** FUNGUS name attributes
574
    //to indicate that the type of the name is asexual or not
575
    @XmlElement(name ="IsAnamorphic")
576
    private boolean anamorphic = false;
577

    
578
// *************** FACTORY METHODS ********************************/
579

    
580
    //TODO move to TaxonNameFactory
581
    protected static TaxonName NewInstance(NomenclaturalCode code, Rank rank,
582
            HomotypicalGroup homotypicalGroup) {
583

    
584
        TaxonName result = new TaxonName(code, rank, homotypicalGroup);
585
        return result;
586
    }
587

    
588
    //TODO move to TaxonNameFactory
589
    public static TaxonName NewInstance(NomenclaturalCode code, Rank rank, String genusOrUninomial,
590
            String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet,
591
            TeamOrPersonBase<?> combinationAuthorship, Reference nomenclaturalReference,
592
            String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
593

    
594
        TaxonName result = new TaxonName(code, rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
595
        return result;
596
    }
597

    
598
// ************* CONSTRUCTORS *************/
599

    
600
    //for hibernate use only, *packet* private required by bytebuddy
601
    TaxonName() {}
602

    
603
    /**
604
     * Class constructor: creates a new taxon name instance
605
     * only containing its {@link Rank rank} and
606
     * its {@link HomotypicalGroup homotypical group} and
607
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy default cache strategy}.
608
     * The new taxon name will be also added to the set of taxon names
609
     * belonging to this homotypical group.
610
     *
611
     * @param  rank  			 the rank to be assigned to <i>this</i> taxon name
612
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
613
     * @see    					 #TaxonName()
614
     * @see    					 #TaxonName(Rank)
615
     * @see    					 #TaxonName(HomotypicalGroup)
616
     */
617
    protected TaxonName(NomenclaturalCode type, Rank rank, HomotypicalGroup homotypicalGroup) {
618
        setNameType(type);
619
        this.setRank(rank);
620
        if (homotypicalGroup == null){
621
            homotypicalGroup = HomotypicalGroup.NewInstance();
622
        }
623
        homotypicalGroup.addTypifiedName(this);
624
        this.homotypicalGroup = homotypicalGroup;
625
    }
626

    
627
    /**
628
     * Class constructor: creates a new non viral taxon name instance
629
     * containing its {@link Rank rank},
630
     * its {@link HomotypicalGroup homotypical group},
631
     * its scientific name components, its {@link eu.etaxonomy.cdm.model.agent.TeamOrPersonBase author(team)},
632
     * its {@link eu.etaxonomy.cdm.model.reference.Reference nomenclatural reference} and
633
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy default cache strategy}.
634
     * The new non viral taxon name instance will be also added to the set of
635
     * non viral taxon names belonging to this homotypical group.
636
     *
637
     * @param   rank  the rank to be assigned to <i>this</i> non viral taxon name
638
     * @param   genusOrUninomial the string for <i>this</i> non viral taxon name
639
     *          if its rank is genus or higher or for the genus part
640
     *          if its rank is lower than genus
641
     * @param   infraGenericEpithet  the string for the first epithet of
642
     *          <i>this</i> non viral taxon name if its rank is lower than genus
643
     *          and higher than species aggregate
644
     * @param   specificEpithet  the string for the first epithet of
645
     *          <i>this</i> non viral taxon name if its rank is species aggregate or lower
646
     * @param   infraSpecificEpithet  the string for the second epithet of
647
     *          <i>this</i> non viral taxon name if its rank is lower than species
648
     * @param   combinationAuthorship  the author or the team who published <i>this</i> non viral taxon name
649
     * @param   nomenclaturalReference  the nomenclatural reference where <i>this</i> non viral taxon name was published
650
     * @param   nomenclMicroRef  the string with the details for precise location within the nomenclatural reference
651
     * @param   homotypicalGroup  the homotypical group to which <i>this</i> non viral taxon name belongs
652
     * @see     #NewInstance(NomenclaturalCode, Rank, HomotypicalGroup)
653
     * @see     #NewInstance(NomenclaturalCode, Rank, String, String, String, String, TeamOrPersonBase, Reference, String, HomotypicalGroup)
654
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
655
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
656
     * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
657
     */
658
    protected TaxonName(NomenclaturalCode type, Rank rank, String genusOrUninomial,
659
            String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet,
660
            TeamOrPersonBase<?> combinationAuthorship, Reference nomenclaturalReference,
661
            String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
662
        this(type, rank, homotypicalGroup);
663
        setGenusOrUninomial(genusOrUninomial);
664
        setInfraGenericEpithet (infraGenericEpithet);
665
        setSpecificEpithet(specificEpithet);
666
        setInfraSpecificEpithet(infraSpecificEpithet);
667
        setCombinationAuthorship(combinationAuthorship);
668
        setNomenclaturalReference(nomenclaturalReference);
669
        this.setNomenclaturalMicroReference(nomenclMicroRef);
670
    }
671

    
672
    @Override
673
    public void initListener(){
674
        PropertyChangeListener nameListener = event-> {
675
            String propName = event.getPropertyName();
676
            if (Objects.equals(event.getOldValue(), event.getNewValue())){
677
                return;
678
            }
679

    
680
            boolean protectedByLowerCache = false;
681
            //authorship cache
682
            if (fieldHasCacheUpdateProperty(propName, "authorshipCache")){
683
                if (protectedAuthorshipCache){
684
                    protectedByLowerCache = true;
685
                }else{
686
                    authorshipCache = null;
687
                }
688
            }
689

    
690
            //nameCache
691
            if (fieldHasCacheUpdateProperty(propName, "nameCache")){
692
                if (protectedNameCache){
693
                    protectedByLowerCache = true;
694
                }else{
695
                    nameCache = null;
696
                }
697
            }
698
            //title cache
699
            if (! fieldHasNoUpdateProperty(propName, "titleCache")){
700
                if (isProtectedTitleCache()|| protectedByLowerCache == true ){
701
                    protectedByLowerCache = true;
702
                }else{
703
                    titleCache = null;
704
                }
705
            }
706
            //full title cache
707
            if (! fieldHasNoUpdateProperty(propName, "fullTitleCache")){
708
                if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
709
                    protectedByLowerCache = true;
710
                }else{
711
                    fullTitleCache = null;
712
                }
713
            }
714

    
715
        };
716
        addPropertyChangeListener(nameListener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
717
    }
718

    
719
    private static Map<String, java.lang.reflect.Field> allFields = null;
720
    protected Map<String, java.lang.reflect.Field> getAllFields(){
721
        if (allFields == null){
722
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
723
        }
724
        return allFields;
725
    }
726

    
727
    private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
728
        try {
729
            java.lang.reflect.Field field = getAllFields().get(propertyName);
730
            if (field != null){
731
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
732
                if (updateAnnotation != null){
733
                    for (String value : updateAnnotation.value()){
734
                        if (cacheName.equals(value)){
735
                            return true;
736
                        }
737
                    }
738
                }
739
            }
740
            return false;
741
        } catch (SecurityException e1) {
742
            throw e1;
743
        }
744
    }
745

    
746
    private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
747
        java.lang.reflect.Field field;
748
        //do not update fields with the same name
749
        if (cacheName.equals(propertyName)){
750
            return true;
751
        }
752
        //evaluate annotation
753
        try {
754
            field = getAllFields().get(propertyName);
755
            if (field != null){
756
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
757
                if (updateAnnotation != null){
758
                    for (String value : updateAnnotation.noUpdate()){
759
                        if (cacheName.equals(value)){
760
                            return true;
761
                        }
762
                    }
763
                }
764
            }
765
            return false;
766
        } catch (SecurityException e1) {
767
            throw e1;
768
        }
769
    }
770

    
771
    @Override
772
    protected void initDefaultCacheStrategy() {
773
        this.cacheStrategy = TaxonNameDefaultCacheStrategy.NewInstance();
774
    }
775

    
776
// ****************** GETTER / SETTER ****************************/
777

    
778
    @Override
779
    public NomenclaturalCode getNameType() {
780
        return nameType;
781
    }
782

    
783
    @Override
784
    public void setNameType(NomenclaturalCode nameType) {
785
        this.nameType = nameType;
786
    }
787

    
788
    /**
789
     * Returns the boolean value of the flag intended to protect (true)
790
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
791
     * string of <i>this</i> non viral taxon name.
792
     *
793
     * @return  the boolean value of the protectedNameCache flag
794
     * @see     #getNameCache()
795
     */
796
    @Override
797
    public boolean isProtectedNameCache() {
798
        return protectedNameCache;
799
    }
800

    
801
    /**
802
     * @see     #isProtectedNameCache()
803
     */
804
    @Override
805
    public void setProtectedNameCache(boolean protectedNameCache) {
806
        this.protectedNameCache = protectedNameCache;
807
    }
808

    
809
    /**
810
     * Returns either the scientific name string (without authorship) for <i>this</i>
811
     * non viral taxon name if its rank is genus or higher (monomial) or the string for
812
     * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
813
     * Genus or uninomial strings begin with an upper case letter.
814
     *
815
     * @return  the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
816
     * @see     #getNameCache()
817
     */
818
    @Override
819
    public String getGenusOrUninomial() {
820
        return genusOrUninomial;
821
    }
822

    
823
    /**
824
     * @see  #getGenusOrUninomial()
825
     */
826
    @Override
827
    public void setGenusOrUninomial(String genusOrUninomial) {
828
        this.genusOrUninomial = isBlank(genusOrUninomial) ? null : genusOrUninomial;
829
    }
830

    
831
    /**
832
     * Returns the genus subdivision epithet string (infrageneric part) for
833
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
834
     * higher than species aggregate: binomial). Genus subdivision epithet
835
     * strings begin with an upper case letter.
836
     *
837
     * @return  the string containing the infrageneric part of <i>this</i> non viral taxon name
838
     * @see     #getNameCache()
839
     */
840
    @Override
841
    public String getInfraGenericEpithet(){
842
        return this.infraGenericEpithet;
843
    }
844

    
845
    /**
846
     * @see  #getInfraGenericEpithet()
847
     */
848
    @Override
849
    public void setInfraGenericEpithet(String infraGenericEpithet){
850
        this.infraGenericEpithet = isBlank(infraGenericEpithet)? null : infraGenericEpithet;
851
    }
852

    
853
    /**
854
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
855
     * species aggregate or lower (bi- or trinomial). Species epithet strings
856
     * begin with a lower case letter.
857
     *
858
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
859
     * @see     #getNameCache()
860
     */
861
    @Override
862
    public String getSpecificEpithet(){
863
        return this.specificEpithet;
864
    }
865

    
866
    /**
867
     * @see  #getSpecificEpithet()
868
     */
869
    @Override
870
    public void setSpecificEpithet(String specificEpithet){
871
        this.specificEpithet = isBlank(specificEpithet) ? null : specificEpithet;
872
    }
873

    
874
    /**
875
     * Returns the species subdivision epithet string (infraspecific part) for
876
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
877
     * (lower than species: trinomial). Species subdivision epithet strings
878
     * begin with a lower case letter.
879
     *
880
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
881
     * @see     #getNameCache()
882
     */
883
    @Override
884
    public String getInfraSpecificEpithet(){
885
        return this.infraSpecificEpithet;
886
    }
887

    
888
    /**
889
     * @see  #getInfraSpecificEpithet()
890
     */
891
    @Override
892
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
893
        this.infraSpecificEpithet = isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
894
    }
895

    
896
    /**
897
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
898
     * taxon name.
899
     *
900
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
901
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
902
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
903
     */
904
    @Override
905
    public TeamOrPersonBase<?> getCombinationAuthorship(){
906
        return this.combinationAuthorship;
907
    }
908

    
909
    /**
910
     * @see  #getCombinationAuthorship()
911
     */
912
    @Override
913
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
914
        this.combinationAuthorship = combinationAuthorship;
915
    }
916

    
917
    /**
918
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
919
     * the publication of <i>this</i> non viral taxon name as generally stated by
920
     * the {@link #getCombinationAuthorship() combination author (team)} itself.<BR>
921
     * An ex-author(-team) is an author(-team) to whom a taxon name was ascribed
922
     * although it is not the author(-team) of a valid publication (for instance
923
     * without the validating description or diagnosis in case of a name for a
924
     * new taxon). The name of this ascribed authorship, followed by "ex", may
925
     * be inserted before the name(s) of the publishing author(s) of the validly
926
     * published name:<BR>
927
     * <i>Lilium tianschanicum</i> was described by Grubov (1977) as a new species and
928
     * its name was ascribed to Ivanova; since there is no indication that
929
     * Ivanova provided the validating description, the name may be cited as
930
     * <i>Lilium tianschanicum</i> N. A. Ivanova ex Grubov or <i>Lilium tianschanicum</i> Grubov.
931
     * <P>
932
     * The presence of an author (team) of <i>this</i> non viral taxon name is a
933
     * condition for the existence of an ex author (team) for <i>this</i> same name.
934
     *
935
     * @return  the nomenclatural ex author (team) of <i>this</i> non viral taxon name
936
     * @see     #getCombinationAuthorship()
937
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
938
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
939
     */
940
    @Override
941
    public TeamOrPersonBase<?> getExCombinationAuthorship(){
942
        return this.exCombinationAuthorship;
943
    }
944
    /**
945
     * @see  #getExCombinationAuthorship()
946
     */
947
    @Override
948
    public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
949
        this.exCombinationAuthorship = exCombinationAuthorship;
950
    }
951

    
952
    @Override
953
    public TeamOrPersonBase<?> getInCombinationAuthorship(){
954
        return this.inCombinationAuthorship;
955
    }
956
    @Override
957
    public void setInCombinationAuthorship(TeamOrPersonBase<?> inCombinationAuthorship) {
958
        this.inCombinationAuthorship = inCombinationAuthorship;
959
    }
960

    
961
    /**
962
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
963
     * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
964
     * author (team) can only exist if <i>this</i> non viral taxon name is a new
965
     * combination due to a taxonomical revision.
966
     *
967
     * @return  the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
968
     * @see     #getCombinationAuthorship()
969
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
970
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
971
     */
972
    @Override
973
    public TeamOrPersonBase<?> getBasionymAuthorship(){
974
        return basionymAuthorship;
975
    }
976

    
977
    /**
978
     * @see  #getBasionymAuthorship()
979
     */
980
    @Override
981
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
982
        this.basionymAuthorship = basionymAuthorship;
983
    }
984

    
985
    /**
986
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
987
     * the publication of the original combination <i>this</i> non viral taxon name is
988
     * based on. This should have been generally stated by
989
     * the {@link #getBasionymAuthorship() basionym author (team)} itself.
990
     * The presence of a basionym author (team) of <i>this</i> non viral taxon name is a
991
     * condition for the existence of an ex basionym author (team)
992
     * for <i>this</i> same name.
993
     *
994
     * @return  the nomenclatural ex basionym author (team) of <i>this</i> non viral taxon name
995
     * @see     #getBasionymAuthorship()
996
     * @see     #getExCombinationAuthorship()
997
     * @see     #getCombinationAuthorship()
998
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
999
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
1000
     */
1001
    @Override
1002
    public TeamOrPersonBase<?> getExBasionymAuthorship(){
1003
        return exBasionymAuthorship;
1004
    }
1005

    
1006
    /**
1007
     * @see  #getExBasionymAuthorship()
1008
     */
1009
    @Override
1010
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
1011
        this.exBasionymAuthorship = exBasionymAuthorship;
1012
    }
1013

    
1014
    @Override
1015
    public TeamOrPersonBase<?> getInBasionymAuthorship(){
1016
        return this.inBasionymAuthorship;
1017
    }
1018
    @Override
1019
    public void setInBasionymAuthorship(TeamOrPersonBase<?> inBasionymAuthorship) {
1020
        this.inBasionymAuthorship = inBasionymAuthorship;
1021
    }
1022

    
1023
    /**
1024
     * Returns the boolean value of the flag intended to protect (true)
1025
     * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
1026
     * of <i>this</i> non viral taxon name.
1027
     *
1028
     * @return  the boolean value of the protectedAuthorshipCache flag
1029
     * @see     #getAuthorshipCache()
1030
     */
1031
    @Override
1032
    public boolean isProtectedAuthorshipCache() {
1033
        return protectedAuthorshipCache;
1034
    }
1035

    
1036
    /**
1037
     * @see     #isProtectedAuthorshipCache()
1038
     * @see     #getAuthorshipCache()
1039
     */
1040
    @Override
1041
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
1042
        this.protectedAuthorshipCache = protectedAuthorshipCache;
1043
    }
1044

    
1045
    /**
1046
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1047
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
1048
     *
1049
     * @see    #getHybridRelationships()
1050
     * @see    #getChildRelationships()
1051
     * @see    HybridRelationshipType
1052
     */
1053
    @Override
1054
    public Set<HybridRelationship> getHybridParentRelations() {
1055
        if(hybridParentRelations == null) {
1056
            this.hybridParentRelations = new HashSet<>();
1057
        }
1058
        return hybridParentRelations;
1059
    }
1060

    
1061
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1062
        this.hybridParentRelations = hybridParentRelations;
1063
    }
1064

    
1065

    
1066
    /**
1067
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1068
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
1069
     *
1070
     * @see    #getHybridRelationships()
1071
     * @see    #getParentRelationships()
1072
     * @see    HybridRelationshipType
1073
     */
1074
    @Override
1075
    public Set<HybridRelationship> getHybridChildRelations() {
1076
        if(hybridChildRelations == null) {
1077
            this.hybridChildRelations = new HashSet<>();
1078
        }
1079
        return hybridChildRelations;
1080
    }
1081

    
1082
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1083
        this.hybridChildRelations = hybridChildRelations;
1084
    }
1085

    
1086
    @Override
1087
    public boolean isProtectedFullTitleCache() {
1088
        return protectedFullTitleCache;
1089
    }
1090

    
1091
    @Override
1092
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1093
        this.protectedFullTitleCache = protectedFullTitleCache;
1094
    }
1095

    
1096
    /**
1097
     * Returns the boolean value of the flag indicating whether the name of <i>this</i>
1098
     * botanical taxon name is a hybrid formula (true) or not (false). A hybrid
1099
     * named by a hybrid formula (composed with its parent names by placing the
1100
     * multiplication sign between them) does not have an own published name
1101
     * and therefore has neither an {@link NonViralName#getAuthorshipCache() autorship}
1102
     * nor other name components. If this flag is set no other hybrid flags may
1103
     * be set.
1104
     *
1105
     * @return  the boolean value of the isHybridFormula flag
1106
     * @see     #isMonomHybrid()
1107
     * @see     #isBinomHybrid()
1108
     * @see     #isTrinomHybrid()
1109
     */
1110
    @Override
1111
    @Transient
1112
    @java.beans.Transient
1113
    public boolean isHybridFormula(){
1114
        return this.hybridFormula;
1115
    }
1116

    
1117
    /**
1118
     * @see  #isHybridFormula()
1119
     */
1120
    @Override
1121
    public void setHybridFormula(boolean hybridFormula){
1122
        this.hybridFormula = hybridFormula;
1123
    }
1124

    
1125
    /**
1126
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1127
     * taxon name is the name of an intergeneric hybrid (true) or not (false).
1128
     * In this case the multiplication sign is placed before the scientific
1129
     * name. If this flag is set no other hybrid flags may be set.
1130
     *
1131
     * @return  the boolean value of the isMonomHybrid flag
1132
     * @see     #isHybridFormula()
1133
     * @see     #isBinomHybrid()
1134
     * @see     #isTrinomHybrid()
1135
     */
1136
    @Override
1137
    public boolean isMonomHybrid(){
1138
        return this.monomHybrid;
1139
    }
1140

    
1141
    /**
1142
     * @see  #isMonomHybrid()
1143
     * @see  #isBinomHybrid()
1144
     * @see  #isTrinomHybrid()
1145
     */
1146
    @Override
1147
    public void setMonomHybrid(boolean monomHybrid){
1148
        this.monomHybrid = monomHybrid;
1149
    }
1150

    
1151
    /**
1152
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1153
     * taxon name is the name of an interspecific hybrid (true) or not (false).
1154
     * In this case the multiplication sign is placed before the species
1155
     * epithet. If this flag is set no other hybrid flags may be set.
1156
     *
1157
     * @return  the boolean value of the isBinomHybrid flag
1158
     * @see     #isHybridFormula()
1159
     * @see     #isMonomHybrid()
1160
     * @see     #isTrinomHybrid()
1161
     */
1162
    @Override
1163
    public boolean isBinomHybrid(){
1164
        return this.binomHybrid;
1165
    }
1166

    
1167
    /**
1168
     * @see  #isBinomHybrid()
1169
     * @see  #isMonomHybrid()
1170
     * @see  #isTrinomHybrid()
1171
     */
1172
    @Override
1173
    public void setBinomHybrid(boolean binomHybrid){
1174
        this.binomHybrid = binomHybrid;
1175
    }
1176

    
1177
    @Override
1178
    public boolean isTrinomHybrid(){
1179
        return this.trinomHybrid;
1180
    }
1181

    
1182
    /**
1183
     * @see  #isTrinomHybrid()
1184
     * @see  #isBinomHybrid()
1185
     * @see  #isMonomHybrid()
1186
     */
1187
    @Override
1188
    public void setTrinomHybrid(boolean trinomHybrid){
1189
        this.trinomHybrid = trinomHybrid;
1190
    }
1191

    
1192
    // ****************** VIRAL NAME ******************/
1193

    
1194
    @Override
1195
    public String getAcronym(){
1196
        return this.acronym;
1197
    }
1198

    
1199
    /**
1200
     * @see  #getAcronym()
1201
     */
1202
    @Override
1203
    public void setAcronym(String acronym){
1204
        this.acronym = isBlank(acronym)? null : acronym;
1205
    }
1206

    
1207
    // ****************** BACTERIAL NAME ******************/
1208

    
1209
    @Override
1210
    public String getSubGenusAuthorship(){
1211
        return this.subGenusAuthorship;
1212
    }
1213

    
1214
    @Override
1215
    public void setSubGenusAuthorship(String subGenusAuthorship){
1216
        this.subGenusAuthorship = subGenusAuthorship;
1217
    }
1218

    
1219

    
1220
    @Override
1221
    public String getNameApprobation(){
1222
        return this.nameApprobation;
1223
    }
1224

    
1225
    /**
1226
     * @see  #getNameApprobation()
1227
     */
1228
    @Override
1229
    public void setNameApprobation(String nameApprobation){
1230
        this.nameApprobation = nameApprobation;
1231
    }
1232

    
1233
    //************ Zoological Name
1234

    
1235

    
1236
    @Override
1237
    public String getBreed(){
1238
        return this.breed;
1239
    }
1240
    /**
1241
     * @see  #getBreed()
1242
     */
1243
    @Override
1244
    public void setBreed(String breed){
1245
        this.breed = isBlank(breed) ? null : breed;
1246
    }
1247

    
1248

    
1249
    @Override
1250
    public Integer getPublicationYear() {
1251
        return publicationYear;
1252
    }
1253
    /**
1254
     * @see  #getPublicationYear()
1255
     */
1256
    @Override
1257
    public void setPublicationYear(Integer publicationYear) {
1258
        this.publicationYear = publicationYear;
1259
    }
1260

    
1261

    
1262
    @Override
1263
    public Integer getOriginalPublicationYear() {
1264
        return originalPublicationYear;
1265
    }
1266
    /**
1267
     * @see  #getOriginalPublicationYear()
1268
     */
1269
    @Override
1270
    public void setOriginalPublicationYear(Integer originalPublicationYear) {
1271
        this.originalPublicationYear = originalPublicationYear;
1272
    }
1273

    
1274
    // **** Cultivar Name ************
1275

    
1276
    @Override
1277
    public String getCultivarEpithet(){
1278
        return this.cultivarEpithet;
1279
    }
1280

    
1281
    /**
1282
     * @see  #getCultivarEpithet()
1283
     */
1284
    @Override
1285
    public void setCultivarEpithet(String cultivarEpithet){
1286
        this.cultivarEpithet = isBlank(cultivarEpithet) ? null : cultivarEpithet;
1287
    }
1288

    
1289
    @Override
1290
    public String getCultivarGroupEpithet(){
1291
        return this.cultivarGroupEpithet;
1292
    }
1293

    
1294
    /**
1295
     * @see  #getCultivarGroupEpithet()
1296
     */
1297
    @Override
1298
    public void setCultivarGroupEpithet(String cultivarGroupEpithet){
1299
        this.cultivarGroupEpithet = isBlank(cultivarGroupEpithet) ? null : cultivarGroupEpithet;
1300
    }
1301

    
1302
    // **************** Fungus Name
1303
    @Override
1304
    public boolean isAnamorphic(){
1305
        return this.anamorphic;
1306
    }
1307

    
1308
    /**
1309
     * @see  #isAnamorphic()
1310
     */
1311
    @Override
1312
    public void setAnamorphic(boolean anamorphic){
1313
        this.anamorphic = anamorphic;
1314
    }
1315

    
1316

    
1317
// **************** ADDER / REMOVE *************************/
1318

    
1319
    /**
1320
     * Adds the given {@link HybridRelationship hybrid relationship} to the set
1321
     * of {@link #getHybridRelationships() hybrid relationships} of both non-viral names
1322
     * involved in this hybrid relationship. One of both non-viral names
1323
     * must be <i>this</i> non-viral name otherwise no addition will be carried
1324
     * out. The {@link eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedTo() child
1325
     * non viral taxon name} must be a hybrid, which means that one of its four hybrid flags must be set.
1326
     *
1327
     * @param relationship  the hybrid relationship to be added
1328
     * @see                 #isHybridFormula()
1329
     * @see                 #isMonomHybrid()
1330
     * @see                 #isBinomHybrid()
1331
     * @see                 #isTrinomHybrid()
1332
     * @see                 #getHybridRelationships()
1333
     * @see                 #getParentRelationships()
1334
     * @see                 #getChildRelationships()
1335
     * @see                 #addRelationship(RelationshipBase)
1336
     * @throws              IllegalArgumentException
1337
     */
1338
    protected void addHybridRelationship(HybridRelationship rel) {
1339
        if (rel!=null && rel.getHybridName().equals(this)){
1340
            this.hybridChildRelations.add(rel);
1341
        }else if(rel!=null && rel.getParentName().equals(this)){
1342
            this.hybridParentRelations.add(rel);
1343
        }else{
1344
            throw new IllegalArgumentException("Hybrid relationship is either null or the relationship does not reference this name");
1345
        }
1346
    }
1347

    
1348

    
1349
    /**
1350
     * Removes one {@link HybridRelationship hybrid relationship} from the set of
1351
     * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1352
     * is involved. The hybrid relationship will also be removed from the set
1353
     * belonging to the second botanical taxon name involved.
1354
     *
1355
     * @param  relationship  the hybrid relationship which should be deleted from the corresponding sets
1356
     * @see                  #getHybridRelationships()
1357
     */
1358
    @Override
1359
    public void removeHybridRelationship(HybridRelationship hybridRelation) {
1360
        if (hybridRelation == null) {
1361
            return;
1362
        }
1363

    
1364
        TaxonName parent = hybridRelation.getParentName();
1365
        TaxonName child = hybridRelation.getHybridName();
1366
        if (this.equals(parent)){
1367
            this.hybridParentRelations.remove(hybridRelation);
1368
            child.hybridChildRelations.remove(hybridRelation);
1369
            hybridRelation.setHybridName(null);
1370
            hybridRelation.setParentName(null);
1371
        }
1372
        if (this.equals(child)){
1373
            parent.hybridParentRelations.remove(hybridRelation);
1374
            this.hybridChildRelations.remove(hybridRelation);
1375
            hybridRelation.setHybridName(null);
1376
            hybridRelation.setParentName(null);
1377
        }
1378
    }
1379

    
1380
//********* METHODS **************************************/
1381

    
1382
    @Override
1383
    public String generateFullTitle(){
1384
        if (cacheStrategy() == null){
1385
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1386
            return null;
1387
        }else{
1388
            return cacheStrategy().getFullTitleCache(this);
1389
        }
1390
    }
1391

    
1392
    @Override
1393
    public void setFullTitleCache(String fullTitleCache){
1394
        setFullTitleCache(fullTitleCache, PROTECTED);
1395
    }
1396

    
1397
    @Override
1398
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1399
        fullTitleCache = getTruncatedCache(fullTitleCache);
1400
        this.fullTitleCache = fullTitleCache;
1401
        this.setProtectedFullTitleCache(protectCache);
1402
    }
1403

    
1404
   /** Checks if this name is an autonym.<BR>
1405
    * An autonym is a taxon name that has equal specific and infra specific epithets.<BR>
1406
    * {@link http://ibot.sav.sk/icbn/frameset/0010Ch2Sec1a006.htm#6.8. Vienna Code §6.8}
1407
    * or a taxon name that has equal generic and infrageneric epithets (A22.2).<BR>
1408
    * Only relevant for botanical names.
1409
    * @return true, if name has Rank, Rank is below species and species epithet equals infraSpeciesEpithtet, else false
1410
    */
1411
    @Override
1412
    @Transient
1413
    public boolean isAutonym(){
1414
        if (isBotanical()){
1415
            if (this.getRank() != null && this.getSpecificEpithet() != null && this.getInfraSpecificEpithet() != null &&
1416
                this.isInfraSpecific() && this.getSpecificEpithet().trim().equals(this.getInfraSpecificEpithet().trim())){
1417
                return true;
1418
            }else if (this.getRank() != null && this.getGenusOrUninomial() != null && this.getInfraGenericEpithet() != null &&
1419
                    this.isInfraGeneric() && this.getGenusOrUninomial().trim().equals(this.getInfraGenericEpithet().trim())){
1420
                return true;
1421
            }else{
1422
                return false;
1423
            }
1424
        }else{
1425
            return false;
1426
        }
1427
    }
1428

    
1429
    @Override
1430
    @Transient
1431
    public List<TaggedText> getTaggedName(){
1432
        INameCacheStrategy strat = cacheStrategy();
1433
        return strat.getTaggedTitle(this);
1434
    }
1435

    
1436
    @Override
1437
    @Transient
1438
    public List<TaggedText> getTaggedFullTitle() {
1439
        INameCacheStrategy strat = cacheStrategy();
1440
        return strat.getTaggedFullTitle(this);
1441
    }
1442

    
1443
    @Override
1444
    @Transient
1445
    public String getFullTitleCache(){
1446
        if (protectedFullTitleCache){
1447
            return this.fullTitleCache;
1448
        }
1449
        updateAuthorshipCache();
1450
        if (fullTitleCache == null ){
1451
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1452
        }
1453
        return fullTitleCache;
1454
    }
1455

    
1456
    @Override
1457
    public String getTitleCache(){
1458
        if(!protectedTitleCache) {
1459
            updateAuthorshipCache();
1460
        }
1461
        return super.getTitleCache();
1462
    }
1463

    
1464
    @Override
1465
    public void setTitleCache(String titleCache, boolean protectCache){
1466
        super.setTitleCache(titleCache, protectCache);
1467
    }
1468

    
1469
    /**
1470
     * Returns the concatenated and formated authorteams string including
1471
     * basionym and combination authors of <i>this</i> non viral taxon name.
1472
     * If the protectedAuthorshipCache flag is set this method returns the
1473
     * string stored in the the authorshipCache attribute, otherwise it
1474
     * generates the complete authorship string, returns it and stores it in
1475
     * the authorshipCache attribute.
1476
     *
1477
     * @return  the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
1478
     * @see     #generateAuthorship()
1479
     */
1480
    @Override
1481
    @Transient
1482
    public String getAuthorshipCache() {
1483
        if (protectedAuthorshipCache){
1484
            return this.authorshipCache;
1485
        }
1486
        if (this.authorshipCache == null ){
1487
            this.authorshipCache = generateAuthorship();
1488
        }else{
1489
            //TODO get isDirty of authors, make better if possible
1490
            this.setAuthorshipCache(generateAuthorship(), protectedAuthorshipCache); //throw change event to inform higher caches
1491

    
1492
        }
1493
        return authorshipCache;
1494
    }
1495

    
1496
    /**
1497
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1498
     * flag to <code>true</code>.
1499
     *
1500
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1501
     * @see    #getAuthorshipCache()
1502
     */
1503
    @Override
1504
    public void setAuthorshipCache(String authorshipCache) {
1505
        setAuthorshipCache(authorshipCache, true);
1506
    }
1507

    
1508
    /**
1509
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1510
     *
1511
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1512
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1513
     * the flag is set to <code>false</code>.
1514
     * @see    #getAuthorshipCache()
1515
     */
1516
    @Override
1517
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1518
        this.authorshipCache = authorshipCache;
1519
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1520
    }
1521

    
1522
    /**
1523
     * Generates and returns a concatenated and formated author team string
1524
     * including basionym and combination authors of <i>this</i> taxon name
1525
     * according to the strategy defined in
1526
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName) INonViralNameCacheStrategy}.
1527
     *
1528
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1529
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName)
1530
     */
1531
    @Override
1532
    public String generateAuthorship(){
1533
        if (cacheStrategy() == null){
1534
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1535
            return null;
1536
        }else{
1537
            return cacheStrategy().getAuthorshipCache(this);
1538
        }
1539
    }
1540

    
1541
    /**
1542
     * Tests if the given name has any authors.
1543
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1544
     */
1545
    @Override
1546
    public boolean hasAuthors() {
1547
        return (this.getCombinationAuthorship() != null ||
1548
                this.getExCombinationAuthorship() != null ||
1549
                this.getBasionymAuthorship() != null ||
1550
                this.getExBasionymAuthorship() != null);
1551
    }
1552

    
1553
    /**
1554
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1555
     */
1556
    @Override
1557
    public String computeCombinationAuthorNomenclaturalTitle() {
1558
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1559
    }
1560

    
1561
    /**
1562
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1563
     */
1564
    @Override
1565
    public String computeBasionymAuthorNomenclaturalTitle() {
1566
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1567
    }
1568

    
1569

    
1570
    /**
1571
     * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1572
     */
1573
    @Override
1574
    public String computeExCombinationAuthorNomenclaturalTitle() {
1575
        return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1576
    }
1577

    
1578
    /**
1579
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1580
     */
1581
    @Override
1582
    public String computeExBasionymAuthorNomenclaturalTitle() {
1583
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1584
    }
1585

    
1586
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1587
        if (author == null){
1588
            return null;
1589
        }else{
1590
            return author.getNomenclaturalTitleCache();
1591
        }
1592
    }
1593

    
1594
    /**
1595
     * Returns the set of all {@link NameRelationship name relationships}
1596
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1597
     * in some name relationships or target in some others.
1598
     *
1599
     * @see    #getRelationsToThisName()
1600
     * @see    #getRelationsFromThisName()
1601
     * @see    #addNameRelationship(NameRelationship)
1602
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1603
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1604
     */
1605
    @Override
1606
    @Transient
1607
    public Set<NameRelationship> getNameRelations() {
1608
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1609
        rels.addAll(getRelationsFromThisName());
1610
        rels.addAll(getRelationsToThisName());
1611
        return rels;
1612
    }
1613

    
1614
    @Override
1615
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1616
        return addRelationshipToName(toName, type, null, null, ruleConsidered, codeEdition);
1617
    }
1618

    
1619
    @Override
1620
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type){
1621
        return addRelationshipToName(toName, type, null, null, null, null);
1622
    }
1623

    
1624
    @Override
1625
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1626
        if (toName == null){
1627
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1628
        }
1629
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered, codeEdition);
1630
        return rel;
1631
    }
1632

    
1633
    @Override
1634
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type){
1635
        return addRelationshipFromName(fromName, type, null, null, null, null);
1636
    }
1637

    
1638
    @Override
1639
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1640
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1641
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered, codeEdition);
1642
    }
1643

    
1644
    @Override
1645
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1646
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered, codeEdition);
1647
    }
1648

    
1649
    /**
1650
     * Adds an existing {@link NameRelationship name relationship} either to the set of
1651
     * {@link #getRelationsToThisName() relations to <i>this</i> taxon name} or to the set of
1652
     * {@link #getRelationsFromThisName() relations from <i>this</i> taxon name}. If neither the
1653
     * source nor the target of the name relationship match with <i>this</i> taxon name
1654
     * no addition will be carried out.
1655
     *
1656
     * @param rel  the name relationship to be added to one of <i>this</i> taxon name's name relationships sets
1657
     * @see    	   #getNameRelations()
1658
     * @see    	   #addRelationshipToName(TaxonName, NameRelationshipType, String)
1659
     * @see    	   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1660
     */
1661
    protected void addNameRelationship(NameRelationship rel) {
1662
        if (rel != null ){
1663
            if (rel.getToName().equals(this)){
1664
                this.relationsToThisName.add(rel);
1665
            }else if(rel.getFromName().equals(this)){
1666
                this.relationsFromThisName.add(rel);
1667
            }
1668
            NameRelationshipType type = rel.getType();
1669
            if (type != null && ( type.isBasionymRelation() || type.isReplacedSynonymRelation() ) ){
1670
                rel.getFromName().mergeHomotypicGroups(rel.getToName());
1671
            }
1672
        }else{
1673
            throw new RuntimeException("NameRelationship is either null or the relationship does not reference this name");
1674
        }
1675
    }
1676
    /**
1677
     * Removes one {@link NameRelationship name relationship} from one of both sets of
1678
     * {@link #getNameRelations() name relationships} in which <i>this</i> taxon name is involved.
1679
     * The name relationship will also be removed from one of both sets belonging
1680
     * to the second taxon name involved. Furthermore the fromName and toName
1681
     * attributes of the name relationship object will be nullified.
1682
     *
1683
     * @param  nameRelation  the name relationship which should be deleted from one of both sets
1684
     * @see    				 #getNameRelations()
1685
     */
1686
    @Override
1687
    public void removeNameRelationship(NameRelationship nameRelation) {
1688

    
1689
        TaxonName fromName = nameRelation.getFromName();
1690
        TaxonName toName = nameRelation.getToName();
1691

    
1692
        if (nameRelation != null) {
1693
            nameRelation.setToName(null);
1694
            nameRelation.setFromName(null);
1695
        }
1696

    
1697
        if (fromName != null) {
1698
            fromName.removeNameRelationship(nameRelation);
1699
        }
1700

    
1701
        if (toName != null) {
1702
            toName.removeNameRelationship(nameRelation);
1703
        }
1704

    
1705
        this.relationsToThisName.remove(nameRelation);
1706
        this.relationsFromThisName.remove(nameRelation);
1707
    }
1708

    
1709
    @Override
1710
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1711
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1712
//		nameRelationships.addAll(this.getNameRelations());
1713
        nameRelationships.addAll(this.getRelationsFromThisName());
1714
        nameRelationships.addAll(this.getRelationsToThisName());
1715
        for(NameRelationship nameRelationship : nameRelationships) {
1716
            // remove name relationship from this side
1717
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1718
                this.removeNameRelationship(nameRelationship);
1719
            }
1720
        }
1721
    }
1722

    
1723
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1724

    
1725
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1726
        for(NameRelationship nameRelationship : tmpRels) {
1727
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1728
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1729
                if (type == null || type.equals(nameRelationship.getType())){
1730
                    this.removeNameRelationship(nameRelationship);
1731
                }
1732
            }
1733
        }
1734
    }
1735

    
1736
    /**
1737
     * If relation is of type NameRelationship, addNameRelationship is called;
1738
     * if relation is of type HybridRelationship addHybridRelationship is called,
1739
     * otherwise an IllegalArgumentException is thrown.
1740
     *
1741
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1742
     * @see    	   		#addNameRelationship(NameRelationship)
1743
     * @see    	   		#getNameRelations()
1744
     * @see    	   		NameRelationship
1745
     * @see    	   		RelationshipBase
1746
     * @see             #addHybridRelationship(HybridRelationship)
1747

    
1748
     * @deprecated to be used by RelationshipBase only
1749
     */
1750
    @Deprecated
1751
    @Override
1752
    public void addRelationship(RelationshipBase relation) {
1753
        if (relation instanceof NameRelationship){
1754
            addNameRelationship((NameRelationship)relation);
1755

    
1756
        }else if (relation instanceof HybridRelationship){
1757
            addHybridRelationship((HybridRelationship)relation);
1758
        }else{
1759
            logger.warn("Relationship not of type NameRelationship!");
1760
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1761
        }
1762
    }
1763

    
1764
    /**
1765
     * Returns the set of all {@link NameRelationship name relationships}
1766
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1767
     *
1768
     * @see    #getNameRelations()
1769
     * @see    #getRelationsToThisName()
1770
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1771
     */
1772
    @Override
1773
    public Set<NameRelationship> getRelationsFromThisName() {
1774
        if(relationsFromThisName == null) {
1775
            this.relationsFromThisName = new HashSet<>();
1776
        }
1777
        return relationsFromThisName;
1778
    }
1779

    
1780
    /**
1781
     * Returns the set of all {@link NameRelationship name relationships}
1782
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1783
     *
1784
     * @see    #getNameRelations()
1785
     * @see    #getRelationsFromThisName()
1786
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1787
     */
1788
    @Override
1789
    public Set<NameRelationship> getRelationsToThisName() {
1790
        if(relationsToThisName == null) {
1791
            this.relationsToThisName = new HashSet<>();
1792
        }
1793
        return relationsToThisName;
1794
    }
1795

    
1796
    /**
1797
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1798
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1799
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1800
     * and the nomenclatural code rule considered.
1801
     *
1802
     * @see     NomenclaturalStatus
1803
     * @see     NomenclaturalStatusType
1804
     */
1805
    @Override
1806
    public Set<NomenclaturalStatus> getStatus() {
1807
        if(status == null) {
1808
            this.status = new HashSet<>();
1809
        }
1810
        return status;
1811
    }
1812

    
1813
    /**
1814
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1815
     * to <i>this</i> taxon name's set of nomenclatural status.
1816
     *
1817
     * @param  nomStatus  the nomenclatural status to be added
1818
     * @see 			  #getStatus()
1819
     */
1820
    @Override
1821
    public void addStatus(NomenclaturalStatus nomStatus) {
1822
        this.status.add(nomStatus);
1823
        if (!this.equals(nomStatus.getName())){
1824
            nomStatus.setName(this);
1825
        }
1826
    }
1827
    @Override
1828
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1829
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1830
        addStatus(newStatus);
1831
        return newStatus;
1832
    }
1833

    
1834
    /**
1835
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1836
     * Type and ruleConsidered attributes of the nomenclatural status object
1837
     * will be nullified.
1838
     *
1839
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1840
     * @see     		  #getStatus()
1841
     */
1842
    @Override
1843
    public void removeStatus(NomenclaturalStatus nomStatus) {
1844
        //TODO to be implemented?
1845
        logger.warn("not yet fully implemented?");
1846
        this.status.remove(nomStatus);
1847
    }
1848

    
1849
    public void setStatus(Set<NomenclaturalStatus> nomStatus) throws SetterAdapterException {
1850
        new EntityCollectionSetterAdapter<TaxonName, NomenclaturalStatus>(TaxonName.class, NomenclaturalStatus.class, "status", "addStatus", "removeStatus").setCollection(this, nomStatus);
1851
    }
1852

    
1853
    /**
1854
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1855
     * strings or year according to the strategy defined in
1856
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1857
     * The result might be stored in {@link #getNameCache() nameCache} if the
1858
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1859
     *
1860
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1861
     * @see     #getNameCache()
1862
     */
1863
    protected String generateNameCache(){
1864
        if (cacheStrategy() == null){
1865
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1866
            return null;
1867
        }else{
1868
            return cacheStrategy().getNameCache(this);
1869
        }
1870
    }
1871

    
1872
    /**
1873
     * Returns or generates the nameCache (scientific name
1874
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1875
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1876
     * the string will be generated according to a defined strategy,
1877
     * otherwise the value of the actual nameCache string will be returned.
1878
     *
1879
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1880
     * @see     #generateNameCache()
1881
     */
1882
    @Override
1883
    @Transient
1884
    public String getNameCache() {
1885
        if (protectedNameCache){
1886
            return this.nameCache;
1887
        }
1888
        // is title dirty, i.e. equal NULL?
1889
        if (nameCache == null){
1890
            this.nameCache = generateNameCache();
1891
        }
1892
        return nameCache;
1893
    }
1894

    
1895
    /**
1896
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1897
     * Sets the protectedNameCache flag to <code>true</code>.
1898
     *
1899
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1900
     * @see    #getNameCache()
1901
     */
1902
    @Override
1903
    public void setNameCache(String nameCache){
1904
        setNameCache(nameCache, true);
1905
    }
1906

    
1907
    /**
1908
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1909
     * Sets the protectedNameCache flag to <code>true</code>.
1910
     *
1911
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1912
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
1913
     * <code>false</code>
1914
     * @see    #getNameCache()
1915
     */
1916
    @Override
1917
    public void setNameCache(String nameCache, boolean protectedNameCache){
1918
        this.nameCache = nameCache;
1919
        this.setProtectedNameCache(protectedNameCache);
1920
    }
1921

    
1922

    
1923
    /**
1924
     * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
1925
     * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1926
     * of any other taxon name. Returns "true", if a basionym or a replaced
1927
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1928
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1929
     * homotypical group).
1930
     */
1931
    @Override
1932
    @Transient
1933
    public boolean isOriginalCombination(){
1934
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1935
        for (NameRelationship relation : relationsFromThisName) {
1936
            if (relation.getType().isBasionymRelation() ||
1937
                    relation.getType().isReplacedSynonymRelation()) {
1938
                return true;
1939
            }
1940
        }
1941
        return false;
1942
    }
1943

    
1944
    /**
1945
     * Indicates <i>this</i> taxon name is a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1946
     * of any other taxon name. Returns "true", if a replaced
1947
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1948
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1949
     * homotypical group).
1950
     */
1951
    @Override
1952
    @Transient
1953
    public boolean isReplacedSynonym(){
1954
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1955
        for (NameRelationship relation : relationsFromThisName) {
1956
            if (relation.getType().isReplacedSynonymRelation()) {
1957
                return true;
1958
            }
1959
        }
1960
        return false;
1961
    }
1962

    
1963
    /**
1964
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
1965
     * The basionym of a taxon name is its epithet-bringing synonym.
1966
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
1967
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
1968
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
1969
     *
1970
     * If more than one basionym exists one is choosen at radom.
1971
     *
1972
     * If no basionym exists null is returned.
1973
     */
1974
    @Override
1975
    @Transient
1976
    public TaxonName getBasionym(){
1977
        Set<TaxonName> basionyms = getBasionyms();
1978
        if (basionyms.size() == 0){
1979
            return null;
1980
        }else{
1981
            return basionyms.iterator().next();
1982
        }
1983
    }
1984

    
1985
    /**
1986
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
1987
     * The basionym of a taxon name is its epithet-bringing synonym.
1988
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
1989
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
1990
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
1991
     */
1992
    @Override
1993
    @Transient
1994
    public Set<TaxonName> getBasionyms(){
1995
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
1996
    }
1997

    
1998
    /**
1999
     *
2000
     * @param direction
2001
     * @param type
2002
     * @return
2003
     */
2004
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
2005
        return getRelatedNames(relationsWithThisName(direction), type);
2006
    }
2007

    
2008
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2009
        Set<TaxonName> result = new HashSet<>();
2010
        for (NameRelationship rel : rels){
2011
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2012
                TaxonName basionym = rel.getFromName();
2013
                result.add(basionym);
2014
            }
2015
        }
2016
        return result;
2017
    }
2018

    
2019
    @Override
2020
    @Transient
2021
    public TaxonName getOriginalSpelling(){
2022
        if (this.getNomenclaturalSource() != null){
2023
            return this.getNomenclaturalSource().getNameUsedInSource();
2024
        }else{
2025
            return null;
2026
        }
2027
    }
2028

    
2029
    @Override
2030
    @Transient
2031
    public void setOriginalSpelling(TaxonName originalSpelling){
2032
        if (originalSpelling != null){
2033
            this.getNomenclaturalSource(true).setNameUsedInSource(originalSpelling);
2034
        }else if (this.getNomenclaturalSource() != null){
2035
            this.getNomenclaturalSource().setNameUsedInSource(null);
2036
            checkNullSource();
2037
        }
2038
    }
2039

    
2040
    @Override
2041
    @Transient
2042
    public void setOriginalInfo(String originalInfo){
2043
        if (isNotBlank(originalInfo)){
2044
            this.getNomenclaturalSource(true).setOriginalInfo(originalInfo);
2045
        }else if (this.getNomenclaturalSource() != null){
2046
            this.getNomenclaturalSource().setOriginalInfo(null);
2047
            checkNullSource();
2048
        }
2049
    }
2050

    
2051
    /**
2052
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2053
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2054
     * and to the basionym. The basionym cannot have itself as a basionym.
2055
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2056
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2057
     *
2058
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2059
     * @see  				#getBasionym()
2060
     * @see  				#addBasionym(TaxonName, String)
2061
     */
2062
    @Override
2063
    public NameRelationship addBasionym(TaxonName basionym){
2064
        return addBasionym(basionym, null, null, null, null);
2065
    }
2066
    /**
2067
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2068
     * and keeps the nomenclatural rule considered for it. The basionym
2069
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2070
     * The basionym cannot have itself as a basionym.
2071
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2072
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2073
     *
2074
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2075
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2076
     * @param codeEdition     the edition of the nomenclatural code where the <code>ruleConsidered</code> has been published.
2077
     * @return
2078
     * @see  					#getBasionym()
2079
     * @see  					#addBasionym(TaxonName)
2080
     */
2081
    @Override
2082
    public NameRelationship addBasionym(TaxonName basionym, Reference citation, String microcitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
2083
        if (basionym != null){
2084
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered, codeEdition);
2085
        }else{
2086
            return null;
2087
        }
2088
    }
2089

    
2090
    /**
2091
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2092
     */
2093
    @Override
2094
    @Transient
2095
    public Set<TaxonName> getReplacedSynonyms(){
2096
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2097
    }
2098

    
2099
    /**
2100
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2101
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2102
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2103
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2104
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2105
     *
2106
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2107
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2108
     * @param codeEdition     the edition of the nomenclatural code where the <code>ruleConsidered</code> has been published.
2109
     * @see  					#getBasionym()
2110
     * @see  					#addBasionym(TaxonName)
2111
     */
2112
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2113
    @Override
2114
    public void addReplacedSynonym(TaxonName replacedSynonym, Reference citation, String microcitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
2115
        if (replacedSynonym != null){
2116
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered, codeEdition);
2117
        }
2118
    }
2119

    
2120
    /**
2121
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2122
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2123
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2124
     * previously used as basionym.
2125
     *
2126
     * @see   #getBasionym()
2127
     * @see   #addBasionym(TaxonName)
2128
     */
2129
    @Override
2130
    public void removeBasionyms(){
2131
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2132
    }
2133

    
2134

    
2135
    /**
2136
     * Removes all {@link NameRelationship relationships} of the given <code>type</code> from the set of
2137
     * relations in the specified <code>direction</code> direction wich are related from or to this
2138
     * <i>this</i> taxon name. The same relationship will be removed from the set of
2139
     * reverse relations of the other taxon name.
2140
     *
2141
     * @param direction
2142
     * @param type
2143
     */
2144
    public void removeNameRelations(Direction direction, NameRelationshipType type) {
2145
        Set<NameRelationship> relationsWithThisName = relationsWithThisName(direction);
2146
        Set<NameRelationship> removeRelations = new HashSet<>();
2147
        for (NameRelationship nameRelation : relationsWithThisName){
2148
            if (nameRelation.getType().isRelationshipType(type)){
2149
                removeRelations.add(nameRelation);
2150
            }
2151
        }
2152
        // Removing relations from a set through which we are iterating causes a
2153
        // ConcurrentModificationException. Therefore, we delete the targeted
2154
        // relations in a second step.
2155
        for (NameRelationship relation : removeRelations){
2156
            this.removeNameRelationship(relation);
2157
        }
2158
    }
2159

    
2160
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2161
        switch(direction) {
2162
            case relatedTo:
2163
                return this.getRelationsToThisName();
2164
            case relatedFrom:
2165
                return this.getRelationsFromThisName();
2166
            default: throw new RuntimeException("invalid Direction:" + direction);
2167
        }
2168
    }
2169

    
2170
    /**
2171
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2172
     *
2173
     * @see 	Rank
2174
     */
2175
    @Override
2176
    public Rank getRank(){
2177
        return this.rank;
2178
    }
2179
    /**
2180
     * @see  #getRank()
2181
     */
2182
    @Override
2183
    public void setRank(Rank rank){
2184
        this.rank = rank;
2185
    }
2186

    
2187
//*************** nom ref/source *******************/
2188

    
2189
    @Override
2190
    public NomenclaturalSource getNomenclaturalSource(){
2191
        return this.nomenclaturalSource;
2192
    }
2193

    
2194
    public NomenclaturalSource getNomenclaturalSource(boolean createIfNotExist){
2195
        if (this.nomenclaturalSource == null && createIfNotExist){
2196
            setNomenclaturalSource(NomenclaturalSource.NewNomenclaturalInstance(this));
2197
        }
2198
        return nomenclaturalSource;
2199
    }
2200

    
2201
    @Override
2202
    public void setNomenclaturalSource(NomenclaturalSource nomenclaturalSource) throws IllegalArgumentException {
2203
        //check state
2204
        if (nomenclaturalSource != null && !OriginalSourceType.PrimaryTaxonomicSource.equals(nomenclaturalSource.getType())
2205
                ){
2206
            throw new IllegalArgumentException("Nomenclatural source must be of type " + OriginalSourceType.PrimaryTaxonomicSource.getLabel());
2207
        }
2208
        this.nomenclaturalSource = nomenclaturalSource;
2209
        if (nomenclaturalSource != null && nomenclaturalSource.getSourcedName() != this){
2210
            nomenclaturalSource.setSourcedName(this);
2211
        }
2212
    }
2213

    
2214
    @Override
2215
    @Transient
2216
    public Reference getNomenclaturalReference(){
2217
        return this.nomenclaturalSource == null? null:this.nomenclaturalSource.getCitation();
2218
    }
2219

    
2220
    @Override
2221
    @Transient
2222
    public void setNomenclaturalReference(Reference nomenclaturalReference){
2223
        if (nomenclaturalReference == null && this.getNomenclaturalSource()==null){
2224
            return;
2225
        }else{
2226
            getNomenclaturalSource(true).setCitation(nomenclaturalReference);
2227
        }
2228
    }
2229

    
2230
    @Override
2231
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2232
        setNomenclaturalReference(CdmBase.deproxy(nomenclaturalReference, Reference.class));
2233
    }
2234

    
2235
    /**
2236
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2237
     * to <i>this</i> taxon name. The details describe the exact localisation within
2238
     * the publication used as nomenclature reference. These are mostly
2239
     * (implicitly) pages but can also be figures or tables or any other
2240
     * element of a publication. A nomenclatural micro reference (details)
2241
     * requires the existence of a nomenclatural reference.
2242
     */
2243
    //Details of the nomenclatural reference (protologue).
2244
    @Override
2245
    @Transient
2246
    public String getNomenclaturalMicroReference(){
2247
        return this.nomenclaturalSource == null? null: this.nomenclaturalSource.getCitationMicroReference();
2248
    }
2249

    
2250
    /**
2251
     * @see  #getNomenclaturalMicroReference()
2252
     */
2253
    @Override
2254
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2255
        nomenclaturalMicroReference = isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
2256
        if (nomenclaturalMicroReference == null && this.getNomenclaturalSource()==null){
2257
            return;
2258
        }else{
2259
            this.getNomenclaturalSource(true).setCitationMicroReference(nomenclaturalMicroReference);
2260
        }
2261
    }
2262

    
2263
    /**
2264
     * Checks if the source is completely empty and if empty removes it from the name.
2265
     */
2266
    //TODO maybe this should be moved to a hibernate listener, but the listener solution may
2267
    //work only for nomenclatural single sources as they are the only which are bidirectional
2268
    protected void checkNullSource() {
2269
        if (this.nomenclaturalSource != null && this.nomenclaturalSource.checkEmpty(true)){
2270
            this.nomenclaturalSource = null;
2271
        }
2272
    }
2273

    
2274
// *********************************************
2275

    
2276
    /**
2277
     * Creates a new external link for given uri with default language description if description exists
2278
     * and adds it to the nomenclatural source.
2279
     * If no nomenclatural source exists a new one is created.
2280
     * @param uri the url to the protogue
2281
     * @param description an additional description for the link for the default language
2282
     * @param type see {@link ExternalLinkType}
2283
     * @return the newly created {@link ExternalLink external link}
2284
     */
2285
    public ExternalLink addProtologue(URI uri, String description, ExternalLinkType type){
2286
        ExternalLink newProtologue = ExternalLink.NewInstance(type, uri, description, null);
2287
        getNomenclaturalSource(true).addLink(newProtologue);
2288
        return newProtologue;
2289
    }
2290

    
2291
    /**
2292
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2293
     * The appended phrase is a non-atomised addition to a name. It is
2294
     * not ruled by a nomenclatural code.
2295
     */
2296
    @Override
2297
    public String getAppendedPhrase(){
2298
        return this.appendedPhrase;
2299
    }
2300

    
2301
    /**
2302
     * @see  #getAppendedPhrase()
2303
     */
2304
    @Override
2305
    public void setAppendedPhrase(String appendedPhrase){
2306
        this.appendedPhrase = isBlank(appendedPhrase)? null : appendedPhrase;
2307
    }
2308

    
2309
    @Override
2310
    public int getParsingProblem(){
2311
        return this.parsingProblem;
2312
    }
2313

    
2314
    @Override
2315
    public void setParsingProblem(int parsingProblem){
2316
        this.parsingProblem = parsingProblem;
2317
    }
2318

    
2319
    @Override
2320
    public void addParsingProblem(ParserProblem problem){
2321
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2322
    }
2323

    
2324
    @Override
2325
    public void removeParsingProblem(ParserProblem problem) {
2326
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2327
    }
2328

    
2329
    /**
2330
     * @param warnings
2331
     */
2332
    @Override
2333
    public void addParsingProblems(int problems){
2334
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2335
    }
2336

    
2337
    @Override
2338
    public boolean hasProblem(){
2339
        return parsingProblem != 0;
2340
    }
2341

    
2342
    @Override
2343
    public boolean hasProblem(ParserProblem problem) {
2344
        return getParsingProblems().contains(problem);
2345
    }
2346

    
2347
    @Override
2348
    public int getProblemStarts(){
2349
        return this.problemStarts;
2350
    }
2351

    
2352
    @Override
2353
    public void setProblemStarts(int start) {
2354
        this.problemStarts = start;
2355
    }
2356

    
2357
    @Override
2358
    public int getProblemEnds(){
2359
        return this.problemEnds;
2360
    }
2361

    
2362
    @Override
2363
    public void setProblemEnds(int end) {
2364
        this.problemEnds = end;
2365
    }
2366

    
2367
//*********************** TYPE DESIGNATION *********************************************//
2368

    
2369
    /**
2370
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2371
     * to <i>this</i> taxon name.
2372
     * @see     NameTypeDesignation
2373
     * @see     SpecimenTypeDesignation
2374
     */
2375
    @Override
2376
    public Set<TypeDesignationBase> getTypeDesignations() {
2377
        if(typeDesignations == null) {
2378
            this.typeDesignations = new HashSet<>();
2379
        }
2380
        return typeDesignations;
2381
    }
2382

    
2383
    /**
2384
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2385
     * <i>this</i> taxon name. The type designation itself will be nullified.
2386
     *
2387
     * @param  typeDesignation  the type designation which should be deleted
2388
     */
2389
    @Override
2390
    @SuppressWarnings("deprecation")
2391
    public void removeTypeDesignation(TypeDesignationBase<?> typeDesignation) {
2392
        this.typeDesignations.remove(typeDesignation);
2393
        typeDesignation.removeTypifiedName(this);
2394
    }
2395

    
2396
    /**
2397
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2398
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2399
     * "species" or below. The specimen type designations include all the
2400
     * specimens on which the typification of this name is based (which are
2401
     * exclusively used to typify taxon names belonging to the same
2402
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2403
     * belongs) and eventually the status of these designations.
2404
     *
2405
     * @see     SpecimenTypeDesignation
2406
     * @see     NameTypeDesignation
2407
     * @see     HomotypicalGroup
2408
     */
2409
    @Override
2410
    @Transient
2411
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2412
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2413
    }
2414

    
2415
//*********************** NAME TYPE DESIGNATION *********************************************//
2416

    
2417
    /**
2418
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2419
     * to <i>this</i> taxon name the rank of which must be above "species".
2420
     * The name type designations include all the taxon names used to typify
2421
     * <i>this</i> taxon name and eventually the rejected or conserved status
2422
     * of these designations.
2423
     *
2424
     * @see     NameTypeDesignation
2425
     * @see     SpecimenTypeDesignation
2426
     */
2427
    @Override
2428
    @Transient
2429
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2430
        Set<NameTypeDesignation> result = new HashSet<>();
2431
        for (TypeDesignationBase<?> typeDesignation : this.typeDesignations){
2432
            if (typeDesignation.isInstanceOf(NameTypeDesignation.class)){
2433
                result.add(CdmBase.deproxy(typeDesignation, NameTypeDesignation.class));
2434
            }
2435
        }
2436
        return result;
2437
    }
2438

    
2439
    /**
2440
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2441
     * to <i>this</i> taxon name's set of type designations.
2442
     *
2443
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2444
     * @param  citation					the reference for this new designation
2445
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2446
     * @param  originalInfo     		any information from the original source, might be the name as written in the source (#10097)
2447
     * @param  isRejectedType			the boolean status for a rejected name type designation
2448
     * @param  isConservedType			the boolean status for a conserved name type designation
2449
     * @param  isLectoType				the boolean status for a lectotype name type designation
2450
     * @param  isNotDesignated			the boolean status for a name type designation without name type
2451
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2452
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2453
     * @return
2454
     * @see 			  				#getNameTypeDesignations()
2455
     * @see 			  				NameTypeDesignation
2456
     * @see 			  				TypeDesignationBase#isNotDesignated()
2457
     */
2458
    @Override
2459
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2460
                Reference citation,
2461
                String citationMicroReference,
2462
                String originalName,
2463
                NameTypeDesignationStatus status,
2464
                boolean isRejectedType,
2465
                boolean isConservedType,
2466
                /*boolean isLectoType, */
2467
                boolean isNotDesignated,
2468
                boolean addToAllHomotypicNames) {
2469
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalName, status, isRejectedType, isConservedType, isNotDesignated);
2470
        //nameTypeDesignation.setLectoType(isLectoType);
2471
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2472
        return nameTypeDesignation;
2473
    }
2474

    
2475
    /**
2476
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2477
     * to <i>this</i> taxon name's set of type designations.
2478
     *
2479
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2480
     * @param  citation					the reference for this new designation
2481
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2482
     * @param  originalInfo     		any information from the original source, might be the name as written in the source (#10097)
2483
     * @param  status                   the name type designation status
2484
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2485
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2486
     * @return
2487
     * @see 			  				#getNameTypeDesignations()
2488
     * @see 			  				NameTypeDesignation
2489
     * @see 			  				TypeDesignationBase#isNotDesignated()
2490
     */
2491
    @Override
2492
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2493
                Reference citation,
2494
                String citationMicroReference,
2495
                String originalInfo,
2496
                NameTypeDesignationStatus status,
2497
                boolean addToAllHomotypicNames) {
2498
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalInfo);
2499
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2500
        return nameTypeDesignation;
2501
    }
2502

    
2503
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2504

    
2505
    /**
2506
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2507
     * that typify <i>this</i> taxon name.
2508
     */
2509
    @Override
2510
    @Transient
2511
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2512
        Set<SpecimenTypeDesignation> result = new HashSet<>();
2513
        for (TypeDesignationBase<?> typeDesignation : this.typeDesignations){
2514
            if (typeDesignation.isInstanceOf(SpecimenTypeDesignation.class)){
2515
                result.add(CdmBase.deproxy(typeDesignation, SpecimenTypeDesignation.class));
2516
            }
2517
        }
2518
        return result;
2519
    }
2520

    
2521

    
2522
    /**
2523
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2524
     * to <i>this</i> taxon name's set of type designations.
2525
     *
2526
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2527
     * @param  status					the specimen type designation status
2528
     * @param  citation					the reference for this new specimen type designation
2529
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2530
     * @param  originalInfo     		any information from the original source, might be the name as written in the source (#10097)
2531
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2532
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2533
     * 									added to all taxon names of the homotypical group the typified
2534
     * 									taxon name belongs to
2535
     * @return
2536
     * @see 			  				#getSpecimenTypeDesignations()
2537
     * @see 			  				SpecimenTypeDesignationStatus
2538
     * @see 			  				SpecimenTypeDesignation
2539
     * @see 			  				TypeDesignationBase#isNotDesignated()
2540
     */
2541
    @Override
2542
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2543
                SpecimenTypeDesignationStatus status,
2544
                Reference citation,
2545
                String citationMicroReference,
2546
                String originalInfo,
2547
                boolean isNotDesignated,
2548
                boolean addToAllHomotypicNames) {
2549
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalInfo, isNotDesignated);
2550
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2551
        return specimenTypeDesignation;
2552
    }
2553

    
2554
    @Override
2555
    public TextualTypeDesignation addTextualTypeDesignation(
2556
                String text,
2557
                Language language,
2558
                boolean isVerbatim,
2559
                Reference citation,
2560
                String citationMicroReference,
2561
                String originalInfo,
2562
                boolean addToAllHomotypicNames) {
2563
        TextualTypeDesignation textualTypeDesignation = TextualTypeDesignation.NewInstance(text, language, isVerbatim, citation, citationMicroReference, originalInfo);
2564
        addTypeDesignation(textualTypeDesignation, addToAllHomotypicNames);
2565
        return textualTypeDesignation;
2566
    }
2567

    
2568
    //used by merge strategy
2569
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2570
        return addTypeDesignation(typeDesignation, true);
2571
    }
2572

    
2573
    /**
2574
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2575
     *
2576
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2577
     * @param addToAllNames				the boolean indicating whether the type designation should be
2578
     * 									added to all taxon names of the homotypical group the typified
2579
     * 									taxon name belongs to
2580
     * @return							true if the operation was successful
2581
     *
2582
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2583
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2584
     *
2585
     */
2586
    @Override
2587
    public boolean addTypeDesignation(TypeDesignationBase<?> typeDesignation, boolean addToAllNames){
2588
        //currently typeDesignations are not persisted with the homotypical group
2589
        //so explicit adding to the homotypical group is not necessary.
2590
        if (typeDesignation != null){
2591
            checkHomotypicalGroup(typeDesignation);
2592
            this.typeDesignations.add(typeDesignation);
2593
            typeDesignation.addTypifiedName(this);
2594

    
2595
            if (addToAllNames){
2596
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2597
                    if (taxonName != this){
2598
                        taxonName.addTypeDesignation(typeDesignation, false);
2599
                    }
2600
                }
2601
            }
2602
        }
2603
        return true;
2604
    }
2605

    
2606
    /**
2607
     * Throws an Exception this type designation already has typified names from another homotypical group.
2608
     * @param typeDesignation
2609
     */
2610
    private void checkHomotypicalGroup(TypeDesignationBase<?> typeDesignation) {
2611
        if(typeDesignation.getTypifiedNames().size() > 0){
2612
            Set<HomotypicalGroup> groups = new HashSet<>();
2613
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2614
            for (TaxonName taxonName: names){
2615
                groups.add(taxonName.getHomotypicalGroup());
2616
            }
2617
            if (groups.size() > 1){
2618
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2619
            }
2620
        }
2621
    }
2622

    
2623

    
2624

    
2625
//*********************** HOMOTYPICAL GROUP *********************************************//
2626

    
2627

    
2628
    /**
2629
     * Returns the {@link HomotypicalGroup homotypical group} to which
2630
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2631
     * that share the same types.
2632
     *
2633
     * @see 	HomotypicalGroup
2634
     */
2635

    
2636
    @Override
2637
    public HomotypicalGroup getHomotypicalGroup() {
2638
        if (homotypicalGroup == null){
2639
            homotypicalGroup = new HomotypicalGroup();
2640
            homotypicalGroup.typifiedNames.add(this);
2641
        }
2642
    	return homotypicalGroup;
2643
    }
2644

    
2645
    /**
2646
     * @see #getHomotypicalGroup()
2647
     */
2648
    @Override
2649
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2650
        if (homotypicalGroup == null){
2651
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2652
        }
2653
        /*if (this.homotypicalGroup != null){
2654
        	this.homotypicalGroup.removeTypifiedName(this, false);
2655
        }*/
2656
        this.homotypicalGroup = homotypicalGroup;
2657
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2658
        	 this.homotypicalGroup.addTypifiedName(this);
2659
        }
2660
    }
2661

    
2662

    
2663

    
2664
// *************************************************************************//
2665

    
2666
    /**
2667
     * Returns the complete string containing the
2668
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2669
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2670
     *
2671
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2672
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2673
     * @see		#getNomenclaturalReference()
2674
     * @see		#getNomenclaturalMicroReference()
2675
     */
2676
    @Override
2677
    @Transient
2678
    public String getCitationString(){
2679
        NomenclaturalSource nomSource = getNomenclaturalSource();
2680
        return NomenclaturalSourceFormatter.INSTANCE().format(nomSource);
2681
    }
2682

    
2683
    /**
2684
     * Returns the parsing problems
2685
     * @return
2686
     */
2687
    @Override
2688
    public List<ParserProblem> getParsingProblems(){
2689
        return ParserProblem.warningList(this.parsingProblem);
2690
    }
2691

    
2692
    /**
2693
     * Returns the string containing the publication date (generally only year)
2694
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2695
     * no nomenclatural reference.
2696
     *
2697
     * @return  the string containing the publication date of <i>this</i> taxon name
2698
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2699
     */
2700
    @Override
2701
    @Transient
2702
    @ValidTaxonomicYear(groups=Level3.class)
2703
    public String getReferenceYear(){
2704
        if (this.getNomenclaturalReference() != null ){
2705
            return this.getNomenclaturalReference().getYear();
2706
        }else{
2707
            return null;
2708
        }
2709
    }
2710

    
2711
    /**
2712
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2713
     * In this context a taxon base means the use of a taxon name by a reference
2714
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2715
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2716
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2717
     * within a taxonomic treatment (identified by one reference).
2718
     *
2719
     * @see	#getTaxa()
2720
     * @see	#getSynonyms()
2721
     */
2722
    @Override
2723
    public Set<TaxonBase> getTaxonBases() {
2724
        if(taxonBases == null) {
2725
            this.taxonBases = new HashSet<>();
2726
        }
2727
        return this.taxonBases;
2728
    }
2729

    
2730
    /**
2731
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2732
     * to the set of taxon bases using <i>this</i> taxon name.
2733
     *
2734
     * @param  taxonBase  the taxon base to be added
2735
     * @see 			  #getTaxonBases()
2736
     * @see 			  #removeTaxonBase(TaxonBase)
2737
     */
2738
    //TODO protected
2739
    @Override
2740
    public void addTaxonBase(TaxonBase taxonBase){
2741
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2742
        ReflectionUtils.makeAccessible(method);
2743
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2744
        taxonBases.add(taxonBase);
2745
    }
2746
    /**
2747
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2748
     *
2749
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2750
     * @see    				#getTaxonBases()
2751
     * @see    				#addTaxonBase(TaxonBase)
2752
     */
2753
    @Override
2754
    public void removeTaxonBase(TaxonBase taxonBase){
2755
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2756
        ReflectionUtils.makeAccessible(method);
2757
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2758

    
2759

    
2760
    }
2761

    
2762
    /**
2763
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2764
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2765
     * the set returned by getTaxonBases().
2766
     *
2767
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2768
     * @see	#getTaxonBases()
2769
     * @see	#getSynonyms()
2770
     */
2771
    @Override
2772
    @Transient
2773
    public Set<Taxon> getTaxa(){
2774
        Set<Taxon> result = new HashSet<>();
2775
        for (TaxonBase<?> taxonBase : this.taxonBases){
2776
            if (taxonBase instanceof Taxon){
2777
                result.add((Taxon)taxonBase);
2778
            }
2779
        }
2780
        return result;
2781
    }
2782

    
2783
    /**
2784
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2785
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2786
     * the set returned by getTaxonBases().
2787
     *
2788
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2789
     * @see	#getTaxonBases()
2790
     * @see	#getTaxa()
2791
     */
2792
    @Override
2793
    @Transient
2794
    public Set<Synonym> getSynonyms() {
2795
        Set<Synonym> result = new HashSet<>();
2796
        for (TaxonBase<?> taxonBase : this.taxonBases){
2797
            if (taxonBase instanceof Synonym){
2798
                result.add((Synonym)taxonBase);
2799
            }
2800
        }
2801
        return result;
2802
    }
2803

    
2804
//***************** REGISTRATION *****************/
2805

    
2806
    @Override
2807
    public Set<Registration> getRegistrations() {
2808
        return this.registrations;
2809
    }
2810

    
2811

    
2812
// ************* RELATIONSHIPS *****************************/
2813

    
2814
    /**
2815
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2816
     * by title cache of the related names.
2817
     * @see #getHybridParentRelations()
2818
     */
2819
    @Override
2820
    @Transient
2821
    public List<HybridRelationship> getOrderedChildRelationships(){
2822
        List<HybridRelationship> result = new ArrayList<>();
2823
        result.addAll(this.hybridChildRelations);
2824
        Collections.sort(result);
2825
        Collections.reverse(result);
2826
        return result;
2827
    }
2828

    
2829
    @Override
2830
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2831
        return addHybridParent(parentName, type, null, null, ruleConsidered, null);
2832
    }
2833

    
2834
    @Override
2835
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, Reference reference,
2836
            String microReference, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
2837
        return new HybridRelationship(this, parentName, type, reference, microReference, ruleConsidered, codeEdition);
2838
    }
2839

    
2840
    /**
2841
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2842
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2843
     * "is first/second parent" or "is male/female parent". By invoking this
2844
     * method <i>this</i> botanical name becomes a parent of the hybrid child
2845
     * botanical name.
2846
     *
2847
     * @param childName       the botanical name of the child for this new hybrid name relationship
2848
     * @param type            the type of this new name relationship
2849
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2850
     * @return
2851
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
2852
     * @see                   #getRelationsToThisName()
2853
     * @see                   #getNameRelations()
2854
     * @see                   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
2855
     * @see                   #addNameRelationship(NameRelationship)
2856
     */
2857
    @Override
2858
    public HybridRelationship addHybridChild(INonViralName childName, HybridRelationshipType type, String ruleConsidered){
2859
        return new HybridRelationship(childName, this, type, ruleConsidered);
2860
    }
2861

    
2862
    @Override
2863
    public void removeHybridChild(INonViralName child) {
2864
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2865
        hybridRelationships.addAll(this.getHybridChildRelations());
2866
        hybridRelationships.addAll(this.getHybridParentRelations());
2867
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2868
            // remove name relationship from this side
2869
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2870
                this.removeHybridRelationship(hybridRelationship);
2871
            }
2872
        }
2873
    }
2874

    
2875
    @Override
2876
    public void removeHybridParent(INonViralName parent) {
2877
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2878
        hybridRelationships.addAll(this.getHybridChildRelations());
2879
        hybridRelationships.addAll(this.getHybridParentRelations());
2880
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2881
            // remove name relationship from this side
2882
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2883
                this.removeHybridRelationship(hybridRelationship);
2884
            }
2885
        }
2886
    }
2887

    
2888

    
2889

    
2890
// *********** DESCRIPTIONS *************************************
2891

    
2892
    /**
2893
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2894
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2895
     * concerning the taxon name like for instance the content of its first
2896
     * publication (protolog) or a picture of this publication.
2897
     *
2898
     * @see	#addDescription(TaxonNameDescription)
2899
     * @see	#removeDescription(TaxonNameDescription)
2900
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2901
     */
2902
    @Override
2903
    public Set<TaxonNameDescription> getDescriptions() {
2904
        return descriptions;
2905
    }
2906

    
2907
    /**
2908
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2909
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2910
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2911
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2912
     *
2913
     * @param  description  the taxon name description to be added
2914
     * @see					#getDescriptions()
2915
     * @see 			  	#removeDescription(TaxonNameDescription)
2916
     */
2917
    @Override
2918
    public void addDescription(TaxonNameDescription description) {
2919
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2920
        ReflectionUtils.makeAccessible(field);
2921
        ReflectionUtils.setField(field, description, this);
2922
        descriptions.add(description);
2923
    }
2924
    /**
2925
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2926
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2927
     * of the description itself will be set to "null".
2928
     *
2929
     * @param  description  the taxon name description which should be removed
2930
     * @see     		  	#getDescriptions()
2931
     * @see     		  	#addDescription(TaxonNameDescription)
2932
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
2933
     */
2934
    @Override
2935
    public void removeDescription(TaxonNameDescription description) {
2936
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2937
        ReflectionUtils.makeAccessible(field);
2938
        ReflectionUtils.setField(field, description, null);
2939
        descriptions.remove(description);
2940
    }
2941

    
2942
// *********** HOMOTYPIC GROUP METHODS **************************************************
2943

    
2944
    @Override
2945
    @Transient
2946
    public void mergeHomotypicGroups(TaxonName name){
2947
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2948
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2949
        name.setHomotypicalGroup(this.homotypicalGroup);
2950
    }
2951

    
2952
    /**
2953
     * Returns the boolean value indicating whether a given taxon name belongs
2954
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
2955
     * or not (false). Returns "true" only if the homotypical groups of both
2956
     * taxon names exist and if they are identical.
2957
     *
2958
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
2959
     * @return  			   the boolean value of the check
2960
     * @see     			   HomotypicalGroup
2961
     */
2962
    @Override
2963
    @Transient
2964
    public boolean isHomotypic(TaxonName homoTypicName) {
2965
        if (homoTypicName == null) {
2966
            return false;
2967
        }
2968
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
2969
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
2970
            return false;
2971
        }
2972
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
2973
            return true;
2974
        }
2975
        return false;
2976
    }
2977

    
2978

    
2979
    /**
2980
     * Checks whether name is a basionym for ALL names
2981
     * in its homotypical group.
2982
     * Returns <code>false</code> if there are no other names in the group
2983
     * @param name
2984
     * @return
2985
     */
2986
    @Override
2987
    @Transient
2988
    public boolean isGroupsBasionym() {
2989
    	if (homotypicalGroup == null){
2990
    		homotypicalGroup = HomotypicalGroup.NewInstance();
2991
    		homotypicalGroup.addTypifiedName(this);
2992
    	}
2993
        Set<TaxonName> typifiedNames = homotypicalGroup.getTypifiedNames();
2994

    
2995
        // Check whether there are any other names in the group
2996
        if (typifiedNames.size() == 1) {
2997
                return false;
2998
        }
2999

    
3000
        for (TaxonName taxonName : typifiedNames) {
3001
                if (!taxonName.equals(this)) {
3002
                        if (! isBasionymFor(taxonName)) {
3003
                                return false;
3004
                        }
3005
                }
3006
        }
3007
        return true;
3008
    }
3009

    
3010
    /**
3011
     * Checks whether a basionym relationship exists between fromName and toName.
3012
     *
3013
     * @param fromName
3014
     * @param toName
3015
     * @return
3016
     */
3017
    @Override
3018
    @Transient
3019
    public boolean isBasionymFor(TaxonName newCombinationName) {
3020
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3021
            for (NameRelationship relation : relations) {
3022
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3023
                                    relation.getFromName().equals(this)) {
3024
                            return true;
3025
                    }
3026
            }
3027
            return false;
3028
    }
3029

    
3030
    /**
3031
     * Creates a basionym relationship to all other names in this names homotypical
3032
     * group.
3033
     *
3034
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3035
     */
3036
    @Override
3037
    @Transient
3038
    public void makeGroupsBasionym() {
3039
        this.homotypicalGroup.setGroupBasionym(this);
3040
    }
3041

    
3042

    
3043
//*********  Rank comparison shortcuts   ********************//
3044
    /**
3045
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3046
     * taxon name is higher than the genus rank (true) or not (false).
3047
     * Suprageneric non viral names are monomials.
3048
     * Returns false if rank is null.
3049
     *
3050
     * @see  #isGenus()
3051
     * @see  #isInfraGeneric()
3052
     * @see  #isSpecies()
3053
     * @see  #isInfraSpecific()
3054
     */
3055
    @Override
3056
    @Transient
3057
    public boolean isSupraGeneric() {
3058
        if (rank == null){
3059
            return false;
3060
        }
3061
        return getRank().isSupraGeneric();
3062
    }
3063
    /**
3064
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3065
     * taxon name is the genus rank (true) or not (false). Non viral names with
3066
     * genus rank are monomials. Returns false if rank is null.
3067
     *
3068
     * @see  #isSupraGeneric()
3069
     * @see  #isInfraGeneric()
3070
     * @see  #isSpecies()
3071
     * @see  #isInfraSpecific()
3072
     */
3073
    @Override
3074
    @Transient
3075
    public boolean isGenus() {
3076
        if (rank == null){
3077
            return false;
3078
        }
3079
        return getRank().isGenus();
3080
    }
3081

    
3082
    @Override
3083
    @Transient
3084
    public boolean isGenusOrSupraGeneric() {
3085
        return isGenus()|| isSupraGeneric();
3086
    }
3087
    /**
3088
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3089
     * taxon name is higher than the species rank and lower than the
3090
     * genus rank (true) or not (false). Infrageneric non viral names are
3091
     * binomials. Returns false if rank is null.
3092
     *
3093
     * @see  #isSupraGeneric()
3094
     * @see  #isGenus()
3095
     * @see  #isSpecies()
3096
     * @see  #isInfraSpecific()
3097
     */
3098
    @Override
3099
    @Transient
3100
    public boolean isInfraGeneric() {
3101
        if (rank == null){
3102
            return false;
3103
        }
3104
        return getRank().isInfraGeneric();
3105
    }
3106

    
3107
    /**
3108
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3109
     * taxon name is higher than the species rank (true) or not (false).
3110
     * Returns false if rank is null.
3111
     *
3112
     * @see  #isGenus()
3113
     * @see  #isInfraGeneric()
3114
     * @see  #isSpecies()
3115
     * @see  #isInfraSpecific()
3116
     */
3117
    @Override
3118
    @Transient
3119
    public boolean isSupraSpecific(){
3120
        if (rank == null) {
3121
            return false;
3122
        }
3123
        return getRank().isHigher(Rank.SPECIES());
3124
    }
3125

    
3126
    /**
3127
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3128
     * taxon name is the species rank (true) or not (false). Non viral names
3129
     * with species rank are binomials.
3130
     * Returns false if rank is null.
3131
     *
3132
     * @see  #isSupraGeneric()
3133
     * @see  #isGenus()
3134
     * @see  #isInfraGeneric()
3135
     * @see  #isInfraSpecific()
3136
     */
3137
    @Override
3138
    @Transient
3139
    public boolean isSpecies() {
3140
        if (rank == null){
3141
            return false;
3142
        }
3143
        return getRank().isSpecies();
3144
    }
3145
    /**
3146
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3147
     * taxon name is lower than the species rank (true) or not (false).
3148
     * Infraspecific non viral names are trinomials.
3149
     * Returns false if rank is null.
3150
     *
3151
     * @see  #isSupraGeneric()
3152
     * @see  #isGenus()
3153
     * @see  #isInfraGeneric()
3154
     * @see  #isSpecies()
3155
     */
3156
    @Override
3157
    @Transient
3158
    public boolean isInfraSpecific() {
3159
        if (rank == null){
3160
            return false;
3161
        }
3162
        return getRank().isInfraSpecific();
3163
    }
3164

    
3165
    /**
3166
     * Returns true if this name's rank indicates a rank that aggregates species like species
3167
     * aggregates or species groups, false otherwise. This methods currently returns false
3168
     * for all user defined ranks.
3169
     *
3170
     *@see Rank#isSpeciesAggregate()
3171
     *
3172
     * @return
3173
     */
3174
    @Override
3175
    @Transient
3176
    public boolean isSpeciesAggregate() {
3177
        if (rank == null){
3178
            return false;
3179
        }
3180
        return getRank().isSpeciesAggregate();
3181
    }
3182

    
3183

    
3184
    /**
3185
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3186
     * the construction of <i>this</i> taxon name since there is no specific
3187
     * nomenclatural code defined. The real implementention takes place in the
3188
     * subclasses {@link IBacterialName BacterialName},
3189
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3190
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3191
     * and only one nomenclatural code.
3192
     *
3193
     * @return  null
3194
     * @see  	#isCodeCompliant()
3195
     * @see  	#getHasProblem()
3196
     * @deprecated use {@link #getNameType()} instead
3197
     */
3198
    @Deprecated
3199
    @Transient
3200
    @java.beans.Transient
3201
    public NomenclaturalCode getNomenclaturalCode() {
3202
        return nameType;
3203
    }
3204

    
3205
    /**
3206
     * Generates and returns the string with the scientific name of <i>this</i>
3207
     * taxon name (only non viral taxon names can be generated from their
3208
     * components). This string may be stored in the inherited
3209
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3210
     * This method overrides the generic and inherited
3211
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3212
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3213
     *
3214
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3215
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3216
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3217
     */
3218
//	@Override
3219
//	public abstract String generateTitle();
3220

    
3221
    /**
3222
     * Creates a basionym relationship between this name and
3223
     * 	each name in its homotypic group.
3224
     *
3225
     * @param basionymName
3226
     */
3227
    @Override
3228
    @Transient
3229
    public void setAsGroupsBasionym() {
3230

    
3231
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3232
        if (homotypicalGroup == null) {
3233
            return;
3234
        }
3235

    
3236
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3237
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3238

    
3239
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3240

    
3241
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3242

    
3243
            for(NameRelationship nameRelation : nameRelations){
3244
                relations.add(nameRelation);
3245
            }
3246
        }
3247

    
3248
        for (NameRelationship relation : relations) {
3249

    
3250
            // If this is a basionym relation, and toName is in the homotypical group,
3251
            //	remove the relationship.
3252
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3253
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3254
                removeRelations.add(relation);
3255
            }
3256
        }
3257

    
3258
        // Removing relations from a set through which we are iterating causes a
3259
        //	ConcurrentModificationException. Therefore, we delete the targeted
3260
        //	relations in a second step.
3261
        for (NameRelationship relation : removeRelations) {
3262
            this.removeNameRelationship(relation);
3263
        }
3264

    
3265
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3266
            if (!name.equals(this)) {
3267

    
3268
                // First check whether the relationship already exists
3269
                if (!this.isBasionymFor(name)) {
3270

    
3271
                    // Then create it
3272
                    name.addRelationshipFromName(this,
3273
                            NameRelationshipType.BASIONYM(), null, null);
3274
                }
3275
            }
3276
        }
3277
    }
3278

    
3279
    /**
3280
     * Removes basionym relationship between this name and
3281
     * 	each name in its homotypic group.
3282
     *
3283
     * @param basionymName
3284
     */
3285
    @Override
3286
    @Transient
3287
    public void removeAsGroupsBasionym() {
3288

    
3289
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3290

    
3291
        if (homotypicalGroup == null) {
3292
            return;
3293
        }
3294

    
3295
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3296
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3297

    
3298
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3299

    
3300
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3301

    
3302
            for(NameRelationship nameRelation : nameRelations){
3303
                relations.add(nameRelation);
3304
            }
3305
        }
3306

    
3307
        for (NameRelationship relation : relations) {
3308

    
3309
            // If this is a basionym relation, and toName is in the homotypical group,
3310
            //	and fromName is basionymName, remove the relationship.
3311
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3312
                    relation.getFromName().equals(this) &&
3313
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3314
                removeRelations.add(relation);
3315
            }
3316
        }
3317

    
3318
        // Removing relations from a set through which we are iterating causes a
3319
        //	ConcurrentModificationException. Therefore, we delete the targeted
3320
        //	relations in a second step.
3321
        for (NameRelationship relation : removeRelations) {
3322
            this.removeNameRelationship(relation);
3323
        }
3324
    }
3325

    
3326

    
3327
    /**
3328
     * Defines the last part of the name.
3329
     * This is for infraspecific taxa, the infraspecific epithet,
3330
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3331
     * else the genusOrUninomial.
3332
     * However, the result does not depend on the rank (which may be not correctly set
3333
     * in case of dirty data) but returns the first name part which is not blank
3334
     * considering the above order.
3335
     * @return the first not blank name part in reverse order
3336
     */
3337
    @Override
3338
    public String getLastNamePart() {
3339
        String result =
3340
                isNotBlank(this.getInfraSpecificEpithet())?
3341
                    this.getInfraSpecificEpithet() :
3342
                isNotBlank(this.getSpecificEpithet()) ?
3343
                    this.getSpecificEpithet():
3344
                isNotBlank(this.getInfraGenericEpithet()) ?
3345
                    this.getInfraGenericEpithet():
3346
                this.getGenusOrUninomial();
3347
        return result;
3348
    }
3349

    
3350
    @Override
3351
    public boolean isHybridName() {
3352
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3353
    }
3354

    
3355
    @Override
3356
    public boolean isHybrid() {
3357
        return this.isHybridName() || this.isHybridFormula();
3358
    }
3359

    
3360
// ******************** NOMENCLATURAL STANDING ****************/
3361

    
3362
    /**
3363
     * Computes the highest priority nomenclatural standing
3364
     * from nomenclatural status and name relationships.
3365
     */
3366
    private NomenclaturalStanding computeNomenclaturalStanding() {
3367
        Set<NomenclaturalStanding> standings = computeNomenclaturalStandings();
3368
        return NomenclaturalStanding.highest(standings);
3369
    }
3370

    
3371
    /**
3372
     * Computes all nomenclatural standings form the nomenclatural status and the name relationships.
3373
     */
3374
    private Set<NomenclaturalStanding> computeNomenclaturalStandings() {
3375
        Set<NomenclaturalStanding> standings = new HashSet<>();
3376
        for (NomenclaturalStatus status : this.status){
3377
            if (status.getType() != null){
3378
                NomenclaturalStanding standing = status.getType().getNomenclaturalStanding();
3379
                standings.add(standing);
3380
            }
3381
        }
3382
        for (NameRelationship nameRel : this.relationsFromThisName){
3383
            if (nameRel.getType() != null){
3384
                NomenclaturalStanding standing = nameRel.getType().getNomenclaturalStanding();
3385
                standings.add(standing);
3386
            }
3387
        }
3388
        for (NameRelationship nameRel : this.relationsToThisName){
3389
            if (nameRel.getType() != null){
3390
                NomenclaturalStanding standing = nameRel.getType().getNomenclaturalStandingInverse();
3391
                standings.add(standing);
3392
            }
3393
        }
3394
        return standings;
3395
    }
3396

    
3397
    @Override
3398
    @Transient
3399
    public boolean isDesignationOnly() {
3400
        return computeNomenclaturalStanding().isDesignationOnly();
3401
    }
3402

    
3403
    @Override
3404
    @Transient
3405
    public boolean isInvalidExplicit() {
3406
        return computeNomenclaturalStanding().isInvalidExplicit();
3407
    }
3408

    
3409
    @Override
3410
    @Transient
3411
    public boolean isIllegitimate() {
3412
        return computeNomenclaturalStanding().isIllegitimate();
3413
    }
3414

    
3415
    @Override
3416
    @Transient
3417
    public boolean isValidExplicit() {
3418
        return computeNomenclaturalStanding().isValidExplicit();
3419
    }
3420

    
3421
    @Override
3422
    @Transient
3423
    public boolean isNoStatus() {
3424
        return computeNomenclaturalStanding().isNoStatus();
3425
    }
3426

    
3427
    @Override
3428
    @Transient
3429
    public boolean isInvalid() {
3430
        return computeNomenclaturalStanding().isInvalid();
3431
    }
3432

    
3433
    @Override
3434
    @Transient
3435
    public boolean isLegitimate() {
3436
        return computeNomenclaturalStanding().isLegitimate();
3437
    }
3438

    
3439
    @Override
3440
    @Transient
3441
    public boolean isValid() {
3442
        return computeNomenclaturalStanding().isValid();
3443
    }
3444

    
3445
// ***************** COMPARE ********************************/
3446

    
3447
    @Override
3448
    public int compareToName(TaxonName otherName){
3449

    
3450
        int result = 0;
3451

    
3452
        if (otherName == null) {
3453
            throw new NullPointerException("Cannot compare to null.");
3454
        }
3455

    
3456
        //other
3457
        otherName = deproxy(otherName);
3458
        String otherNameCache = otherName.getNameCache();
3459
        String otherTitleCache = otherName.getTitleCache();
3460
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3461
        if (otherName.isAutonym()){
3462
            boolean isProtected = otherName.isProtectedNameCache();
3463
            String oldNameCache = otherName.getNameCache();
3464
            otherName.setProtectedNameCache(false);
3465
            otherName.setNameCache(null, false);
3466
            otherNameCache = otherName.getNameCache();
3467
            otherName.setNameCache(oldNameCache, isProtected);
3468
        }
3469

    
3470
        //this
3471
        String thisNameCache = this.getNameCache();
3472
        String thisTitleCache = this.getTitleCache();
3473

    
3474
        if (this.isAutonym()){
3475
            boolean isProtected = this.isProtectedNameCache();
3476
            String oldNameCache = this.getNameCache();
3477
            this.setProtectedNameCache(false);
3478
            this.setNameCache(null, false);
3479
            thisNameCache = this.getNameCache();
3480
            this.setNameCache(oldNameCache, isProtected);
3481
        }
3482

    
3483
        // Compare name cache of taxon names
3484
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3485
            String thisNormalized = normalizeName(thisNameCache);
3486
            String otherNormalized = normalizeName(otherNameCache);
3487
            result = thisNormalized.compareTo(otherNormalized);
3488
        }
3489

    
3490
        // Compare title cache of taxon names
3491
        if (result == 0){
3492
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3493
                String thisNormalized = normalizeName(thisTitleCache);
3494
                String otherNormalized = normalizeName(otherTitleCache);
3495
                result = CdmUtils.nullSafeCompareTo(thisNormalized, otherNormalized);
3496
                if (result == 0){
3497
                    result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3498
                }
3499
            }
3500
        }
3501
        if (result == 0){
3502
            result =  CdmUtils.nullSafeCompareTo(thisNameCache, otherNameCache);
3503
        }
3504

    
3505
        return result;
3506
    }
3507

    
3508
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3509
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3510

    
3511
    private String normalizeName(String thisNameCache) {
3512
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3513
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3514
        return thisNameCache;
3515
    }
3516

    
3517
// ********************** INTERFACES ********************************************/
3518

    
3519
    /**
3520
     * Method to cast a interfaced name to a concrete name.
3521
     * The method includes a deproxy to guarantee that no
3522
     * class cast exception is thrown.
3523
     *
3524
     * @see #castAndDeproxy(Set)
3525
     * @param interfacedName
3526
     * @return
3527
     */
3528
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3529
        return deproxy(interfacedName, TaxonName.class);
3530
    }
3531

    
3532
    /**
3533
     * Method to cast a set of interfaced names to concrete namex.
3534
     * The method includes a deproxy to guarantee that no
3535
     * class cast exception is thrown.
3536
     *
3537
     * @see #castAndDeproxy(ITaxonNameBase)
3538
     * @param naminterfacedNames
3539
     * @return
3540
     */
3541
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3542
        Set<TaxonName> result = new HashSet<>();
3543
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3544
            result.add(castAndDeproxy(naminterfacedName));
3545
        }
3546
        return result;
3547
    }
3548

    
3549
//************************ isType ***********************************************/
3550

    
3551
    @Override
3552
    public boolean isNonViral() {
3553
        return nameType.isNonViral();
3554
    }
3555
    @Override
3556
    public boolean isZoological(){
3557
        return nameType.isZoological();
3558
    }
3559
    @Override
3560
    public boolean isBotanical() {
3561
        if (nameType == null){
3562
            throw new RuntimeException("Name has no nameType: " +  this.getUuid() + ", " + getId()+ ", species epi: " + getSpecificEpithet() );
3563
        }
3564
        return nameType.isBotanical();
3565
    }
3566
    @Override
3567
    public boolean isCultivar() {
3568
        return nameType.isCultivar();
3569
    }
3570
    @Override
3571
    public boolean isBacterial() {
3572
        return nameType.isBacterial();
3573
    }
3574
    @Override
3575
    public boolean isViral() {
3576
        return nameType != null? nameType.isViral(): false;
3577
    }
3578

    
3579
// *********************** CACHES ***************************************************/
3580

    
3581
    @Override
3582
    public boolean updateCaches() {
3583
        boolean result = updateAuthorshipCache();
3584
        result |= updateNameCache();
3585
        result |= super.updateCaches();
3586
        result |= updateFullTitleCache();
3587
        return result;
3588
    }
3589

    
3590
    /**
3591
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
3592
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened.
3593
     * @return <code>true</code> if something changed
3594
     */
3595
    private boolean updateAuthorshipCache() {
3596
        //updates the authorship cache if necessary and via the listener updates all higher caches
3597
        if (protectedAuthorshipCache == false){
3598
            String oldCache = this.authorshipCache;
3599
            String newCache = cacheStrategy().getAuthorshipCache(this);
3600
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3601
                this.setAuthorshipCache(null, false);
3602
                this.getAuthorshipCache();
3603
                return true;
3604
            }
3605
        }
3606
        return false;
3607
    }
3608

    
3609
    private boolean updateNameCache() {
3610
        //updates the name cache if necessary and via the listener updates all higher caches
3611
        if (protectedNameCache == false){
3612
            String oldCache = this.nameCache;
3613
            String newCache = cacheStrategy().getNameCache(this);
3614
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3615
                this.setNameCache(null, false);
3616
                this.getNameCache();
3617
                return true;
3618
            }
3619
        }
3620
        return false;
3621
    }
3622

    
3623
    private boolean updateFullTitleCache() {
3624
        if (protectedFullTitleCache == false){
3625
            String oldCache = this.fullTitleCache;
3626
            String newCache = getTruncatedCache(cacheStrategy().getFullTitleCache(this));
3627
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3628
                this.setFullTitleCache(null, false);
3629
                this.getFullTitleCache();
3630
                return true;
3631
            }
3632
        }
3633
        return false;
3634
    }
3635

    
3636
//*********************** CLONE ********************************************************/
3637

    
3638
    @Override
3639
    public TaxonName clone() {
3640
        return this.clone(true);
3641
    }
3642

    
3643
    @Override
3644
    public TaxonName clone(boolean newHomotypicGroup) {
3645
        try {
3646
            TaxonName result = (TaxonName)super.clone();
3647

    
3648
            //taxonBases -> empty
3649
            result.taxonBases = new HashSet<>();
3650

    
3651
            //empty caches
3652
            if (! protectedFullTitleCache){
3653
                result.fullTitleCache = null;
3654
            }
3655

    
3656
            //descriptions
3657
            result.descriptions = new HashSet<>();
3658
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3659
                TaxonNameDescription newDescription = taxonNameDescription.clone();
3660
                result.descriptions.add(newDescription);
3661
            }
3662

    
3663
            //status
3664
            result.status = new HashSet<>();
3665
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3666
                NomenclaturalStatus newStatus = nomenclaturalStatus.clone();
3667
                result.status.add(newStatus);
3668
            }
3669

    
3670
            //to relations
3671
            result.relationsToThisName = new HashSet<>();
3672
            for (NameRelationship toRelationship : getRelationsToThisName()){
3673
                NameRelationship newRelationship = toRelationship.clone();
3674
                newRelationship.setRelatedTo(result);
3675
                result.relationsToThisName.add(newRelationship);
3676
            }
3677

    
3678
            //from relations
3679
            result.relationsFromThisName = new HashSet<>();
3680
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3681
                NameRelationship newRelationship = fromRelationship.clone();
3682
                newRelationship.setRelatedFrom(result);
3683
                result.relationsFromThisName.add(newRelationship);
3684
            }
3685

    
3686
            //type designations
3687
            result.typeDesignations = new HashSet<>();
3688
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3689
                TypeDesignationBase<?> newDesignation = typeDesignation.clone();
3690
                this.removeTypeDesignation(newDesignation);
3691
                result.addTypeDesignation(newDesignation, false);
3692
            }
3693

    
3694
            //homotypicalGroup
3695
            if (newHomotypicGroup){
3696
                HomotypicalGroup homotypicalGroup = HomotypicalGroup.NewInstance();
3697
                homotypicalGroup.addTypifiedName(result);
3698
            }else{
3699
                result.homotypicalGroup.addTypifiedName(result);  //to immediately handle bidirectionality
3700
            }
3701

    
3702
            //HybridChildRelations
3703
            result.hybridChildRelations = new HashSet<>();
3704
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3705
                HybridRelationship newChildRelationship = hybridRelationship.clone();
3706
                newChildRelationship.setRelatedTo(result);
3707
                result.hybridChildRelations.add(newChildRelationship);
3708
            }
3709

    
3710
            //HybridParentRelations
3711
            result.hybridParentRelations = new HashSet<>();
3712
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3713
                HybridRelationship newParentRelationship = hybridRelationship.clone();
3714
                newParentRelationship.setRelatedFrom(result);
3715
                result.hybridParentRelations.add(newParentRelationship);
3716
            }
3717

    
3718
            //empty caches
3719
            if (! protectedNameCache){
3720
                result.nameCache = null;
3721
            }
3722

    
3723
            //empty caches
3724
            if (! protectedAuthorshipCache){
3725
                result.authorshipCache = null;
3726
            }
3727

    
3728
            //registrations
3729
            result.registrations = new HashSet<>();
3730
            for (Registration registration : getRegistrations()){
3731
                result.registrations.add(registration);
3732
            }
3733

    
3734
            //nomenclatural source
3735
            if (this.getNomenclaturalSource() != null){
3736
                result.setNomenclaturalSource(this.getNomenclaturalSource().clone());
3737
                result.getNomenclaturalSource().setSourcedName(result);
3738
            }
3739

    
3740

    
3741
            //no changes to: appendedPharse, parsingProblem, problemEnds, problemStarts
3742
            //protectedFullTitleCache, rank
3743
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3744
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3745
            //protectedAuthorshipCache, protectedNameCache,
3746
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3747
            //acronym
3748
            //subGenusAuthorship, nameApprobation
3749
            //anamorphic
3750
            //cultivarEpithet,cultivarGroupEpithet
3751
            return result;
3752
        } catch (CloneNotSupportedException e) {
3753
            logger.warn("Object does not implement cloneable");
3754
            e.printStackTrace();
3755
            return null;
3756
        }
3757
    }
3758

    
3759
}
(33-33/39)