Project

General

Profile

Download (142 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;import org.apache.logging.log4j.Logger;
49
import org.hibernate.annotations.Cascade;
50
import org.hibernate.annotations.CascadeType;
51
import org.hibernate.annotations.Type;
52
import org.hibernate.envers.Audited;
53
import org.hibernate.search.annotations.Analyze;
54
import org.hibernate.search.annotations.Analyzer;
55
import org.hibernate.search.annotations.Field;
56
import org.hibernate.search.annotations.Fields;
57
import org.hibernate.search.annotations.Index;
58
import org.hibernate.search.annotations.Indexed;
59
import org.hibernate.search.annotations.IndexedEmbedded;
60
import org.hibernate.search.annotations.Store;
61
import org.springframework.util.ReflectionUtils;
62

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
350
//****** Non-ViralName attributes ***************************************/
351

    
352
    @XmlElement(name = "NameCache")
353
    @Fields({
354
        @Field(name = "nameCache_tokenized"),
355
        @Field(store = Store.YES, index = Index.YES, analyze = Analyze.YES)
356
    })
357
    @Analyzer(impl = org.apache.lucene.analysis.core.KeywordAnalyzer.class)
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
    //see TaxonNameFactory
581
    /**
582
     * @param code
583
     * @param rank
584
     * @param homotypicalGroup
585
     * @return
586
     */
587
    protected static TaxonName NewInstance(NomenclaturalCode code, Rank rank,
588
            HomotypicalGroup homotypicalGroup) {
589

    
590
        TaxonName result = new TaxonName(code, rank, homotypicalGroup);
591
        return result;
592
    }
593

    
594
    //TODO move to TaxonNameFactory
595
    public static TaxonName NewInstance(NomenclaturalCode code, Rank rank, String genusOrUninomial,
596
            String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet,
597
            TeamOrPersonBase<?> combinationAuthorship, Reference nomenclaturalReference,
598
            String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
599

    
600
        TaxonName result = new TaxonName(code, rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
601
        return result;
602
    }
603

    
604
// ************* CONSTRUCTORS *************/
605

    
606
    /**
607
     * Class constructor: creates a new empty taxon name.
608
     * @param code
609
     *
610
     * @see #TaxonName(Rank)
611
     * @see #TaxonName(HomotypicalGroup)
612
     * @see #TaxonName(Rank, HomotypicalGroup)
613
     */
614
    protected TaxonName() {}
615

    
616
    /**
617
     * Class constructor: creates a new taxon name instance
618
     * only containing its {@link Rank rank} and
619
     * its {@link HomotypicalGroup homotypical group} and
620
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy default cache strategy}.
621
     * The new taxon name will be also added to the set of taxon names
622
     * belonging to this homotypical group.
623
     *
624
     * @param  rank  			 the rank to be assigned to <i>this</i> taxon name
625
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
626
     * @see    					 #TaxonName()
627
     * @see    					 #TaxonName(Rank)
628
     * @see    					 #TaxonName(HomotypicalGroup)
629
     */
630
    protected TaxonName(NomenclaturalCode type, Rank rank, HomotypicalGroup homotypicalGroup) {
631
        this();
632
        setNameType(type);
633
        this.setRank(rank);
634
        if (homotypicalGroup == null){
635
            homotypicalGroup = HomotypicalGroup.NewInstance();
636
        }
637
        homotypicalGroup.addTypifiedName(this);
638
        this.homotypicalGroup = homotypicalGroup;
639
    }
640

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

    
686
    @Override
687
    public void initListener(){
688
        PropertyChangeListener nameListener = event-> {
689
            String propName = event.getPropertyName();
690
            if (Objects.equals(event.getOldValue(), event.getNewValue())){
691
                return;
692
            }
693

    
694
            boolean protectedByLowerCache = false;
695
            //authorship cache
696
            if (fieldHasCacheUpdateProperty(propName, "authorshipCache")){
697
                if (protectedAuthorshipCache){
698
                    protectedByLowerCache = true;
699
                }else{
700
                    authorshipCache = null;
701
                }
702
            }
703

    
704
            //nameCache
705
            if (fieldHasCacheUpdateProperty(propName, "nameCache")){
706
                if (protectedNameCache){
707
                    protectedByLowerCache = true;
708
                }else{
709
                    nameCache = null;
710
                }
711
            }
712
            //title cache
713
            if (! fieldHasNoUpdateProperty(propName, "titleCache")){
714
                if (isProtectedTitleCache()|| protectedByLowerCache == true ){
715
                    protectedByLowerCache = true;
716
                }else{
717
                    titleCache = null;
718
                }
719
            }
720
            //full title cache
721
            if (! fieldHasNoUpdateProperty(propName, "fullTitleCache")){
722
                if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
723
                    protectedByLowerCache = true;
724
                }else{
725
                    fullTitleCache = null;
726
                }
727
            }
728

    
729
        };
730
        addPropertyChangeListener(nameListener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
731
    }
732

    
733
    private static Map<String, java.lang.reflect.Field> allFields = null;
734
    protected Map<String, java.lang.reflect.Field> getAllFields(){
735
        if (allFields == null){
736
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
737
        }
738
        return allFields;
739
    }
740

    
741
    private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
742
        try {
743
            java.lang.reflect.Field field = getAllFields().get(propertyName);
744
            if (field != null){
745
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
746
                if (updateAnnotation != null){
747
                    for (String value : updateAnnotation.value()){
748
                        if (cacheName.equals(value)){
749
                            return true;
750
                        }
751
                    }
752
                }
753
            }
754
            return false;
755
        } catch (SecurityException e1) {
756
            throw e1;
757
        }
758
    }
759

    
760
    private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
761
        java.lang.reflect.Field field;
762
        //do not update fields with the same name
763
        if (cacheName.equals(propertyName)){
764
            return true;
765
        }
766
        //evaluate annotation
767
        try {
768
            field = getAllFields().get(propertyName);
769
            if (field != null){
770
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
771
                if (updateAnnotation != null){
772
                    for (String value : updateAnnotation.noUpdate()){
773
                        if (cacheName.equals(value)){
774
                            return true;
775
                        }
776
                    }
777
                }
778
            }
779
            return false;
780
        } catch (SecurityException e1) {
781
            throw e1;
782
        }
783
    }
784

    
785
    @Override
786
    protected void initDefaultCacheStrategy() {
787
        this.cacheStrategy = TaxonNameDefaultCacheStrategy.NewInstance();
788
    }
789

    
790
// ****************** GETTER / SETTER ****************************/
791

    
792
    @Override
793
    public NomenclaturalCode getNameType() {
794
        return nameType;
795
    }
796

    
797
    @Override
798
    public void setNameType(NomenclaturalCode nameType) {
799
        this.nameType = nameType;
800
    }
801

    
802
    /**
803
     * Returns the boolean value of the flag intended to protect (true)
804
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
805
     * string of <i>this</i> non viral taxon name.
806
     *
807
     * @return  the boolean value of the protectedNameCache flag
808
     * @see     #getNameCache()
809
     */
810
    @Override
811
    public boolean isProtectedNameCache() {
812
        return protectedNameCache;
813
    }
814

    
815
    /**
816
     * @see     #isProtectedNameCache()
817
     */
818
    @Override
819
    public void setProtectedNameCache(boolean protectedNameCache) {
820
        this.protectedNameCache = protectedNameCache;
821
    }
822

    
823
    /**
824
     * Returns either the scientific name string (without authorship) for <i>this</i>
825
     * non viral taxon name if its rank is genus or higher (monomial) or the string for
826
     * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
827
     * Genus or uninomial strings begin with an upper case letter.
828
     *
829
     * @return  the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
830
     * @see     #getNameCache()
831
     */
832
    @Override
833
    public String getGenusOrUninomial() {
834
        return genusOrUninomial;
835
    }
836

    
837
    /**
838
     * @see  #getGenusOrUninomial()
839
     */
840
    @Override
841
    public void setGenusOrUninomial(String genusOrUninomial) {
842
        this.genusOrUninomial = isBlank(genusOrUninomial) ? null : genusOrUninomial;
843
    }
844

    
845
    /**
846
     * Returns the genus subdivision epithet string (infrageneric part) for
847
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
848
     * higher than species aggregate: binomial). Genus subdivision epithet
849
     * strings begin with an upper case letter.
850
     *
851
     * @return  the string containing the infrageneric part of <i>this</i> non viral taxon name
852
     * @see     #getNameCache()
853
     */
854
    @Override
855
    public String getInfraGenericEpithet(){
856
        return this.infraGenericEpithet;
857
    }
858

    
859
    /**
860
     * @see  #getInfraGenericEpithet()
861
     */
862
    @Override
863
    public void setInfraGenericEpithet(String infraGenericEpithet){
864
        this.infraGenericEpithet = isBlank(infraGenericEpithet)? null : infraGenericEpithet;
865
    }
866

    
867
    /**
868
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
869
     * species aggregate or lower (bi- or trinomial). Species epithet strings
870
     * begin with a lower case letter.
871
     *
872
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
873
     * @see     #getNameCache()
874
     */
875
    @Override
876
    public String getSpecificEpithet(){
877
        return this.specificEpithet;
878
    }
879

    
880
    /**
881
     * @see  #getSpecificEpithet()
882
     */
883
    @Override
884
    public void setSpecificEpithet(String specificEpithet){
885
        this.specificEpithet = isBlank(specificEpithet) ? null : specificEpithet;
886
    }
887

    
888
    /**
889
     * Returns the species subdivision epithet string (infraspecific part) for
890
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
891
     * (lower than species: trinomial). Species subdivision epithet strings
892
     * begin with a lower case letter.
893
     *
894
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
895
     * @see     #getNameCache()
896
     */
897
    @Override
898
    public String getInfraSpecificEpithet(){
899
        return this.infraSpecificEpithet;
900
    }
901

    
902
    /**
903
     * @see  #getInfraSpecificEpithet()
904
     */
905
    @Override
906
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
907
        this.infraSpecificEpithet = isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
908
    }
909

    
910
    /**
911
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
912
     * taxon name.
913
     *
914
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
915
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
916
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
917
     */
918
    @Override
919
    public TeamOrPersonBase<?> getCombinationAuthorship(){
920
        return this.combinationAuthorship;
921
    }
922

    
923
    /**
924
     * @see  #getCombinationAuthorship()
925
     */
926
    @Override
927
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
928
        this.combinationAuthorship = combinationAuthorship;
929
    }
930

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

    
966
    @Override
967
    public TeamOrPersonBase<?> getInCombinationAuthorship(){
968
        return this.inCombinationAuthorship;
969
    }
970
    @Override
971
    public void setInCombinationAuthorship(TeamOrPersonBase<?> inCombinationAuthorship) {
972
        this.inCombinationAuthorship = inCombinationAuthorship;
973
    }
974

    
975
    /**
976
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
977
     * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
978
     * author (team) can only exist if <i>this</i> non viral taxon name is a new
979
     * combination due to a taxonomical revision.
980
     *
981
     * @return  the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
982
     * @see     #getCombinationAuthorship()
983
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
984
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
985
     */
986
    @Override
987
    public TeamOrPersonBase<?> getBasionymAuthorship(){
988
        return basionymAuthorship;
989
    }
990

    
991
    /**
992
     * @see  #getBasionymAuthorship()
993
     */
994
    @Override
995
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
996
        this.basionymAuthorship = basionymAuthorship;
997
    }
998

    
999
    /**
1000
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
1001
     * the publication of the original combination <i>this</i> non viral taxon name is
1002
     * based on. This should have been generally stated by
1003
     * the {@link #getBasionymAuthorship() basionym author (team)} itself.
1004
     * The presence of a basionym author (team) of <i>this</i> non viral taxon name is a
1005
     * condition for the existence of an ex basionym author (team)
1006
     * for <i>this</i> same name.
1007
     *
1008
     * @return  the nomenclatural ex basionym author (team) of <i>this</i> non viral taxon name
1009
     * @see     #getBasionymAuthorship()
1010
     * @see     #getExCombinationAuthorship()
1011
     * @see     #getCombinationAuthorship()
1012
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
1013
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
1014
     */
