Project

General

Profile

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

    
10
package eu.etaxonomy.cdm.model.name;
11

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

    
22
import javax.persistence.Column;
23
import javax.persistence.Entity;
24
import javax.persistence.FetchType;
25
import javax.persistence.Inheritance;
26
import javax.persistence.InheritanceType;
27
import javax.persistence.JoinColumn;
28
import javax.persistence.JoinTable;
29
import javax.persistence.ManyToMany;
30
import javax.persistence.ManyToOne;
31
import javax.persistence.OneToMany;
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.commons.lang.StringUtils;
48
import org.apache.log4j.Logger;
49
import org.hibernate.annotations.Cascade;
50
import org.hibernate.annotations.CascadeType;
51
import org.hibernate.annotations.Table;
52
import org.hibernate.annotations.Type;
53
import org.hibernate.envers.Audited;
54
import org.hibernate.search.annotations.Analyze;
55
import org.hibernate.search.annotations.Analyzer;
56
import org.hibernate.search.annotations.Field;
57
import org.hibernate.search.annotations.Fields;
58
import org.hibernate.search.annotations.Index;
59
import org.hibernate.search.annotations.Indexed;
60
import org.hibernate.search.annotations.IndexedEmbedded;
61
import org.hibernate.search.annotations.Store;
62
import org.hibernate.validator.constraints.NotEmpty;
63
import org.springframework.util.ReflectionUtils;
64

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

    
108
/**
109
 * The upmost (abstract) class for scientific taxon names regardless of any
110
 * particular {@link NomenclaturalCode nomenclature code}. The scientific taxon name does not depend
111
 * on the use made of it in a publication or a treatment
112
 * ({@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon concept respectively potential taxon})
113
 * as an {@link eu.etaxonomy.cdm.model.taxon.Taxon "accepted" respectively "correct" (taxon) name}
114
 * or as a {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
115
 * <P>
116
 * This class corresponds partially to: <ul>
117
 * <li> TaxonName according to the TDWG ontology
118
 * <li> ScientificName and CanonicalName according to the TCS
119
 * <li> ScientificName according to the ABCD schema
120
 * </ul>
121
 *
122
 * @author m.doering
123
 * @since 08-Nov-2007 13:06:57
124
 */
125
@XmlAccessorType(XmlAccessType.FIELD)
126
@XmlType(name = "TaxonName", propOrder = {
127
    "nameType",
128
    "appendedPhrase",
129
    "nomenclaturalMicroReference",
130
    "nomenclaturalReference",
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

    
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

    
163
    "acronym",
164

    
165
    "subGenusAuthorship",
166
    "nameApprobation",
167

    
168
    "breed",
169
    "publicationYear",
170
    "originalPublicationYear",
171

    
172
    "anamorphic",
173

    
174
    "cultivarName"
175
})
176
@XmlRootElement(name = "TaxonName")
177
@Entity
178
@Audited
179
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
180
@Table(appliesTo="TaxonName", indexes = {
181
        @org.hibernate.annotations.Index(name = "taxonNameBaseTitleCacheIndex", columnNames = { "titleCache" }),
182
        @org.hibernate.annotations.Index(name = "taxonNameBaseNameCacheIndex", columnNames = { "nameCache" }) })
183
@NameMustFollowCode
184
@CorrectEpithetsForRank(groups = Level2.class)
185
@NameMustHaveAuthority(groups = Level2.class)
186
@NoDuplicateNames(groups = Level3.class)
187
@Indexed(index = "eu.etaxonomy.cdm.model.name.TaxonName")
188
public class TaxonName
189
            extends IdentifiableEntity<INameCacheStrategy>
190
            implements ITaxonNameBase, INonViralName, IViralName, IBacterialName, IZoologicalName,
191
                IBotanicalName, ICultivarPlantName, IFungusName,
192
                IParsable, IRelated, IMatchable, IIntextReferenceTarget, Cloneable,
193
                IDescribable<TaxonNameDescription>{
194

    
195
    private static final long serialVersionUID = -791164269603409712L;
196
    private static final Logger logger = Logger.getLogger(TaxonName.class);
197

    
198

    
199
    /**
200
     * The {@link TermType type} of this term. Needs to be the same type in a {@link DefinedTermBase defined term}
201
     * and in it's {@link TermVocabulary vocabulary}.
202
     */
203
    @XmlAttribute(name ="NameType")
204
    @Column(name="nameType", length=15)
205
    @NotNull
206
    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
207
        parameters = {@org.hibernate.annotations.Parameter(name = "enumClass", value = "eu.etaxonomy.cdm.model.name.NomenclaturalCode")}
208
    )
209
    @Audited //needed ?
210
    private NomenclaturalCode nameType;
211

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

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

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

    
231
    @XmlElement(name = "AppendedPhrase")
232
    @Field
233
    @CacheUpdate(value ="nameCache")
234
    //TODO Val #3379
235
//    @NullOrNotEmpty
236
    @Column(length=255)
237
    private String appendedPhrase;
238
//
239
//    @XmlElement(name = "NomenclaturalMicroReference")
240
//    @Field
241
//    @CacheUpdate(noUpdate ="titleCache")
242
//    //TODO Val #3379
243
////    @NullOrNotEmpty
244
//    @Column(length=255)
245
//    private String nomenclaturalMicroReference;
246

    
247
    @XmlAttribute
248
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
249
    private int parsingProblem = 0;
250

    
251
    @XmlAttribute
252
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
253
    private int problemStarts = -1;
254

    
255
    @XmlAttribute
256
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
257
    private int problemEnds = -1;
258

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

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

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

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

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

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

    
321
    @XmlElement(name = "Rank")
322
    @XmlIDREF
323
    @XmlSchemaType(name = "IDREF")
324
    @ManyToOne(fetch = FetchType.EAGER)
325
    @CacheUpdate(value ="nameCache")
326
    //TODO Val #3379, handle maybe as groups = Level2.class ??
327
//    @NotNull
328
    @IndexedEmbedded(depth=1)
329
    private Rank rank;
330
//
331
//    @XmlElement(name = "NomenclaturalReference")
332
//    @XmlIDREF
333
//    @XmlSchemaType(name = "IDREF")
334
//    @ManyToOne(fetch = FetchType.LAZY)
335
//    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
336
//    @CacheUpdate(noUpdate ="titleCache")
337
//    @IndexedEmbedded
338
//    private Reference nomenclaturalReference;
339

    
340
    @XmlElement(name = "NomenclaturalSource")
341
    @XmlIDREF
342
    @XmlSchemaType(name = "IDREF")
343
    @ManyToOne(fetch = FetchType.LAZY)
344
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
345
    @CacheUpdate(noUpdate ="titleCache")
346
    @IndexedEmbedded
347
    private DescriptionElementSource nomenclaturalSource;
348

    
349

    
350

    
351
    @XmlElementWrapper(name = "Registrations")
352
    @XmlElement(name = "Registration")
353
    @XmlIDREF
354
    @XmlSchemaType(name = "IDREF")
355
    @OneToMany(mappedBy="name", fetch= FetchType.LAZY)
356
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
357
    @NotNull
358
    @IndexedEmbedded(depth=1)
359
    private Set<Registration> registrations = new HashSet<>();
360

    
361
//****** Non-ViralName attributes ***************************************/
362

    
363
    @XmlElement(name = "NameCache")
364
    @Fields({
365
        @Field(name = "nameCache_tokenized"),
366
        @Field(store = Store.YES, index = Index.YES, analyze = Analyze.YES)
367
    })
368
    @Analyzer(impl = org.apache.lucene.analysis.core.KeywordAnalyzer.class)
369
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
370
            cacheReplacedProperties={"genusOrUninomial", "infraGenericEpithet", "specificEpithet", "infraSpecificEpithet"} )
371
    @NotEmpty(groups = Level2.class) // implicitly NotNull
372
    @Column(length=255)
373
    private String nameCache;
374

    
375
    @XmlElement(name = "ProtectedNameCache")
376
    @CacheUpdate(value="nameCache")
377
    protected boolean protectedNameCache;
378

    
379
    @XmlElement(name = "GenusOrUninomial")
380
    @Field(analyze = Analyze.YES, indexNullAs=Field.DEFAULT_NULL_TOKEN)
381
    @Match(MatchMode.EQUAL_REQUIRED)
382
    @CacheUpdate("nameCache")
383
    @Column(length=255)
384
    @Pattern(regexp = "[A-Z][a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForUninomial.message}")
385
    @NullOrNotEmpty
386
    @NotNull(groups = Level2.class)
387
    private String genusOrUninomial;
388

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

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

    
407
    @XmlElement(name = "InfraSpecificEpithet")
408
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
409
    @CacheUpdate("nameCache")
410
    //TODO Val #3379
411
//    @NullOrNotEmpty
412
    @Column(length=255)
413
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
414
    private String infraSpecificEpithet;
415

    
416
    @XmlElement(name = "CombinationAuthorship", type = TeamOrPersonBase.class)
417
    @XmlIDREF
418
    @XmlSchemaType(name = "IDREF")
419
    @ManyToOne(fetch = FetchType.LAZY)
420
//    @Target(TeamOrPersonBase.class)
421
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
422
    @JoinColumn(name="combinationAuthorship_id")
423
    @CacheUpdate("authorshipCache")
424
    @IndexedEmbedded
425
    private TeamOrPersonBase<?> combinationAuthorship;
426

    
427
    @XmlElement(name = "ExCombinationAuthorship", type = TeamOrPersonBase.class)
428
    @XmlIDREF
429
    @XmlSchemaType(name = "IDREF")
430
    @ManyToOne(fetch = FetchType.LAZY)
431
//    @Target(TeamOrPersonBase.class)
432
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
433
    @JoinColumn(name="exCombinationAuthorship_id")
434
    @CacheUpdate("authorshipCache")
435
    @IndexedEmbedded
436
    private TeamOrPersonBase<?> exCombinationAuthorship;
437

    
438
    @XmlElement(name = "BasionymAuthorship", type = TeamOrPersonBase.class)
439
    @XmlIDREF
440
    @XmlSchemaType(name = "IDREF")
441
    @ManyToOne(fetch = FetchType.LAZY)
442
//    @Target(TeamOrPersonBase.class)
443
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
444
    @JoinColumn(name="basionymAuthorship_id")
445
    @CacheUpdate("authorshipCache")
446
    @IndexedEmbedded
447
    private TeamOrPersonBase<?> basionymAuthorship;
448

    
449
    @XmlElement(name = "ExBasionymAuthorship", type = TeamOrPersonBase.class)
450
    @XmlIDREF
451
    @XmlSchemaType(name = "IDREF")
452
    @ManyToOne(fetch = FetchType.LAZY)
453
//    @Target(TeamOrPersonBase.class)
454
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
455
    @JoinColumn(name="exBasionymAuthorship_id")
