Project

General

Profile

Download (141 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.model.name;
10

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

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

    
47
import org.apache.log4j.Logger;
48
import org.hibernate.annotations.Cascade;
49
import org.hibernate.annotations.CascadeType;
50
import org.hibernate.annotations.Type;
51
import org.hibernate.envers.Audited;
52
import org.hibernate.search.annotations.Analyze;
53
import org.hibernate.search.annotations.Analyzer;
54
import org.hibernate.search.annotations.Field;
55
import org.hibernate.search.annotations.Fields;
56
import org.hibernate.search.annotations.Index;
57
import org.hibernate.search.annotations.Indexed;
58
import org.hibernate.search.annotations.IndexedEmbedded;
59
import org.hibernate.search.annotations.Store;
60
import org.hibernate.validator.constraints.NotEmpty;
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.NameMustFollowCode;
105
import eu.etaxonomy.cdm.validation.annotation.NameMustHaveAuthority;
106
import eu.etaxonomy.cdm.validation.annotation.NoDuplicateNames;
107
import eu.etaxonomy.cdm.validation.annotation.NullOrNotEmpty;
108
import eu.etaxonomy.cdm.validation.annotation.ValidTaxonomicYear;
109

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

    
198
    private static final long serialVersionUID = -791164269603409712L;
199
    private static final Logger logger = Logger.getLogger(TaxonName.class);
200

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
347
//****** Non-ViralName attributes ***************************************/
348

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

    
361
    @XmlElement(name = "ProtectedNameCache")
362
    @CacheUpdate(value="nameCache")
363
    protected boolean protectedNameCache;
364

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

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

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

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

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

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

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

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

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

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

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

    
471
    @XmlElement(name = "ProtectedAuthorshipCache")
472
    @CacheUpdate("authorshipCache")
473
    protected boolean protectedAuthorshipCache;
474

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

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

    
491

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

    
499
    @XmlElement(name ="IsMonomHybrid")
500
    @CacheUpdate("nameCache")
501
    private boolean monomHybrid = false;
502

    
503
    @XmlElement(name ="IsBinomHybrid")
504
    @CacheUpdate("nameCache")
505
    private boolean binomHybrid = false;
506

    
507
    @XmlElement(name ="IsTrinomHybrid")
508
    @CacheUpdate("nameCache")
509
    private boolean trinomHybrid = false;
510

    
511
// ViralName attributes ************************* /
512

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

    
520
// BacterialName attributes ***********************/
521

    
522
    //Author team and year of the subgenus name
523
    @XmlElement(name = "SubGenusAuthorship")
524
    @Field
525
    private String subGenusAuthorship;
526

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

    
532
    //ZOOLOGICAL NAME
533

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

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

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

    
553
    //Cultivar attribute(s)
554

    
555
    //the characteristical name of the cultivar
556
    @XmlElement(name = "CultivarName")
557
    //TODO Val #3379
558
    //@NullOrNotEmpty
559
    @Column(length=255)
560
    private String cultivarName;
561

    
562
    // ************** FUNGUS name attributes
563
    //to indicate that the type of the name is asexual or not
564
    @XmlElement(name ="IsAnamorphic")
565
    private boolean anamorphic = false;
566

    
567
// *************** FACTORY METHODS ********************************/
568

    
569
    //see TaxonNameFactory
570
    /**
571
     * @param code
572
     * @param rank
573
     * @param homotypicalGroup
574
     * @return
575
     */
576
    protected static TaxonName NewInstance(NomenclaturalCode code, Rank rank,
577
            HomotypicalGroup homotypicalGroup) {
578

    
579
        TaxonName result = new TaxonName(code, rank, homotypicalGroup);
580
        return result;
581
    }
582

    
583
    //TODO move to TaxonNameFactory
584
    public static TaxonName NewInstance(NomenclaturalCode code, Rank rank, String genusOrUninomial,
585
            String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet,
586
            TeamOrPersonBase<?> combinationAuthorship, Reference nomenclaturalReference,
587
            String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
588

    
589
        TaxonName result = new TaxonName(code, rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
590
        return result;
591
    }
592

    
593
// ************* CONSTRUCTORS *************/
594

    
595
    /**
596
     * Class constructor: creates a new empty taxon name.
597
     * @param code
598
     *
599
     * @see #TaxonName(Rank)
600
     * @see #TaxonName(HomotypicalGroup)
601
     * @see #TaxonName(Rank, HomotypicalGroup)
602
     */
603
    protected TaxonName() {}
604

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

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

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

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

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

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

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

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

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

    
774
    @Override
775
    protected void initDefaultCacheStrategy() {
776
        this.cacheStrategy = TaxonNameDefaultCacheStrategy.NewInstance();
777
    }
778

    
779
// ****************** GETTER / SETTER ****************************/
780

    
781
    @Override
782
    public NomenclaturalCode getNameType() {
783
        return nameType;
784
    }
785

    
786
    @Override
787
    public void setNameType(NomenclaturalCode nameType) {
788
        this.nameType = nameType;
789
    }
790

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1064
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1065
        this.hybridParentRelations = hybridParentRelations;
1066
    }
1067

    
1068

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

    
1085
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1086
        this.hybridChildRelations = hybridChildRelations;
1087
    }
1088

    
1089
    @Override
1090
    public boolean isProtectedFullTitleCache() {
1091
        return protectedFullTitleCache;
1092
    }
1093

    
1094
    @Override
1095
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1096
        this.protectedFullTitleCache = protectedFullTitleCache;
1097
    }
1098

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

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

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

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

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

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

    
1180
    @Override
1181
    public boolean isTrinomHybrid(){
1182
        return this.trinomHybrid;
1183
    }
1184

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

    
1195
    // ****************** VIRAL NAME ******************/
1196

    
1197
    @Override
1198
    public String getAcronym(){
1199
        return this.acronym;
1200
    }
1201

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

    
1210
    // ****************** BACTERIAL NAME ******************/
1211

    
1212
    @Override
1213
    public String getSubGenusAuthorship(){
1214
        return this.subGenusAuthorship;
1215
    }
1216

    
1217
    @Override
1218
    public void setSubGenusAuthorship(String subGenusAuthorship){
1219
        this.subGenusAuthorship = subGenusAuthorship;
1220
    }
1221

    
1222

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

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

    
1236
    //************ Zoological Name
1237

    
1238

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

    
1251

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

    
1264

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

    
1277
    // **** Cultivar Name ************
1278

    
1279

    
1280
    @Override
1281
    public String getCultivarName(){
1282
        return this.cultivarName;
1283
    }
1284

    
1285
    /**
1286
     * @see  #getCultivarName()
1287
     */
1288
    @Override
1289
    public void setCultivarName(String cultivarName){
1290
        this.cultivarName = isBlank(cultivarName) ? null : cultivarName;
1291
    }
1292

    
1293
    // **************** Fungus Name
1294
    @Override
1295
    public boolean isAnamorphic(){
1296
        return this.anamorphic;
1297
    }
1298

    
1299
    /**
1300
     * @see  #isAnamorphic()
1301
     */
1302
    @Override
1303
    public void setAnamorphic(boolean anamorphic){
1304
        this.anamorphic = anamorphic;
1305
    }
1306

    
1307

    
1308
// **************** ADDER / REMOVE *************************/
1309

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

    
1339

    
1340
    /**
1341
     * Removes one {@link HybridRelationship hybrid relationship} from the set of
1342
     * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1343
     * is involved. The hybrid relationship will also be removed from the set
1344
     * belonging to the second botanical taxon name involved.
1345
     *
1346
     * @param  relationship  the hybrid relationship which should be deleted from the corresponding sets
1347
     * @see                  #getHybridRelationships()
1348
     */