1015
    @Override
1016
    public TeamOrPersonBase<?> getExBasionymAuthorship(){
1017
        return exBasionymAuthorship;
1018
    }
1019

    
1020
    /**
1021
     * @see  #getExBasionymAuthorship()
1022
     */
1023
    @Override
1024
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
1025
        this.exBasionymAuthorship = exBasionymAuthorship;
1026
    }
1027

    
1028
    @Override
1029
    public TeamOrPersonBase<?> getInBasionymAuthorship(){
1030
        return this.inBasionymAuthorship;
1031
    }
1032
    @Override
1033
    public void setInBasionymAuthorship(TeamOrPersonBase<?> inBasionymAuthorship) {
1034
        this.inBasionymAuthorship = inBasionymAuthorship;
1035
    }
1036

    
1037
    /**
1038
     * Returns the boolean value of the flag intended to protect (true)
1039
     * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
1040
     * of <i>this</i> non viral taxon name.
1041
     *
1042
     * @return  the boolean value of the protectedAuthorshipCache flag
1043
     * @see     #getAuthorshipCache()
1044
     */
1045
    @Override
1046
    public boolean isProtectedAuthorshipCache() {
1047
        return protectedAuthorshipCache;
1048
    }
1049

    
1050
    /**
1051
     * @see     #isProtectedAuthorshipCache()
1052
     * @see     #getAuthorshipCache()
1053
     */
1054
    @Override
1055
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
1056
        this.protectedAuthorshipCache = protectedAuthorshipCache;
1057
    }
1058

    
1059
    /**
1060
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1061
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
1062
     *
1063
     * @see    #getHybridRelationships()
1064
     * @see    #getChildRelationships()
1065
     * @see    HybridRelationshipType
1066
     */
1067
    @Override
1068
    public Set<HybridRelationship> getHybridParentRelations() {
1069
        if(hybridParentRelations == null) {
1070
            this.hybridParentRelations = new HashSet<>();
1071
        }
1072
        return hybridParentRelations;
1073
    }
1074

    
1075
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1076
        this.hybridParentRelations = hybridParentRelations;
1077
    }
1078

    
1079

    
1080
    /**
1081
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1082
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
1083
     *
1084
     * @see    #getHybridRelationships()
1085
     * @see    #getParentRelationships()
1086
     * @see    HybridRelationshipType
1087
     */
1088
    @Override
1089
    public Set<HybridRelationship> getHybridChildRelations() {
1090
        if(hybridChildRelations == null) {
1091
            this.hybridChildRelations = new HashSet<>();
1092
        }
1093
        return hybridChildRelations;
1094
    }
1095

    
1096
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1097
        this.hybridChildRelations = hybridChildRelations;
1098
    }
1099

    
1100
    @Override
1101
    public boolean isProtectedFullTitleCache() {
1102
        return protectedFullTitleCache;
1103
    }
1104

    
1105
    @Override
1106
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1107
        this.protectedFullTitleCache = protectedFullTitleCache;
1108
    }
1109

    
1110
    /**
1111
     * Returns the boolean value of the flag indicating whether the name of <i>this</i>
1112
     * botanical taxon name is a hybrid formula (true) or not (false). A hybrid
1113
     * named by a hybrid formula (composed with its parent names by placing the
1114
     * multiplication sign between them) does not have an own published name
1115
     * and therefore has neither an {@link NonViralName#getAuthorshipCache() autorship}
1116
     * nor other name components. If this flag is set no other hybrid flags may
1117
     * be set.
1118
     *
1119
     * @return  the boolean value of the isHybridFormula flag
1120
     * @see     #isMonomHybrid()
1121
     * @see     #isBinomHybrid()
1122
     * @see     #isTrinomHybrid()
1123
     */
1124
    @Override
1125
    @Transient
1126
    @java.beans.Transient
1127
    public boolean isHybridFormula(){
1128
        return this.hybridFormula;
1129
    }
1130

    
1131
    /**
1132
     * @see  #isHybridFormula()
1133
     */
1134
    @Override
1135
    public void setHybridFormula(boolean hybridFormula){
1136
        this.hybridFormula = hybridFormula;
1137
    }
1138

    
1139
    /**
1140
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1141
     * taxon name is the name of an intergeneric hybrid (true) or not (false).
1142
     * In this case the multiplication sign is placed before the scientific
1143
     * name. If this flag is set no other hybrid flags may be set.
1144
     *
1145
     * @return  the boolean value of the isMonomHybrid flag
1146
     * @see     #isHybridFormula()
1147
     * @see     #isBinomHybrid()
1148
     * @see     #isTrinomHybrid()
1149
     */
1150
    @Override
1151
    public boolean isMonomHybrid(){
1152
        return this.monomHybrid;
1153
    }
1154

    
1155
    /**
1156
     * @see  #isMonomHybrid()
1157
     * @see  #isBinomHybrid()
1158
     * @see  #isTrinomHybrid()
1159
     */
1160
    @Override
1161
    public void setMonomHybrid(boolean monomHybrid){
1162
        this.monomHybrid = monomHybrid;
1163
    }
1164

    
1165
    /**
1166
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1167
     * taxon name is the name of an interspecific hybrid (true) or not (false).
1168
     * In this case the multiplication sign is placed before the species
1169
     * epithet. If this flag is set no other hybrid flags may be set.
1170
     *
1171
     * @return  the boolean value of the isBinomHybrid flag
1172
     * @see     #isHybridFormula()
1173
     * @see     #isMonomHybrid()
1174
     * @see     #isTrinomHybrid()
1175
     */
1176
    @Override
1177
    public boolean isBinomHybrid(){
1178
        return this.binomHybrid;
1179
    }
1180

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

    
1191
    @Override
1192
    public boolean isTrinomHybrid(){
1193
        return this.trinomHybrid;
1194
    }
1195

    
1196
    /**
1197
     * @see  #isTrinomHybrid()
1198
     * @see  #isBinomHybrid()
1199
     * @see  #isMonomHybrid()
1200
     */
1201
    @Override
1202
    public void setTrinomHybrid(boolean trinomHybrid){
1203
        this.trinomHybrid = trinomHybrid;
1204
    }
1205

    
1206
    // ****************** VIRAL NAME ******************/
1207

    
1208
    @Override
1209
    public String getAcronym(){
1210
        return this.acronym;
1211
    }
1212

    
1213
    /**
1214
     * @see  #getAcronym()
1215
     */
1216
    @Override
1217
    public void setAcronym(String acronym){
1218
        this.acronym = isBlank(acronym)? null : acronym;
1219
    }
1220

    
1221
    // ****************** BACTERIAL NAME ******************/
1222

    
1223
    @Override
1224
    public String getSubGenusAuthorship(){
1225
        return this.subGenusAuthorship;
1226
    }
1227

    
1228
    @Override
1229
    public void setSubGenusAuthorship(String subGenusAuthorship){
1230
        this.subGenusAuthorship = subGenusAuthorship;
1231
    }
1232

    
1233

    
1234
    @Override
1235
    public String getNameApprobation(){
1236
        return this.nameApprobation;
1237
    }
1238

    
1239
    /**
1240
     * @see  #getNameApprobation()
1241
     */
1242
    @Override
1243
    public void setNameApprobation(String nameApprobation){
1244
        this.nameApprobation = nameApprobation;
1245
    }
1246

    
1247
    //************ Zoological Name
1248

    
1249

    
1250
    @Override
1251
    public String getBreed(){
1252
        return this.breed;
1253
    }
1254
    /**
1255
     * @see  #getBreed()
1256
     */
1257
    @Override
1258
    public void setBreed(String breed){
1259
        this.breed = isBlank(breed) ? null : breed;
1260
    }
1261

    
1262

    
1263
    @Override
1264
    public Integer getPublicationYear() {
1265
        return publicationYear;
1266
    }
1267
    /**
1268
     * @see  #getPublicationYear()
1269
     */
1270
    @Override
1271
    public void setPublicationYear(Integer publicationYear) {
1272
        this.publicationYear = publicationYear;
1273
    }
1274

    
1275

    
1276
    @Override
1277
    public Integer getOriginalPublicationYear() {
1278
        return originalPublicationYear;
1279
    }
1280
    /**
1281
     * @see  #getOriginalPublicationYear()
1282
     */
1283
    @Override
1284
    public void setOriginalPublicationYear(Integer originalPublicationYear) {
1285
        this.originalPublicationYear = originalPublicationYear;
1286
    }
1287

    
1288
    // **** Cultivar Name ************
1289

    
1290
    @Override
1291
    public String getCultivarEpithet(){
1292
        return this.cultivarEpithet;
1293
    }
1294

    
1295
    /**
1296
     * @see  #getCultivarEpithet()
1297
     */
1298
    @Override
1299
    public void setCultivarEpithet(String cultivarEpithet){
1300
        this.cultivarEpithet = isBlank(cultivarEpithet) ? null : cultivarEpithet;
1301
    }
1302

    
1303
    @Override
1304
    public String getCultivarGroupEpithet(){
1305
        return this.cultivarGroupEpithet;
1306
    }
1307

    
1308
    /**
1309
     * @see  #getCultivarGroupEpithet()
1310
     */
1311
    @Override
1312
    public void setCultivarGroupEpithet(String cultivarGroupEpithet){
1313
        this.cultivarGroupEpithet = isBlank(cultivarGroupEpithet) ? null : cultivarGroupEpithet;
1314
    }
1315

    
1316
    // **************** Fungus Name
1317
    @Override
1318
    public boolean isAnamorphic(){
1319
        return this.anamorphic;
1320
    }
1321

    
1322
    /**
1323
     * @see  #isAnamorphic()
1324
     */
1325
    @Override
1326
    public void setAnamorphic(boolean anamorphic){
1327
        this.anamorphic = anamorphic;
1328
    }
1329

    
1330

    
1331
// **************** ADDER / REMOVE *************************/
1332

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

    
1362

    
1363
    /**
1364
     * Removes one {@link HybridRelationship hybrid relationship} from the set of
1365
     * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1366
     * is involved. The hybrid relationship will also be removed from the set
1367
     * belonging to the second botanical taxon name involved.
1368
     *
1369
     * @param  relationship  the hybrid relationship which should be deleted from the corresponding sets
1370
     * @see                  #getHybridRelationships()
1371
     */
1372
    @Override
1373
    public void removeHybridRelationship(HybridRelationship hybridRelation) {
1374
        if (hybridRelation == null) {
1375
            return;
1376
        }
1377

    
1378
        TaxonName parent = hybridRelation.getParentName();
1379
        TaxonName child = hybridRelation.getHybridName();
1380
        if (this.equals(parent)){
1381
            this.hybridParentRelations.remove(hybridRelation);
1382
            child.hybridChildRelations.remove(hybridRelation);
1383
            hybridRelation.setHybridName(null);
1384
            hybridRelation.setParentName(null);
1385
        }
1386
        if (this.equals(child)){
1387
            parent.hybridParentRelations.remove(hybridRelation);
1388
            this.hybridChildRelations.remove(hybridRelation);
1389
            hybridRelation.setHybridName(null);
1390
            hybridRelation.setParentName(null);
1391
        }
1392
    }
1393

    
1394
//********* METHODS **************************************/
1395

    
1396
    @Override
1397
    public String generateFullTitle(){
1398
        if (cacheStrategy() == null){
1399
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1400
            return null;
1401
        }else{
1402
            return cacheStrategy().getFullTitleCache(this);
1403
        }
1404
    }
1405

    
1406
    @Override
1407
    public void setFullTitleCache(String fullTitleCache){
1408
        setFullTitleCache(fullTitleCache, PROTECTED);
1409
    }
1410

    
1411
    @Override
1412
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1413
        fullTitleCache = getTruncatedCache(fullTitleCache);
1414
        this.fullTitleCache = fullTitleCache;