456
    @CacheUpdate("authorshipCache")
457
    @IndexedEmbedded
458
    private TeamOrPersonBase<?> exBasionymAuthorship;
459

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

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

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

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

    
493

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

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

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

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

    
513
// ViralName attributes ************************* /
514

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

    
522
// BacterialName attributes ***********************/
523

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

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

    
534
    //ZOOLOGICAL NAME
535

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

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

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

    
555
    //Cultivar attribute(s)
556

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

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

    
569
// *************** FACTORY METHODS ********************************/
570

    
571
    //see TaxonNameFactory
572
    /**
573
     * @param code
574
     * @param rank
575
     * @param homotypicalGroup
576
     * @return
577
     */
578
    protected static TaxonName NewInstance(NomenclaturalCode code, Rank rank,
579
            HomotypicalGroup homotypicalGroup) {
580
        TaxonName result = new TaxonName(code, rank, homotypicalGroup);
581
        return result;
582
    }
583

    
584

    
585
    /**
586
     * @param icnafp
587
     * @param rank2
588
     * @param genusOrUninomial2
589
     * @param infraGenericEpithet2
590
     * @param specificEpithet2
591
     * @param infraSpecificEpithet2
592
     * @param combinationAuthorship2
593
     * @param nomenclaturalReference2
594
     * @param nomenclMicroRef
595
     * @param homotypicalGroup2
596
     * @return
597
     */
598
    public static TaxonName NewInstance(NomenclaturalCode code, Rank rank, String genusOrUninomial,
599
            String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet,
600
            TeamOrPersonBase combinationAuthorship, Reference nomenclaturalReference,
601
            String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
602
        TaxonName result = new TaxonName(code, rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
603
        return result;
604
    }
605

    
606

    
607
// ************* CONSTRUCTORS *************/
608
    /**
609
     * Class constructor: creates a new empty taxon name.
610
     * @param code
611
     *
612
     * @see #TaxonName(Rank)
613
     * @see #TaxonName(HomotypicalGroup)
614
     * @see #TaxonName(Rank, HomotypicalGroup)
615
     */
616
    protected TaxonName() {
617
        super();
618
        rectifyNameCacheStrategy();
619
    }
620

    
621

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

    
647

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

    
693

    
694
    /**
695
     * This method was originally needed to distinguish cache strategies
696
     * depending on the name type. Now we have a unified cache strategy
697
     * which does not require this anymore. Maybe we could even further remove this method.
698
     */
699
    private void rectifyNameCacheStrategy(){
700
        if (this.cacheStrategy == null){
701
            this.cacheStrategy = TaxonNameDefaultCacheStrategy.NewInstance();
702
        }
703
    }
704

    
705

    
706
    @Override
707
    public void initListener(){
708
        PropertyChangeListener listener = new PropertyChangeListener() {
709
            @Override
710
            public void propertyChange(PropertyChangeEvent e) {
711
                boolean protectedByLowerCache = false;
712
                //authorship cache
713
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
714
                    if (protectedAuthorshipCache){
715
                        protectedByLowerCache = true;
716
                    }else{
717
                        authorshipCache = null;
718
                    }
719
                }
720

    
721
                //nameCache
722
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "nameCache")){
723
                    if (protectedNameCache){
724
                        protectedByLowerCache = true;
725
                    }else{
726
                        nameCache = null;
727
                    }
728
                }
729
                //title cache
730
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "titleCache")){
731
                    if (isProtectedTitleCache()|| protectedByLowerCache == true ){
732
                        protectedByLowerCache = true;
733
                    }else{
734
                        titleCache = null;
735
                    }
736
                }
737
                //full title cache
738
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "fullTitleCache")){
739
                    if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
740
                        protectedByLowerCache = true;
741
                    }else{
742
                        fullTitleCache = null;
743
                    }
744
                }
745
            }
746
        };
747
        addPropertyChangeListener(listener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
748
    }
749

    
750
    private static Map<String, java.lang.reflect.Field> allFields = null;
751
    protected Map<String, java.lang.reflect.Field> getAllFields(){
752
        if (allFields == null){
753
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
754
        }
755
        return allFields;
756
    }
757

    
758
    /**
759
     * @param propertyName
760
     * @param string
761
     * @return
762
     */
763
    private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
764
        java.lang.reflect.Field field;
765
        try {
766
            field = getAllFields().get(propertyName);
767
            if (field != null){
768
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
769
                if (updateAnnotation != null){
770
                    for (String value : updateAnnotation.value()){
771
                        if (cacheName.equals(value)){
772
                            return true;
773
                        }
774
                    }
775
                }
776
            }
777
            return false;
778
        } catch (SecurityException e1) {
779
            throw e1;
780
        }
781
    }
782

    
783
    private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
784
        java.lang.reflect.Field field;
785
        //do not update fields with the same name
786
        if (cacheName.equals(propertyName)){
787
            return true;
788
        }
789
        //evaluate annotation
790
        try {
791
            field = getAllFields().get(propertyName);
792
            if (field != null){
793
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
794
                if (updateAnnotation != null){
795
                    for (String value : updateAnnotation.noUpdate()){
796
                        if (cacheName.equals(value)){
797
                            return true;
798
                        }
799
                    }
800
                }
801
            }
802
            return false;
803
        } catch (SecurityException e1) {
804
            throw e1;
805
        }
806
    }
807

    
808
// ****************** GETTER / SETTER ****************************/
809

    
810
    @Override
811
    public NomenclaturalCode getNameType() {
812
        return nameType;
813
    }
814

    
815
    @Override
816
    public void setNameType(NomenclaturalCode nameType) {
817
        this.nameType = nameType;
818
    }
819

    
820
    /**
821
     * Returns the boolean value of the flag intended to protect (true)
822
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
823
     * string of <i>this</i> non viral taxon name.
824
     *
825
     * @return  the boolean value of the protectedNameCache flag
826
     * @see     #getNameCache()
827
     */
828
    @Override
829
    public boolean isProtectedNameCache() {
830
        return protectedNameCache;
831
    }
832

    
833
    /**
834
     * @see     #isProtectedNameCache()
835
     */
836
    @Override
837
    public void setProtectedNameCache(boolean protectedNameCache) {
838
        this.protectedNameCache = protectedNameCache;
839
    }
840

    
841
    /**
842
     * Returns either the scientific name string (without authorship) for <i>this</i>
843
     * non viral taxon name if its rank is genus or higher (monomial) or the string for
844
     * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
845
     * Genus or uninomial strings begin with an upper case letter.
846
     *
847
     * @return  the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
848
     * @see     #getNameCache()
849
     */
850
    @Override
851
    public String getGenusOrUninomial() {
852
        return genusOrUninomial;
853
    }
854

    
855
    /**
856
     * @see  #getGenusOrUninomial()
857
     */
858
    @Override
859
    public void setGenusOrUninomial(String genusOrUninomial) {
860
        this.genusOrUninomial = StringUtils.isBlank(genusOrUninomial) ? null : genusOrUninomial;
861
    }
862

    
863
    /**
864
     * Returns the genus subdivision epithet string (infrageneric part) for
865
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
866
     * higher than species aggregate: binomial). Genus subdivision epithet
867
     * strings begin with an upper case letter.
868
     *
869
     * @return  the string containing the infrageneric part of <i>this</i> non viral taxon name
870
     * @see     #getNameCache()
871
     */
872
    @Override
873
    public String getInfraGenericEpithet(){
874
        return this.infraGenericEpithet;
875
    }
876

    
877
    /**
878
     * @see  #getInfraGenericEpithet()
879
     */
880
    @Override
881
    public void setInfraGenericEpithet(String infraGenericEpithet){
882
        this.infraGenericEpithet = StringUtils.isBlank(infraGenericEpithet)? null : infraGenericEpithet;
883
    }
884

    
885
    /**
886
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
887
     * species aggregate or lower (bi- or trinomial). Species epithet strings
888
     * begin with a lower case letter.
889
     *
890
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
891
     * @see     #getNameCache()
892
     */
893
    @Override
894
    public String getSpecificEpithet(){
895
        return this.specificEpithet;
896
    }
897

    
898
    /**
899
     * @see  #getSpecificEpithet()
900
     */
901
    @Override
902
    public void setSpecificEpithet(String specificEpithet){
903
        this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
904
    }
905

    
906
    /**
907
     * Returns the species subdivision epithet string (infraspecific part) for
908
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
909
     * (lower than species: trinomial). Species subdivision epithet strings
910
     * begin with a lower case letter.
911
     *
912
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
913
     * @see     #getNameCache()
914
     */
915
    @Override
916
    public String getInfraSpecificEpithet(){
917
        return this.infraSpecificEpithet;
918
    }
919

    
920
    /**
921
     * @see  #getInfraSpecificEpithet()
922
     */
923
    @Override
924
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
925
        this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
926
    }
927

    
928
    /**
929
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
930
     * taxon name.
931
     *
932
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
933
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
934
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
935
     */
936
    @Override
937
    public TeamOrPersonBase<?> getCombinationAuthorship(){
938
        return this.combinationAuthorship;
939
    }
940

    
941
    /**
942
     * @see  #getCombinationAuthorship()
943
     */
944
    @Override
945
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
946
        this.combinationAuthorship = combinationAuthorship;
947
    }
948

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

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

    
985
    /**
986
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
987
     * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
988
     * author (team) can only exist if <i>this</i> non viral taxon name is a new
989
     * combination due to a taxonomical revision.
990
     *
991
     * @return  the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
992
     * @see     #getCombinationAuthorship()
993
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
994
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
995
     */
996
    @Override
997
    public TeamOrPersonBase<?> getBasionymAuthorship(){
998
        return basionymAuthorship;
999
    }
1000

    
1001
    /**
1002
     * @see  #getBasionymAuthorship()
1003
     */
1004
    @Override
1005
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
1006
        this.basionymAuthorship = basionymAuthorship;
1007
    }
1008

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

    
1030
    /**
1031
     * @see  #getExBasionymAuthorship()
1032
     */
1033
    @Override
1034
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
1035
        this.exBasionymAuthorship = exBasionymAuthorship;
1036
    }
1037

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

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

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

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

    
1080

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

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

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

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

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

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

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

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

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

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

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

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

    
1207
    // ****************** VIRAL NAME ******************/
1208

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

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

    
1222
    // ****************** BACTERIAL NAME ******************/
1223

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

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

    
1234

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

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

    
1248
    //************ Zoological Name
1249

    
1250

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

    
1263

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

    
1276

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

    
1289
    // **** Cultivar Name ************
1290

    
1291

    
1292
    @Override
1293
    public String getCultivarName(){
1294
        return this.cultivarName;
1295
    }
1296

    
1297
    /**
1298
     * @see  #getCultivarName()
1299
     */