1349
    @Override
1350
    public void removeHybridRelationship(HybridRelationship hybridRelation) {
1351
        if (hybridRelation == null) {
1352
            return;
1353
        }
1354

    
1355
        TaxonName parent = hybridRelation.getParentName();
1356
        TaxonName child = hybridRelation.getHybridName();
1357
        if (this.equals(parent)){
1358
            this.hybridParentRelations.remove(hybridRelation);
1359
            child.hybridChildRelations.remove(hybridRelation);
1360
            hybridRelation.setHybridName(null);
1361
            hybridRelation.setParentName(null);
1362
        }
1363
        if (this.equals(child)){
1364
            parent.hybridParentRelations.remove(hybridRelation);
1365
            this.hybridChildRelations.remove(hybridRelation);
1366
            hybridRelation.setHybridName(null);
1367
            hybridRelation.setParentName(null);
1368
        }
1369
    }
1370

    
1371
//********* METHODS **************************************/
1372

    
1373
    @Override
1374
    public String generateFullTitle(){
1375
        if (getCacheStrategy() == null){
1376
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1377
            return null;
1378
        }else{
1379
            return getCacheStrategy().getFullTitleCache(this);
1380
        }
1381
    }
1382

    
1383
    @Override
1384
    public void setFullTitleCache(String fullTitleCache){
1385
        setFullTitleCache(fullTitleCache, PROTECTED);
1386
    }
1387

    
1388
    @Override
1389
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1390
        fullTitleCache = getTruncatedCache(fullTitleCache);
1391
        this.fullTitleCache = fullTitleCache;
1392
        this.setProtectedFullTitleCache(protectCache);
1393
    }
1394

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

    
1420
    @Override
1421
    @Transient
1422
    public List<TaggedText> getTaggedName(){
1423
        INameCacheStrategy strat = getCacheStrategy();
1424
        return strat.getTaggedTitle(this);
1425
    }
1426

    
1427
    @Override
1428
    @Transient
1429
    public List<TaggedText> getTaggedFullTitle() {
1430
        INameCacheStrategy strat = getCacheStrategy();
1431
        return strat.getTaggedFullTitle(this);
1432
    }
1433

    
1434
    @Override
1435
    @Transient
1436
    public String getFullTitleCache(){
1437
        if (protectedFullTitleCache){
1438
            return this.fullTitleCache;
1439
        }
1440
        updateAuthorshipCache();
1441
        if (fullTitleCache == null ){
1442
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1443
        }
1444
        return fullTitleCache;
1445
    }
1446

    
1447
    @Override
1448
    public String getTitleCache(){
1449
        if(!protectedTitleCache) {
1450
            updateAuthorshipCache();
1451
        }
1452
        return super.getTitleCache();
1453
    }
1454

    
1455
    @Override
1456
    public void setTitleCache(String titleCache, boolean protectCache){
1457
        super.setTitleCache(titleCache, protectCache);
1458
    }
1459

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

    
1483
        }
1484
        return authorshipCache;
1485
    }
1486

    
1487
    /**
1488
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1489
     * flag to <code>true</code>.
1490
     *
1491
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1492
     * @see    #getAuthorshipCache()
1493
     */
1494
    @Override
1495
    public void setAuthorshipCache(String authorshipCache) {
1496
        setAuthorshipCache(authorshipCache, true);
1497
    }
1498

    
1499
    /**
1500
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1501
     *
1502
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1503
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1504
     * the flag is set to <code>false</code>.
1505
     * @see    #getAuthorshipCache()
1506
     */
1507
    @Override
1508
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1509
        this.authorshipCache = authorshipCache;
1510
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1511
    }
1512

    
1513
    /**
1514
     * Generates and returns a concatenated and formated author team string
1515
     * including basionym and combination authors of <i>this</i> taxon name
1516
     * according to the strategy defined in
1517
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName) INonViralNameCacheStrategy}.
1518
     *
1519
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1520
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName)
1521
     */
1522
    @Override
1523
    public String generateAuthorship(){
1524
        if (getCacheStrategy() == null){
1525
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1526
            return null;
1527
        }else{
1528
            return getCacheStrategy().getAuthorshipCache(this);
1529
        }
1530
    }
1531

    
1532
    /**
1533
     * Tests if the given name has any authors.
1534
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1535
     */
1536
    @Override
1537
    public boolean hasAuthors() {
1538
        return (this.getCombinationAuthorship() != null ||
1539
                this.getExCombinationAuthorship() != null ||
1540
                this.getBasionymAuthorship() != null ||
1541
                this.getExBasionymAuthorship() != null);
1542
    }
1543

    
1544
    /**
1545
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1546
     */
1547
    @Override
1548
    public String computeCombinationAuthorNomenclaturalTitle() {
1549
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1550
    }
1551

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

    
1560

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

    
1569
    /**
1570
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1571
     */
1572
    @Override
1573
    public String computeExBasionymAuthorNomenclaturalTitle() {
1574
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1575
    }
1576

    
1577
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1578
        if (author == null){
1579
            return null;
1580
        }else{
1581
            return author.getNomenclaturalTitleCache();
1582
        }
1583
    }
1584

    
1585
    /**
1586
     * Returns the set of all {@link NameRelationship name relationships}
1587
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1588
     * in some name relationships or target in some others.
1589
     *
1590
     * @see    #getRelationsToThisName()
1591
     * @see    #getRelationsFromThisName()
1592
     * @see    #addNameRelationship(NameRelationship)
1593
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1594
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1595
     */
1596
    @Override
1597
    @Transient
1598
    public Set<NameRelationship> getNameRelations() {
1599
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1600
        rels.addAll(getRelationsFromThisName());
1601
        rels.addAll(getRelationsToThisName());
1602
        return rels;
1603
    }
1604

    
1605
    @Override
1606
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1607
        return addRelationshipToName(toName, type, null, null, ruleConsidered, codeEdition);
1608
    }
1609

    
1610
    @Override
1611
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type){
1612
        return addRelationshipToName(toName, type, null, null, null, null);
1613
    }
1614

    
1615
    @Override
1616
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1617
        if (toName == null){
1618
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1619
        }
1620
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered, codeEdition);
1621
        return rel;
1622
    }
1623

    
1624
    @Override
1625
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type){
1626
        return addRelationshipFromName(fromName, type, null, null, null, null);
1627
    }
1628

    
1629
    @Override
1630
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1631
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1632
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered, codeEdition);
1633
    }
1634

    
1635
    @Override
1636
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1637
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered, codeEdition);
1638
    }
1639

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

    
1680
        TaxonName fromName = nameRelation.getFromName();
1681
        TaxonName toName = nameRelation.getToName();
1682

    
1683
        if (nameRelation != null) {
1684
            nameRelation.setToName(null);
1685
            nameRelation.setFromName(null);
1686
        }
1687

    
1688
        if (fromName != null) {
1689
            fromName.removeNameRelationship(nameRelation);
1690
        }
1691

    
1692
        if (toName != null) {
1693
            toName.removeNameRelationship(nameRelation);
1694
        }
1695

    
1696
        this.relationsToThisName.remove(nameRelation);
1697
        this.relationsFromThisName.remove(nameRelation);
1698
    }
1699

    
1700
    @Override
1701
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1702
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1703
//		nameRelationships.addAll(this.getNameRelations());
1704
        nameRelationships.addAll(this.getRelationsFromThisName());
1705
        nameRelationships.addAll(this.getRelationsToThisName());
1706
        for(NameRelationship nameRelationship : nameRelationships) {
1707
            // remove name relationship from this side
1708
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1709
                this.removeNameRelationship(nameRelationship);
1710
            }