1415
        this.setProtectedFullTitleCache(protectCache);
1416
    }
1417

    
1418
   /** Checks if this name is an autonym.<BR>
1419
    * An autonym is a taxon name that has equal specific and infra specific epithets.<BR>
1420
    * {@link http://ibot.sav.sk/icbn/frameset/0010Ch2Sec1a006.htm#6.8. Vienna Code §6.8}
1421
    * or a taxon name that has equal generic and infrageneric epithets (A22.2).<BR>
1422
    * Only relevant for botanical names.
1423
    * @return true, if name has Rank, Rank is below species and species epithet equals infraSpeciesEpithtet, else false
1424
    */
1425
    @Override
1426
    @Transient
1427
    public boolean isAutonym(){
1428
        if (isBotanical()){
1429
            if (this.getRank() != null && this.getSpecificEpithet() != null && this.getInfraSpecificEpithet() != null &&
1430
                this.isInfraSpecific() && this.getSpecificEpithet().trim().equals(this.getInfraSpecificEpithet().trim())){
1431
                return true;
1432
            }else if (this.getRank() != null && this.getGenusOrUninomial() != null && this.getInfraGenericEpithet() != null &&
1433
                    this.isInfraGeneric() && this.getGenusOrUninomial().trim().equals(this.getInfraGenericEpithet().trim())){
1434
                return true;
1435
            }else{
1436
                return false;
1437
            }
1438
        }else{
1439
            return false;
1440
        }
1441
    }
1442

    
1443
    @Override
1444
    @Transient
1445
    public List<TaggedText> getTaggedName(){
1446
        INameCacheStrategy strat = cacheStrategy();
1447
        return strat.getTaggedTitle(this);
1448
    }
1449

    
1450
    @Override
1451
    @Transient
1452
    public List<TaggedText> getTaggedFullTitle() {
1453
        INameCacheStrategy strat = cacheStrategy();
1454
        return strat.getTaggedFullTitle(this);
1455
    }
1456

    
1457
    @Override
1458
    @Transient
1459
    public String getFullTitleCache(){
1460
        if (protectedFullTitleCache){
1461
            return this.fullTitleCache;
1462
        }
1463
        updateAuthorshipCache();
1464
        if (fullTitleCache == null ){
1465
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1466
        }
1467
        return fullTitleCache;
1468
    }
1469

    
1470
    @Override
1471
    public String getTitleCache(){
1472
        if(!protectedTitleCache) {
1473
            updateAuthorshipCache();
1474
        }
1475
        return super.getTitleCache();
1476
    }
1477

    
1478
    @Override
1479
    public void setTitleCache(String titleCache, boolean protectCache){
1480
        super.setTitleCache(titleCache, protectCache);
1481
    }
1482

    
1483
    /**
1484
     * Returns the concatenated and formated authorteams string including
1485
     * basionym and combination authors of <i>this</i> non viral taxon name.
1486
     * If the protectedAuthorshipCache flag is set this method returns the
1487
     * string stored in the the authorshipCache attribute, otherwise it
1488
     * generates the complete authorship string, returns it and stores it in
1489
     * the authorshipCache attribute.
1490
     *
1491
     * @return  the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
1492
     * @see     #generateAuthorship()
1493
     */
1494
    @Override
1495
    @Transient
1496
    public String getAuthorshipCache() {
1497
        if (protectedAuthorshipCache){
1498
            return this.authorshipCache;
1499
        }
1500
        if (this.authorshipCache == null ){
1501
            this.authorshipCache = generateAuthorship();
1502
        }else{
1503
            //TODO get isDirty of authors, make better if possible
1504
            this.setAuthorshipCache(generateAuthorship(), protectedAuthorshipCache); //throw change event to inform higher caches
1505

    
1506
        }
1507
        return authorshipCache;
1508
    }
1509

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

    
1522
    /**
1523
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1524
     *
1525
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1526
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1527
     * the flag is set to <code>false</code>.
1528
     * @see    #getAuthorshipCache()
1529
     */
1530
    @Override
1531
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1532
        this.authorshipCache = authorshipCache;
1533
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1534
    }
1535

    
1536
    /**
1537
     * Generates and returns a concatenated and formated author team string
1538
     * including basionym and combination authors of <i>this</i> taxon name
1539
     * according to the strategy defined in
1540
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName) INonViralNameCacheStrategy}.
1541
     *
1542
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1543
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName)
1544
     */
1545
    @Override
1546
    public String generateAuthorship(){
1547
        if (cacheStrategy() == null){
1548
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1549
            return null;
1550
        }else{
1551
            return cacheStrategy().getAuthorshipCache(this);
1552
        }
1553
    }
1554

    
1555
    /**
1556
     * Tests if the given name has any authors.
1557
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1558
     */
1559
    @Override
1560
    public boolean hasAuthors() {
1561
        return (this.getCombinationAuthorship() != null ||
1562
                this.getExCombinationAuthorship() != null ||
1563
                this.getBasionymAuthorship() != null ||
1564
                this.getExBasionymAuthorship() != null);
1565
    }
1566

    
1567
    /**
1568
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1569
     */
1570
    @Override
1571
    public String computeCombinationAuthorNomenclaturalTitle() {
1572
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1573
    }
1574

    
1575
    /**
1576
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1577
     */
1578
    @Override
1579
    public String computeBasionymAuthorNomenclaturalTitle() {
1580
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1581
    }
1582

    
1583

    
1584
    /**
1585
     * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1586
     */
1587
    @Override
1588
    public String computeExCombinationAuthorNomenclaturalTitle() {
1589
        return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1590
    }
1591

    
1592
    /**
1593
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1594
     */
1595
    @Override
1596
    public String computeExBasionymAuthorNomenclaturalTitle() {
1597
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1598
    }
1599

    
1600
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1601
        if (author == null){
1602
            return null;
1603
        }else{
1604
            return author.getNomenclaturalTitleCache();
1605
        }
1606
    }
1607

    
1608
    /**
1609
     * Returns the set of all {@link NameRelationship name relationships}
1610
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1611
     * in some name relationships or target in some others.
1612
     *
1613
     * @see    #getRelationsToThisName()
1614
     * @see    #getRelationsFromThisName()
1615
     * @see    #addNameRelationship(NameRelationship)
1616
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1617
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1618
     */
1619
    @Override
1620
    @Transient
1621
    public Set<NameRelationship> getNameRelations() {
1622
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1623
        rels.addAll(getRelationsFromThisName());
1624
        rels.addAll(getRelationsToThisName());
1625
        return rels;
1626
    }
1627

    
1628
    @Override
1629
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1630
        return addRelationshipToName(toName, type, null, null, ruleConsidered, codeEdition);
1631
    }
1632

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

    
1638
    @Override
1639
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1640
        if (toName == null){
1641
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1642
        }
1643
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered, codeEdition);
1644
        return rel;
1645
    }
1646

    
1647
    @Override
1648
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type){
1649
        return addRelationshipFromName(fromName, type, null, null, null, null);
1650
    }
1651

    
1652
    @Override
1653
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1654
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1655
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered, codeEdition);
1656
    }
1657

    
1658
    @Override
1659
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1660
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered, codeEdition);
1661
    }
1662

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

    
1703
        TaxonName fromName = nameRelation.getFromName();
1704
        TaxonName toName = nameRelation.getToName();
1705

    
1706
        if (nameRelation != null) {
1707
            nameRelation.setToName(null);
1708
            nameRelation.setFromName(null);
1709
        }
1710

    
1711
        if (fromName != null) {
1712
            fromName.removeNameRelationship(nameRelation);
1713
        }
1714

    
1715
        if (toName != null) {
1716
            toName.removeNameRelationship(nameRelation);
1717
        }
1718

    
1719
        this.relationsToThisName.remove(nameRelation);
1720
        this.relationsFromThisName.remove(nameRelation);
1721
    }
1722

    
1723
    @Override
1724
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1725
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1726
//		nameRelationships.addAll(this.getNameRelations());
1727
        nameRelationships.addAll(this.getRelationsFromThisName());
1728
        nameRelationships.addAll(this.getRelationsToThisName());
1729
        for(NameRelationship nameRelationship : nameRelationships) {
1730
            // remove name relationship from this side
1731
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1732
                this.removeNameRelationship(nameRelationship);
1733
            }
1734
        }
1735
    }
1736

    
1737
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1738

    
1739
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1740
        for(NameRelationship nameRelationship : tmpRels) {
1741
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1742
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1743
                if (type == null || type.equals(nameRelationship.getType())){
1744
                    this.removeNameRelationship(nameRelationship);
1745
                }
1746
            }
1747
        }
1748
    }
1749

    
1750
    /**
1751
     * If relation is of type NameRelationship, addNameRelationship is called;
1752
     * if relation is of type HybridRelationship addHybridRelationship is called,
1753
     * otherwise an IllegalArgumentException is thrown.
1754
     *
1755
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1756
     * @see    	   		#addNameRelationship(NameRelationship)
1757
     * @see    	   		#getNameRelations()
1758
     * @see    	   		NameRelationship
1759
     * @see    	   		RelationshipBase
1760
     * @see             #addHybridRelationship(HybridRelationship)
1761

    
1762
     * @deprecated to be used by RelationshipBase only
1763
     */
1764
    @Deprecated
1765
    @Override
1766
    public void addRelationship(RelationshipBase relation) {
1767
        if (relation instanceof NameRelationship){
1768
            addNameRelationship((NameRelationship)relation);
1769

    
1770
        }else if (relation instanceof HybridRelationship){
1771
            addHybridRelationship((HybridRelationship)relation);
1772
        }else{
1773
            logger.warn("Relationship not of type NameRelationship!");
1774
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1775
        }
1776
    }
1777

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

    
1794
    /**
1795
     * Returns the set of all {@link NameRelationship name relationships}
1796
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1797
     *
1798
     * @see    #getNameRelations()
1799
     * @see    #getRelationsFromThisName()
1800
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1801
     */
1802
    @Override
1803
    public Set<NameRelationship> getRelationsToThisName() {
1804
        if(relationsToThisName == null) {
1805
            this.relationsToThisName = new HashSet<>();
1806
        }
1807
        return relationsToThisName;
1808
    }
1809

    
1810
    /**
1811
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1812
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1813
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1814
     * and the nomenclatural code rule considered.
1815
     *
1816
     * @see     NomenclaturalStatus
1817
     * @see     NomenclaturalStatusType
1818
     */
1819
    @Override
1820
    public Set<NomenclaturalStatus> getStatus() {
1821
        if(status == null) {
1822
            this.status = new HashSet<>();
1823
        }
1824
        return status;
1825
    }
1826

    
1827
    /**
1828
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1829
     * to <i>this</i> taxon name's set of nomenclatural status.
1830
     *
1831
     * @param  nomStatus  the nomenclatural status to be added
1832
     * @see 			  #getStatus()
1833
     */
1834
    @Override
1835
    public void addStatus(NomenclaturalStatus nomStatus) {
1836
        this.status.add(nomStatus);
1837
        if (!this.equals(nomStatus.getName())){
1838
            nomStatus.setName(this);
1839
        }
1840
    }
1841
    @Override
1842
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1843
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1844
        addStatus(newStatus);
1845
        return newStatus;
1846
    }
1847

    
1848
    /**
1849
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1850
     * Type and ruleConsidered attributes of the nomenclatural status object
1851
     * will be nullified.
1852
     *
1853
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1854
     * @see     		  #getStatus()
1855
     */
1856
    @Override
1857
    public void removeStatus(NomenclaturalStatus nomStatus) {
1858
        //TODO to be implemented?
1859
        logger.warn("not yet fully implemented?");
1860
        this.status.remove(nomStatus);
1861
    }
1862

    
1863
    public void setStatus(Set<NomenclaturalStatus> nomStatus) throws SetterAdapterException {
1864
        new EntityCollectionSetterAdapter<TaxonName, NomenclaturalStatus>(TaxonName.class, NomenclaturalStatus.class, "status", "addStatus", "removeStatus").setCollection(this, nomStatus);
1865
    }