1300
    @Override
1301
    public void setCultivarName(String cultivarName){
1302
        this.cultivarName = StringUtils.isBlank(cultivarName) ? null : cultivarName;
1303
    }
1304

    
1305
    // **************** Fungus Name
1306
    @Override
1307
    public boolean isAnamorphic(){
1308
        return this.anamorphic;
1309
    }
1310

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

    
1319

    
1320
// **************** ADDER / REMOVE *************************/
1321

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

    
1351

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

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

    
1383
//********* METHODS **************************************/
1384

    
1385
    @Override
1386
    public INameCacheStrategy getCacheStrategy() {
1387
        rectifyNameCacheStrategy();
1388
        return this.cacheStrategy;
1389
    }
1390

    
1391
    @Override
1392
    public String generateFullTitle(){
1393
        if (getCacheStrategy() == null){
1394
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1395
            return null;
1396
        }else{
1397
            return cacheStrategy.getFullTitleCache(this);
1398
        }
1399
    }
1400

    
1401

    
1402
    @Override
1403
    public void setFullTitleCache(String fullTitleCache){
1404
        setFullTitleCache(fullTitleCache, PROTECTED);
1405
    }
1406

    
1407
    @Override
1408
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1409
        fullTitleCache = getTruncatedCache(fullTitleCache);
1410
        this.fullTitleCache = fullTitleCache;
1411
        this.setProtectedFullTitleCache(protectCache);
1412
    }
1413

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

    
1439

    
1440
    @Override
1441
    @Transient
1442
    public List<TaggedText> getTaggedName(){
1443
        INameCacheStrategy strat = getCacheStrategy();
1444
        return strat.getTaggedTitle(this);
1445
    }
1446

    
1447
    @Override
1448
    @Transient
1449
    public String getFullTitleCache(){
1450
        if (protectedFullTitleCache){
1451
            return this.fullTitleCache;
1452
        }
1453
        updateAuthorshipCache();
1454
        if (fullTitleCache == null ){
1455
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1456
        }
1457
        return fullTitleCache;
1458
    }
1459

    
1460

    
1461
    @Override
1462
    public String getTitleCache(){
1463
        if(!protectedTitleCache) {
1464
            updateAuthorshipCache();
1465
        }
1466
        return super.getTitleCache();
1467
    }
1468

    
1469
    @Override
1470
    public void setTitleCache(String titleCache, boolean protectCache){
1471
        super.setTitleCache(titleCache, protectCache);
1472
    }
1473

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

    
1497
        }
1498
        return authorshipCache;
1499
    }
1500

    
1501

    
1502

    
1503
    /**
1504
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
1505
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
1506
     * @return
1507
     */
1508
    private void updateAuthorshipCache() {
1509
        //updates the authorship cache if necessary and via the listener updates all higher caches
1510
        if (protectedAuthorshipCache == false){
1511
            String oldCache = this.authorshipCache;
1512
            String newCache = this.getAuthorshipCache();
1513
            if ( (oldCache == null && newCache != null)  ||  CdmUtils.nullSafeEqual(oldCache,newCache)){
1514
                this.setAuthorshipCache(this.getAuthorshipCache(), false);
1515
            }
1516
        }
1517
    }
1518

    
1519

    
1520
    /**
1521
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1522
     * flag to <code>true</code>.
1523
     *
1524
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1525
     * @see    #getAuthorshipCache()
1526
     */
1527
    @Override
1528
    public void setAuthorshipCache(String authorshipCache) {
1529
        setAuthorshipCache(authorshipCache, true);
1530
    }
1531

    
1532

    
1533
    /**
1534
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1535
     *
1536
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1537
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1538
     * the flag is set to <code>false</code>.
1539
     * @see    #getAuthorshipCache()
1540
     */
1541
    @Override
1542
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1543
        this.authorshipCache = authorshipCache;
1544
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1545
    }
1546

    
1547
    /**
1548
     * Generates and returns a concatenated and formated authorteams string
1549
     * including basionym and combination authors of <i>this</i> non viral taxon name
1550
     * according to the strategy defined in
1551
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName) INonViralNameCacheStrategy}.
1552
     *
1553
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1554
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName)
1555
     */
1556
    @Override
1557
    public String generateAuthorship(){
1558
        if (getCacheStrategy() == null){
1559
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1560
            return null;
1561
        }else{
1562
            return cacheStrategy.getAuthorshipCache(this);
1563
        }
1564
    }
1565

    
1566

    
1567

    
1568
    /**
1569
     * Tests if the given name has any authors.
1570
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1571
     */
1572
    @Override
1573
    public boolean hasAuthors() {
1574
        return (this.getCombinationAuthorship() != null ||
1575
                this.getExCombinationAuthorship() != null ||
1576
                this.getBasionymAuthorship() != null ||
1577
                this.getExBasionymAuthorship() != null);
1578
    }
1579

    
1580
    /**
1581
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1582
     * @return
1583
     */
1584
    @Override
1585
    public String computeCombinationAuthorNomenclaturalTitle() {
1586
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1587
    }
1588

    
1589
    /**
1590
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1591
     * @return
1592
     */
1593
    @Override
1594
    public String computeBasionymAuthorNomenclaturalTitle() {
1595
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1596
    }
1597

    
1598

    
1599
    /**
1600
     * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1601
     * @return
1602
     */
1603
    @Override
1604
    public String computeExCombinationAuthorNomenclaturalTitle() {
1605
        return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1606
    }
1607

    
1608
    /**
1609
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1610
     * @return
1611
     */
1612
    @Override
1613
    public String computeExBasionymAuthorNomenclaturalTitle() {
1614
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1615
    }
1616

    
1617
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1618
        if (author == null){
1619
            return null;
1620
        }else{
1621
            return author.getNomenclaturalTitle();
1622
        }
1623
    }
1624

    
1625
    /**
1626
     * Returns the set of all {@link NameRelationship name relationships}
1627
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1628
     * in some name relationships or target in some others.
1629
     *
1630
     * @see    #getRelationsToThisName()
1631
     * @see    #getRelationsFromThisName()
1632
     * @see    #addNameRelationship(NameRelationship)
1633
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1634
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1635
     */
1636
    @Override
1637
    @Transient
1638
    public Set<NameRelationship> getNameRelations() {
1639
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1640
        rels.addAll(getRelationsFromThisName());
1641
        rels.addAll(getRelationsToThisName());
1642
        return rels;
1643
    }
1644

    
1645
    /**
1646
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1647
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1648
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1649
     *
1650
     * @param toName		  the taxon name of the target for this new name relationship
1651
     * @param type			  the type of this new name relationship
1652
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1653
     * @return
1654
     * @see    				  #getRelationsToThisName()
1655
     * @see    				  #getNameRelations()
1656
     * @see    				  #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1657
     * @see    				  #addNameRelationship(NameRelationship)
1658
     */
1659
    @Override
1660
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, String ruleConsidered){
1661
        return addRelationshipToName(toName, type, null, null, ruleConsidered);
1662
    }
1663

    
1664
    /**
1665
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1666
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1667
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1668
     *
1669
     * @param toName		  the taxon name of the target for this new name relationship
1670
     * @param type			  the type of this new name relationship
1671
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1672
     * @return
1673
     * @see    				  #getRelationsToThisName()
1674
     * @see    				  #getNameRelations()
1675
     * @see    				  #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1676
     * @see    				  #addNameRelationship(NameRelationship)
1677
     */
1678
    @Override
1679
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1680
        if (toName == null){
1681
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1682
        }
1683
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered);
1684
        return rel;
1685
    }
1686

    
1687
    /**
1688
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1689
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1690
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1691
     *
1692
     * @param fromName		  the taxon name of the source for this new name relationship
1693
     * @param type			  the type of this new name relationship
1694
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1695
     * @param citation		  the reference in which this relation was described
1696
     * @param microCitation	  the reference detail for this relation (e.g. page)
1697
     * @see    				  #getRelationsFromThisName()
1698
     * @see    				  #getNameRelations()
1699
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1700
     * @see    				  #addNameRelationship(NameRelationship)
1701
     */
1702
    @Override
1703
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered){
1704
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1705
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
1706
    }
1707
    /**
1708
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1709
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1710
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1711
     *
1712
     * @param fromName		  the taxon name of the source for this new name relationship
1713
     * @param type			  the type of this new name relationship
1714
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1715
     * @param citation		  the reference in which this relation was described
1716
     * @param microCitation	  the reference detail for this relation (e.g. page)
1717
     * @see    				  #getRelationsFromThisName()
1718
     * @see    				  #getNameRelations()
1719
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1720
     * @see    				  #addNameRelationship(NameRelationship)
1721
     */
1722
    @Override
1723
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1724
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1725
    }
1726

    
1727
    /**
1728
     * Adds an existing {@link NameRelationship name relationship} either to the set of
1729
     * {@link #getRelationsToThisName() relations to <i>this</i> taxon name} or to the set of
1730
     * {@link #getRelationsFromThisName() relations from <i>this</i> taxon name}. If neither the
1731
     * source nor the target of the name relationship match with <i>this</i> taxon name
1732
     * no addition will be carried out.
1733
     *
1734
     * @param rel  the name relationship to be added to one of <i>this</i> taxon name's name relationships sets
1735
     * @see    	   #getNameRelations()
1736
     * @see    	   #addRelationshipToName(TaxonName, NameRelationshipType, String)
1737
     * @see    	   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1738
     */
1739
    protected void addNameRelationship(NameRelationship rel) {
1740
        if (rel != null ){
1741
            if (rel.getToName().equals(this)){
1742
                this.relationsToThisName.add(rel);
1743
            }else if(rel.getFromName().equals(this)){
1744
                this.relationsFromThisName.add(rel);
1745
            }
1746
            NameRelationshipType type = rel.getType();
1747
            if (type != null && ( type.isBasionymRelation() || type.isReplacedSynonymRelation() ) ){
1748
                rel.getFromName().mergeHomotypicGroups(rel.getToName());
1749
            }
1750
        }else{
1751
            throw new RuntimeException("NameRelationship is either null or the relationship does not reference this name");
1752
        }
1753
    }
1754
    /**
1755
     * Removes one {@link NameRelationship name relationship} from one of both sets of
1756
     * {@link #getNameRelations() name relationships} in which <i>this</i> taxon name is involved.
1757
     * The name relationship will also be removed from one of both sets belonging
1758
     * to the second taxon name involved. Furthermore the fromName and toName
1759
     * attributes of the name relationship object will be nullified.
1760
     *
1761
     * @param  nameRelation  the name relationship which should be deleted from one of both sets
1762
     * @see    				 #getNameRelations()
1763
     */
1764
    @Override