1711
        }
1712
    }
1713

    
1714
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1715

    
1716
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1717
        for(NameRelationship nameRelationship : tmpRels) {
1718
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1719
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1720
                if (type == null || type.equals(nameRelationship.getType())){
1721
                    this.removeNameRelationship(nameRelationship);
1722
                }
1723
            }
1724
        }
1725
    }
1726

    
1727
    /**
1728
     * If relation is of type NameRelationship, addNameRelationship is called;
1729
     * if relation is of type HybridRelationship addHybridRelationship is called,
1730
     * otherwise an IllegalArgumentException is thrown.
1731
     *
1732
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1733
     * @see    	   		#addNameRelationship(NameRelationship)
1734
     * @see    	   		#getNameRelations()
1735
     * @see    	   		NameRelationship
1736
     * @see    	   		RelationshipBase
1737
     * @see             #addHybridRelationship(HybridRelationship)
1738

    
1739
     * @deprecated to be used by RelationshipBase only
1740
     */
1741
    @Deprecated
1742
    @Override
1743
    public void addRelationship(RelationshipBase relation) {
1744
        if (relation instanceof NameRelationship){
1745
            addNameRelationship((NameRelationship)relation);
1746

    
1747
        }else if (relation instanceof HybridRelationship){
1748
            addHybridRelationship((HybridRelationship)relation);
1749
        }else{
1750
            logger.warn("Relationship not of type NameRelationship!");
1751
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1752
        }
1753
    }
1754

    
1755
    /**
1756
     * Returns the set of all {@link NameRelationship name relationships}
1757
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1758
     *
1759
     * @see    #getNameRelations()
1760
     * @see    #getRelationsToThisName()
1761
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1762
     */
1763
    @Override
1764
    public Set<NameRelationship> getRelationsFromThisName() {
1765
        if(relationsFromThisName == null) {
1766
            this.relationsFromThisName = new HashSet<>();
1767
        }
1768
        return relationsFromThisName;
1769
    }
1770

    
1771
    /**
1772
     * Returns the set of all {@link NameRelationship name relationships}
1773
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1774
     *
1775
     * @see    #getNameRelations()
1776
     * @see    #getRelationsFromThisName()
1777
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1778
     */
1779
    @Override
1780
    public Set<NameRelationship> getRelationsToThisName() {
1781
        if(relationsToThisName == null) {
1782
            this.relationsToThisName = new HashSet<>();
1783
        }
1784
        return relationsToThisName;
1785
    }
1786

    
1787
    /**
1788
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1789
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1790
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1791
     * and the nomenclatural code rule considered.
1792
     *
1793
     * @see     NomenclaturalStatus
1794
     * @see     NomenclaturalStatusType
1795
     */
1796
    @Override
1797
    public Set<NomenclaturalStatus> getStatus() {
1798
        if(status == null) {
1799
            this.status = new HashSet<>();
1800
        }
1801
        return status;
1802
    }
1803

    
1804
    /**
1805
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1806
     * to <i>this</i> taxon name's set of nomenclatural status.
1807
     *
1808
     * @param  nomStatus  the nomenclatural status to be added
1809
     * @see 			  #getStatus()
1810
     */
1811
    @Override
1812
    public void addStatus(NomenclaturalStatus nomStatus) {
1813
        this.status.add(nomStatus);
1814
        if (!this.equals(nomStatus.getName())){
1815
            nomStatus.setName(this);
1816
        }
1817
    }
1818
    @Override
1819
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1820
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1821
        addStatus(newStatus);
1822
        return newStatus;
1823
    }
1824

    
1825
    /**
1826
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1827
     * Type and ruleConsidered attributes of the nomenclatural status object
1828
     * will be nullified.
1829
     *
1830
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1831
     * @see     		  #getStatus()
1832
     */
1833
    @Override
1834
    public void removeStatus(NomenclaturalStatus nomStatus) {
1835
        //TODO to be implemented?
1836
        logger.warn("not yet fully implemented?");
1837
        this.status.remove(nomStatus);
1838
    }
1839

    
1840
    public void setStatus(Set<NomenclaturalStatus> nomStatus) throws SetterAdapterException {
1841
        new EntityCollectionSetterAdapter<TaxonName, NomenclaturalStatus>(TaxonName.class, NomenclaturalStatus.class, "status", "addStatus", "removeStatus").setCollection(this, nomStatus);
1842
    }
1843

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

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

    
1886
    /**
1887
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1888
     * Sets the protectedNameCache flag to <code>true</code>.
1889
     *
1890
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1891
     * @see    #getNameCache()
1892
     */
1893
    @Override
1894
    public void setNameCache(String nameCache){
1895
        setNameCache(nameCache, true);
1896
    }
1897

    
1898
    /**
1899
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1900
     * Sets the protectedNameCache flag to <code>true</code>.
1901
     *
1902
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1903
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
1904
     * <code>false</code>
1905
     * @see    #getNameCache()
1906
     */
1907
    @Override
1908
    public void setNameCache(String nameCache, boolean protectedNameCache){
1909
        this.nameCache = nameCache;
1910
        this.setProtectedNameCache(protectedNameCache);
1911
    }
1912

    
1913

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

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

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

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

    
1989
    /**
1990
     *
1991
     * @param direction
1992
     * @param type
1993
     * @return
1994
     */
1995
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
1996
        return getRelatedNames(relationsWithThisName(direction), type);
1997
    }
1998

    
1999
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2000
        Set<TaxonName> result = new HashSet<>();
2001
        for (NameRelationship rel : rels){
2002
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2003
                TaxonName basionym = rel.getFromName();
2004
                result.add(basionym);
2005
            }
2006
        }
2007
        return result;
2008
    }
2009

    
2010
    @Override
2011
    @Transient
2012
    public TaxonName getOriginalSpelling(){
2013
        if (this.getNomenclaturalSource() != null){
2014
            return this.getNomenclaturalSource().getNameUsedInSource();
2015
        }else{
2016
            return null;
2017
        }
2018
    }
2019

    
2020
    @Override
2021
    @Transient
2022
    public void setOriginalSpelling(TaxonName originalSpelling){
2023
        if (originalSpelling != null){
2024
            this.getNomenclaturalSource(true).setNameUsedInSource(originalSpelling);
2025
        }else if (this.getNomenclaturalSource() != null){
2026
            this.getNomenclaturalSource().setNameUsedInSource(null);
2027
            checkNullSource();
2028
        }
2029
    }
2030

    
2031
    @Override
2032
    @Transient
2033
    public void setOriginalNameString(String originalNameString){
2034
        if (isNotBlank(originalNameString)){
2035
            this.getNomenclaturalSource(true).setOriginalNameString(originalNameString);
2036
        }else if (this.getNomenclaturalSource() != null){
2037
            this.getNomenclaturalSource().setOriginalNameString(null);
2038
            checkNullSource();
2039
        }
2040
    }
2041

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

    
2081
    /**
2082
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2083
     */
2084
    @Override
2085
    @Transient
2086
    public Set<TaxonName> getReplacedSynonyms(){
2087
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2088
    }
2089

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

    
2111
    /**
2112
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2113
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2114
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2115
     * previously used as basionym.
2116
     *
2117
     * @see   #getBasionym()
2118
     * @see   #addBasionym(TaxonName)
2119
     */
2120
    @Override
2121
    public void removeBasionyms(){
2122
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2123
    }
2124

    
2125

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

    
2151
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2152
        switch(direction) {
2153
            case relatedTo:
2154
                return this.getRelationsToThisName();
2155
            case relatedFrom:
2156
                return this.getRelationsFromThisName();
2157
            default: throw new RuntimeException("invalid Direction:" + direction);
2158
        }