1866

    
1867
    /**
1868
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1869
     * strings or year according to the strategy defined in
1870
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1871
     * The result might be stored in {@link #getNameCache() nameCache} if the
1872
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1873
     *
1874
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1875
     * @see     #getNameCache()
1876
     */
1877
    protected String generateNameCache(){
1878
        if (cacheStrategy() == null){
1879
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1880
            return null;
1881
        }else{
1882
            return cacheStrategy().getNameCache(this);
1883
        }
1884
    }
1885

    
1886
    /**
1887
     * Returns or generates the nameCache (scientific name
1888
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1889
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1890
     * the string will be generated according to a defined strategy,
1891
     * otherwise the value of the actual nameCache string will be returned.
1892
     *
1893
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1894
     * @see     #generateNameCache()
1895
     */
1896
    @Override
1897
    @Transient
1898
    public String getNameCache() {
1899
        if (protectedNameCache){
1900
            return this.nameCache;
1901
        }
1902
        // is title dirty, i.e. equal NULL?
1903
        if (nameCache == null){
1904
            this.nameCache = generateNameCache();
1905
        }
1906
        return nameCache;
1907
    }
1908

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

    
1921
    /**
1922
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1923
     * Sets the protectedNameCache flag to <code>true</code>.
1924
     *
1925
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1926
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
1927
     * <code>false</code>
1928
     * @see    #getNameCache()
1929
     */
1930
    @Override
1931
    public void setNameCache(String nameCache, boolean protectedNameCache){
1932
        this.nameCache = nameCache;
1933
        this.setProtectedNameCache(protectedNameCache);
1934
    }
1935

    
1936

    
1937
    /**
1938
     * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
1939
     * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1940
     * of any other taxon name. Returns "true", if a basionym or a replaced
1941
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1942
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1943
     * homotypical group).
1944
     */
1945
    @Override
1946
    @Transient
1947
    public boolean isOriginalCombination(){
1948
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1949
        for (NameRelationship relation : relationsFromThisName) {
1950
            if (relation.getType().isBasionymRelation() ||
1951
                    relation.getType().isReplacedSynonymRelation()) {
1952
                return true;
1953
            }
1954
        }
1955
        return false;
1956
    }
1957

    
1958
    /**
1959
     * Indicates <i>this</i> taxon name is a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1960
     * of any other taxon name. Returns "true", if a replaced
1961
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1962
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1963
     * homotypical group).
1964
     */
1965
    @Override
1966
    @Transient
1967
    public boolean isReplacedSynonym(){
1968
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1969
        for (NameRelationship relation : relationsFromThisName) {
1970
            if (relation.getType().isReplacedSynonymRelation()) {
1971
                return true;
1972
            }
1973
        }
1974
        return false;
1975
    }
1976

    
1977
    /**
1978
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
1979
     * The basionym of a taxon name is its epithet-bringing synonym.
1980
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
1981
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
1982
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
1983
     *
1984
     * If more than one basionym exists one is choosen at radom.
1985
     *
1986
     * If no basionym exists null is returned.
1987
     */
1988
    @Override
1989
    @Transient
1990
    public TaxonName getBasionym(){
1991
        Set<TaxonName> basionyms = getBasionyms();
1992
        if (basionyms.size() == 0){
1993
            return null;
1994
        }else{
1995
            return basionyms.iterator().next();
1996
        }
1997
    }
1998

    
1999
    /**
2000
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2001
     * The basionym of a taxon name is its epithet-bringing synonym.
2002
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2003
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2004
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2005
     */
2006
    @Override
2007
    @Transient
2008
    public Set<TaxonName> getBasionyms(){
2009
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
2010
    }
2011

    
2012
    /**
2013
     *
2014
     * @param direction
2015
     * @param type
2016
     * @return
2017
     */
2018
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
2019
        return getRelatedNames(relationsWithThisName(direction), type);
2020
    }
2021

    
2022
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2023
        Set<TaxonName> result = new HashSet<>();
2024
        for (NameRelationship rel : rels){
2025
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2026
                TaxonName basionym = rel.getFromName();
2027
                result.add(basionym);
2028
            }
2029
        }
2030
        return result;
2031
    }
2032

    
2033
    @Override
2034
    @Transient
2035
    public TaxonName getOriginalSpelling(){
2036
        if (this.getNomenclaturalSource() != null){
2037
            return this.getNomenclaturalSource().getNameUsedInSource();
2038
        }else{
2039
            return null;
2040
        }
2041
    }
2042

    
2043
    @Override
2044
    @Transient
2045
    public void setOriginalSpelling(TaxonName originalSpelling){
2046
        if (originalSpelling != null){
2047
            this.getNomenclaturalSource(true).setNameUsedInSource(originalSpelling);
2048
        }else if (this.getNomenclaturalSource() != null){
2049
            this.getNomenclaturalSource().setNameUsedInSource(null);
2050
            checkNullSource();
2051
        }
2052
    }
2053

    
2054
    @Override
2055
    @Transient
2056
    public void setOriginalNameString(String originalNameString){
2057
        if (isNotBlank(originalNameString)){
2058
            this.getNomenclaturalSource(true).setOriginalNameString(originalNameString);
2059
        }else if (this.getNomenclaturalSource() != null){
2060
            this.getNomenclaturalSource().setOriginalNameString(null);
2061
            checkNullSource();
2062
        }
2063
    }
2064

    
2065
    /**
2066
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2067
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2068
     * and to the basionym. The basionym cannot have itself as a basionym.
2069
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2070
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2071
     *
2072
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2073
     * @see  				#getBasionym()
2074
     * @see  				#addBasionym(TaxonName, String)
2075
     */
2076
    @Override
2077
    public NameRelationship addBasionym(TaxonName basionym){
2078
        return addBasionym(basionym, null, null, null, null);
2079
    }
2080
    /**
2081
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2082
     * and keeps the nomenclatural rule considered for it. The basionym
2083
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2084
     * The basionym cannot have itself as a basionym.
2085
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2086
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2087
     *
2088
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2089
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2090
     * @param codeEdition     the edition of the nomenclatural code where the <code>ruleConsidered</code> has been published.
2091
     * @return
2092
     * @see  					#getBasionym()
2093
     * @see  					#addBasionym(TaxonName)
2094
     */
2095
    @Override
2096
    public NameRelationship addBasionym(TaxonName basionym, Reference citation, String microcitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
2097
        if (basionym != null){
2098
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered, codeEdition);
2099
        }else{
2100
            return null;
2101
        }
2102
    }
2103

    
2104
    /**
2105
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2106
     */
2107
    @Override
2108
    @Transient
2109
    public Set<TaxonName> getReplacedSynonyms(){
2110
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2111
    }
2112

    
2113
    /**
2114
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2115
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2116
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2117
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2118
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2119
     *
2120
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2121
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2122
     * @param codeEdition     the edition of the nomenclatural code where the <code>ruleConsidered</code> has been published.
2123
     * @see  					#getBasionym()
2124
     * @see  					#addBasionym(TaxonName)
2125
     */
2126
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2127
    @Override
2128
    public void addReplacedSynonym(TaxonName replacedSynonym, Reference citation, String microcitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
2129
        if (replacedSynonym != null){
2130
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered, codeEdition);
2131
        }
2132
    }
2133

    
2134
    /**
2135
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2136
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2137
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2138
     * previously used as basionym.
2139
     *
2140
     * @see   #getBasionym()
2141
     * @see   #addBasionym(TaxonName)
2142
     */
2143
    @Override
2144
    public void removeBasionyms(){
2145
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2146
    }
2147

    
2148

    
2149
    /**
2150
     * Removes all {@link NameRelationship relationships} of the given <code>type</code> from the set of
2151
     * relations in the specified <code>direction</code> direction wich are related from or to this
2152
     * <i>this</i> taxon name. The same relationship will be removed from the set of
2153
     * reverse relations of the other taxon name.
2154
     *
2155
     * @param direction
2156
     * @param type
2157
     */
2158
    public void removeNameRelations(Direction direction, NameRelationshipType type) {
2159
        Set<NameRelationship> relationsWithThisName = relationsWithThisName(direction);
2160
        Set<NameRelationship> removeRelations = new HashSet<>();
2161
        for (NameRelationship nameRelation : relationsWithThisName){
2162
            if (nameRelation.getType().isRelationshipType(type)){
2163
                removeRelations.add(nameRelation);
2164
            }
2165
        }
2166
        // Removing relations from a set through which we are iterating causes a
2167
        // ConcurrentModificationException. Therefore, we delete the targeted
2168
        // relations in a second step.
2169
        for (NameRelationship relation : removeRelations){
2170
            this.removeNameRelationship(relation);
2171
        }
2172
    }
2173

    
2174
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2175
        switch(direction) {
2176
            case relatedTo:
2177
                return this.getRelationsToThisName();
2178
            case relatedFrom:
2179
                return this.getRelationsFromThisName();
2180
            default: throw new RuntimeException("invalid Direction:" + direction);
2181
        }
2182
    }
2183

    
2184
    /**
2185
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2186
     *
2187
     * @see 	Rank
2188
     */
2189
    @Override
2190
    public Rank getRank(){
2191
        return this.rank;
2192
    }
2193
    /**
2194
     * @see  #getRank()
2195
     */
2196
    @Override
2197
    public void setRank(Rank rank){
2198
        this.rank = rank;
2199
    }
2200

    
2201
//*************** nom ref/source *******************/
2202

    
2203
    @Override
2204
    public NomenclaturalSource getNomenclaturalSource(){
2205
        return this.nomenclaturalSource;
2206
    }
2207

    
2208
    public NomenclaturalSource getNomenclaturalSource(boolean createIfNotExist){
2209
        if (this.nomenclaturalSource == null && createIfNotExist){
2210
            setNomenclaturalSource(NomenclaturalSource.NewNomenclaturalInstance(this));
2211
        }
2212
        return nomenclaturalSource;
2213
    }
2214

    
2215
    @Override
2216
    public void setNomenclaturalSource(NomenclaturalSource nomenclaturalSource) throws IllegalArgumentException {
2217
        //check state
2218
        if (nomenclaturalSource != null && !OriginalSourceType.PrimaryTaxonomicSource.equals(nomenclaturalSource.getType())
2219
                ){
2220
            throw new IllegalArgumentException("Nomenclatural source must be of type " + OriginalSourceType.PrimaryTaxonomicSource.getLabel());
2221
        }
2222
        this.nomenclaturalSource = nomenclaturalSource;
2223
        if (nomenclaturalSource != null && nomenclaturalSource.getSourcedName() != this){
2224
            nomenclaturalSource.setSourcedName(this);
2225
        }
2226
    }
2227

    
2228
    @Override
2229
    @Transient
2230
    public Reference getNomenclaturalReference(){
2231
        return this.nomenclaturalSource == null? null:this.nomenclaturalSource.getCitation();
2232
    }
2233

    
2234
    @Override
2235
    @Transient
2236
    public void setNomenclaturalReference(Reference nomenclaturalReference){
2237
        if (nomenclaturalReference == null && this.getNomenclaturalSource()==null){
2238
            return;
2239
        }else{
2240
            getNomenclaturalSource(true).setCitation(nomenclaturalReference);
2241
        }
2242
    }
2243

    
2244
    @Override
2245
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2246
        setNomenclaturalReference(CdmBase.deproxy(nomenclaturalReference, Reference.class));
2247
    }
2248

    
2249
    /**
2250
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2251
     * to <i>this</i> taxon name. The details describe the exact localisation within
2252
     * the publication used as nomenclature reference. These are mostly
2253
     * (implicitly) pages but can also be figures or tables or any other
2254
     * element of a publication. A nomenclatural micro reference (details)
2255
     * requires the existence of a nomenclatural reference.
2256
     */
2257
    //Details of the nomenclatural reference (protologue).
2258
    @Override