1765
    public void removeNameRelationship(NameRelationship nameRelation) {
1766

    
1767
        TaxonName fromName = nameRelation.getFromName();
1768
        TaxonName toName = nameRelation.getToName();
1769

    
1770
        if (nameRelation != null) {
1771
            nameRelation.setToName(null);
1772
            nameRelation.setFromName(null);
1773
        }
1774

    
1775
        if (fromName != null) {
1776
            fromName.removeNameRelationship(nameRelation);
1777
        }
1778

    
1779
        if (toName != null) {
1780
            toName.removeNameRelationship(nameRelation);
1781
        }
1782

    
1783
        this.relationsToThisName.remove(nameRelation);
1784
        this.relationsFromThisName.remove(nameRelation);
1785
    }
1786

    
1787
    @Override
1788
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1789
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1790
//		nameRelationships.addAll(this.getNameRelations());
1791
        nameRelationships.addAll(this.getRelationsFromThisName());
1792
        nameRelationships.addAll(this.getRelationsToThisName());
1793
        for(NameRelationship nameRelationship : nameRelationships) {
1794
            // remove name relationship from this side
1795
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1796
                this.removeNameRelationship(nameRelationship);
1797
            }
1798
        }
1799
    }
1800

    
1801
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1802

    
1803
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1804
        for(NameRelationship nameRelationship : tmpRels) {
1805
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1806
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1807
                if (type == null || type.equals(nameRelationship.getType())){
1808
                    this.removeNameRelationship(nameRelationship);
1809
                }
1810
            }
1811
        }
1812
    }
1813

    
1814

    
1815
    /**
1816
     * If relation is of type NameRelationship, addNameRelationship is called;
1817
     * if relation is of type HybridRelationship addHybridRelationship is called,
1818
     * otherwise an IllegalArgumentException is thrown.
1819
     *
1820
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1821
     * @see    	   		#addNameRelationship(NameRelationship)
1822
     * @see    	   		#getNameRelations()
1823
     * @see    	   		NameRelationship
1824
     * @see    	   		RelationshipBase
1825
     * @see             #addHybridRelationship(HybridRelationship)
1826

    
1827
     * @deprecated to be used by RelationshipBase only
1828
     */
1829
    @Deprecated
1830
    @Override
1831
    public void addRelationship(RelationshipBase relation) {
1832
        if (relation instanceof NameRelationship){
1833
            addNameRelationship((NameRelationship)relation);
1834

    
1835
        }else if (relation instanceof HybridRelationship){
1836
            addHybridRelationship((HybridRelationship)relation);
1837
        }else{
1838
            logger.warn("Relationship not of type NameRelationship!");
1839
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1840
        }
1841
    }
1842

    
1843
    /**
1844
     * Returns the set of all {@link NameRelationship name relationships}
1845
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1846
     *
1847
     * @see    #getNameRelations()
1848
     * @see    #getRelationsToThisName()
1849
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1850
     */
1851
    @Override
1852
    public Set<NameRelationship> getRelationsFromThisName() {
1853
        if(relationsFromThisName == null) {
1854
            this.relationsFromThisName = new HashSet<>();
1855
        }
1856
        return relationsFromThisName;
1857
    }
1858

    
1859
    /**
1860
     * Returns the set of all {@link NameRelationship name relationships}
1861
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1862
     *
1863
     * @see    #getNameRelations()
1864
     * @see    #getRelationsFromThisName()
1865
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1866
     */
1867
    @Override
1868
    public Set<NameRelationship> getRelationsToThisName() {
1869
        if(relationsToThisName == null) {
1870
            this.relationsToThisName = new HashSet<>();
1871
        }
1872
        return relationsToThisName;
1873
    }
1874

    
1875
    /**
1876
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1877
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1878
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1879
     * and the nomenclatural code rule considered.
1880
     *
1881
     * @see     NomenclaturalStatus
1882
     * @see     NomenclaturalStatusType
1883
     */
1884
    @Override
1885
    public Set<NomenclaturalStatus> getStatus() {
1886
        if(status == null) {
1887
            this.status = new HashSet<>();
1888
        }
1889
        return status;
1890
    }
1891

    
1892
    /**
1893
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1894
     * to <i>this</i> taxon name's set of nomenclatural status.
1895
     *
1896
     * @param  nomStatus  the nomenclatural status to be added
1897
     * @see 			  #getStatus()
1898
     */
1899
    @Override
1900
    public void addStatus(NomenclaturalStatus nomStatus) {
1901
        this.status.add(nomStatus);
1902
    }
1903
    @Override
1904
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1905
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1906
        this.status.add(newStatus);
1907
        return newStatus;
1908
    }
1909

    
1910
    /**
1911
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1912
     * Type and ruleConsidered attributes of the nomenclatural status object
1913
     * will be nullified.
1914
     *
1915
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1916
     * @see     		  #getStatus()
1917
     */
1918
    @Override
1919
    public void removeStatus(NomenclaturalStatus nomStatus) {
1920
        //TODO to be implemented?
1921
        logger.warn("not yet fully implemented?");
1922
        this.status.remove(nomStatus);
1923
    }
1924

    
1925

    
1926
    /**
1927
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1928
     * strings or year according to the strategy defined in
1929
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1930
     * The result might be stored in {@link #getNameCache() nameCache} if the
1931
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1932
     *
1933
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1934
     * @see     #getNameCache()
1935
     */
1936
    protected String generateNameCache(){
1937
        if (getCacheStrategy() == null){
1938
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1939
            return null;
1940
        }else{
1941
            return cacheStrategy.getNameCache(this);
1942
        }
1943
    }
1944

    
1945
    /**
1946
     * Returns or generates the nameCache (scientific name
1947
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1948
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1949
     * the string will be generated according to a defined strategy,
1950
     * otherwise the value of the actual nameCache string will be returned.
1951
     *
1952
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1953
     * @see     #generateNameCache()
1954
     */
1955
    @Override
1956
    @Transient
1957
    public String getNameCache() {
1958
        if (protectedNameCache){
1959
            return this.nameCache;
1960
        }
1961
        // is title dirty, i.e. equal NULL?
1962
        if (nameCache == null){
1963
            this.nameCache = generateNameCache();
1964
        }
1965
        return nameCache;
1966
    }
1967

    
1968
    /**
1969
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1970
     * Sets the protectedNameCache flag to <code>true</code>.
1971
     *
1972
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1973
     * @see    #getNameCache()
1974
     */
1975
    @Override
1976
    public void setNameCache(String nameCache){
1977
        setNameCache(nameCache, true);
1978
    }
1979

    
1980
    /**
1981
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1982
     * Sets the protectedNameCache flag to <code>true</code>.
1983
     *
1984
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1985
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
1986
     * <code>false</code>
1987
     * @see    #getNameCache()
1988
     */
1989
    @Override
1990
    public void setNameCache(String nameCache, boolean protectedNameCache){
1991
        this.nameCache = nameCache;
1992
        this.setProtectedNameCache(protectedNameCache);
1993
    }
1994

    
1995

    
1996
    /**
1997
     * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
1998
     * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1999
     * of any other taxon name. Returns "true", if a basionym or a replaced
2000
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
2001
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
2002
     * homotypical group).
2003
     */
2004
    @Override
2005
    @Transient
2006
    public boolean isOriginalCombination(){
2007
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
2008
        for (NameRelationship relation : relationsFromThisName) {
2009
            if (relation.getType().isBasionymRelation() ||
2010
                    relation.getType().isReplacedSynonymRelation()) {
2011
                return true;
2012
            }
2013
        }
2014
        return false;
2015
    }
2016

    
2017
    /**
2018
     * Indicates <i>this</i> taxon name is a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
2019
     * of any other taxon name. Returns "true", if a replaced
2020
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
2021
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
2022
     * homotypical group).
2023
     */
2024
    @Override
2025
    @Transient
2026
    public boolean isReplacedSynonym(){
2027
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
2028
        for (NameRelationship relation : relationsFromThisName) {
2029
            if (relation.getType().isReplacedSynonymRelation()) {
2030
                return true;
2031
            }
2032
        }
2033
        return false;
2034
    }
2035

    
2036
    /**
2037
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2038
     * The basionym of a taxon name is its epithet-bringing synonym.
2039
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2040
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2041
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2042
     *
2043
     * If more than one basionym exists one is choosen at radom.
2044
     *
2045
     * If no basionym exists null is returned.
2046
     */
2047
    @Override
2048
    @Transient
2049
    public TaxonName getBasionym(){
2050
        Set<TaxonName> basionyms = getBasionyms();
2051
        if (basionyms.size() == 0){
2052
            return null;
2053
        }else{
2054
            return basionyms.iterator().next();
2055
        }
2056
    }
2057

    
2058
    /**
2059
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2060
     * The basionym of a taxon name is its epithet-bringing synonym.
2061
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2062
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2063
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2064
     */
2065
    @Override
2066
    @Transient
2067
    public Set<TaxonName> getBasionyms(){
2068

    
2069
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
2070
    }
2071

    
2072
    /**
2073
     *
2074
     * @param direction
2075
     * @param type
2076
     * @return
2077
     */
2078
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
2079

    
2080
        return getRelatedNames(relationsWithThisName(direction), type);
2081
    }
2082

    
2083
    /**
2084
     * @param rels
2085
     * @param type
2086
     * @return
2087
     */
2088
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2089
        Set<TaxonName> result = new HashSet<>();
2090
        for (NameRelationship rel : rels){
2091
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2092
                TaxonName basionym = rel.getFromName();
2093
                result.add(basionym);
2094
            }
2095
        }
2096
        return result;
2097
    }
2098

    
2099
    /**
2100
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2101
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2102
     * and to the basionym. The basionym cannot have itself as a basionym.
2103
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2104
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2105
     *
2106
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2107
     * @see  				#getBasionym()
2108
     * @see  				#addBasionym(TaxonName, String)
2109
     */
2110
    @Override
2111
    public void addBasionym(TaxonName basionym){
2112
        addBasionym(basionym, null, null, null);
2113
    }
2114
    /**
2115
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2116
     * and keeps the nomenclatural rule considered for it. The basionym
2117
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2118
     * The basionym cannot have itself as a basionym.
2119
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2120
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2121
     *
2122
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2123
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2124
     * @return
2125
     * @see  					#getBasionym()
2126
     * @see  					#addBasionym(TaxonName)
2127
     */
2128
    @Override
2129
    public NameRelationship addBasionym(TaxonName basionym, Reference citation, String microcitation, String ruleConsidered){
2130
        if (basionym != null){
2131
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
2132
        }else{
2133
            return null;
2134
        }
2135
    }
2136

    
2137
    /**
2138
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2139
     *
2140
     */
2141
    @Override
2142
    @Transient
2143
    public Set<TaxonName> getReplacedSynonyms(){
2144

    
2145
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2146
    }