2159
    }
2160

    
2161
    /**
2162
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2163
     *
2164
     * @see 	Rank
2165
     */
2166
    @Override
2167
    public Rank getRank(){
2168
        return this.rank;
2169
    }
2170
    /**
2171
     * @see  #getRank()
2172
     */
2173
    @Override
2174
    public void setRank(Rank rank){
2175
        this.rank = rank;
2176
    }
2177

    
2178
//*************** nom ref/source *******************/
2179

    
2180
    @Override
2181
    public NomenclaturalSource getNomenclaturalSource(){
2182
        return this.nomenclaturalSource;
2183
    }
2184

    
2185
    public NomenclaturalSource getNomenclaturalSource(boolean createIfNotExist){
2186
        if (this.nomenclaturalSource == null && createIfNotExist){
2187
            setNomenclaturalSource(NomenclaturalSource.NewNomenclaturalInstance(this));
2188
        }
2189
        return nomenclaturalSource;
2190
    }
2191

    
2192
    @Override
2193
    public void setNomenclaturalSource(NomenclaturalSource nomenclaturalSource) throws IllegalArgumentException {
2194
        //check state
2195
        if (nomenclaturalSource != null && !OriginalSourceType.PrimaryTaxonomicSource.equals(nomenclaturalSource.getType())
2196
                ){
2197
            throw new IllegalArgumentException("Nomenclatural source must be of type " + OriginalSourceType.PrimaryTaxonomicSource.getLabel());
2198
        }
2199
        this.nomenclaturalSource = nomenclaturalSource;
2200
        if (nomenclaturalSource != null && nomenclaturalSource.getSourcedName() != this){
2201
            nomenclaturalSource.setSourcedName(this);
2202
        }
2203
    }
2204

    
2205
    @Override
2206
    @Transient
2207
    public Reference getNomenclaturalReference(){
2208
        return this.nomenclaturalSource == null? null:this.nomenclaturalSource.getCitation();
2209
    }
2210

    
2211
    @Override
2212
    @Transient
2213
    public void setNomenclaturalReference(Reference nomenclaturalReference){
2214
        if (nomenclaturalReference == null && this.getNomenclaturalSource()==null){
2215
            return;
2216
        }else{
2217
            getNomenclaturalSource(true).setCitation(nomenclaturalReference);
2218
        }
2219
    }
2220

    
2221
    @Override
2222
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2223
        setNomenclaturalReference(CdmBase.deproxy(nomenclaturalReference, Reference.class));
2224
    }
2225

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

    
2241
    /**
2242
     * @see  #getNomenclaturalMicroReference()
2243
     */
2244
    @Override
2245
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2246
        nomenclaturalMicroReference = isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
2247
        if (nomenclaturalMicroReference == null && this.getNomenclaturalSource()==null){
2248
            return;
2249
        }else{
2250
            this.getNomenclaturalSource(true).setCitationMicroReference(nomenclaturalMicroReference);
2251
        }
2252
    }
2253

    
2254
    /**
2255
     * Checks if the source is completely empty and if empty removes it from the name.
2256
     */
2257
    //TODO maybe this should be moved to a hibernate listener, but the listener solution may
2258
    //work only for nomenclatural single sources as they are the only which are bidirectional
2259
    protected void checkNullSource() {
2260
        if (this.nomenclaturalSource != null && this.nomenclaturalSource.checkEmpty(true)){
2261
            this.nomenclaturalSource = null;
2262
        }
2263
    }
2264

    
2265
// *********************************************
2266

    
2267
    /**
2268
     * Creates a new external link for given uri with default language description if description exists
2269
     * and adds it to the nomenclatural source.
2270
     * If no nomenclatural source exists a new one is created.
2271
     * @param uri the url to the protogue
2272
     * @param description an additional description for the link for the default language
2273
     * @param type see {@link ExternalLinkType}
2274
     * @return the newly created {@link ExternalLink external link}
2275
     */
2276
    public ExternalLink addProtologue(URI uri, String description, ExternalLinkType type){
2277
        ExternalLink newProtologue = ExternalLink.NewInstance(type, uri, description, null);
2278
        getNomenclaturalSource(true).addLink(newProtologue);
2279
        return newProtologue;
2280
    }
2281

    
2282
    /**
2283
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2284
     * The appended phrase is a non-atomised addition to a name. It is
2285
     * not ruled by a nomenclatural code.
2286
     */
2287
    @Override
2288
    public String getAppendedPhrase(){
2289
        return this.appendedPhrase;
2290
    }
2291

    
2292
    /**
2293
     * @see  #getAppendedPhrase()
2294
     */
2295
    @Override
2296
    public void setAppendedPhrase(String appendedPhrase){
2297
        this.appendedPhrase = isBlank(appendedPhrase)? null : appendedPhrase;
2298
    }
2299

    
2300
    @Override
2301
    public int getParsingProblem(){
2302
        return this.parsingProblem;
2303
    }
2304

    
2305
    @Override
2306
    public void setParsingProblem(int parsingProblem){
2307
        this.parsingProblem = parsingProblem;
2308
    }
2309

    
2310
    @Override
2311
    public void addParsingProblem(ParserProblem problem){
2312
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2313
    }
2314

    
2315
    @Override
2316
    public void removeParsingProblem(ParserProblem problem) {
2317
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2318
    }
2319

    
2320
    /**
2321
     * @param warnings
2322
     */
2323
    @Override
2324
    public void addParsingProblems(int problems){
2325
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2326
    }
2327

    
2328
    @Override
2329
    public boolean hasProblem(){
2330
        return parsingProblem != 0;
2331
    }
2332

    
2333
    @Override
2334
    public boolean hasProblem(ParserProblem problem) {
2335
        return getParsingProblems().contains(problem);
2336
    }
2337

    
2338
    @Override
2339
    public int getProblemStarts(){
2340
        return this.problemStarts;
2341
    }
2342

    
2343
    @Override
2344
    public void setProblemStarts(int start) {
2345
        this.problemStarts = start;
2346
    }
2347

    
2348
    @Override
2349
    public int getProblemEnds(){
2350
        return this.problemEnds;
2351
    }
2352

    
2353
    @Override
2354
    public void setProblemEnds(int end) {
2355
        this.problemEnds = end;
2356
    }
2357

    
2358
//*********************** TYPE DESIGNATION *********************************************//
2359

    
2360
    /**
2361
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2362
     * to <i>this</i> taxon name.
2363
     * @see     NameTypeDesignation
2364
     * @see     SpecimenTypeDesignation
2365
     */
2366
    @Override
2367
    public Set<TypeDesignationBase> getTypeDesignations() {
2368
        if(typeDesignations == null) {
2369
            this.typeDesignations = new HashSet<>();
2370
        }
2371
        return typeDesignations;
2372
    }
2373

    
2374
    /**
2375
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2376
     * <i>this</i> taxon name. The type designation itself will be nullified.
2377
     *
2378
     * @param  typeDesignation  the type designation which should be deleted
2379
     */
2380
    @Override
2381
    @SuppressWarnings("deprecation")
2382
    public void removeTypeDesignation(TypeDesignationBase<?> typeDesignation) {
2383
        this.typeDesignations.remove(typeDesignation);
2384
        typeDesignation.removeTypifiedName(this);
2385
    }