2259
    @Transient
2260
    public String getNomenclaturalMicroReference(){
2261
        return this.nomenclaturalSource == null? null: this.nomenclaturalSource.getCitationMicroReference();
2262
    }
2263

    
2264
    /**
2265
     * @see  #getNomenclaturalMicroReference()
2266
     */
2267
    @Override
2268
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2269
        nomenclaturalMicroReference = isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
2270
        if (nomenclaturalMicroReference == null && this.getNomenclaturalSource()==null){
2271
            return;
2272
        }else{
2273
            this.getNomenclaturalSource(true).setCitationMicroReference(nomenclaturalMicroReference);
2274
        }
2275
    }
2276

    
2277
    /**
2278
     * Checks if the source is completely empty and if empty removes it from the name.
2279
     */
2280
    //TODO maybe this should be moved to a hibernate listener, but the listener solution may
2281
    //work only for nomenclatural single sources as they are the only which are bidirectional
2282
    protected void checkNullSource() {
2283
        if (this.nomenclaturalSource != null && this.nomenclaturalSource.checkEmpty(true)){
2284
            this.nomenclaturalSource = null;
2285
        }
2286
    }
2287

    
2288
// *********************************************
2289

    
2290
    /**
2291
     * Creates a new external link for given uri with default language description if description exists
2292
     * and adds it to the nomenclatural source.
2293
     * If no nomenclatural source exists a new one is created.
2294
     * @param uri the url to the protogue
2295
     * @param description an additional description for the link for the default language
2296
     * @param type see {@link ExternalLinkType}
2297
     * @return the newly created {@link ExternalLink external link}
2298
     */
2299
    public ExternalLink addProtologue(URI uri, String description, ExternalLinkType type){
2300
        ExternalLink newProtologue = ExternalLink.NewInstance(type, uri, description, null);
2301
        getNomenclaturalSource(true).addLink(newProtologue);
2302
        return newProtologue;
2303
    }
2304

    
2305
    /**
2306
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2307
     * The appended phrase is a non-atomised addition to a name. It is
2308
     * not ruled by a nomenclatural code.
2309
     */
2310
    @Override
2311
    public String getAppendedPhrase(){
2312
        return this.appendedPhrase;
2313
    }
2314

    
2315
    /**
2316
     * @see  #getAppendedPhrase()
2317
     */
2318
    @Override
2319
    public void setAppendedPhrase(String appendedPhrase){
2320
        this.appendedPhrase = isBlank(appendedPhrase)? null : appendedPhrase;
2321
    }
2322

    
2323
    @Override
2324
    public int getParsingProblem(){
2325
        return this.parsingProblem;
2326
    }
2327

    
2328
    @Override
2329
    public void setParsingProblem(int parsingProblem){
2330
        this.parsingProblem = parsingProblem;
2331
    }
2332

    
2333
    @Override
2334
    public void addParsingProblem(ParserProblem problem){
2335
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2336
    }
2337

    
2338
    @Override
2339
    public void removeParsingProblem(ParserProblem problem) {
2340
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2341
    }
2342

    
2343
    /**
2344
     * @param warnings
2345
     */
2346
    @Override
2347
    public void addParsingProblems(int problems){
2348
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2349
    }
2350

    
2351
    @Override
2352
    public boolean hasProblem(){
2353
        return parsingProblem != 0;
2354
    }
2355

    
2356
    @Override
2357
    public boolean hasProblem(ParserProblem problem) {
2358
        return getParsingProblems().contains(problem);
2359
    }
2360

    
2361
    @Override
2362
    public int getProblemStarts(){
2363
        return this.problemStarts;
2364
    }
2365

    
2366
    @Override
2367
    public void setProblemStarts(int start) {
2368
        this.problemStarts = start;
2369
    }
2370

    
2371
    @Override
2372
    public int getProblemEnds(){
2373
        return this.problemEnds;
2374
    }
2375

    
2376
    @Override
2377
    public void setProblemEnds(int end) {
2378
        this.problemEnds = end;
2379
    }
2380

    
2381
//*********************** TYPE DESIGNATION *********************************************//
2382

    
2383
    /**
2384
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2385
     * to <i>this</i> taxon name.
2386
     * @see     NameTypeDesignation
2387
     * @see     SpecimenTypeDesignation
2388
     */
2389
    @Override
2390
    public Set<TypeDesignationBase> getTypeDesignations() {
2391
        if(typeDesignations == null) {
2392
            this.typeDesignations = new HashSet<>();
2393
        }
2394
        return typeDesignations;
2395
    }
2396

    
2397
    /**
2398
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2399
     * <i>this</i> taxon name. The type designation itself will be nullified.
2400
     *
2401
     * @param  typeDesignation  the type designation which should be deleted
2402
     */
2403
    @Override
2404
    @SuppressWarnings("deprecation")
2405
    public void removeTypeDesignation(TypeDesignationBase<?> typeDesignation) {
2406
        this.typeDesignations.remove(typeDesignation);
2407
        typeDesignation.removeTypifiedName(this);
2408
    }
2409

    
2410
    /**
2411
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2412
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2413
     * "species" or below. The specimen type designations include all the
2414
     * specimens on which the typification of this name is based (which are
2415
     * exclusively used to typify taxon names belonging to the same
2416
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2417
     * belongs) and eventually the status of these designations.
2418
     *
2419
     * @see     SpecimenTypeDesignation
2420
     * @see     NameTypeDesignation
2421
     * @see     HomotypicalGroup
2422
     */
2423
    @Override
2424
    @Transient
2425
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2426
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2427
    }
2428

    
2429
//*********************** NAME TYPE DESIGNATION *********************************************//
2430

    
2431
    /**
2432
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2433
     * to <i>this</i> taxon name the rank of which must be above "species".
2434
     * The name type designations include all the taxon names used to typify
2435
     * <i>this</i> taxon name and eventually the rejected or conserved status
2436
     * of these designations.
2437
     *
2438
     * @see     NameTypeDesignation
2439
     * @see     SpecimenTypeDesignation
2440
     */
2441
    @Override
2442
    @Transient
2443
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2444
        Set<NameTypeDesignation> result = new HashSet<>();
2445
        for (TypeDesignationBase<?> typeDesignation : this.typeDesignations){
2446
            if (typeDesignation.isInstanceOf(NameTypeDesignation.class)){
2447
                result.add(CdmBase.deproxy(typeDesignation, NameTypeDesignation.class));
2448
            }
2449
        }
2450
        return result;
2451
    }
2452

    
2453
    /**
2454
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2455
     * to <i>this</i> taxon name's set of type designations.
2456
     *
2457
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2458
     * @param  citation					the reference for this new designation
2459
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2460
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2461
     * @param  isRejectedType			the boolean status for a rejected name type designation
2462
     * @param  isConservedType			the boolean status for a conserved name type designation
2463
     * @param  isLectoType				the boolean status for a lectotype name type designation
2464
     * @param  isNotDesignated			the boolean status for a name type designation without name type
2465
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2466
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2467
     * @return
2468
     * @see 			  				#getNameTypeDesignations()
2469
     * @see 			  				NameTypeDesignation
2470
     * @see 			  				TypeDesignationBase#isNotDesignated()
2471
     */
2472
    @Override
2473
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2474
                Reference citation,
2475
                String citationMicroReference,
2476
                String originalNameString,
2477
                NameTypeDesignationStatus status,
2478
                boolean isRejectedType,
2479
                boolean isConservedType,
2480
                /*boolean isLectoType, */
2481
                boolean isNotDesignated,
2482
                boolean addToAllHomotypicNames) {
2483
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalNameString, status, isRejectedType, isConservedType, isNotDesignated);
2484
        //nameTypeDesignation.setLectoType(isLectoType);
2485
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2486
        return nameTypeDesignation;
2487
    }
2488

    
2489
    /**
2490
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2491
     * to <i>this</i> taxon name's set of type designations.
2492
     *
2493
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2494
     * @param  citation					the reference for this new designation
2495
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2496
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2497
     * @param  status                   the name type designation status
2498
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2499
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2500
     * @return
2501
     * @see 			  				#getNameTypeDesignations()
2502
     * @see 			  				NameTypeDesignation
2503
     * @see 			  				TypeDesignationBase#isNotDesignated()
2504
     */
2505
    @Override
2506
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2507
                Reference citation,
2508
                String citationMicroReference,
2509
                String originalNameString,
2510
                NameTypeDesignationStatus status,
2511
                boolean addToAllHomotypicNames) {
2512
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2513
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2514
        return nameTypeDesignation;
2515
    }
2516

    
2517
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2518

    
2519
    /**
2520
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2521
     * that typify <i>this</i> taxon name.
2522
     */
2523
    @Override
2524
    @Transient
2525
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2526
        Set<SpecimenTypeDesignation> result = new HashSet<>();
2527
        for (TypeDesignationBase<?> typeDesignation : this.typeDesignations){
2528
            if (typeDesignation.isInstanceOf(SpecimenTypeDesignation.class)){
2529
                result.add(CdmBase.deproxy(typeDesignation, SpecimenTypeDesignation.class));
2530
            }
2531
        }
2532
        return result;
2533
    }
2534

    
2535

    
2536
    /**
2537
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2538
     * to <i>this</i> taxon name's set of type designations.
2539
     *
2540
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2541
     * @param  status					the specimen type designation status
2542
     * @param  citation					the reference for this new specimen type designation
2543
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2544
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2545
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2546
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2547
     * 									added to all taxon names of the homotypical group the typified
2548
     * 									taxon name belongs to
2549
     * @return
2550
     * @see 			  				#getSpecimenTypeDesignations()
2551
     * @see 			  				SpecimenTypeDesignationStatus
2552
     * @see 			  				SpecimenTypeDesignation
2553
     * @see 			  				TypeDesignationBase#isNotDesignated()
2554
     */
2555
    @Override
2556
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2557
                SpecimenTypeDesignationStatus status,
2558
                Reference citation,
2559
                String citationMicroReference,
2560
                String originalNameString,
2561
                boolean isNotDesignated,
2562
                boolean addToAllHomotypicNames) {
2563
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2564
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2565
        return specimenTypeDesignation;
2566
    }
2567

    
2568
    @Override
2569
    public TextualTypeDesignation addTextualTypeDesignation(
2570
                String text,
2571
                Language language,
2572
                boolean isVerbatim,
2573
                Reference citation,
2574
                String citationMicroReference,
2575
                String originalNameString,
2576
                boolean addToAllHomotypicNames) {
2577
        TextualTypeDesignation textualTypeDesignation = TextualTypeDesignation.NewInstance(text, language, isVerbatim, citation, citationMicroReference, originalNameString);
2578
        addTypeDesignation(textualTypeDesignation, addToAllHomotypicNames);
2579
        return textualTypeDesignation;
2580
    }
2581

    
2582
    //used by merge strategy
2583
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2584
        return addTypeDesignation(typeDesignation, true);
2585
    }
2586

    
2587
    /**
2588
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2589
     *
2590
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2591
     * @param addToAllNames				the boolean indicating whether the type designation should be
2592
     * 									added to all taxon names of the homotypical group the typified
2593
     * 									taxon name belongs to
2594
     * @return							true if the operation was successful
2595
     *
2596
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2597
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2598
     *
2599
     */
2600
    @Override
2601
    public boolean addTypeDesignation(TypeDesignationBase<?> typeDesignation, boolean addToAllNames){
2602
        //currently typeDesignations are not persisted with the homotypical group
2603
        //so explicit adding to the homotypical group is not necessary.
2604
        if (typeDesignation != null){
2605
            checkHomotypicalGroup(typeDesignation);
2606
            this.typeDesignations.add(typeDesignation);
2607
            typeDesignation.addTypifiedName(this);
2608

    
2609
            if (addToAllNames){
2610
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2611
                    if (taxonName != this){
2612
                        taxonName.addTypeDesignation(typeDesignation, false);
2613
                    }
2614
                }
2615
            }
2616
        }
2617
        return true;
2618
    }