2147

    
2148
    /**
2149
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2150
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2151
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2152
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2153
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2154
     *
2155
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2156
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2157
     * @see  					#getBasionym()
2158
     * @see  					#addBasionym(TaxonName)
2159
     */
2160
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2161
    @Override
2162
    public void addReplacedSynonym(TaxonName replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
2163
        if (replacedSynonym != null){
2164
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
2165
        }
2166
    }
2167

    
2168
    /**
2169
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2170
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2171
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2172
     * previously used as basionym.
2173
     *
2174
     * @see   #getBasionym()
2175
     * @see   #addBasionym(TaxonName)
2176
     */
2177
    @Override
2178
    public void removeBasionyms(){
2179
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2180
    }
2181

    
2182

    
2183
    /**
2184
     * Removes all {@link NameRelationship relationships} of the given <code>type</code> from the set of
2185
     * relations in the specified <code>direction</code> direction wich are related from or to this
2186
     * <i>this</i> taxon name. The same relationship will be removed from the set of
2187
     * reverse relations of the other taxon name.
2188
     *
2189
     * @param direction
2190
     * @param type
2191
     */
2192
    public void removeNameRelations(Direction direction, NameRelationshipType type) {
2193
        Set<NameRelationship> relationsWithThisName = relationsWithThisName(direction);
2194
        Set<NameRelationship> removeRelations = new HashSet<>();
2195
        for (NameRelationship nameRelation : relationsWithThisName){
2196
            if (nameRelation.getType().isRelationshipType(type)){
2197
                removeRelations.add(nameRelation);
2198
            }
2199
        }
2200
        // Removing relations from a set through which we are iterating causes a
2201
        // ConcurrentModificationException. Therefore, we delete the targeted
2202
        // relations in a second step.
2203
        for (NameRelationship relation : removeRelations){
2204
            this.removeNameRelationship(relation);
2205
        }
2206
    }
2207

    
2208

    
2209
    /**
2210
     * @param direction
2211
     * @return
2212
     */
2213
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2214

    
2215
        switch(direction) {
2216
            case relatedTo:
2217
                return this.getRelationsToThisName();
2218
            case relatedFrom:
2219
                return this.getRelationsFromThisName();
2220
            default: throw new RuntimeException("invalid Direction:" + direction);
2221
        }
2222
    }
2223

    
2224
    /**
2225
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2226
     *
2227
     * @see 	Rank
2228
     */
2229
    @Override
2230
    public Rank getRank(){
2231
        return this.rank;
2232
    }
2233

    
2234
    /**
2235
     * @see  #getRank()
2236
     */
2237
    @Override
2238
    public void setRank(Rank rank){
2239
        this.rank = rank;
2240
    }
2241

    
2242

    
2243
    @Override
2244
    public Reference getNomenclaturalReference(){
2245
        if (this.nomenclaturalSource == null){
2246
            return null;
2247
        }
2248
        return this.nomenclaturalSource.getCitation();
2249
    }
2250

    
2251
    @Override
2252
    public DescriptionElementSource getNomenclaturalSource(){
2253
        return this.nomenclaturalSource;
2254
    }
2255

    
2256
    protected DescriptionElementSource getNomenclaturalSource(boolean createIfNotExist){
2257
        if (this.nomenclaturalSource == null){
2258
            if (!createIfNotExist){
2259
                return null;
2260
            }
2261
            this.nomenclaturalSource = DescriptionElementSource.NewInstance(OriginalSourceType.NomenclaturalReference);
2262
        }
2263
        return this.nomenclaturalSource;
2264
    }
2265

    
2266
    /**
2267
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2268
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2269
     * as it is obviously used for nomenclatural purposes.
2270
     *
2271
     * Shortcut to set the nomenclatural reference.
2272
     *
2273
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2274
     * @see  #getNomenclaturalReference()
2275
     */
2276

    
2277
    @Override
2278
    public void setNomenclaturalReference(Reference nomenclaturalReference){
2279
        getNomenclaturalSource(true).setCitation(nomenclaturalReference);
2280
        checkNullSource();
2281
    }
2282

    
2283

    
2284
    /**
2285
     *
2286
     */
2287
    private void checkNullSource() {
2288
        if (this.nomenclaturalSource == null){
2289
            return;
2290
        }else if (this.nomenclaturalSource.getCitation() != null
2291
           || this.nomenclaturalSource.getCitationMicroReference() != null
2292
           || this.nomenclaturalSource.getNameUsedInSource() != null
2293
           || isBlank(this.nomenclaturalSource.getOriginalNameString())){
2294
            //TODO what about supplemental data?
2295
                return;
2296
        }else{
2297
            this.nomenclaturalSource = null;
2298
        }
2299
    }
2300

    
2301

    
2302
    @Override
2303
    public void setNomenclaturalSource(DescriptionElementSource nomenclaturalSource) throws IllegalArgumentException {
2304
        if (!OriginalSourceType.NomenclaturalReference.equals(nomenclaturalSource.getType()) ){
2305
            throw new IllegalArgumentException("Nomenclatural source must be of type " + OriginalSourceType.NomenclaturalReference.getMessage());
2306
        }
2307
        this.nomenclaturalSource = nomenclaturalSource;
2308
    }
2309

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

    
2320
    /**
2321
     * @see  #getAppendedPhrase()
2322
     */
2323
    @Override
2324
    public void setAppendedPhrase(String appendedPhrase){
2325
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2326
    }
2327

    
2328
    /**
2329
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2330
     * to <i>this</i> taxon name. The details describe the exact localisation within
2331
     * the publication used as nomenclature reference. These are mostly
2332
     * (implicitly) pages but can also be figures or tables or any other
2333
     * element of a publication. A nomenclatural micro reference (details)
2334
     * requires the existence of a nomenclatural reference.
2335
     */
2336
    //Details of the nomenclatural reference (protologue).
2337
    @Override
2338
    public String getNomenclaturalMicroReference(){
2339
        if (this.nomenclaturalSource == null){
2340
            return null;
2341
        }
2342
        return this.nomenclaturalSource.getCitationMicroReference();
2343
    }
2344
    /**
2345
     * @see  #getNomenclaturalMicroReference()
2346
     */
2347
    @Override
2348
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2349
        this.getNomenclaturalSource(true).setCitationMicroReference(StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference);
2350
        checkNullSource();
2351
    }
2352

    
2353
    @Override
2354
    public int getParsingProblem(){
2355
        return this.parsingProblem;
2356
    }
2357

    
2358
    @Override
2359
    public void setParsingProblem(int parsingProblem){
2360
        this.parsingProblem = parsingProblem;
2361
    }
2362

    
2363
    @Override
2364
    public void addParsingProblem(ParserProblem problem){
2365
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2366
    }
2367

    
2368
    @Override
2369
    public void removeParsingProblem(ParserProblem problem) {
2370
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2371
    }
2372

    
2373
    /**
2374
     * @param warnings
2375
     */
2376
    @Override
2377
    public void addParsingProblems(int problems){
2378
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2379
    }
2380

    
2381
    @Override
2382
    public boolean hasProblem(){
2383
        return parsingProblem != 0;
2384
    }
2385

    
2386
    @Override
2387
    public boolean hasProblem(ParserProblem problem) {
2388
        return getParsingProblems().contains(problem);
2389
    }
2390

    
2391
    @Override
2392
    public int getProblemStarts(){
2393
        return this.problemStarts;
2394
    }
2395

    
2396
    @Override
2397
    public void setProblemStarts(int start) {
2398
        this.problemStarts = start;
2399
    }
2400

    
2401
    @Override
2402
    public int getProblemEnds(){
2403
        return this.problemEnds;
2404
    }
2405

    
2406
    @Override
2407
    public void setProblemEnds(int end) {
2408
        this.problemEnds = end;
2409
    }
2410

    
2411
//*********************** TYPE DESIGNATION *********************************************//
2412

    
2413
    /**
2414
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2415
     * to <i>this</i> taxon name.
2416
     * @see     NameTypeDesignation
2417
     * @see     SpecimenTypeDesignation
2418
     */
2419
    @Override
2420
    public Set<TypeDesignationBase> getTypeDesignations() {
2421
        if(typeDesignations == null) {
2422
            this.typeDesignations = new HashSet<TypeDesignationBase>();
2423
        }
2424
        return typeDesignations;
2425
    }
2426

    
2427
    /**
2428
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2429
     * <i>this</i> taxon name. The type designation itself will be nullified.
2430
     *
2431
     * @param  typeDesignation  the type designation which should be deleted
2432
     */
2433
    @Override
2434
    @SuppressWarnings("deprecation")
2435
    public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2436
        this.typeDesignations.remove(typeDesignation);
2437
        typeDesignation.removeTypifiedName(this);
2438
    }
2439

    
2440
    /**
2441
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2442
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2443
     * "species" or below. The specimen type designations include all the
2444
     * specimens on which the typification of this name is based (which are
2445
     * exclusively used to typify taxon names belonging to the same
2446
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2447
     * belongs) and eventually the status of these designations.
2448
     *
2449
     * @see     SpecimenTypeDesignation
2450
     * @see     NameTypeDesignation
2451
     * @see     HomotypicalGroup
2452
     */
2453
    @Override
2454
    @Transient
2455
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2456
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2457
    }
2458

    
2459
//*********************** NAME TYPE DESIGNATION *********************************************//
2460

    
2461
    /**
2462
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2463
     * to <i>this</i> taxon name the rank of which must be above "species".
2464
     * The name type designations include all the taxon names used to typify
2465
     * <i>this</i> taxon name and eventually the rejected or conserved status
2466
     * of these designations.
2467
     *
2468
     * @see     NameTypeDesignation
2469
     * @see     SpecimenTypeDesignation
2470
     */
2471
    @Override
2472
    @Transient
2473
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2474
        Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2475
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2476
            if (typeDesignation instanceof NameTypeDesignation){
2477
                result.add((NameTypeDesignation)typeDesignation);
2478
            }
2479
        }
2480
        return result;
2481
    }
2482

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

    
2519
    /**
2520
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2521
     * to <i>this</i> taxon name's set of type designations.
2522
     *
2523
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2524
     * @param  citation					the reference for this new designation
2525
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2526
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2527
     * @param  status                   the name type designation status
2528
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2529
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2530
     * @return
2531
     * @see 			  				#getNameTypeDesignations()
2532
     * @see 			  				NameTypeDesignation
2533
     * @see 			  				TypeDesignationBase#isNotDesignated()
2534
     */
2535
    @Override
2536
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2537
                Reference citation,
2538
                String citationMicroReference,
2539
                String originalNameString,
2540
                NameTypeDesignationStatus status,
2541
                boolean addToAllHomotypicNames) {
2542
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2543
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2544
        return nameTypeDesignation;
2545
    }
2546

    
2547
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2548

    
2549
    /**
2550
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2551
     * that typify <i>this</i> taxon name.
2552
     */