2386

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

    
2406
//*********************** NAME TYPE DESIGNATION *********************************************//
2407

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

    
2430
    /**
2431
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2432
     * to <i>this</i> taxon name's set of type designations.
2433
     *
2434
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2435
     * @param  citation					the reference for this new designation
2436
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2437
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2438
     * @param  isRejectedType			the boolean status for a rejected name type designation
2439
     * @param  isConservedType			the boolean status for a conserved name type designation
2440
     * @param  isLectoType				the boolean status for a lectotype name type designation
2441
     * @param  isNotDesignated			the boolean status for a name type designation without name type
2442
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2443
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2444
     * @return
2445
     * @see 			  				#getNameTypeDesignations()
2446
     * @see 			  				NameTypeDesignation
2447
     * @see 			  				TypeDesignationBase#isNotDesignated()
2448
     */
2449
    @Override
2450
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2451
                Reference citation,
2452
                String citationMicroReference,
2453
                String originalNameString,
2454
                NameTypeDesignationStatus status,
2455
                boolean isRejectedType,
2456
                boolean isConservedType,
2457
                /*boolean isLectoType, */
2458
                boolean isNotDesignated,
2459
                boolean addToAllHomotypicNames) {
2460
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalNameString, status, isRejectedType, isConservedType, isNotDesignated);
2461
        //nameTypeDesignation.setLectoType(isLectoType);
2462
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2463
        return nameTypeDesignation;
2464
    }
2465

    
2466
    /**
2467
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2468
     * to <i>this</i> taxon name's set of type designations.
2469
     *
2470
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2471
     * @param  citation					the reference for this new designation
2472
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2473
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2474
     * @param  status                   the name type designation status
2475
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2476
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2477
     * @return
2478
     * @see 			  				#getNameTypeDesignations()
2479
     * @see 			  				NameTypeDesignation
2480
     * @see 			  				TypeDesignationBase#isNotDesignated()
2481
     */
2482
    @Override
2483
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2484
                Reference citation,
2485
                String citationMicroReference,
2486
                String originalNameString,
2487
                NameTypeDesignationStatus status,
2488
                boolean addToAllHomotypicNames) {
2489
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2490
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2491
        return nameTypeDesignation;
2492
    }
2493

    
2494
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2495

    
2496
    /**
2497
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2498
     * that typify <i>this</i> taxon name.
2499
     */
2500
    @Override
2501
    @Transient
2502
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2503
        Set<SpecimenTypeDesignation> result = new HashSet<>();
2504
        for (TypeDesignationBase<?> typeDesignation : this.typeDesignations){
2505
            if (typeDesignation.isInstanceOf(SpecimenTypeDesignation.class)){
2506
                result.add(CdmBase.deproxy(typeDesignation, SpecimenTypeDesignation.class));
2507
            }
2508
        }
2509
        return result;
2510
    }
2511

    
2512

    
2513
    /**
2514
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2515
     * to <i>this</i> taxon name's set of type designations.
2516
     *
2517
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2518
     * @param  status					the specimen type designation status
2519
     * @param  citation					the reference for this new specimen type designation
2520
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2521
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2522
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2523
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2524
     * 									added to all taxon names of the homotypical group the typified
2525
     * 									taxon name belongs to
2526
     * @return
2527
     * @see 			  				#getSpecimenTypeDesignations()
2528
     * @see 			  				SpecimenTypeDesignationStatus
2529
     * @see 			  				SpecimenTypeDesignation
2530
     * @see 			  				TypeDesignationBase#isNotDesignated()
2531
     */
2532
    @Override
2533
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2534
                SpecimenTypeDesignationStatus status,
2535
                Reference citation,
2536
                String citationMicroReference,
2537
                String originalNameString,
2538
                boolean isNotDesignated,
2539
                boolean addToAllHomotypicNames) {
2540
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2541
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2542
        return specimenTypeDesignation;
2543
    }
2544

    
2545
    @Override
2546
    public TextualTypeDesignation addTextualTypeDesignation(
2547
                String text,
2548
                Language language,
2549
                boolean isVerbatim,
2550
                Reference citation,
2551
                String citationMicroReference,
2552
                String originalNameString,
2553
                boolean addToAllHomotypicNames) {
2554
        TextualTypeDesignation textualTypeDesignation = TextualTypeDesignation.NewInstance(text, language, isVerbatim, citation, citationMicroReference, originalNameString);
2555
        addTypeDesignation(textualTypeDesignation, addToAllHomotypicNames);
2556
        return textualTypeDesignation;
2557
    }
2558

    
2559
    //used by merge strategy
2560
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2561
        return addTypeDesignation(typeDesignation, true);
2562
    }
2563

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

    
2586
            if (addToAllNames){
2587
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2588
                    if (taxonName != this){
2589
                        taxonName.addTypeDesignation(typeDesignation, false);
2590
                    }
2591
                }
2592
            }
2593
        }
2594
        return true;
2595
    }
2596

    
2597
    /**
2598
     * Throws an Exception this type designation already has typified names from another homotypical group.
2599
     * @param typeDesignation
2600
     */
2601
    private void checkHomotypicalGroup(TypeDesignationBase<?> typeDesignation) {
2602
        if(typeDesignation.getTypifiedNames().size() > 0){
2603
            Set<HomotypicalGroup> groups = new HashSet<>();
2604
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2605
            for (TaxonName taxonName: names){
2606
                groups.add(taxonName.getHomotypicalGroup());
2607
            }
2608
            if (groups.size() > 1){
2609
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2610
            }
2611
        }
2612
    }
2613

    
2614

    
2615

    
2616
//*********************** HOMOTYPICAL GROUP *********************************************//
2617

    
2618

    
2619
    /**
2620
     * Returns the {@link HomotypicalGroup homotypical group} to which
2621
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2622
     * that share the same types.
2623
     *
2624
     * @see 	HomotypicalGroup
2625
     */
2626

    
2627
    @Override
2628
    public HomotypicalGroup getHomotypicalGroup() {
2629
        if (homotypicalGroup == null){
2630
            homotypicalGroup = new HomotypicalGroup();
2631
            homotypicalGroup.typifiedNames.add(this);
2632
        }
2633
    	return homotypicalGroup;
2634
    }
2635

    
2636
    /**
2637
     * @see #getHomotypicalGroup()
2638
     */
2639
    @Override
2640
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2641
        if (homotypicalGroup == null){
2642
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2643
        }
2644
        /*if (this.homotypicalGroup != null){
2645
        	this.homotypicalGroup.removeTypifiedName(this, false);
2646
        }*/
2647
        this.homotypicalGroup = homotypicalGroup;
2648
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2649
        	 this.homotypicalGroup.addTypifiedName(this);
2650
        }
2651
    }
2652

    
2653

    
2654

    
2655
// *************************************************************************//
2656

    
2657
    /**
2658
     * Returns the complete string containing the
2659
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2660
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2661
     *
2662
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2663
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2664
     * @see		#getNomenclaturalReference()
2665
     * @see		#getNomenclaturalMicroReference()
2666
     */
2667
    @Override
2668
    @Transient
2669
    public String getCitationString(){
2670
        NomenclaturalSource nomSource = getNomenclaturalSource();
2671
        return NomenclaturalSourceFormatter.INSTANCE().format(nomSource);
2672
    }
2673

    
2674
    /**
2675
     * Returns the parsing problems
2676
     * @return
2677
     */
2678
    @Override
2679
    public List<ParserProblem> getParsingProblems(){
2680
        return ParserProblem.warningList(this.parsingProblem);
2681
    }
2682

    
2683
    /**
2684
     * Returns the string containing the publication date (generally only year)
2685
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2686
     * no nomenclatural reference.
2687
     *
2688
     * @return  the string containing the publication date of <i>this</i> taxon name
2689
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2690
     */
2691
    @Override
2692
    @Transient