2619

    
2620
    /**
2621
     * Throws an Exception this type designation already has typified names from another homotypical group.
2622
     * @param typeDesignation
2623
     */
2624
    private void checkHomotypicalGroup(TypeDesignationBase<?> typeDesignation) {
2625
        if(typeDesignation.getTypifiedNames().size() > 0){
2626
            Set<HomotypicalGroup> groups = new HashSet<>();
2627
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2628
            for (TaxonName taxonName: names){
2629
                groups.add(taxonName.getHomotypicalGroup());
2630
            }
2631
            if (groups.size() > 1){
2632
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2633
            }
2634
        }
2635
    }
2636

    
2637

    
2638

    
2639
//*********************** HOMOTYPICAL GROUP *********************************************//
2640

    
2641

    
2642
    /**
2643
     * Returns the {@link HomotypicalGroup homotypical group} to which
2644
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2645
     * that share the same types.
2646
     *
2647
     * @see 	HomotypicalGroup
2648
     */
2649

    
2650
    @Override
2651
    public HomotypicalGroup getHomotypicalGroup() {
2652
        if (homotypicalGroup == null){
2653
            homotypicalGroup = new HomotypicalGroup();
2654
            homotypicalGroup.typifiedNames.add(this);
2655
        }
2656
    	return homotypicalGroup;
2657
    }
2658

    
2659
    /**
2660
     * @see #getHomotypicalGroup()
2661
     */
2662
    @Override
2663
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2664
        if (homotypicalGroup == null){
2665
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2666
        }
2667
        /*if (this.homotypicalGroup != null){
2668
        	this.homotypicalGroup.removeTypifiedName(this, false);
2669
        }*/
2670
        this.homotypicalGroup = homotypicalGroup;
2671
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2672
        	 this.homotypicalGroup.addTypifiedName(this);
2673
        }
2674
    }
2675

    
2676

    
2677

    
2678
// *************************************************************************//
2679

    
2680
    /**
2681
     * Returns the complete string containing the
2682
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2683
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2684
     *
2685
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2686
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2687
     * @see		#getNomenclaturalReference()
2688
     * @see		#getNomenclaturalMicroReference()
2689
     */
2690
    @Override
2691
    @Transient
2692
    public String getCitationString(){
2693
        NomenclaturalSource nomSource = getNomenclaturalSource();
2694
        return NomenclaturalSourceFormatter.INSTANCE().format(nomSource);
2695
    }
2696

    
2697
    /**
2698
     * Returns the parsing problems
2699
     * @return
2700
     */
2701
    @Override
2702
    public List<ParserProblem> getParsingProblems(){
2703
        return ParserProblem.warningList(this.parsingProblem);
2704
    }
2705

    
2706
    /**
2707
     * Returns the string containing the publication date (generally only year)
2708
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2709
     * no nomenclatural reference.
2710
     *
2711
     * @return  the string containing the publication date of <i>this</i> taxon name
2712
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2713
     */
2714
    @Override
2715
    @Transient
2716
    @ValidTaxonomicYear(groups=Level3.class)
2717
    public String getReferenceYear(){
2718
        if (this.getNomenclaturalReference() != null ){
2719
            return this.getNomenclaturalReference().getYear();
2720
        }else{
2721
            return null;
2722
        }
2723
    }
2724

    
2725
    /**
2726
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2727
     * In this context a taxon base means the use of a taxon name by a reference
2728
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2729
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2730
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2731
     * within a taxonomic treatment (identified by one reference).
2732
     *
2733
     * @see	#getTaxa()
2734
     * @see	#getSynonyms()
2735
     */
2736
    @Override
2737
    public Set<TaxonBase> getTaxonBases() {
2738
        if(taxonBases == null) {
2739
            this.taxonBases = new HashSet<>();
2740
        }
2741
        return this.taxonBases;
2742
    }
2743

    
2744
    /**
2745
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2746
     * to the set of taxon bases using <i>this</i> taxon name.
2747
     *
2748
     * @param  taxonBase  the taxon base to be added
2749
     * @see 			  #getTaxonBases()
2750
     * @see 			  #removeTaxonBase(TaxonBase)
2751
     */
2752
    //TODO protected
2753
    @Override
2754
    public void addTaxonBase(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[] {this});
2758
        taxonBases.add(taxonBase);
2759
    }
2760
    /**
2761
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2762
     *
2763
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2764
     * @see    				#getTaxonBases()
2765
     * @see    				#addTaxonBase(TaxonBase)
2766
     */
2767
    @Override
2768
    public void removeTaxonBase(TaxonBase taxonBase){
2769
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2770
        ReflectionUtils.makeAccessible(method);
2771
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2772

    
2773

    
2774
    }
2775

    
2776
    /**
2777
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2778
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2779
     * the set returned by getTaxonBases().
2780
     *
2781
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2782
     * @see	#getTaxonBases()
2783
     * @see	#getSynonyms()
2784
     */
2785
    @Override
2786
    @Transient
2787
    public Set<Taxon> getTaxa(){
2788
        Set<Taxon> result = new HashSet<>();
2789
        for (TaxonBase<?> taxonBase : this.taxonBases){
2790
            if (taxonBase instanceof Taxon){
2791
                result.add((Taxon)taxonBase);
2792
            }
2793
        }
2794
        return result;
2795
    }
2796

    
2797
    /**
2798
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2799
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2800
     * the set returned by getTaxonBases().
2801
     *
2802
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2803
     * @see	#getTaxonBases()
2804
     * @see	#getTaxa()
2805
     */
2806
    @Override
2807
    @Transient
2808
    public Set<Synonym> getSynonyms() {
2809
        Set<Synonym> result = new HashSet<>();
2810
        for (TaxonBase<?> taxonBase : this.taxonBases){
2811
            if (taxonBase instanceof Synonym){
2812
                result.add((Synonym)taxonBase);
2813
            }
2814
        }
2815
        return result;
2816
    }
2817

    
2818
//***************** REGISTRATION *****************/
2819

    
2820
    @Override
2821
    public Set<Registration> getRegistrations() {
2822
        return this.registrations;
2823
    }
2824

    
2825

    
2826
// ************* RELATIONSHIPS *****************************/
2827

    
2828
    /**
2829
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2830
     * by title cache of the related names.
2831
     * @see #getHybridParentRelations()
2832
     */
2833
    @Override
2834
    @Transient
2835
    public List<HybridRelationship> getOrderedChildRelationships(){
2836
        List<HybridRelationship> result = new ArrayList<>();
2837
        result.addAll(this.hybridChildRelations);
2838
        Collections.sort(result);
2839
        Collections.reverse(result);
2840
        return result;
2841
    }
2842

    
2843
    @Override
2844
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2845
        return addHybridParent(parentName, type, null, null, ruleConsidered, null);
2846
    }
2847

    
2848
    @Override
2849
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, Reference reference,
2850
            String microReference, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
2851
        return new HybridRelationship(this, parentName, type, reference, microReference, ruleConsidered, codeEdition);
2852
    }
2853

    
2854
    /**
2855
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2856
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2857
     * "is first/second parent" or "is male/female parent". By invoking this
2858
     * method <i>this</i> botanical name becomes a parent of the hybrid child
2859
     * botanical name.
2860
     *
2861
     * @param childName       the botanical name of the child for this new hybrid name relationship
2862
     * @param type            the type of this new name relationship
2863
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2864
     * @return
2865
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
2866
     * @see                   #getRelationsToThisName()
2867
     * @see                   #getNameRelations()
2868
     * @see                   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
2869
     * @see                   #addNameRelationship(NameRelationship)
2870
     */
2871
    @Override
2872
    public HybridRelationship addHybridChild(INonViralName childName, HybridRelationshipType type, String ruleConsidered){
2873
        return new HybridRelationship(childName, this, type, ruleConsidered);
2874
    }
2875

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

    
2889
    @Override
2890
    public void removeHybridParent(INonViralName parent) {
2891
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2892
        hybridRelationships.addAll(this.getHybridChildRelations());
2893
        hybridRelationships.addAll(this.getHybridParentRelations());
2894
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2895
            // remove name relationship from this side
2896
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2897
                this.removeHybridRelationship(hybridRelationship);
2898
            }
2899
        }
2900
    }
2901

    
2902

    
2903

    
2904
// *********** DESCRIPTIONS *************************************
2905

    
2906
    /**
2907
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2908
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2909
     * concerning the taxon name like for instance the content of its first
2910
     * publication (protolog) or a picture of this publication.
2911
     *
2912
     * @see	#addDescription(TaxonNameDescription)
2913
     * @see	#removeDescription(TaxonNameDescription)
2914
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2915
     */
2916
    @Override
2917
    public Set<TaxonNameDescription> getDescriptions() {
2918
        return descriptions;
2919
    }
2920

    
2921
    /**
2922
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2923
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2924
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2925
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2926
     *
2927
     * @param  description  the taxon name description to be added
2928
     * @see					#getDescriptions()
2929
     * @see 			  	#removeDescription(TaxonNameDescription)
2930
     */
2931
    @Override
2932
    public void addDescription(TaxonNameDescription description) {
2933
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2934
        ReflectionUtils.makeAccessible(field);
2935
        ReflectionUtils.setField(field, description, this);
2936
        descriptions.add(description);
2937
    }
2938
    /**
2939
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2940
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2941
     * of the description itself will be set to "null".
2942
     *
2943
     * @param  description  the taxon name description which should be removed
2944
     * @see     		  	#getDescriptions()
2945
     * @see     		  	#addDescription(TaxonNameDescription)
2946
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
2947
     */
2948
    @Override
2949
    public void removeDescription(TaxonNameDescription description) {
2950
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2951
        ReflectionUtils.makeAccessible(field);
2952
        ReflectionUtils.setField(field, description, null);
2953
        descriptions.remove(description);
2954
    }
2955

    
2956
// *********** HOMOTYPIC GROUP METHODS **************************************************
2957

    
2958
    @Override
2959
    @Transient
2960
    public void mergeHomotypicGroups(TaxonName name){
2961
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2962
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2963
        name.setHomotypicalGroup(this.homotypicalGroup);
2964
    }
2965

    
2966
    /**
2967
     * Returns the boolean value indicating whether a given taxon name belongs
2968
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
2969
     * or not (false). Returns "true" only if the homotypical groups of both
2970
     * taxon names exist and if they are identical.
2971
     *
2972
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
2973
     * @return  			   the boolean value of the check
2974
     * @see     			   HomotypicalGroup
2975
     */
2976
    @Override
2977
    @Transient
2978
    public boolean isHomotypic(TaxonName homoTypicName) {
2979
        if (homoTypicName == null) {
2980
            return false;
2981
        }
2982
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
2983
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
2984
            return false;
2985
        }
2986
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
2987
            return true;
2988
        }
2989
        return false;
2990
    }
2991

    
2992

    
2993
    /**
2994
     * Checks whether name is a basionym for ALL names
2995
     * in its homotypical group.
2996
     * Returns <code>false</code> if there are no other names in the group
2997
     * @param name
2998
     * @return
2999
     */
3000
    @Override
3001
    @Transient
3002
    public boolean isGroupsBasionym() {
3003
    	if (homotypicalGroup == null){
3004
    		homotypicalGroup = HomotypicalGroup.NewInstance();
3005
    		homotypicalGroup.addTypifiedName(this);
3006
    	}
3007
        Set<TaxonName> typifiedNames = homotypicalGroup.getTypifiedNames();
3008

    
3009
        // Check whether there are any other names in the group
3010
        if (typifiedNames.size() == 1) {
3011
                return false;
3012
        }
3013

    
3014
        for (TaxonName taxonName : typifiedNames) {
3015
                if (!taxonName.equals(this)) {
3016
                        if (! isBasionymFor(taxonName)) {
3017
                                return false;
3018
                        }
3019
                }
3020
        }
3021
        return true;
3022
    }