2553
    @Override
2554
    @Transient
2555
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2556
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2557
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2558
            if (typeDesignation instanceof SpecimenTypeDesignation){
2559
                result.add((SpecimenTypeDesignation)typeDesignation);
2560
            }
2561
        }
2562
        return result;
2563
    }
2564

    
2565

    
2566
    /**
2567
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2568
     * to <i>this</i> taxon name's set of type designations.
2569
     *
2570
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2571
     * @param  status					the specimen type designation status
2572
     * @param  citation					the reference for this new specimen type designation
2573
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2574
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2575
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2576
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2577
     * 									added to all taxon names of the homotypical group the typified
2578
     * 									taxon name belongs to
2579
     * @return
2580
     * @see 			  				#getSpecimenTypeDesignations()
2581
     * @see 			  				SpecimenTypeDesignationStatus
2582
     * @see 			  				SpecimenTypeDesignation
2583
     * @see 			  				TypeDesignationBase#isNotDesignated()
2584
     */
2585
    @Override
2586
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2587
                SpecimenTypeDesignationStatus status,
2588
                Reference citation,
2589
                String citationMicroReference,
2590
                String originalNameString,
2591
                boolean isNotDesignated,
2592
                boolean addToAllHomotypicNames) {
2593
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2594
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2595
        return specimenTypeDesignation;
2596
    }
2597

    
2598
    //used by merge strategy
2599
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2600
        return addTypeDesignation(typeDesignation, true);
2601
    }
2602

    
2603
    /**
2604
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2605
     *
2606
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2607
     * @param addToAllNames				the boolean indicating whether the type designation should be
2608
     * 									added to all taxon names of the homotypical group the typified
2609
     * 									taxon name belongs to
2610
     * @return							true if the operation was successful
2611
     *
2612
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2613
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2614
     *
2615
     */
2616
    @Override
2617
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2618
        //currently typeDesignations are not persisted with the homotypical group
2619
        //so explicit adding to the homotypical group is not necessary.
2620
        if (typeDesignation != null){
2621
            checkHomotypicalGroup(typeDesignation);
2622
            this.typeDesignations.add(typeDesignation);
2623
            typeDesignation.addTypifiedName(this);
2624

    
2625
            if (addToAllNames){
2626
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2627
                    if (taxonName != this){
2628
                        taxonName.addTypeDesignation(typeDesignation, false);
2629
                    }
2630
                }
2631
            }
2632
        }
2633
        return true;
2634
    }
2635

    
2636
    /**
2637
     * Throws an Exception this type designation already has typified names from another homotypical group.
2638
     * @param typeDesignation
2639
     */
2640
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2641
        if(typeDesignation.getTypifiedNames().size() > 0){
2642
            Set<HomotypicalGroup> groups = new HashSet<>();
2643
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2644
            for (TaxonName taxonName: names){
2645
                groups.add(taxonName.getHomotypicalGroup());
2646
            }
2647
            if (groups.size() > 1){
2648
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2649
            }
2650
        }
2651
    }
2652

    
2653

    
2654

    
2655
//*********************** HOMOTYPICAL GROUP *********************************************//
2656

    
2657

    
2658
    /**
2659
     * Returns the {@link HomotypicalGroup homotypical group} to which
2660
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2661
     * that share the same types.
2662
     *
2663
     * @see 	HomotypicalGroup
2664
     */
2665

    
2666
    @Override
2667
    public HomotypicalGroup getHomotypicalGroup() {
2668
        if (homotypicalGroup == null){
2669
            homotypicalGroup = new HomotypicalGroup();
2670
            homotypicalGroup.typifiedNames.add(this);
2671
        }
2672
    	return homotypicalGroup;
2673
    }
2674

    
2675
    /**
2676
     * @see #getHomotypicalGroup()
2677
     */
2678
    @Override
2679
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2680
        if (homotypicalGroup == null){
2681
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2682
        }
2683
        /*if (this.homotypicalGroup != null){
2684
        	this.homotypicalGroup.removeTypifiedName(this, false);
2685
        }*/
2686
        this.homotypicalGroup = homotypicalGroup;
2687
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2688
        	 this.homotypicalGroup.addTypifiedName(this);
2689
        }
2690
    }
2691

    
2692

    
2693

    
2694
// *************************************************************************//
2695

    
2696
    /**
2697
     * Returns the complete string containing the
2698
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2699
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2700
     *
2701
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2702
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2703
     * @see		#getNomenclaturalReference()
2704
     * @see		#getNomenclaturalMicroReference()
2705
     */
2706
    @Override
2707
    @Transient
2708
    public String getCitationString(){
2709
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2710
    }
2711

    
2712
    /**
2713
     * Returns the parsing problems
2714
     * @return
2715
     */
2716
    @Override
2717
    public List<ParserProblem> getParsingProblems(){
2718
        return ParserProblem.warningList(this.parsingProblem);
2719
    }
2720

    
2721
    /**
2722
     * Returns the string containing the publication date (generally only year)
2723
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2724
     * no nomenclatural reference.
2725
     *
2726
     * @return  the string containing the publication date of <i>this</i> taxon name
2727
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2728
     */
2729
    @Override
2730
    @Transient
2731
    @ValidTaxonomicYear(groups=Level3.class)
2732
    public String getReferenceYear(){
2733
        if (this.getNomenclaturalReference() != null ){
2734
            return this.getNomenclaturalReference().getYear();
2735
        }else{
2736
            return null;
2737
        }
2738
    }
2739

    
2740
    /**
2741
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2742
     * In this context a taxon base means the use of a taxon name by a reference
2743
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2744
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2745
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2746
     * within a taxonomic treatment (identified by one reference).
2747
     *
2748
     * @see	#getTaxa()
2749
     * @see	#getSynonyms()
2750
     */
2751
    @Override
2752
    public Set<TaxonBase> getTaxonBases() {
2753
        if(taxonBases == null) {
2754
            this.taxonBases = new HashSet<TaxonBase>();
2755
        }
2756
        return this.taxonBases;
2757
    }
2758

    
2759
    /**
2760
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2761
     * to the set of taxon bases using <i>this</i> taxon name.
2762
     *
2763
     * @param  taxonBase  the taxon base to be added
2764
     * @see 			  #getTaxonBases()
2765
     * @see 			  #removeTaxonBase(TaxonBase)
2766
     */
2767
    //TODO protected
2768
    @Override
2769
    public void addTaxonBase(TaxonBase taxonBase){
2770
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2771
        ReflectionUtils.makeAccessible(method);
2772
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2773
        taxonBases.add(taxonBase);
2774
    }
2775
    /**
2776
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2777
     *
2778
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2779
     * @see    				#getTaxonBases()
2780
     * @see    				#addTaxonBase(TaxonBase)
2781
     */
2782
    @Override
2783
    public void removeTaxonBase(TaxonBase taxonBase){
2784
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2785
        ReflectionUtils.makeAccessible(method);
2786
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2787

    
2788

    
2789
    }
2790

    
2791
    /**
2792
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2793
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2794
     * the set returned by getTaxonBases().
2795
     *
2796
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2797
     * @see	#getTaxonBases()
2798
     * @see	#getSynonyms()
2799
     */
2800
    @Override
2801
    @Transient
2802
    public Set<Taxon> getTaxa(){
2803
        Set<Taxon> result = new HashSet<>();
2804
        for (TaxonBase taxonBase : this.taxonBases){
2805
            if (taxonBase instanceof Taxon){
2806
                result.add((Taxon)taxonBase);
2807
            }
2808
        }
2809
        return result;
2810
    }
2811

    
2812
    /**
2813
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2814
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2815
     * the set returned by getTaxonBases().
2816
     *
2817
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2818
     * @see	#getTaxonBases()
2819
     * @see	#getTaxa()
2820
     */
2821
    @Override
2822
    @Transient
2823
    public Set<Synonym> getSynonyms() {
2824
        Set<Synonym> result = new HashSet<>();
2825
        for (TaxonBase taxonBase : this.taxonBases){
2826
            if (taxonBase instanceof Synonym){
2827
                result.add((Synonym)taxonBase);
2828
            }
2829
        }
2830
        return result;
2831
    }
2832

    
2833
    //******* REGISTRATION *****************/
2834

    
2835
    @Override
2836
    public Set<Registration> getRegistrations() {
2837
        return this.registrations;
2838
    }
2839

    
2840

    
2841
// ************* RELATIONSHIPS *****************************/
2842

    
2843

    
2844
    /**
2845
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2846
     * by title cache of the related names.
2847
     * @see #getHybridParentRelations()
2848
     */
2849
    @Override
2850
    @Transient
2851
    public List<HybridRelationship> getOrderedChildRelationships(){
2852
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2853
        result.addAll(this.hybridChildRelations);
2854
        Collections.sort(result);
2855
        Collections.reverse(result);
2856
        return result;
2857

    
2858
    }
2859

    
2860

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

    
2883
    /**
2884
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2885
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2886
     * "is first/second parent" or "is male/female parent". By invoking this
2887
     * method <i>this</i> botanical name becomes a parent of the hybrid child
2888
     * botanical name.
2889
     *
2890
     * @param childName       the botanical name of the child for this new hybrid name relationship
2891
     * @param type            the type of this new name relationship
2892
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2893
     * @return
2894
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
2895
     * @see                   #getRelationsToThisName()
2896
     * @see                   #getNameRelations()
2897
     * @see                   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
2898
     * @see                   #addNameRelationship(NameRelationship)
2899
     */
2900
    @Override
2901
    public HybridRelationship addHybridChild(INonViralName childName, HybridRelationshipType type, String ruleConsidered){
2902
        return new HybridRelationship(childName, this, type, ruleConsidered);
2903
    }
2904

    
2905
    @Override
2906
    public void removeHybridChild(INonViralName child) {
2907
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2908
        hybridRelationships.addAll(this.getHybridChildRelations());
2909
        hybridRelationships.addAll(this.getHybridParentRelations());
2910
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2911
            // remove name relationship from this side
2912
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2913
                this.removeHybridRelationship(hybridRelationship);
2914
            }
2915
        }
2916
    }
2917

    
2918
    @Override
2919
    public void removeHybridParent(INonViralName parent) {
2920
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2921
        hybridRelationships.addAll(this.getHybridChildRelations());
2922
        hybridRelationships.addAll(this.getHybridParentRelations());
2923
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2924
            // remove name relationship from this side
2925
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2926
                this.removeHybridRelationship(hybridRelationship);
2927
            }
2928
        }
2929
    }
2930

    
2931

    
2932

    
2933
// *********** DESCRIPTIONS *************************************
2934

    
2935
    /**
2936
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2937
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2938
     * concerning the taxon name like for instance the content of its first
2939
     * publication (protolog) or a picture of this publication.
2940
     *
2941
     * @see	#addDescription(TaxonNameDescription)
2942
     * @see	#removeDescription(TaxonNameDescription)
2943
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2944
     */