2693
    @ValidTaxonomicYear(groups=Level3.class)
2694
    public String getReferenceYear(){
2695
        if (this.getNomenclaturalReference() != null ){
2696
            return this.getNomenclaturalReference().getYear();
2697
        }else{
2698
            return null;
2699
        }
2700
    }
2701

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

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

    
2750

    
2751
    }
2752

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

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

    
2795
//***************** REGISTRATION *****************/
2796

    
2797
    @Override
2798
    public Set<Registration> getRegistrations() {
2799
        return this.registrations;
2800
    }
2801

    
2802

    
2803
// ************* RELATIONSHIPS *****************************/
2804

    
2805
    /**
2806
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2807
     * by title cache of the related names.
2808
     * @see #getHybridParentRelations()
2809
     */
2810
    @Override
2811
    @Transient
2812
    public List<HybridRelationship> getOrderedChildRelationships(){
2813
        List<HybridRelationship> result = new ArrayList<>();
2814
        result.addAll(this.hybridChildRelations);
2815
        Collections.sort(result);
2816
        Collections.reverse(result);
2817
        return result;
2818
    }
2819

    
2820
    @Override
2821
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2822
        return addHybridParent(parentName, type, null, null, ruleConsidered, null);
2823
    }
2824

    
2825
    @Override
2826
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, Reference reference,
2827
            String microReference, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
2828
        return new HybridRelationship(this, parentName, type, reference, microReference, ruleConsidered, codeEdition);
2829
    }
2830

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

    
2853
    @Override
2854
    public void removeHybridChild(INonViralName child) {
2855
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2856
        hybridRelationships.addAll(this.getHybridChildRelations());
2857
        hybridRelationships.addAll(this.getHybridParentRelations());
2858
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2859
            // remove name relationship from this side
2860
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2861
                this.removeHybridRelationship(hybridRelationship);
2862
            }
2863
        }
2864
    }
2865

    
2866
    @Override
2867
    public void removeHybridParent(INonViralName parent) {
2868
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2869
        hybridRelationships.addAll(this.getHybridChildRelations());
2870
        hybridRelationships.addAll(this.getHybridParentRelations());
2871
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2872
            // remove name relationship from this side
2873
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2874
                this.removeHybridRelationship(hybridRelationship);
2875
            }
2876
        }
2877
    }
2878

    
2879

    
2880

    
2881
// *********** DESCRIPTIONS *************************************
2882

    
2883
    /**
2884
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2885
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2886
     * concerning the taxon name like for instance the content of its first
2887
     * publication (protolog) or a picture of this publication.
2888
     *
2889
     * @see	#addDescription(TaxonNameDescription)
2890
     * @see	#removeDescription(TaxonNameDescription)
2891
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2892
     */
2893
    @Override
2894
    public Set<TaxonNameDescription> getDescriptions() {
2895
        return descriptions;
2896
    }
2897

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

    
2933
// *********** HOMOTYPIC GROUP METHODS **************************************************
2934

    
2935
    @Override
2936
    @Transient
2937
    public void mergeHomotypicGroups(TaxonName name){
2938
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2939
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2940
        name.setHomotypicalGroup(this.homotypicalGroup);
2941
    }
2942

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

    
2969

    
2970
    /**
2971
     * Checks whether name is a basionym for ALL names
2972
     * in its homotypical group.
2973
     * Returns <code>false</code> if there are no other names in the group
2974
     * @param name
2975
     * @return
2976
     */
2977
    @Override
2978
    @Transient
2979
    public boolean isGroupsBasionym() {
2980
    	if (homotypicalGroup == null){
2981
    		homotypicalGroup = HomotypicalGroup.NewInstance();
2982
    		homotypicalGroup.addTypifiedName(this);
2983
    	}
2984
        Set<TaxonName> typifiedNames = homotypicalGroup.getTypifiedNames();
2985

    
2986
        // Check whether there are any other names in the group
2987
        if (typifiedNames.size() == 1) {
2988
                return false;
2989
        }
2990

    
2991
        for (TaxonName taxonName : typifiedNames) {
2992
                if (!taxonName.equals(this)) {
2993
                        if (! isBasionymFor(taxonName)) {
2994
                                return false;
2995
                        }
2996
                }
2997
        }
2998
        return true;
2999
    }
3000

    
3001
    /**
3002
     * Checks whether a basionym relationship exists between fromName and toName.
3003
     *
3004
     * @param fromName
3005
     * @param toName
3006
     * @return
3007
     */
3008
    @Override
3009
    @Transient
3010
    public boolean isBasionymFor(TaxonName newCombinationName) {
3011
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3012
            for (NameRelationship relation : relations) {
3013
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3014
                                    relation.getFromName().equals(this)) {
3015
                            return true;
3016
                    }
3017
            }
3018
            return false;
3019
    }
3020

    
3021
    /**
3022
     * Creates a basionym relationship to all other names in this names homotypical
3023
     * group.
3024
     *
3025
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3026
     */
3027
    @Override
3028
    @Transient
3029
    public void makeGroupsBasionym() {
3030
        this.homotypicalGroup.setGroupBasionym(this);
3031
    }
3032

    
3033

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

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

    
3098
    /**
3099
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3100
     * taxon name is higher than the species rank (true) or not (false).
3101
     * Returns false if rank is null.
3102
     *
3103
     * @see  #isGenus()
3104
     * @see  #isInfraGeneric()
3105
     * @see  #isSpecies()
3106
     * @see  #isInfraSpecific()
3107
     */
3108
    @Override
3109
    @Transient
3110
    public boolean isSupraSpecific(){
3111
        if (rank == null) {
3112
            return false;
3113
        }
3114
        return getRank().isHigher(Rank.SPECIES());
3115
    }
3116

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

    
3156
    /**
3157
     * Returns true if this name's rank indicates a rank that aggregates species like species
3158
     * aggregates or species groups, false otherwise. This methods currently returns false
3159
     * for all user defined ranks.
3160
     *
3161
     *@see Rank#isSpeciesAggregate()
3162
     *
3163
     * @return
3164
     */
3165
    @Override
3166
    @Transient
3167
    public boolean isSpeciesAggregate() {
3168
        if (rank == null){
3169
            return false;
3170
        }
3171
        return getRank().isSpeciesAggregate();
3172
    }
3173

    
3174

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

    
3196
    /**
3197
     * Generates and returns the string with the scientific name of <i>this</i>
3198
     * taxon name (only non viral taxon names can be generated from their
3199
     * components). This string may be stored in the inherited
3200
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3201
     * This method overrides the generic and inherited
3202
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3203
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3204
     *
3205
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3206
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3207
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3208
     */
3209
//	@Override
3210
//	public abstract String generateTitle();
3211

    
3212
    /**
3213
     * Creates a basionym relationship between this name and
3214
     * 	each name in its homotypic group.
3215
     *
3216
     * @param basionymName
3217
     */
3218
    @Override
3219
    @Transient
3220
    public void setAsGroupsBasionym() {
3221

    
3222
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3223
        if (homotypicalGroup == null) {
3224
            return;
3225
        }
3226

    
3227
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3228
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3229

    
3230
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3231

    
3232
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3233

    
3234
            for(NameRelationship nameRelation : nameRelations){
3235
                relations.add(nameRelation);
3236
            }
3237
        }
3238

    
3239
        for (NameRelationship relation : relations) {
3240

    
3241
            // If this is a basionym relation, and toName is in the homotypical group,
3242
            //	remove the relationship.
3243
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3244
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3245
                removeRelations.add(relation);
3246
            }
3247
        }
3248

    
3249
        // Removing relations from a set through which we are iterating causes a