3023

    
3024
    /**
3025
     * Checks whether a basionym relationship exists between fromName and toName.
3026
     *
3027
     * @param fromName
3028
     * @param toName
3029
     * @return
3030
     */
3031
    @Override
3032
    @Transient
3033
    public boolean isBasionymFor(TaxonName newCombinationName) {
3034
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3035
            for (NameRelationship relation : relations) {
3036
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3037
                                    relation.getFromName().equals(this)) {
3038
                            return true;
3039
                    }
3040
            }
3041
            return false;
3042
    }
3043

    
3044
    /**
3045
     * Creates a basionym relationship to all other names in this names homotypical
3046
     * group.
3047
     *
3048
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3049
     */
3050
    @Override
3051
    @Transient
3052
    public void makeGroupsBasionym() {
3053
        this.homotypicalGroup.setGroupBasionym(this);
3054
    }
3055

    
3056

    
3057
//*********  Rank comparison shortcuts   ********************//
3058
    /**
3059
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3060
     * taxon name is higher than the genus rank (true) or not (false).
3061
     * Suprageneric non viral names are monomials.
3062
     * Returns false if rank is null.
3063
     *
3064
     * @see  #isGenus()
3065
     * @see  #isInfraGeneric()
3066
     * @see  #isSpecies()
3067
     * @see  #isInfraSpecific()
3068
     */
3069
    @Override
3070
    @Transient
3071
    public boolean isSupraGeneric() {
3072
        if (rank == null){
3073
            return false;
3074
        }
3075
        return getRank().isSupraGeneric();
3076
    }
3077
    /**
3078
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3079
     * taxon name is the genus rank (true) or not (false). Non viral names with
3080
     * genus rank are monomials. Returns false if rank is null.
3081
     *
3082
     * @see  #isSupraGeneric()
3083
     * @see  #isInfraGeneric()
3084
     * @see  #isSpecies()
3085
     * @see  #isInfraSpecific()
3086
     */
3087
    @Override
3088
    @Transient
3089
    public boolean isGenus() {
3090
        if (rank == null){
3091
            return false;
3092
        }
3093
        return getRank().isGenus();
3094
    }
3095

    
3096
    @Override
3097
    @Transient
3098
    public boolean isGenusOrSupraGeneric() {
3099
        return isGenus()|| isSupraGeneric();
3100
    }
3101
    /**
3102
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3103
     * taxon name is higher than the species rank and lower than the
3104
     * genus rank (true) or not (false). Infrageneric non viral names are
3105
     * binomials. Returns false if rank is null.
3106
     *
3107
     * @see  #isSupraGeneric()
3108
     * @see  #isGenus()
3109
     * @see  #isSpecies()
3110
     * @see  #isInfraSpecific()
3111
     */
3112
    @Override
3113
    @Transient
3114
    public boolean isInfraGeneric() {
3115
        if (rank == null){
3116
            return false;
3117
        }
3118
        return getRank().isInfraGeneric();
3119
    }
3120

    
3121
    /**
3122
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3123
     * taxon name is higher than the species rank (true) or not (false).
3124
     * Returns false if rank is null.
3125
     *
3126
     * @see  #isGenus()
3127
     * @see  #isInfraGeneric()
3128
     * @see  #isSpecies()
3129
     * @see  #isInfraSpecific()
3130
     */
3131
    @Override
3132
    @Transient
3133
    public boolean isSupraSpecific(){
3134
        if (rank == null) {
3135
            return false;
3136
        }
3137
        return getRank().isHigher(Rank.SPECIES());
3138
    }
3139

    
3140
    /**
3141
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3142
     * taxon name is the species rank (true) or not (false). Non viral names
3143
     * with species rank are binomials.
3144
     * Returns false if rank is null.
3145
     *
3146
     * @see  #isSupraGeneric()
3147
     * @see  #isGenus()
3148
     * @see  #isInfraGeneric()
3149
     * @see  #isInfraSpecific()
3150
     */
3151
    @Override
3152
    @Transient
3153
    public boolean isSpecies() {
3154
        if (rank == null){
3155
            return false;
3156
        }
3157
        return getRank().isSpecies();
3158
    }
3159
    /**
3160
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3161
     * taxon name is lower than the species rank (true) or not (false).
3162
     * Infraspecific non viral names are trinomials.
3163
     * Returns false if rank is null.
3164
     *
3165
     * @see  #isSupraGeneric()
3166
     * @see  #isGenus()
3167
     * @see  #isInfraGeneric()
3168
     * @see  #isSpecies()
3169
     */
3170
    @Override
3171
    @Transient
3172
    public boolean isInfraSpecific() {
3173
        if (rank == null){
3174
            return false;
3175
        }
3176
        return getRank().isInfraSpecific();
3177
    }
3178

    
3179
    /**
3180
     * Returns true if this name's rank indicates a rank that aggregates species like species
3181
     * aggregates or species groups, false otherwise. This methods currently returns false
3182
     * for all user defined ranks.
3183
     *
3184
     *@see Rank#isSpeciesAggregate()
3185
     *
3186
     * @return
3187
     */
3188
    @Override
3189
    @Transient
3190
    public boolean isSpeciesAggregate() {
3191
        if (rank == null){
3192
            return false;
3193
        }
3194
        return getRank().isSpeciesAggregate();
3195
    }
3196

    
3197

    
3198
    /**
3199
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3200
     * the construction of <i>this</i> taxon name since there is no specific
3201
     * nomenclatural code defined. The real implementention takes place in the
3202
     * subclasses {@link IBacterialName BacterialName},
3203
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3204
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3205
     * and only one nomenclatural code.
3206
     *
3207
     * @return  null
3208
     * @see  	#isCodeCompliant()
3209
     * @see  	#getHasProblem()
3210
     * @deprecated use {@link #getNameType()} instead
3211
     */
3212
    @Deprecated
3213
    @Transient
3214
    @java.beans.Transient
3215
    public NomenclaturalCode getNomenclaturalCode() {
3216
        return nameType;
3217
    }
3218

    
3219
    /**
3220
     * Generates and returns the string with the scientific name of <i>this</i>
3221
     * taxon name (only non viral taxon names can be generated from their
3222
     * components). This string may be stored in the inherited
3223
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3224
     * This method overrides the generic and inherited
3225
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3226
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3227
     *
3228
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3229
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3230
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3231
     */
3232
//	@Override
3233
//	public abstract String generateTitle();
3234

    
3235
    /**
3236
     * Creates a basionym relationship between this name and
3237
     * 	each name in its homotypic group.
3238
     *
3239
     * @param basionymName
3240
     */
3241
    @Override
3242
    @Transient
3243
    public void setAsGroupsBasionym() {
3244

    
3245
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3246
        if (homotypicalGroup == null) {
3247
            return;
3248
        }
3249

    
3250
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3251
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3252

    
3253
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3254

    
3255
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3256

    
3257
            for(NameRelationship nameRelation : nameRelations){
3258
                relations.add(nameRelation);
3259
            }
3260
        }
3261

    
3262
        for (NameRelationship relation : relations) {
3263

    
3264
            // If this is a basionym relation, and toName is in the homotypical group,
3265
            //	remove the relationship.
3266
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3267
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3268
                removeRelations.add(relation);
3269
            }
3270
        }
3271

    
3272
        // Removing relations from a set through which we are iterating causes a
3273
        //	ConcurrentModificationException. Therefore, we delete the targeted
3274
        //	relations in a second step.
3275
        for (NameRelationship relation : removeRelations) {
3276
            this.removeNameRelationship(relation);
3277
        }
3278

    
3279
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3280
            if (!name.equals(this)) {
3281

    
3282
                // First check whether the relationship already exists
3283
                if (!this.isBasionymFor(name)) {
3284

    
3285
                    // Then create it
3286
                    name.addRelationshipFromName(this,
3287
                            NameRelationshipType.BASIONYM(), null, null);
3288
                }
3289
            }
3290
        }
3291
    }
3292

    
3293
    /**
3294
     * Removes basionym relationship between this name and
3295
     * 	each name in its homotypic group.
3296
     *
3297
     * @param basionymName
3298
     */
3299
    @Override
3300
    @Transient
3301
    public void removeAsGroupsBasionym() {
3302

    
3303
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3304

    
3305
        if (homotypicalGroup == null) {
3306
            return;
3307
        }
3308

    
3309
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3310
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3311

    
3312
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3313

    
3314
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3315

    
3316
            for(NameRelationship nameRelation : nameRelations){
3317
                relations.add(nameRelation);
3318
            }
3319
        }
3320

    
3321
        for (NameRelationship relation : relations) {
3322

    
3323
            // If this is a basionym relation, and toName is in the homotypical group,
3324
            //	and fromName is basionymName, remove the relationship.
3325
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3326
                    relation.getFromName().equals(this) &&
3327
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3328
                removeRelations.add(relation);
3329
            }
3330
        }
3331

    
3332
        // Removing relations from a set through which we are iterating causes a
3333
        //	ConcurrentModificationException. Therefore, we delete the targeted
3334
        //	relations in a second step.
3335
        for (NameRelationship relation : removeRelations) {
3336
            this.removeNameRelationship(relation);
3337
        }
3338
    }
3339

    
3340

    
3341
    /**
3342
     * Defines the last part of the name.
3343
     * This is for infraspecific taxa, the infraspecific epithet,
3344
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3345
     * else the genusOrUninomial.
3346
     * However, the result does not depend on the rank (which may be not correctly set
3347
     * in case of dirty data) but returns the first name part which is not blank
3348
     * considering the above order.
3349
     * @return the first not blank name part in reverse order
3350
     */
3351
    @Override
3352
    public String getLastNamePart() {
3353
        String result =
3354
                isNotBlank(this.getInfraSpecificEpithet())?
3355
                    this.getInfraSpecificEpithet() :
3356
                isNotBlank(this.getSpecificEpithet()) ?
3357
                    this.getSpecificEpithet():
3358
                isNotBlank(this.getInfraGenericEpithet()) ?
3359
                    this.getInfraGenericEpithet():
3360
                this.getGenusOrUninomial();
3361
        return result;
3362
    }
3363

    
3364
    @Override
3365
    public boolean isHybridName() {
3366
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3367
    }
3368

    
3369
    @Override
3370
    public boolean isHybrid() {
3371
        return this.isHybridName() || this.isHybridFormula();
3372
    }
3373

    
3374
// ******************** NOMENCLATURAL STANDING ****************/
3375

    
3376
    /**
3377
     * Computes the highest priority nomenclatural standing
3378
     * from nomenclatural status and name relationships.
3379
     */
3380
    private NomenclaturalStanding computeNomenclaturalStanding() {
3381
        Set<NomenclaturalStanding> standings = computeNomenclaturalStandings();
3382
        return NomenclaturalStanding.highest(standings);
3383
    }
3384

    
3385
    /**
3386
     * Computes all nomenclatural standings form the nomenclatural status and the name relationships.
3387
     */
3388
    private Set<NomenclaturalStanding> computeNomenclaturalStandings() {
3389
        Set<NomenclaturalStanding> standings = new HashSet<>();
3390
        for (NomenclaturalStatus status : this.status){
3391
            if (status.getType() != null){
3392
                NomenclaturalStanding standing = status.getType().getNomenclaturalStanding();
3393
                standings.add(standing);
3394
            }
3395
        }
3396
        for (NameRelationship nameRel : this.relationsFromThisName){
3397
            if (nameRel.getType() != null){
3398
                NomenclaturalStanding standing = nameRel.getType().getNomenclaturalStanding();
3399
                standings.add(standing);
3400
            }
3401
        }
3402
        for (NameRelationship nameRel : this.relationsToThisName){
3403
            if (nameRel.getType() != null){
3404
                NomenclaturalStanding standing = nameRel.getType().getNomenclaturalStandingInverse();
3405
                standings.add(standing);
3406
            }
3407
        }
3408
        return standings;
3409
    }
3410

    
3411
    @Override
3412
    @Transient
3413
    public boolean isDesignationOnly() {
3414
        return computeNomenclaturalStanding().isDesignationOnly();
3415
    }