2945
    @Override
2946
    public Set<TaxonNameDescription> getDescriptions() {
2947
        return descriptions;
2948
    }
2949

    
2950
    /**
2951
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2952
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2953
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2954
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2955
     *
2956
     * @param  description  the taxon name description to be added
2957
     * @see					#getDescriptions()
2958
     * @see 			  	#removeDescription(TaxonNameDescription)
2959
     */
2960
    @Override
2961
    public void addDescription(TaxonNameDescription description) {
2962
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2963
        ReflectionUtils.makeAccessible(field);
2964
        ReflectionUtils.setField(field, description, this);
2965
        descriptions.add(description);
2966
    }
2967
    /**
2968
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2969
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2970
     * of the description itself will be set to "null".
2971
     *
2972
     * @param  description  the taxon name description which should be removed
2973
     * @see     		  	#getDescriptions()
2974
     * @see     		  	#addDescription(TaxonNameDescription)
2975
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
2976
     */
2977
    @Override
2978
    public void removeDescription(TaxonNameDescription description) {
2979
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2980
        ReflectionUtils.makeAccessible(field);
2981
        ReflectionUtils.setField(field, description, null);
2982
        descriptions.remove(description);
2983
    }
2984

    
2985
// *********** HOMOTYPIC GROUP METHODS **************************************************
2986

    
2987
    @Override
2988
    @Transient
2989
    public void mergeHomotypicGroups(TaxonName name){
2990
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2991
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2992
        name.setHomotypicalGroup(this.homotypicalGroup);
2993
    }
2994

    
2995
    /**
2996
     * Returns the boolean value indicating whether a given taxon name belongs
2997
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
2998
     * or not (false). Returns "true" only if the homotypical groups of both
2999
     * taxon names exist and if they are identical.
3000
     *
3001
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
3002
     * @return  			   the boolean value of the check
3003
     * @see     			   HomotypicalGroup
3004
     */
3005
    @Override
3006
    @Transient
3007
    public boolean isHomotypic(TaxonName homoTypicName) {
3008
        if (homoTypicName == null) {
3009
            return false;
3010
        }
3011
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
3012
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
3013
            return false;
3014
        }
3015
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
3016
            return true;
3017
        }
3018
        return false;
3019
    }
3020

    
3021

    
3022
    /**
3023
     * Checks whether name is a basionym for ALL names
3024
     * in its homotypical group.
3025
     * Returns <code>false</code> if there are no other names in the group
3026
     * @param name
3027
     * @return
3028
     */
3029
    @Override
3030
    @Transient
3031
    public boolean isGroupsBasionym() {
3032
    	if (homotypicalGroup == null){
3033
    		homotypicalGroup = HomotypicalGroup.NewInstance();
3034
    		homotypicalGroup.addTypifiedName(this);
3035
    	}
3036
        Set<TaxonName> typifiedNames = homotypicalGroup.getTypifiedNames();
3037

    
3038
        // Check whether there are any other names in the group
3039
        if (typifiedNames.size() == 1) {
3040
                return false;
3041
        }
3042

    
3043
        for (TaxonName taxonName : typifiedNames) {
3044
                if (!taxonName.equals(this)) {
3045
                        if (! isBasionymFor(taxonName)) {
3046
                                return false;
3047
                        }
3048
                }
3049
        }
3050
        return true;
3051
    }
3052

    
3053
    /**
3054
     * Checks whether a basionym relationship exists between fromName and toName.
3055
     *
3056
     * @param fromName
3057
     * @param toName
3058
     * @return
3059
     */
3060
    @Override
3061
    @Transient
3062
    public boolean isBasionymFor(TaxonName newCombinationName) {
3063
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3064
            for (NameRelationship relation : relations) {
3065
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3066
                                    relation.getFromName().equals(this)) {
3067
                            return true;
3068
                    }
3069
            }
3070
            return false;
3071
    }
3072

    
3073
    /**
3074
     * Creates a basionym relationship to all other names in this names homotypical
3075
     * group.
3076
     *
3077
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3078
     */
3079
    @Override
3080
    @Transient
3081
    public void makeGroupsBasionym() {
3082
        this.homotypicalGroup.setGroupBasionym(this);
3083
    }
3084

    
3085

    
3086
//*********  Rank comparison shortcuts   ********************//
3087
    /**
3088
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3089
     * taxon name is higher than the genus rank (true) or not (false).
3090
     * Suprageneric non viral names are monomials.
3091
     * Returns false if rank is null.
3092
     *
3093
     * @see  #isGenus()
3094
     * @see  #isInfraGeneric()
3095
     * @see  #isSpecies()
3096
     * @see  #isInfraSpecific()
3097
     */
3098
    @Override
3099
    @Transient
3100
    public boolean isSupraGeneric() {
3101
        if (rank == null){
3102
            return false;
3103
        }
3104
        return getRank().isSupraGeneric();
3105
    }
3106
    /**
3107
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3108
     * taxon name is the genus rank (true) or not (false). Non viral names with
3109
     * genus rank are monomials. Returns false if rank is null.
3110
     *
3111
     * @see  #isSupraGeneric()
3112
     * @see  #isInfraGeneric()
3113
     * @see  #isSpecies()
3114
     * @see  #isInfraSpecific()
3115
     */
3116
    @Override
3117
    @Transient
3118
    public boolean isGenus() {
3119
        if (rank == null){
3120
            return false;
3121
        }
3122
        return getRank().isGenus();
3123
    }
3124

    
3125
    @Override
3126
    @Transient
3127
    public boolean isGenusOrSupraGeneric() {
3128
        return isGenus()|| isSupraGeneric();
3129
    }
3130
    /**
3131
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3132
     * taxon name is higher than the species rank and lower than the
3133
     * genus rank (true) or not (false). Infrageneric non viral names are
3134
     * binomials. Returns false if rank is null.
3135
     *
3136
     * @see  #isSupraGeneric()
3137
     * @see  #isGenus()
3138
     * @see  #isSpecies()
3139
     * @see  #isInfraSpecific()
3140
     */
3141
    @Override
3142
    @Transient
3143
    public boolean isInfraGeneric() {
3144
        if (rank == null){
3145
            return false;
3146
        }
3147
        return getRank().isInfraGeneric();
3148
    }
3149

    
3150
    /**
3151
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3152
     * taxon name is higher than the species rank (true) or not (false).
3153
     * Returns false if rank is null.
3154
     *
3155
     * @see  #isGenus()
3156
     * @see  #isInfraGeneric()
3157
     * @see  #isSpecies()
3158
     * @see  #isInfraSpecific()
3159
     */
3160
    @Override
3161
    @Transient
3162
    public boolean isSupraSpecific(){
3163
        if (rank == null) {
3164
            return false;
3165
        }
3166
        return getRank().isHigher(Rank.SPECIES());
3167
    }
3168

    
3169
    /**
3170
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3171
     * taxon name is the species rank (true) or not (false). Non viral names
3172
     * with species rank are binomials.
3173
     * Returns false if rank is null.
3174
     *
3175
     * @see  #isSupraGeneric()
3176
     * @see  #isGenus()
3177
     * @see  #isInfraGeneric()
3178
     * @see  #isInfraSpecific()
3179
     */
3180
    @Override
3181
    @Transient
3182
    public boolean isSpecies() {
3183
        if (rank == null){
3184
            return false;
3185
        }
3186
        return getRank().isSpecies();
3187
    }
3188
    /**
3189
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3190
     * taxon name is lower than the species rank (true) or not (false).
3191
     * Infraspecific non viral names are trinomials.
3192
     * Returns false if rank is null.
3193
     *
3194
     * @see  #isSupraGeneric()
3195
     * @see  #isGenus()
3196
     * @see  #isInfraGeneric()
3197
     * @see  #isSpecies()
3198
     */
3199
    @Override
3200
    @Transient
3201
    public boolean isInfraSpecific() {
3202
        if (rank == null){
3203
            return false;
3204
        }
3205
        return getRank().isInfraSpecific();
3206
    }
3207

    
3208
    /**
3209
     * Returns true if this name's rank indicates a rank that aggregates species like species
3210
     * aggregates or species groups, false otherwise. This methods currently returns false
3211
     * for all user defined ranks.
3212
     *
3213
     *@see Rank#isSpeciesAggregate()
3214
     *
3215
     * @return
3216
     */
3217
    @Override
3218
    @Transient
3219
    public boolean isSpeciesAggregate() {
3220
        if (rank == null){
3221
            return false;
3222
        }
3223
        return getRank().isSpeciesAggregate();
3224
    }
3225

    
3226

    
3227
    /**
3228
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3229
     * the construction of <i>this</i> taxon name since there is no specific
3230
     * nomenclatural code defined. The real implementention takes place in the
3231
     * subclasses {@link IBacterialName BacterialName},
3232
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3233
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3234
     * and only one nomenclatural code.
3235
     *
3236
     * @return  null
3237
     * @see  	#isCodeCompliant()
3238
     * @see  	#getHasProblem()
3239
     * @deprecated use {@link #getNameType()} instead
3240
     */
3241
    @Override
3242
    @Deprecated
3243
    @Transient
3244
    @java.beans.Transient
3245
    public NomenclaturalCode getNomenclaturalCode() {
3246
        return nameType;
3247
    }
3248

    
3249

    
3250
    /**
3251
     * Generates and returns the string with the scientific name of <i>this</i>
3252
     * taxon name (only non viral taxon names can be generated from their
3253
     * components). This string may be stored in the inherited
3254
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3255
     * This method overrides the generic and inherited
3256
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3257
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3258
     *
3259
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3260
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3261
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3262
     */
3263
//	@Override
3264
//	public abstract String generateTitle();
3265

    
3266
    /**
3267
     * Creates a basionym relationship between this name and
3268
     * 	each name in its homotypic group.
3269
     *
3270
     * @param basionymName
3271
     */
3272
    @Override
3273
    @Transient
3274
    public void setAsGroupsBasionym() {
3275

    
3276
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3277
        if (homotypicalGroup == null) {
3278
            return;
3279
        }
3280

    
3281
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3282
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3283

    
3284
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3285

    
3286
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3287

    
3288
            for(NameRelationship nameRelation : nameRelations){
3289
                relations.add(nameRelation);
3290
            }
3291
        }
3292

    
3293
        for (NameRelationship relation : relations) {
3294

    
3295
            // If this is a basionym relation, and toName is in the homotypical group,
3296
            //	remove the relationship.
3297
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3298
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3299
                removeRelations.add(relation);
3300
            }
3301
        }
3302

    
3303
        // Removing relations from a set through which we are iterating causes a
3304
        //	ConcurrentModificationException. Therefore, we delete the targeted