3250
        //	ConcurrentModificationException. Therefore, we delete the targeted
3251
        //	relations in a second step.
3252
        for (NameRelationship relation : removeRelations) {
3253
            this.removeNameRelationship(relation);
3254
        }
3255

    
3256
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3257
            if (!name.equals(this)) {
3258

    
3259
                // First check whether the relationship already exists
3260
                if (!this.isBasionymFor(name)) {
3261

    
3262
                    // Then create it
3263
                    name.addRelationshipFromName(this,
3264
                            NameRelationshipType.BASIONYM(), null, null);
3265
                }
3266
            }
3267
        }
3268
    }
3269

    
3270
    /**
3271
     * Removes basionym relationship between this name and
3272
     * 	each name in its homotypic group.
3273
     *
3274
     * @param basionymName
3275
     */
3276
    @Override
3277
    @Transient
3278
    public void removeAsGroupsBasionym() {
3279

    
3280
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3281

    
3282
        if (homotypicalGroup == null) {
3283
            return;
3284
        }
3285

    
3286
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3287
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3288

    
3289
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3290

    
3291
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3292

    
3293
            for(NameRelationship nameRelation : nameRelations){
3294
                relations.add(nameRelation);
3295
            }
3296
        }
3297

    
3298
        for (NameRelationship relation : relations) {
3299

    
3300
            // If this is a basionym relation, and toName is in the homotypical group,
3301
            //	and fromName is basionymName, remove the relationship.
3302
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3303
                    relation.getFromName().equals(this) &&
3304
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3305
                removeRelations.add(relation);
3306
            }
3307
        }
3308

    
3309
        // Removing relations from a set through which we are iterating causes a
3310
        //	ConcurrentModificationException. Therefore, we delete the targeted
3311
        //	relations in a second step.
3312
        for (NameRelationship relation : removeRelations) {
3313
            this.removeNameRelationship(relation);
3314
        }
3315
    }
3316

    
3317

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

    
3341
    @Override
3342
    public boolean isHybridName() {
3343
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3344
    }
3345

    
3346
    @Override
3347
    public boolean isHybrid() {
3348
        return this.isHybridName() || this.isHybridFormula();
3349
    }
3350

    
3351
// ******************** NOMENCLATURAL STANDING ****************/
3352

    
3353
    /**
3354
     * Computes the highest priority nomenclatural standing
3355
     * from nomenclatural status and name relationships.
3356
     */
3357
    private NomenclaturalStanding computeNomenclaturalStanding() {
3358
        Set<NomenclaturalStanding> standings = computeNomenclaturalStandings();
3359
        return NomenclaturalStanding.highest(standings);
3360
    }
3361

    
3362
    /**
3363
     * Computes all nomenclatural standings form the nomenclatural status and the name relationships.
3364
     */
3365
    private Set<NomenclaturalStanding> computeNomenclaturalStandings() {
3366
        Set<NomenclaturalStanding> standings = new HashSet<>();
3367
        for (NomenclaturalStatus status : this.status){
3368
            if (status.getType() != null){
3369
                NomenclaturalStanding standing = status.getType().getNomenclaturalStanding();
3370
                standings.add(standing);
3371
            }
3372
        }
3373
        for (NameRelationship nameRel : this.relationsFromThisName){
3374
            if (nameRel.getType() != null){
3375
                NomenclaturalStanding standing = nameRel.getType().getNomenclaturalStanding();
3376
                standings.add(standing);
3377
            }
3378
        }
3379
        for (NameRelationship nameRel : this.relationsToThisName){
3380
            if (nameRel.getType() != null){
3381
                NomenclaturalStanding standing = nameRel.getType().getNomenclaturalStandingInverse();
3382
                standings.add(standing);
3383
            }
3384
        }
3385
        return standings;
3386
    }
3387

    
3388
    @Override
3389
    @Transient
3390
    public boolean isDesignationOnly() {
3391
        return computeNomenclaturalStanding().isDesignationOnly();
3392
    }
3393

    
3394
    @Override
3395
    @Transient
3396
    public boolean isInvalidExplicit() {
3397
        return computeNomenclaturalStanding().isInvalidExplicit();
3398
    }
3399

    
3400
    @Override
3401
    @Transient
3402
    public boolean isIllegitimate() {
3403
        return computeNomenclaturalStanding().isIllegitimate();
3404
    }
3405

    
3406
    @Override
3407
    @Transient
3408
    public boolean isValidExplicit() {
3409
        return computeNomenclaturalStanding().isValidExplicit();
3410
    }
3411

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

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

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

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

    
3436
// ***************** COMPARE ********************************/
3437

    
3438
    @Override
3439
    public int compareToName(TaxonName otherName){
3440

    
3441
        int result = 0;
3442

    
3443
        if (otherName == null) {
3444
            throw new NullPointerException("Cannot compare to null.");
3445
        }
3446

    
3447
        //other
3448
        otherName = deproxy(otherName);
3449
        String otherNameCache = otherName.getNameCache();
3450
        String otherTitleCache = otherName.getTitleCache();
3451
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3452
        if (otherName.isAutonym()){
3453
            boolean isProtected = otherName.isProtectedNameCache();
3454
            String oldNameCache = otherName.getNameCache();
3455
            otherName.setProtectedNameCache(false);
3456
            otherName.setNameCache(null, false);
3457
            otherNameCache = otherName.getNameCache();
3458
            otherName.setNameCache(oldNameCache, isProtected);
3459
        }
3460

    
3461
        //this
3462
        String thisNameCache = this.getNameCache();
3463
        String thisTitleCache = this.getTitleCache();
3464

    
3465
        if (this.isAutonym()){
3466
            boolean isProtected = this.isProtectedNameCache();
3467
            String oldNameCache = this.getNameCache();
3468
            this.setProtectedNameCache(false);
3469
            this.setNameCache(null, false);
3470
            thisNameCache = this.getNameCache();
3471
            this.setNameCache(oldNameCache, isProtected);
3472
        }
3473

    
3474
        // Compare name cache of taxon names
3475
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3476
            String thisNormalized = normalizeName(thisNameCache);
3477
            String otherNormalized = normalizeName(otherNameCache);
3478
            result = thisNormalized.compareTo(otherNormalized);
3479
        }
3480

    
3481
        // Compare title cache of taxon names
3482
        if (result == 0){
3483
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3484
                String thisNormalized = normalizeName(thisTitleCache);
3485
                String otherNormalized = normalizeName(otherTitleCache);
3486
                result = CdmUtils.nullSafeCompareTo(thisNormalized, otherNormalized);
3487
                if (result == 0){
3488
                    result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3489
                }
3490
            }
3491
        }
3492
        if (result == 0){
3493
            result =  CdmUtils.nullSafeCompareTo(thisNameCache, otherNameCache);
3494
        }
3495

    
3496
        return result;
3497
    }
3498

    
3499
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3500
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3501

    
3502
    private String normalizeName(String thisNameCache) {
3503
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3504
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3505
        return thisNameCache;
3506
    }
3507

    
3508
// ********************** INTERFACES ********************************************/
3509

    
3510
    /**
3511
     * Method to cast a interfaced name to a concrete name.
3512
     * The method includes a deproxy to guarantee that no
3513
     * class cast exception is thrown.
3514
     *
3515
     * @see #castAndDeproxy(Set)
3516
     * @param interfacedName
3517
     * @return
3518
     */
3519
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3520
        return deproxy(interfacedName, TaxonName.class);
3521
    }
3522

    
3523
    /**
3524
     * Method to cast a set of interfaced names to concrete namex.
3525
     * The method includes a deproxy to guarantee that no
3526
     * class cast exception is thrown.
3527
     *
3528
     * @see #castAndDeproxy(ITaxonNameBase)
3529
     * @param naminterfacedNames
3530
     * @return
3531
     */