3416

    
3417
    @Override
3418
    @Transient
3419
    public boolean isInvalidExplicit() {
3420
        return computeNomenclaturalStanding().isInvalidExplicit();
3421
    }
3422

    
3423
    @Override
3424
    @Transient
3425
    public boolean isIllegitimate() {
3426
        return computeNomenclaturalStanding().isIllegitimate();
3427
    }
3428

    
3429
    @Override
3430
    @Transient
3431
    public boolean isValidExplicit() {
3432
        return computeNomenclaturalStanding().isValidExplicit();
3433
    }
3434

    
3435
    @Override
3436
    @Transient
3437
    public boolean isNoStatus() {
3438
        return computeNomenclaturalStanding().isNoStatus();
3439
    }
3440

    
3441
    @Override
3442
    @Transient
3443
    public boolean isInvalid() {
3444
        return computeNomenclaturalStanding().isInvalid();
3445
    }
3446

    
3447
    @Override
3448
    @Transient
3449
    public boolean isLegitimate() {
3450
        return computeNomenclaturalStanding().isLegitimate();
3451
    }
3452

    
3453
    @Override
3454
    @Transient
3455
    public boolean isValid() {
3456
        return computeNomenclaturalStanding().isValid();
3457
    }
3458

    
3459
// ***************** COMPARE ********************************/
3460

    
3461
    @Override
3462
    public int compareToName(TaxonName otherName){
3463

    
3464
        int result = 0;
3465

    
3466
        if (otherName == null) {
3467
            throw new NullPointerException("Cannot compare to null.");
3468
        }
3469

    
3470
        //other
3471
        otherName = deproxy(otherName);
3472
        String otherNameCache = otherName.getNameCache();
3473
        String otherTitleCache = otherName.getTitleCache();
3474
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3475
        if (otherName.isAutonym()){
3476
            boolean isProtected = otherName.isProtectedNameCache();
3477
            String oldNameCache = otherName.getNameCache();
3478
            otherName.setProtectedNameCache(false);
3479
            otherName.setNameCache(null, false);
3480
            otherNameCache = otherName.getNameCache();
3481
            otherName.setNameCache(oldNameCache, isProtected);
3482
        }
3483

    
3484
        //this
3485
        String thisNameCache = this.getNameCache();
3486
        String thisTitleCache = this.getTitleCache();
3487

    
3488
        if (this.isAutonym()){
3489
            boolean isProtected = this.isProtectedNameCache();
3490
            String oldNameCache = this.getNameCache();
3491
            this.setProtectedNameCache(false);
3492
            this.setNameCache(null, false);
3493
            thisNameCache = this.getNameCache();
3494
            this.setNameCache(oldNameCache, isProtected);
3495
        }
3496

    
3497
        // Compare name cache of taxon names
3498
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3499
            String thisNormalized = normalizeName(thisNameCache);
3500
            String otherNormalized = normalizeName(otherNameCache);
3501
            result = thisNormalized.compareTo(otherNormalized);
3502
        }
3503

    
3504
        // Compare title cache of taxon names
3505
        if (result == 0){
3506
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3507
                String thisNormalized = normalizeName(thisTitleCache);
3508
                String otherNormalized = normalizeName(otherTitleCache);
3509
                result = CdmUtils.nullSafeCompareTo(thisNormalized, otherNormalized);
3510
                if (result == 0){
3511
                    result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3512
                }
3513
            }
3514
        }
3515
        if (result == 0){
3516
            result =  CdmUtils.nullSafeCompareTo(thisNameCache, otherNameCache);
3517
        }
3518

    
3519
        return result;
3520
    }
3521

    
3522
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3523
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3524

    
3525
    private String normalizeName(String thisNameCache) {
3526
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3527
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3528
        return thisNameCache;
3529
    }
3530

    
3531
// ********************** INTERFACES ********************************************/
3532

    
3533
    /**
3534
     * Method to cast a interfaced name to a concrete name.
3535
     * The method includes a deproxy to guarantee that no
3536
     * class cast exception is thrown.
3537
     *
3538
     * @see #castAndDeproxy(Set)
3539
     * @param interfacedName
3540
     * @return
3541
     */
3542
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3543
        return deproxy(interfacedName, TaxonName.class);
3544
    }
3545

    
3546
    /**
3547
     * Method to cast a set of interfaced names to concrete namex.
3548
     * The method includes a deproxy to guarantee that no
3549
     * class cast exception is thrown.
3550
     *
3551
     * @see #castAndDeproxy(ITaxonNameBase)
3552
     * @param naminterfacedNames
3553
     * @return
3554
     */
3555
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3556
        Set<TaxonName> result = new HashSet<>();
3557
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3558
            result.add(castAndDeproxy(naminterfacedName));
3559
        }
3560
        return result;
3561
    }
3562

    
3563
//************************ isType ***********************************************/
3564

    
3565
    @Override
3566
    public boolean isNonViral() {
3567
        return nameType.isNonViral();
3568
    }
3569
    @Override
3570
    public boolean isZoological(){
3571
        return nameType.isZoological();
3572
    }
3573
    @Override
3574
    public boolean isBotanical() {
3575
        if (nameType == null){
3576
            throw new RuntimeException("Name has no nameType: " +  this.getUuid() + ", " + getId()+ ", species epi: " + getSpecificEpithet() );
3577
        }
3578
        return nameType.isBotanical();
3579
    }
3580
    @Override
3581
    public boolean isCultivar() {
3582
        return nameType.isCultivar();
3583
    }
3584
    @Override
3585
    public boolean isBacterial() {
3586
        return nameType.isBacterial();
3587
    }
3588
    @Override
3589
    public boolean isViral() {
3590
        return nameType != null? nameType.isViral(): false;
3591
    }
3592

    
3593
// *********************** CACHES ***************************************************/
3594

    
3595
    @Override
3596
    public boolean updateCaches() {
3597
        boolean result = updateAuthorshipCache();
3598
        result |= updateNameCache();
3599
        result |= super.updateCaches();
3600
        result |= updateFullTitleCache();
3601
        return result;
3602
    }
3603

    
3604
    /**
3605
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
3606
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened.
3607
     * @return <code>true</code> if something changed
3608
     */
3609
    private boolean updateAuthorshipCache() {
3610
        //updates the authorship cache if necessary and via the listener updates all higher caches
3611
        if (protectedAuthorshipCache == false){
3612
            String oldCache = this.authorshipCache;
3613
            String newCache = cacheStrategy().getAuthorshipCache(this);
3614
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3615
                this.setAuthorshipCache(null, false);
3616
                this.getAuthorshipCache();
3617
                return true;
3618
            }
3619
        }
3620
        return false;
3621
    }
3622

    
3623
    private boolean updateNameCache() {
3624
        //updates the name cache if necessary and via the listener updates all higher caches
3625
        if (protectedNameCache == false){
3626
            String oldCache = this.nameCache;
3627
            String newCache = cacheStrategy().getNameCache(this);
3628
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3629
                this.setNameCache(null, false);
3630
                this.getNameCache();
3631
                return true;
3632
            }
3633
        }
3634
        return false;
3635
    }
3636

    
3637
    private boolean updateFullTitleCache() {
3638
        if (protectedFullTitleCache == false){
3639
            String oldCache = this.fullTitleCache;
3640
            String newCache = getTruncatedCache(cacheStrategy().getFullTitleCache(this));
3641
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3642
                this.setFullTitleCache(null, false);
3643
                this.getFullTitleCache();
3644
                return true;
3645
            }
3646
        }
3647
        return false;
3648
    }
3649

    
3650
//*********************** CLONE ********************************************************/
3651

    
3652
    @Override
3653
    public TaxonName clone() {
3654
        return this.clone(true);
3655
    }
3656

    
3657
    @Override
3658
    public TaxonName clone(boolean newHomotypicGroup) {
3659
        try {
3660
            TaxonName result = (TaxonName)super.clone();
3661

    
3662
            //taxonBases -> empty
3663
            result.taxonBases = new HashSet<>();
3664

    
3665
            //empty caches
3666
            if (! protectedFullTitleCache){
3667
                result.fullTitleCache = null;
3668
            }
3669

    
3670
            //descriptions
3671
            result.descriptions = new HashSet<>();
3672
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3673
                TaxonNameDescription newDescription = taxonNameDescription.clone();
3674
                result.descriptions.add(newDescription);
3675
            }
3676

    
3677
            //status
3678
            result.status = new HashSet<>();
3679
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3680
                NomenclaturalStatus newStatus = nomenclaturalStatus.clone();
3681
                result.status.add(newStatus);
3682
            }
3683

    
3684
            //to relations
3685
            result.relationsToThisName = new HashSet<>();
3686
            for (NameRelationship toRelationship : getRelationsToThisName()){
3687
                NameRelationship newRelationship = toRelationship.clone();
3688
                newRelationship.setRelatedTo(result);
3689
                result.relationsToThisName.add(newRelationship);
3690
            }
3691

    
3692
            //from relations
3693
            result.relationsFromThisName = new HashSet<>();
3694
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3695
                NameRelationship newRelationship = fromRelationship.clone();
3696
                newRelationship.setRelatedFrom(result);
3697
                result.relationsFromThisName.add(newRelationship);
3698
            }
3699

    
3700
            //type designations
3701
            result.typeDesignations = new HashSet<>();
3702
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3703
                TypeDesignationBase<?> newDesignation = typeDesignation.clone();
3704
                this.removeTypeDesignation(newDesignation);
3705
                result.addTypeDesignation(newDesignation, false);
3706
            }
3707

    
3708
            //homotypicalGroup
3709
            if (newHomotypicGroup){
3710
                HomotypicalGroup homotypicalGroup = HomotypicalGroup.NewInstance();
3711
                homotypicalGroup.addTypifiedName(result);
3712
            }else{
3713
                result.homotypicalGroup.addTypifiedName(result);  //to immediately handle bidirectionality
3714
            }
3715

    
3716
            //HybridChildRelations
3717
            result.hybridChildRelations = new HashSet<>();
3718
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3719
                HybridRelationship newChildRelationship = hybridRelationship.clone();
3720
                newChildRelationship.setRelatedTo(result);
3721
                result.hybridChildRelations.add(newChildRelationship);
3722
            }
3723

    
3724
            //HybridParentRelations
3725
            result.hybridParentRelations = new HashSet<>();
3726
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3727
                HybridRelationship newParentRelationship = hybridRelationship.clone();
3728
                newParentRelationship.setRelatedFrom(result);
3729
                result.hybridParentRelations.add(newParentRelationship);
3730
            }
3731

    
3732
            //empty caches
3733
            if (! protectedNameCache){
3734
                result.nameCache = null;
3735
            }
3736

    
3737
            //empty caches
3738
            if (! protectedAuthorshipCache){
3739
                result.authorshipCache = null;
3740
            }
3741

    
3742
            //registrations
3743
            result.registrations = new HashSet<>();
3744
            for (Registration registration : getRegistrations()){
3745
                result.registrations.add(registration);
3746
            }
3747

    
3748
            //nomenclatural source
3749
            if (this.getNomenclaturalSource() != null){
3750
                result.setNomenclaturalSource(this.getNomenclaturalSource().clone());
3751
                result.getNomenclaturalSource().setSourcedName(result);
3752
            }
3753

    
3754

    
3755
            //no changes to: appendedPharse, parsingProblem, problemEnds, problemStarts
3756
            //protectedFullTitleCache, rank
3757
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3758
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3759
            //protectedAuthorshipCache, protectedNameCache,
3760
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3761
            //acronym
3762
            //subGenusAuthorship, nameApprobation
3763
            //anamorphic
3764
            //cultivarEpithet,cultivarGroupEpithet
3765
            return result;
3766
        } catch (CloneNotSupportedException e) {
3767
            logger.warn("Object does not implement cloneable");
3768
            e.printStackTrace();
3769
            return null;
3770
        }
3771
    }
3772

    
3773
}
(33-33/39)