3305
        //	relations in a second step.
3306
        for (NameRelationship relation : removeRelations) {
3307
            this.removeNameRelationship(relation);
3308
        }
3309

    
3310
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3311
            if (!name.equals(this)) {
3312

    
3313
                // First check whether the relationship already exists
3314
                if (!this.isBasionymFor(name)) {
3315

    
3316
                    // Then create it
3317
                    name.addRelationshipFromName(this,
3318
                            NameRelationshipType.BASIONYM(), null);
3319
                }
3320
            }
3321
        }
3322
    }
3323

    
3324
    /**
3325
     * Removes basionym relationship between this name and
3326
     * 	each name in its homotypic group.
3327
     *
3328
     * @param basionymName
3329
     */
3330
    @Override
3331
    @Transient
3332
    public void removeAsGroupsBasionym() {
3333

    
3334
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3335

    
3336
        if (homotypicalGroup == null) {
3337
            return;
3338
        }
3339

    
3340
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3341
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3342

    
3343
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3344

    
3345
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3346

    
3347
            for(NameRelationship nameRelation : nameRelations){
3348
                relations.add(nameRelation);
3349
            }
3350
        }
3351

    
3352
        for (NameRelationship relation : relations) {
3353

    
3354
            // If this is a basionym relation, and toName is in the homotypical group,
3355
            //	and fromName is basionymName, remove the relationship.
3356
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3357
                    relation.getFromName().equals(this) &&
3358
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3359
                removeRelations.add(relation);
3360
            }
3361
        }
3362

    
3363
        // Removing relations from a set through which we are iterating causes a
3364
        //	ConcurrentModificationException. Therefore, we delete the targeted
3365
        //	relations in a second step.
3366
        for (NameRelationship relation : removeRelations) {
3367
            this.removeNameRelationship(relation);
3368
        }
3369
    }
3370

    
3371

    
3372
    /**
3373
     * Defines the last part of the name.
3374
     * This is for infraspecific taxa, the infraspecific epithet,
3375
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3376
     * else the genusOrUninomial.
3377
     * However, the result does not depend on the rank (which may be not correctly set
3378
     * in case of dirty data) but returns the first name part which is not blank
3379
     * considering the above order.
3380
     * @return the first not blank name part in reverse order
3381
     */
3382
    @Override
3383
    public String getLastNamePart() {
3384
        String result =
3385
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3386
                    this.getInfraSpecificEpithet() :
3387
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3388
                    this.getSpecificEpithet():
3389
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3390
                    this.getInfraGenericEpithet():
3391
                this.getGenusOrUninomial();
3392
        return result;
3393
    }
3394

    
3395
    /**
3396
     * {@inheritDoc}
3397
     */
3398
    @Override
3399
    public boolean isHybridName() {
3400
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3401
    }
3402

    
3403
    /**
3404
     * {@inheritDoc}
3405
     */
3406
    @Override
3407
    public boolean isHybrid() {
3408
        return this.isHybridName() || this.isHybridFormula();
3409
    }
3410

    
3411
// ***************** COMPARE ********************************/
3412

    
3413
    @Override
3414
    public int compareToName(TaxonName otherName){
3415

    
3416
        int result = 0;
3417

    
3418
        if (otherName == null) {
3419
            throw new NullPointerException("Cannot compare to null.");
3420
        }
3421

    
3422
        //other
3423
        otherName = deproxy(otherName);
3424
        String otherNameCache = otherName.getNameCache();
3425
        String otherTitleCache = otherName.getTitleCache();
3426
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3427
        if (otherName.isAutonym()){
3428
            boolean isProtected = otherName.isProtectedNameCache();
3429
            String oldNameCache = otherName.getNameCache();
3430
            otherName.setProtectedNameCache(false);
3431
            otherName.setNameCache(null, false);
3432
            otherNameCache = otherName.getNameCache();
3433
            otherName.setNameCache(oldNameCache, isProtected);
3434
        }
3435

    
3436
        //this
3437
        String thisNameCache = this.getNameCache();
3438
        String thisTitleCache = this.getTitleCache();
3439

    
3440
        if (this.isAutonym()){
3441
            boolean isProtected = this.isProtectedNameCache();
3442
            String oldNameCache = this.getNameCache();
3443
            this.setProtectedNameCache(false);
3444
            this.setNameCache(null, false);
3445
            thisNameCache = this.getNameCache();
3446
            this.setNameCache(oldNameCache, isProtected);
3447
        }
3448

    
3449

    
3450
        // Compare name cache of taxon names
3451
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3452
            thisNameCache = normalizeName(thisNameCache);
3453
            otherNameCache = normalizeName(otherNameCache);
3454
            result = thisNameCache.compareTo(otherNameCache);
3455
        }
3456

    
3457
        // Compare title cache of taxon names
3458
        if (result == 0){
3459
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3460
                thisTitleCache = normalizeName(thisTitleCache);
3461
                otherTitleCache = normalizeName(otherTitleCache);
3462
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3463
            }
3464
        }
3465

    
3466
        return result;
3467
    }
3468

    
3469
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3470
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3471

    
3472
    /**
3473
     * @param thisNameCache
3474
     * @param HYBRID_SIGN
3475
     * @param QUOT_SIGN
3476
     * @return
3477
     */
3478
    private String normalizeName(String thisNameCache) {
3479
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3480
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3481
        return thisNameCache;
3482
    }
3483

    
3484
// ********************** INTERFACES ********************************************/
3485

    
3486
    /**
3487
     * Method to cast a interfaced name to a concrete name.
3488
     * The method includes a deproxy to guarantee that no
3489
     * class cast exception is thrown.
3490
     *
3491
     * @see #castAndDeproxy(Set)
3492
     * @param interfacedName
3493
     * @return
3494
     */
3495
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3496
        return deproxy(interfacedName, TaxonName.class);
3497
    }
3498

    
3499
    /**
3500
     * Method to cast a set of interfaced names to concrete namex.
3501
     * The method includes a deproxy to guarantee that no
3502
     * class cast exception is thrown.
3503
     *
3504
     * @see #castAndDeproxy(ITaxonNameBase)
3505
     * @param naminterfacedNames
3506
     * @return
3507
     */
3508
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3509
        Set<TaxonName> result = new HashSet<>();
3510
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3511
            result.add(castAndDeproxy(naminterfacedName));
3512
        }
3513
        return result;
3514
    }
3515

    
3516
//************************ isType ***********************************************/
3517

    
3518
    /**
3519
     * @return
3520
     */
3521
    @Override
3522
    public boolean isNonViral() {
3523
        return nameType.isNonViral();
3524
    }
3525

    
3526
    @Override
3527
    public boolean isZoological(){
3528
        return nameType.isZoological();
3529
    }
3530
    @Override
3531
    public boolean isBotanical() {
3532
        return nameType.isBotanical();
3533
    }
3534
    @Override
3535
    public boolean isCultivar() {
3536
        return nameType.isCultivar();
3537
    }
3538
    @Override
3539
    public boolean isBacterial() {
3540
        return nameType.isBacterial();
3541
    }
3542
    @Override
3543
    public boolean isViral() {
3544
        return nameType.isViral();
3545
    }
3546

    
3547

    
3548
//*********************** CLONE ********************************************************/
3549

    
3550
    /**
3551
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3552
     * a new instance that differs only slightly from <i>this</i> taxon name by
3553
     * modifying only some of the attributes.<BR><BR>
3554
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3555
     * <b>The name gets a newly created homotypical group</b><BR>
3556
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3557
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3558
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3559
     *
3560
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3561
     * @see java.lang.Object#clone()
3562
     */
3563
    @Override
3564
    public Object clone() {
3565
        TaxonName result;
3566
        try {
3567
            result = (TaxonName)super.clone();
3568

    
3569
            //taxonBases -> empty
3570
            result.taxonBases = new HashSet<>();
3571

    
3572
            //empty caches
3573
            if (! protectedFullTitleCache){
3574
                result.fullTitleCache = null;
3575
            }
3576

    
3577
            //descriptions
3578
            result.descriptions = new HashSet<>();
3579
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3580
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3581
                result.descriptions.add(newDescription);
3582
            }
3583

    
3584
            //status
3585
            result.status = new HashSet<>();
3586
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3587
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3588
                result.status.add(newStatus);
3589
            }
3590

    
3591

    
3592
            //to relations
3593
            result.relationsToThisName = new HashSet<>();
3594
            for (NameRelationship toRelationship : getRelationsToThisName()){
3595
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3596
                newRelationship.setRelatedTo(result);
3597
                result.relationsToThisName.add(newRelationship);
3598
            }
3599

    
3600
            //from relations
3601
            result.relationsFromThisName = new HashSet<>();
3602
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3603
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3604
                newRelationship.setRelatedFrom(result);
3605
                result.relationsFromThisName.add(newRelationship);
3606
            }
3607

    
3608
            //type designations
3609
            result.typeDesignations = new HashSet<>();
3610
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3611
                TypeDesignationBase<?> newDesignation = (TypeDesignationBase<?>)typeDesignation.clone();
3612
                this.removeTypeDesignation(newDesignation);
3613
                result.addTypeDesignation(newDesignation, false);
3614
            }
3615

    
3616
            //homotypicalGroup
3617
            //TODO still needs to be discussed
3618
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3619
            result.homotypicalGroup.addTypifiedName(this);
3620

    
3621

    
3622
            //HybridChildRelations
3623
            result.hybridChildRelations = new HashSet<>();
3624
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3625
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3626
                newChildRelationship.setRelatedTo(result);
3627
                result.hybridChildRelations.add(newChildRelationship);
3628
            }
3629

    
3630
            //HybridParentRelations
3631
            result.hybridParentRelations = new HashSet<>();
3632
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3633
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3634
                newParentRelationship.setRelatedFrom(result);
3635
                result.hybridParentRelations.add(newParentRelationship);
3636
            }
3637

    
3638
            //empty caches
3639
            if (! protectedNameCache){
3640
                result.nameCache = null;
3641
            }
3642

    
3643
            //empty caches
3644
            if (! protectedAuthorshipCache){
3645
                result.authorshipCache = null;
3646
            }
3647

    
3648
            //no changes to: appendedPharse, nomenclaturalReference,
3649
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3650
            //protectedFullTitleCache, rank
3651
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3652
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3653
            //protectedAuthorshipCache, protectedNameCache,
3654
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3655
            //acronym
3656
            //subGenusAuthorship, nameApprobation
3657
            //anamorphic
3658
            //cultivarName
3659
            return result;
3660
        } catch (CloneNotSupportedException e) {
3661
            logger.warn("Object does not implement cloneable");
3662
            e.printStackTrace();
3663
            return null;
3664
        }
3665

    
3666
    }
3667

    
3668

    
3669
}
3670

    
(29-29/36)