3532
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3533
        Set<TaxonName> result = new HashSet<>();
3534
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3535
            result.add(castAndDeproxy(naminterfacedName));
3536
        }
3537
        return result;
3538
    }
3539

    
3540
//************************ isType ***********************************************/
3541

    
3542
    @Override
3543
    public boolean isNonViral() {
3544
        return nameType.isNonViral();
3545
    }
3546
    @Override
3547
    public boolean isZoological(){
3548
        return nameType.isZoological();
3549
    }
3550
    @Override
3551
    public boolean isBotanical() {
3552
        if (nameType == null){
3553
            throw new RuntimeException("Name has no nameType: " +  this.getUuid() + ", " + getId()+ ", species epi: " + getSpecificEpithet() );
3554
        }
3555
        return nameType.isBotanical();
3556
    }
3557
    @Override
3558
    public boolean isCultivar() {
3559
        return nameType.isCultivar();
3560
    }
3561
    @Override
3562
    public boolean isBacterial() {
3563
        return nameType.isBacterial();
3564
    }
3565
    @Override
3566
    public boolean isViral() {
3567
        return nameType != null? nameType.isViral(): false;
3568
    }
3569

    
3570
// *********************** CACHES ***************************************************/
3571

    
3572
    @Override
3573
    public boolean updateCaches() {
3574
        boolean result = updateAuthorshipCache();
3575
        result |= updateNameCache();
3576
        result |= super.updateCaches();
3577
        result |= updateFullTitleCache();
3578
        return result;
3579
    }
3580

    
3581
    /**
3582
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
3583
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened.
3584
     * @return <code>true</code> if something changed
3585
     */
3586
    private boolean updateAuthorshipCache() {
3587
        //updates the authorship cache if necessary and via the listener updates all higher caches
3588
        if (protectedAuthorshipCache == false){
3589
            String oldCache = this.authorshipCache;
3590
            String newCache = getCacheStrategy().getAuthorshipCache(this);
3591
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3592
                this.setAuthorshipCache(null, false);
3593
                this.getAuthorshipCache();
3594
                return true;
3595
            }
3596
        }
3597
        return false;
3598
    }
3599

    
3600
    private boolean updateNameCache() {
3601
        //updates the name cache if necessary and via the listener updates all higher caches
3602
        if (protectedNameCache == false){
3603
            String oldCache = this.nameCache;
3604
            String newCache = getCacheStrategy().getNameCache(this);
3605
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3606
                this.setNameCache(null, false);
3607
                this.getNameCache();
3608
                return true;
3609
            }
3610
        }
3611
        return false;
3612
    }
3613

    
3614
    private boolean updateFullTitleCache() {
3615
        if (protectedFullTitleCache == false){
3616
            String oldCache = this.fullTitleCache;
3617
            String newCache = getTruncatedCache(getCacheStrategy().getFullTitleCache(this));
3618
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3619
                this.setFullTitleCache(null, false);
3620
                this.getFullTitleCache();
3621
                return true;
3622
            }
3623
        }
3624
        return false;
3625
    }
3626

    
3627
//*********************** CLONE ********************************************************/
3628

    
3629
    @Override
3630
    public TaxonName clone() {
3631
        return this.clone(true);
3632
    }
3633

    
3634
    @Override
3635
    public TaxonName clone(boolean newHomotypicGroup) {
3636
        try {
3637
            TaxonName result = (TaxonName)super.clone();
3638

    
3639
            //taxonBases -> empty
3640
            result.taxonBases = new HashSet<>();
3641

    
3642
            //empty caches
3643
            if (! protectedFullTitleCache){
3644
                result.fullTitleCache = null;
3645
            }
3646

    
3647
            //descriptions
3648
            result.descriptions = new HashSet<>();
3649
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3650
                TaxonNameDescription newDescription = taxonNameDescription.clone();
3651
                result.descriptions.add(newDescription);
3652
            }
3653

    
3654
            //status
3655
            result.status = new HashSet<>();
3656
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3657
                NomenclaturalStatus newStatus = nomenclaturalStatus.clone();
3658
                result.status.add(newStatus);
3659
            }
3660

    
3661
            //to relations
3662
            result.relationsToThisName = new HashSet<>();
3663
            for (NameRelationship toRelationship : getRelationsToThisName()){
3664
                NameRelationship newRelationship = toRelationship.clone();
3665
                newRelationship.setRelatedTo(result);
3666
                result.relationsToThisName.add(newRelationship);
3667
            }
3668

    
3669
            //from relations
3670
            result.relationsFromThisName = new HashSet<>();
3671
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3672
                NameRelationship newRelationship = fromRelationship.clone();
3673
                newRelationship.setRelatedFrom(result);
3674
                result.relationsFromThisName.add(newRelationship);
3675
            }
3676

    
3677
            //type designations
3678
            result.typeDesignations = new HashSet<>();
3679
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3680
                TypeDesignationBase<?> newDesignation = typeDesignation.clone();
3681
                this.removeTypeDesignation(newDesignation);
3682
                result.addTypeDesignation(newDesignation, false);
3683
            }
3684

    
3685
            //homotypicalGroup
3686
            if (newHomotypicGroup){
3687
                HomotypicalGroup homotypicalGroup = HomotypicalGroup.NewInstance();
3688
                homotypicalGroup.addTypifiedName(result);
3689
            }else{
3690
                result.homotypicalGroup.addTypifiedName(result);  //to immediately handle bidirectionality
3691
            }
3692

    
3693
            //HybridChildRelations
3694
            result.hybridChildRelations = new HashSet<>();
3695
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3696
                HybridRelationship newChildRelationship = hybridRelationship.clone();
3697
                newChildRelationship.setRelatedTo(result);
3698
                result.hybridChildRelations.add(newChildRelationship);
3699
            }
3700

    
3701
            //HybridParentRelations
3702
            result.hybridParentRelations = new HashSet<>();
3703
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3704
                HybridRelationship newParentRelationship = hybridRelationship.clone();
3705
                newParentRelationship.setRelatedFrom(result);
3706
                result.hybridParentRelations.add(newParentRelationship);
3707
            }
3708

    
3709
            //empty caches
3710
            if (! protectedNameCache){
3711
                result.nameCache = null;
3712
            }
3713

    
3714
            //empty caches
3715
            if (! protectedAuthorshipCache){
3716
                result.authorshipCache = null;
3717
            }
3718

    
3719
            //registrations
3720
            result.registrations = new HashSet<>();
3721
            for (Registration registration : getRegistrations()){
3722
                result.registrations.add(registration);
3723
            }
3724

    
3725
            //nomenclatural source
3726
            if (this.getNomenclaturalSource() != null){
3727
                result.setNomenclaturalSource(this.getNomenclaturalSource().clone());
3728
                result.getNomenclaturalSource().setSourcedName(result);
3729
            }
3730

    
3731

    
3732
            //no changes to: appendedPharse, parsingProblem, problemEnds, problemStarts
3733
            //protectedFullTitleCache, rank
3734
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3735
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3736
            //protectedAuthorshipCache, protectedNameCache,
3737
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3738
            //acronym
3739
            //subGenusAuthorship, nameApprobation
3740
            //anamorphic
3741
            //cultivarName
3742
            return result;
3743
        } catch (CloneNotSupportedException e) {
3744
            logger.warn("Object does not implement cloneable");
3745
            e.printStackTrace();
3746
            return null;
3747
        }
3748
    }
3749

    
3750
}
(33-33/39)