Project

General

Profile

Download (137 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.RelationshipBase;
76
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
77
import eu.etaxonomy.cdm.model.common.TermType;
78
import eu.etaxonomy.cdm.model.common.TermVocabulary;
79
import eu.etaxonomy.cdm.model.description.IDescribable;
80
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
81
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
82
import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
83
import eu.etaxonomy.cdm.model.reference.Reference;
84
import eu.etaxonomy.cdm.model.taxon.Synonym;
85
import eu.etaxonomy.cdm.model.taxon.Taxon;
86
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
87
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
88
import eu.etaxonomy.cdm.strategy.cache.name.CacheUpdate;
89
import eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy;
90
import eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy;
91
import eu.etaxonomy.cdm.strategy.match.IMatchable;
92
import eu.etaxonomy.cdm.strategy.match.Match;
93
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
94
import eu.etaxonomy.cdm.strategy.match.MatchMode;
95
import eu.etaxonomy.cdm.strategy.merge.Merge;
96
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
97
import eu.etaxonomy.cdm.strategy.parser.ParserProblem;
98
import eu.etaxonomy.cdm.validation.Level2;
99
import eu.etaxonomy.cdm.validation.Level3;
100
import eu.etaxonomy.cdm.validation.annotation.CorrectEpithetsForRank;
101
import eu.etaxonomy.cdm.validation.annotation.NameMustFollowCode;
102
import eu.etaxonomy.cdm.validation.annotation.NameMustHaveAuthority;
103
import eu.etaxonomy.cdm.validation.annotation.NoDuplicateNames;
104
import eu.etaxonomy.cdm.validation.annotation.NullOrNotEmpty;
105
import eu.etaxonomy.cdm.validation.annotation.ValidTaxonomicYear;
106

    
107
/**
108
 * The upmost (abstract) class for scientific taxon names regardless of any
109
 * particular {@link NomenclaturalCode nomenclature code}. The scientific taxon name does not depend
110
 * on the use made of it in a publication or a treatment
111
 * ({@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon concept respectively potential taxon})
112
 * as an {@link eu.etaxonomy.cdm.model.taxon.Taxon "accepted" respectively "correct" (taxon) name}
113
 * or as a {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
114
 * <P>
115
 * This class corresponds partially to: <ul>
116
 * <li> TaxonName according to the TDWG ontology
117
 * <li> ScientificName and CanonicalName according to the TCS
118
 * <li> ScientificName according to the ABCD schema
119
 * </ul>
120
 *
121
 * @author m.doering
122
 * @created 08-Nov-2007 13:06:57
123
 */
124
@XmlAccessorType(XmlAccessType.FIELD)
125
@XmlType(name = "TaxonName", propOrder = {
126
    "nameType",
127
    "appendedPhrase",
128
    "nomenclaturalMicroReference",
129
    "nomenclaturalReference",
130
    "rank",
131
    "fullTitleCache",
132
    "protectedFullTitleCache",
133
    "homotypicalGroup",
134
    "typeDesignations",
135
    "relationsFromThisName",
136
    "relationsToThisName",
137
    "status",
138
    "descriptions",
139
    "taxonBases",
140
    "registrations",
141

    
142
    "nameCache",
143
    "genusOrUninomial",
144
    "infraGenericEpithet",
145
    "specificEpithet",
146
    "infraSpecificEpithet",
147
    "combinationAuthorship",
148
    "exCombinationAuthorship",
149
    "basionymAuthorship",
150
    "exBasionymAuthorship",
151
    "authorshipCache",
152
    "protectedAuthorshipCache",
153
    "protectedNameCache",
154
    "hybridParentRelations",
155
    "hybridChildRelations",
156
    "hybridFormula",
157
    "monomHybrid",
158
    "binomHybrid",
159
    "trinomHybrid",
160

    
161
    "acronym",
162

    
163
    "subGenusAuthorship",
164
    "nameApprobation",
165

    
166
    "breed",
167
    "publicationYear",
168
    "originalPublicationYear",
169

    
170
    "anamorphic",
171

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

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

    
196

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

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

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

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

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

    
237
    @XmlElement(name = "NomenclaturalMicroReference")
238
    @Field
239
    @CacheUpdate(noUpdate ="titleCache")
240
    //TODO Val #3379
241
//    @NullOrNotEmpty
242
    @Column(length=255)
243
    private String nomenclaturalMicroReference;
244

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

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

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

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

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

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

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

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

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

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

    
329
    @XmlElement(name = "NomenclaturalReference")
330
    @XmlIDREF
331
    @XmlSchemaType(name = "IDREF")
332
    @ManyToOne(fetch = FetchType.LAZY)
333
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
334
    @CacheUpdate(noUpdate ="titleCache")
335
    @IndexedEmbedded
336
    private Reference nomenclaturalReference;
337

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

    
348
//****** Non-ViralName attributes ***************************************/
349

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

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

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

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

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

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

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

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

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

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

    
447
    @XmlElement(name = "AuthorshipCache")
448
    @Fields({
449
        @Field(name = "authorshipCache_tokenized"),
450
        @Field(analyze = Analyze.NO)
451
    })
452
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
453
            cacheReplacedProperties={"combinationAuthorship", "basionymAuthorship", "exCombinationAuthorship", "exBasionymAuthorship"} )
454
    //TODO Val #3379
455
//    @NotNull
456
    @Column(length=255)
457
    @Pattern(regexp = "^[A-Za-z0-9 \\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-\\&\\,\\(\\)\\.]+$", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForAuthority.message}")
458
    private String authorshipCache;
459

    
460
    @XmlElement(name = "ProtectedAuthorshipCache")
461
    @CacheUpdate("authorshipCache")
462
    protected boolean protectedAuthorshipCache;
463

    
464
    @XmlElementWrapper(name = "HybridRelationsFromThisName")
465
    @XmlElement(name = "HybridRelationsFromThisName")
466
    @OneToMany(mappedBy="relatedFrom", fetch = FetchType.LAZY)
467
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
468
    @Merge(MergeMode.RELATION)
469
    @NotNull
470
    private Set<HybridRelationship> hybridParentRelations = new HashSet<>();
471

    
472
    @XmlElementWrapper(name = "HybridRelationsToThisName")
473
    @XmlElement(name = "HybridRelationsToThisName")
474
    @OneToMany(mappedBy="relatedTo", fetch = FetchType.LAZY, orphanRemoval=true) //a hybrid relation can be deleted automatically if the child is deleted.
475
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
476
    @Merge(MergeMode.RELATION)
477
    @NotNull
478
    private Set<HybridRelationship> hybridChildRelations = new HashSet<>();
479

    
480

    
481
    //if set: this name is a hybrid formula (a hybrid that does not have an own name) and no
482
    //other hybrid flags may be set. A
483
    //hybrid name  may not have either an authorteam nor other name components.
484
    @XmlElement(name ="IsHybridFormula")
485
    @CacheUpdate("nameCache")
486
    private boolean hybridFormula = false;
487

    
488
    @XmlElement(name ="IsMonomHybrid")
489
    @CacheUpdate("nameCache")
490
    private boolean monomHybrid = false;
491

    
492
    @XmlElement(name ="IsBinomHybrid")
493
    @CacheUpdate("nameCache")
494
    private boolean binomHybrid = false;
495

    
496
    @XmlElement(name ="IsTrinomHybrid")
497
    @CacheUpdate("nameCache")
498
    private boolean trinomHybrid = false;
499

    
500
// ViralName attributes ************************* /
501

    
502
    @XmlElement(name = "Acronym")
503
    @Field
504
    //TODO Val #3379
505
//  @NullOrNotEmpty
506
    @Column(length=255)
507
    private String acronym;
508

    
509
// BacterialName attributes ***********************/
510

    
511
    //Author team and year of the subgenus name
512
    @XmlElement(name = "SubGenusAuthorship")
513
    @Field
514
    private String subGenusAuthorship;
515

    
516
    //Approbation of name according to approved list, validation list, or validly published, paper in IJSB after 1980
517
    @XmlElement(name = "NameApprobation")
518
    @Field
519
    private String nameApprobation;
520

    
521
    //ZOOLOGICAL NAME
522

    
523
    //Name of the breed of an animal
524
    @XmlElement(name = "Breed")
525
    @Field
526
    @NullOrNotEmpty
527
    @Column(length=255)
528
    private String breed;
529

    
530
    @XmlElement(name = "PublicationYear")
531
    @Field(analyze = Analyze.NO)
532
    @CacheUpdate(value ="authorshipCache")
533
    @Min(0)
534
    private Integer publicationYear;
535

    
536
    @XmlElement(name = "OriginalPublicationYear")
537
    @Field(analyze = Analyze.NO)
538
    @CacheUpdate(value ="authorshipCache")
539
    @Min(0)
540
    private Integer originalPublicationYear;
541

    
542
    //Cultivar attribute(s)
543

    
544
    //the characteristical name of the cultivar
545
    @XmlElement(name = "CultivarName")
546
    //TODO Val #3379
547
    //@NullOrNotEmpty
548
    @Column(length=255)
549
    private String cultivarName;
550

    
551
    // ************** FUNGUS name attributes
552
    //to indicate that the type of the name is asexual or not
553
    @XmlElement(name ="IsAnamorphic")
554
    private boolean anamorphic = false;
555

    
556
// *************** FACTORY METHODS ********************************/
557

    
558
    //see TaxonNameFactory
559
    /**
560
     * @param code
561
     * @param rank
562
     * @param homotypicalGroup
563
     * @return
564
     */
565
    protected static TaxonName NewInstance(NomenclaturalCode code, Rank rank,
566
            HomotypicalGroup homotypicalGroup) {
567
        TaxonName result = new TaxonName(code, rank, homotypicalGroup);
568
        return result;
569
    }
570

    
571

    
572
    /**
573
     * @param icnafp
574
     * @param rank2
575
     * @param genusOrUninomial2
576
     * @param infraGenericEpithet2
577
     * @param specificEpithet2
578
     * @param infraSpecificEpithet2
579
     * @param combinationAuthorship2
580
     * @param nomenclaturalReference2
581
     * @param nomenclMicroRef
582
     * @param homotypicalGroup2
583
     * @return
584
     */
585
    public static TaxonName NewInstance(NomenclaturalCode code, Rank rank, String genusOrUninomial,
586
            String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet,
587
            TeamOrPersonBase combinationAuthorship, Reference nomenclaturalReference,
588
            String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
589
        TaxonName result = new TaxonName(code, rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
590
        return result;
591
    }
592

    
593

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

    
608

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

    
634

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

    
677

    
678
    /**
679
     * This method was originally needed to distinguish cache strategies
680
     * depending on the name type. Now we have a unified cache strategy
681
     * which does not require this anymore. Maybe we could even further remove this method.
682
     */
683
    private void rectifyNameCacheStrategy(){
684
        if (this.cacheStrategy == null){
685
            this.cacheStrategy = TaxonNameDefaultCacheStrategy.NewInstance();
686
        }
687
    }
688

    
689

    
690
    @Override
691
    public void initListener(){
692
        PropertyChangeListener listener = new PropertyChangeListener() {
693
            @Override
694
            public void propertyChange(PropertyChangeEvent e) {
695
                boolean protectedByLowerCache = false;
696
                //authorship cache
697
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
698
                    if (protectedAuthorshipCache){
699
                        protectedByLowerCache = true;
700
                    }else{
701
                        authorshipCache = null;
702
                    }
703
                }
704

    
705
                //nameCache
706
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "nameCache")){
707
                    if (protectedNameCache){
708
                        protectedByLowerCache = true;
709
                    }else{
710
                        nameCache = null;
711
                    }
712
                }
713
                //title cache
714
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "titleCache")){
715
                    if (isProtectedTitleCache()|| protectedByLowerCache == true ){
716
                        protectedByLowerCache = true;
717
                    }else{
718
                        titleCache = null;
719
                    }
720
                }
721
                //full title cache
722
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "fullTitleCache")){
723
                    if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
724
                        protectedByLowerCache = true;
725
                    }else{
726
                        fullTitleCache = null;
727
                    }
728
                }
729
            }
730
        };
731
        addPropertyChangeListener(listener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
732
    }
733

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

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

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

    
792
// ****************** GETTER / SETTER ****************************/
793

    
794
    @Override
795
    public NomenclaturalCode getNameType() {
796
        return nameType;
797
    }
798

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
961
    /**
962
     * @see  #getExCombinationAuthorship()
963
     */
964
    @Override
965
    public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
966
        this.exCombinationAuthorship = exCombinationAuthorship;
967
    }
968

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

    
985
    /**
986
     * @see  #getBasionymAuthorship()
987
     */
988
    @Override
989
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
990
        this.basionymAuthorship = basionymAuthorship;
991
    }
992

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

    
1014
    /**
1015
     * @see  #getExBasionymAuthorship()
1016
     */
1017
    @Override
1018
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
1019
        this.exBasionymAuthorship = exBasionymAuthorship;
1020
    }
1021

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

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

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

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

    
1064

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

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

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

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

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

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

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

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

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

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

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

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

    
1191
    // ****************** VIRAL NAME ******************/
1192

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

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

    
1206
    // ****************** BACTERIAL NAME ******************/
1207

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

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

    
1218

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

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

    
1232
    //************ Zoological Name
1233

    
1234

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

    
1247

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

    
1260

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

    
1273
    // **** Cultivar Name ************
1274

    
1275

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

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

    
1289
    // **************** Fungus Name
1290
    @Override
1291
    public boolean isAnamorphic(){
1292
        return this.anamorphic;
1293
    }
1294

    
1295
    /**
1296
     * @see  #isAnamorphic()
1297
     */
1298
    @Override
1299
    public void setAnamorphic(boolean anamorphic){
1300
        this.anamorphic = anamorphic;
1301
    }
1302

    
1303

    
1304
// **************** ADDER / REMOVE *************************/
1305

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

    
1335

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

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

    
1367
//********* METHODS **************************************/
1368

    
1369
    @Override
1370
    public INameCacheStrategy getCacheStrategy() {
1371
        rectifyNameCacheStrategy();
1372
        return this.cacheStrategy;
1373
    }
1374

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

    
1385

    
1386
    @Override
1387
    public void setFullTitleCache(String fullTitleCache){
1388
        setFullTitleCache(fullTitleCache, PROTECTED);
1389
    }
1390

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

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

    
1423

    
1424
    @Override
1425
    @Transient
1426
    public List<TaggedText> getTaggedName(){
1427
        INameCacheStrategy strat = getCacheStrategy();
1428
        return strat.getTaggedTitle(this);
1429
    }
1430

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

    
1444

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

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

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

    
1481
        }
1482
        return authorshipCache;
1483
    }
1484

    
1485

    
1486

    
1487
    /**
1488
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
1489
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
1490
     * @return
1491
     */
1492
    private void updateAuthorshipCache() {
1493
        //updates the authorship cache if necessary and via the listener updates all higher caches
1494
        if (protectedAuthorshipCache == false){
1495
            String oldCache = this.authorshipCache;
1496
            String newCache = this.getAuthorshipCache();
1497
            if ( (oldCache == null && newCache != null)  ||  CdmUtils.nullSafeEqual(oldCache,newCache)){
1498
                this.setAuthorshipCache(this.getAuthorshipCache(), false);
1499
            }
1500
        }
1501
    }
1502

    
1503

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

    
1516

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

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

    
1550

    
1551

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

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

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

    
1582

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

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

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

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

    
1629
    /**
1630
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1631
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1632
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1633
     *
1634
     * @param toName		  the taxon name of the target for this new name relationship
1635
     * @param type			  the type of this new name relationship
1636
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1637
     * @return
1638
     * @see    				  #getRelationsToThisName()
1639
     * @see    				  #getNameRelations()
1640
     * @see    				  #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1641
     * @see    				  #addNameRelationship(NameRelationship)
1642
     */
1643
    @Override
1644
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, String ruleConsidered){
1645
        return addRelationshipToName(toName, type, null, null, ruleConsidered);
1646
    }
1647

    
1648
    /**
1649
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1650
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1651
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1652
     *
1653
     * @param toName		  the taxon name of the target for this new name relationship
1654
     * @param type			  the type of this new name relationship
1655
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1656
     * @return
1657
     * @see    				  #getRelationsToThisName()
1658
     * @see    				  #getNameRelations()
1659
     * @see    				  #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1660
     * @see    				  #addNameRelationship(NameRelationship)
1661
     */
1662
    @Override
1663
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1664
        if (toName == null){
1665
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1666
        }
1667
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered);
1668
        return rel;
1669
    }
1670

    
1671
    /**
1672
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1673
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1674
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1675
     *
1676
     * @param fromName		  the taxon name of the source for this new name relationship
1677
     * @param type			  the type of this new name relationship
1678
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1679
     * @param citation		  the reference in which this relation was described
1680
     * @param microCitation	  the reference detail for this relation (e.g. page)
1681
     * @see    				  #getRelationsFromThisName()
1682
     * @see    				  #getNameRelations()
1683
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1684
     * @see    				  #addNameRelationship(NameRelationship)
1685
     */
1686
    @Override
1687
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered){
1688
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1689
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
1690
    }
1691
    /**
1692
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1693
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1694
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1695
     *
1696
     * @param fromName		  the taxon name of the source for this new name relationship
1697
     * @param type			  the type of this new name relationship
1698
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1699
     * @param citation		  the reference in which this relation was described
1700
     * @param microCitation	  the reference detail for this relation (e.g. page)
1701
     * @see    				  #getRelationsFromThisName()
1702
     * @see    				  #getNameRelations()
1703
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1704
     * @see    				  #addNameRelationship(NameRelationship)
1705
     */
1706
    @Override
1707
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1708
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1709
    }
1710

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

    
1751
        TaxonName fromName = nameRelation.getFromName();
1752
        TaxonName toName = nameRelation.getToName();
1753

    
1754
        if (nameRelation != null) {
1755
            nameRelation.setToName(null);
1756
            nameRelation.setFromName(null);
1757
        }
1758

    
1759
        if (fromName != null) {
1760
            fromName.removeNameRelationship(nameRelation);
1761
        }
1762

    
1763
        if (toName != null) {
1764
            toName.removeNameRelationship(nameRelation);
1765
        }
1766

    
1767
        this.relationsToThisName.remove(nameRelation);
1768
        this.relationsFromThisName.remove(nameRelation);
1769
    }
1770

    
1771
    @Override
1772
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1773
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1774
//		nameRelationships.addAll(this.getNameRelations());
1775
        nameRelationships.addAll(this.getRelationsFromThisName());
1776
        nameRelationships.addAll(this.getRelationsToThisName());
1777
        for(NameRelationship nameRelationship : nameRelationships) {
1778
            // remove name relationship from this side
1779
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1780
                this.removeNameRelationship(nameRelationship);
1781
            }
1782
        }
1783
    }
1784

    
1785
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1786

    
1787
        for(NameRelationship nameRelationship : relationsWithThisName(direction)) {
1788
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1789
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1790
                this.removeNameRelationship(nameRelationship);
1791
            }
1792
        }
1793
    }
1794

    
1795

    
1796
    /**
1797
     * If relation is of type NameRelationship, addNameRelationship is called;
1798
     * if relation is of type HybridRelationship addHybridRelationship is called,
1799
     * otherwise an IllegalArgumentException is thrown.
1800
     *
1801
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1802
     * @see    	   		#addNameRelationship(NameRelationship)
1803
     * @see    	   		#getNameRelations()
1804
     * @see    	   		NameRelationship
1805
     * @see    	   		RelationshipBase
1806
     * @see             #addHybridRelationship(HybridRelationship)
1807

    
1808
     * @deprecated to be used by RelationshipBase only
1809
     */
1810
    @Deprecated
1811
    @Override
1812
    public void addRelationship(RelationshipBase relation) {
1813
        if (relation instanceof NameRelationship){
1814
            addNameRelationship((NameRelationship)relation);
1815

    
1816
        }else if (relation instanceof HybridRelationship){
1817
            addHybridRelationship((HybridRelationship)relation);
1818
        }else{
1819
            logger.warn("Relationship not of type NameRelationship!");
1820
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1821
        }
1822
    }
1823

    
1824
    /**
1825
     * Returns the set of all {@link NameRelationship name relationships}
1826
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1827
     *
1828
     * @see    #getNameRelations()
1829
     * @see    #getRelationsToThisName()
1830
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1831
     */
1832
    @Override
1833
    public Set<NameRelationship> getRelationsFromThisName() {
1834
        if(relationsFromThisName == null) {
1835
            this.relationsFromThisName = new HashSet<>();
1836
        }
1837
        return relationsFromThisName;
1838
    }
1839

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

    
1856
    /**
1857
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1858
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1859
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1860
     * and the nomenclatural code rule considered.
1861
     *
1862
     * @see     NomenclaturalStatus
1863
     * @see     NomenclaturalStatusType
1864
     */
1865
    @Override
1866
    public Set<NomenclaturalStatus> getStatus() {
1867
        if(status == null) {
1868
            this.status = new HashSet<>();
1869
        }
1870
        return status;
1871
    }
1872

    
1873
    /**
1874
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1875
     * to <i>this</i> taxon name's set of nomenclatural status.
1876
     *
1877
     * @param  nomStatus  the nomenclatural status to be added
1878
     * @see 			  #getStatus()
1879
     */
1880
    @Override
1881
    public void addStatus(NomenclaturalStatus nomStatus) {
1882
        this.status.add(nomStatus);
1883
    }
1884
    @Override
1885
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1886
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1887
        this.status.add(newStatus);
1888
        return newStatus;
1889
    }
1890

    
1891
    /**
1892
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1893
     * Type and ruleConsidered attributes of the nomenclatural status object
1894
     * will be nullified.
1895
     *
1896
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1897
     * @see     		  #getStatus()
1898
     */
1899
    @Override
1900
    public void removeStatus(NomenclaturalStatus nomStatus) {
1901
        //TODO to be implemented?
1902
        logger.warn("not yet fully implemented?");
1903
        this.status.remove(nomStatus);
1904
    }
1905

    
1906

    
1907
    /**
1908
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1909
     * strings or year according to the strategy defined in
1910
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1911
     * The result might be stored in {@link #getNameCache() nameCache} if the
1912
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1913
     *
1914
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1915
     * @see     #getNameCache()
1916
     */
1917
    protected String generateNameCache(){
1918
        if (getCacheStrategy() == null){
1919
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1920
            return null;
1921
        }else{
1922
            return cacheStrategy.getNameCache(this);
1923
        }
1924
    }
1925

    
1926
    /**
1927
     * Returns or generates the nameCache (scientific name
1928
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1929
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1930
     * the string will be generated according to a defined strategy,
1931
     * otherwise the value of the actual nameCache string will be returned.
1932
     *
1933
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1934
     * @see     #generateNameCache()
1935
     */
1936
    @Override
1937
    @Transient
1938
    public String getNameCache() {
1939
        if (protectedNameCache){
1940
            return this.nameCache;
1941
        }
1942
        // is title dirty, i.e. equal NULL?
1943
        if (nameCache == null){
1944
            this.nameCache = generateNameCache();
1945
        }
1946
        return nameCache;
1947
    }
1948

    
1949
    /**
1950
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1951
     * Sets the protectedNameCache flag to <code>true</code>.
1952
     *
1953
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1954
     * @see    #getNameCache()
1955
     */
1956
    @Override
1957
    public void setNameCache(String nameCache){
1958
        setNameCache(nameCache, true);
1959
    }
1960

    
1961
    /**
1962
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1963
     * Sets the protectedNameCache flag to <code>true</code>.
1964
     *
1965
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1966
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
1967
     * <code>false</code>
1968
     * @see    #getNameCache()
1969
     */
1970
    @Override
1971
    public void setNameCache(String nameCache, boolean protectedNameCache){
1972
        this.nameCache = nameCache;
1973
        this.setProtectedNameCache(protectedNameCache);
1974
    }
1975

    
1976

    
1977
    /**
1978
     * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
1979
     * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1980
     * of any other taxon name. Returns "true", if a basionym or a replaced
1981
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1982
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1983
     * homotypical group).
1984
     */
1985
    @Override
1986
    @Transient
1987
    public boolean isOriginalCombination(){
1988
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1989
        for (NameRelationship relation : relationsFromThisName) {
1990
            if (relation.getType().isBasionymRelation() ||
1991
                    relation.getType().isReplacedSynonymRelation()) {
1992
                return true;
1993
            }
1994
        }
1995
        return false;
1996
    }
1997

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

    
2017
    /**
2018
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2019
     * The basionym of a taxon name is its epithet-bringing synonym.
2020
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2021
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2022
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2023
     *
2024
     * If more than one basionym exists one is choosen at radom.
2025
     *
2026
     * If no basionym exists null is returned.
2027
     */
2028
    @Override
2029
    @Transient
2030
    public TaxonName getBasionym(){
2031
        Set<TaxonName> basionyms = getBasionyms();
2032
        if (basionyms.size() == 0){
2033
            return null;
2034
        }else{
2035
            return basionyms.iterator().next();
2036
        }
2037
    }
2038

    
2039
    /**
2040
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2041
     * The basionym of a taxon name is its epithet-bringing synonym.
2042
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2043
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2044
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2045
     */
2046
    @Override
2047
    @Transient
2048
    public Set<TaxonName> getBasionyms(){
2049

    
2050
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
2051
    }
2052

    
2053
    /**
2054
     *
2055
     * @param direction
2056
     * @param type
2057
     * @return
2058
     */
2059
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
2060

    
2061
        return getRelatedNames(relationsWithThisName(direction), type);
2062
    }
2063

    
2064
    /**
2065
     * @param rels
2066
     * @param type
2067
     * @return
2068
     */
2069
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2070
        Set<TaxonName> result = new HashSet<>();
2071
        for (NameRelationship rel : rels){
2072
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2073
                TaxonName basionym = rel.getFromName();
2074
                result.add(basionym);
2075
            }
2076
        }
2077
        return result;
2078
    }
2079

    
2080
    /**
2081
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2082
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2083
     * and to the basionym. The basionym cannot have itself as a basionym.
2084
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2085
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2086
     *
2087
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2088
     * @see  				#getBasionym()
2089
     * @see  				#addBasionym(TaxonName, String)
2090
     */
2091
    @Override
2092
    public void addBasionym(TaxonName basionym){
2093
        addBasionym(basionym, null, null, null);
2094
    }
2095
    /**
2096
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2097
     * and keeps the nomenclatural rule considered for it. The basionym
2098
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2099
     * The basionym cannot have itself as a basionym.
2100
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2101
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2102
     *
2103
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2104
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2105
     * @return
2106
     * @see  					#getBasionym()
2107
     * @see  					#addBasionym(TaxonName)
2108
     */
2109
    @Override
2110
    public NameRelationship addBasionym(TaxonName basionym, Reference citation, String microcitation, String ruleConsidered){
2111
        if (basionym != null){
2112
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
2113
        }else{
2114
            return null;
2115
        }
2116
    }
2117

    
2118
    /**
2119
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2120
     *
2121
     */
2122
    @Override
2123
    @Transient
2124
    public Set<TaxonName> getReplacedSynonyms(){
2125

    
2126
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2127
    }
2128

    
2129
    /**
2130
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2131
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2132
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2133
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2134
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2135
     *
2136
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2137
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2138
     * @see  					#getBasionym()
2139
     * @see  					#addBasionym(TaxonName)
2140
     */
2141
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2142
    @Override
2143
    public void addReplacedSynonym(TaxonName replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
2144
        if (replacedSynonym != null){
2145
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
2146
        }
2147
    }
2148

    
2149
    /**
2150
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2151
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2152
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2153
     * previously used as basionym.
2154
     *
2155
     * @see   #getBasionym()
2156
     * @see   #addBasionym(TaxonName)
2157
     */
2158
    @Override
2159
    public void removeBasionyms(){
2160
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2161
    }
2162

    
2163

    
2164
    /**
2165
     * Removes all {@link NameRelationship relationships} of the given <code>type</code> from the set of
2166
     * relations in the specified <code>direction</code> direction wich are related from or to this
2167
     * <i>this</i> taxon name. The same relationship will be removed from the set of
2168
     * reverse relations of the other taxon name.
2169
     *
2170
     * @param direction
2171
     * @param type
2172
     */
2173
    public void removeNameRelations(Direction direction, NameRelationshipType type) {
2174
        Set<NameRelationship> relationsWithThisName = relationsWithThisName(direction);
2175
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
2176
        for (NameRelationship nameRelation : relationsWithThisName){
2177
            if (nameRelation.getType().isRelationshipType(type)){
2178
                removeRelations.add(nameRelation);
2179
            }
2180
        }
2181
        // Removing relations from a set through which we are iterating causes a
2182
        // ConcurrentModificationException. Therefore, we delete the targeted
2183
        // relations in a second step.
2184
        for (NameRelationship relation : removeRelations){
2185
            this.removeNameRelationship(relation);
2186
        }
2187
    }
2188

    
2189

    
2190
    /**
2191
     * @param direction
2192
     * @return
2193
     */
2194
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2195

    
2196
        switch(direction) {
2197
            case relatedTo:
2198
                return this.getRelationsToThisName();
2199
            case relatedFrom:
2200
                return this.getRelationsFromThisName();
2201
            default: throw new RuntimeException("invalid Direction:" + direction);
2202
        }
2203
    }
2204

    
2205
    /**
2206
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2207
     *
2208
     * @see 	Rank
2209
     */
2210
    @Override
2211
    public Rank getRank(){
2212
        return this.rank;
2213
    }
2214

    
2215
    /**
2216
     * @see  #getRank()
2217
     */
2218
    @Override
2219
    public void setRank(Rank rank){
2220
        this.rank = rank;
2221
    }
2222

    
2223
    /**
2224
     * Returns the {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} of <i>this</i> taxon name.
2225
     * The nomenclatural reference is here meant to be the one publication
2226
     * <i>this</i> taxon name was originally published in while fulfilling the formal
2227
     * requirements as specified by the corresponding {@link NomenclaturalCode nomenclatural code}.
2228
     *
2229
     * @see 	eu.etaxonomy.cdm.model.reference.INomenclaturalReference
2230
     * @see 	eu.etaxonomy.cdm.model.reference.Reference
2231
     */
2232
    @Override
2233
    public INomenclaturalReference getNomenclaturalReference(){
2234
        return this.nomenclaturalReference;
2235
    }
2236
    /**
2237
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2238
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2239
     * as it is obviously used for nomenclatural purposes.
2240
     *
2241
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2242
     * @see  #getNomenclaturalReference()
2243
     */
2244
    @Override
2245
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2246
        if(nomenclaturalReference != null){
2247
            if(!INomenclaturalReference.class.isAssignableFrom(nomenclaturalReference.getClass())){
2248
                throw new IllegalArgumentException("Parameter nomenclaturalReference is not assignable from INomenclaturalReference");
2249
            }
2250
            this.nomenclaturalReference = (Reference)nomenclaturalReference;
2251
        } else {
2252
            this.nomenclaturalReference = null;
2253
        }
2254
    }
2255

    
2256
    /**
2257
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2258
     * The appended phrase is a non-atomised addition to a name. It is
2259
     * not ruled by a nomenclatural code.
2260
     */
2261
    @Override
2262
    public String getAppendedPhrase(){
2263
        return this.appendedPhrase;
2264
    }
2265

    
2266
    /**
2267
     * @see  #getAppendedPhrase()
2268
     */
2269
    @Override
2270
    public void setAppendedPhrase(String appendedPhrase){
2271
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2272
    }
2273

    
2274
    /**
2275
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2276
     * to <i>this</i> taxon name. The details describe the exact localisation within
2277
     * the publication used as nomenclature reference. These are mostly
2278
     * (implicitly) pages but can also be figures or tables or any other
2279
     * element of a publication. A nomenclatural micro reference (details)
2280
     * requires the existence of a nomenclatural reference.
2281
     */
2282
    //Details of the nomenclatural reference (protologue).
2283
    @Override
2284
    public String getNomenclaturalMicroReference(){
2285
        return this.nomenclaturalMicroReference;
2286
    }
2287
    /**
2288
     * @see  #getNomenclaturalMicroReference()
2289
     */
2290
    @Override
2291
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2292
        this.nomenclaturalMicroReference = StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
2293
    }
2294

    
2295
    @Override
2296
    public int getParsingProblem(){
2297
        return this.parsingProblem;
2298
    }
2299

    
2300
    @Override
2301
    public void setParsingProblem(int parsingProblem){
2302
        this.parsingProblem = parsingProblem;
2303
    }
2304

    
2305
    @Override
2306
    public void addParsingProblem(ParserProblem problem){
2307
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2308
    }
2309

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

    
2315
    /**
2316
     * @param warnings
2317
     */
2318
    @Override
2319
    public void addParsingProblems(int problems){
2320
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2321
    }
2322

    
2323
    @Override
2324
    public boolean hasProblem(){
2325
        return parsingProblem != 0;
2326
    }
2327

    
2328
    @Override
2329
    public boolean hasProblem(ParserProblem problem) {
2330
        return getParsingProblems().contains(problem);
2331
    }
2332

    
2333
    @Override
2334
    public int getProblemStarts(){
2335
        return this.problemStarts;
2336
    }
2337

    
2338
    @Override
2339
    public void setProblemStarts(int start) {
2340
        this.problemStarts = start;
2341
    }
2342

    
2343
    @Override
2344
    public int getProblemEnds(){
2345
        return this.problemEnds;
2346
    }
2347

    
2348
    @Override
2349
    public void setProblemEnds(int end) {
2350
        this.problemEnds = end;
2351
    }
2352

    
2353
//*********************** TYPE DESIGNATION *********************************************//
2354

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

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

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

    
2401
//*********************** NAME TYPE DESIGNATION *********************************************//
2402

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

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

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

    
2489
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2490

    
2491
    /**
2492
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2493
     * that typify <i>this</i> taxon name.
2494
     */
2495
    @Override
2496
    @Transient
2497
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2498
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2499
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2500
            if (typeDesignation instanceof SpecimenTypeDesignation){
2501
                result.add((SpecimenTypeDesignation)typeDesignation);
2502
            }
2503
        }
2504
        return result;
2505
    }
2506

    
2507

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

    
2540
    //used by merge strategy
2541
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2542
        return addTypeDesignation(typeDesignation, true);
2543
    }
2544

    
2545
    /**
2546
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2547
     *
2548
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2549
     * @param addToAllNames				the boolean indicating whether the type designation should be
2550
     * 									added to all taxon names of the homotypical group the typified
2551
     * 									taxon name belongs to
2552
     * @return							true if the operation was successful
2553
     *
2554
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2555
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2556
     *
2557
     */
2558
    @Override
2559
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2560
        //currently typeDesignations are not persisted with the homotypical group
2561
        //so explicit adding to the homotypical group is not necessary.
2562
        if (typeDesignation != null){
2563
            checkHomotypicalGroup(typeDesignation);
2564
            this.typeDesignations.add(typeDesignation);
2565
            typeDesignation.addTypifiedName(this);
2566

    
2567
            if (addToAllNames){
2568
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2569
                    if (taxonName != this){
2570
                        taxonName.addTypeDesignation(typeDesignation, false);
2571
                    }
2572
                }
2573
            }
2574
        }
2575
        return true;
2576
    }
2577

    
2578
    /**
2579
     * Throws an Exception this type designation already has typified names from another homotypical group.
2580
     * @param typeDesignation
2581
     */
2582
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2583
        if(typeDesignation.getTypifiedNames().size() > 0){
2584
            Set<HomotypicalGroup> groups = new HashSet<>();
2585
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2586
            for (TaxonName taxonName: names){
2587
                groups.add(taxonName.getHomotypicalGroup());
2588
            }
2589
            if (groups.size() > 1){
2590
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2591
            }
2592
        }
2593
    }
2594

    
2595

    
2596

    
2597
//*********************** HOMOTYPICAL GROUP *********************************************//
2598

    
2599

    
2600
    /**
2601
     * Returns the {@link HomotypicalGroup homotypical group} to which
2602
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2603
     * that share the same types.
2604
     *
2605
     * @see 	HomotypicalGroup
2606
     */
2607

    
2608
    @Override
2609
    public HomotypicalGroup getHomotypicalGroup() {
2610
        if (homotypicalGroup == null){
2611
            homotypicalGroup = new HomotypicalGroup();
2612
            homotypicalGroup.typifiedNames.add(this);
2613
        }
2614
    	return homotypicalGroup;
2615
    }
2616

    
2617
    /**
2618
     * @see #getHomotypicalGroup()
2619
     */
2620
    @Override
2621
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2622
        if (homotypicalGroup == null){
2623
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2624
        }
2625
        /*if (this.homotypicalGroup != null){
2626
        	this.homotypicalGroup.removeTypifiedName(this, false);
2627
        }*/
2628
        this.homotypicalGroup = homotypicalGroup;
2629
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2630
        	 this.homotypicalGroup.addTypifiedName(this);
2631
        }
2632
    }
2633

    
2634

    
2635

    
2636
// *************************************************************************//
2637

    
2638
    /**
2639
     * Returns the complete string containing the
2640
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2641
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2642
     *
2643
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2644
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2645
     * @see		#getNomenclaturalReference()
2646
     * @see		#getNomenclaturalMicroReference()
2647
     */
2648
    @Override
2649
    @Transient
2650
    public String getCitationString(){
2651
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2652
    }
2653

    
2654
    /**
2655
     * Returns the parsing problems
2656
     * @return
2657
     */
2658
    @Override
2659
    public List<ParserProblem> getParsingProblems(){
2660
        return ParserProblem.warningList(this.parsingProblem);
2661
    }
2662

    
2663
    /**
2664
     * Returns the string containing the publication date (generally only year)
2665
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2666
     * no nomenclatural reference.
2667
     *
2668
     * @return  the string containing the publication date of <i>this</i> taxon name
2669
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2670
     */
2671
    @Override
2672
    @Transient
2673
    @ValidTaxonomicYear(groups=Level3.class)
2674
    public String getReferenceYear(){
2675
        if (this.getNomenclaturalReference() != null ){
2676
            return this.getNomenclaturalReference().getYear();
2677
        }else{
2678
            return null;
2679
        }
2680
    }
2681

    
2682
    /**
2683
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2684
     * In this context a taxon base means the use of a taxon name by a reference
2685
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2686
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2687
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2688
     * within a taxonomic treatment (identified by one reference).
2689
     *
2690
     * @see	#getTaxa()
2691
     * @see	#getSynonyms()
2692
     */
2693
    @Override
2694
    public Set<TaxonBase> getTaxonBases() {
2695
        if(taxonBases == null) {
2696
            this.taxonBases = new HashSet<TaxonBase>();
2697
        }
2698
        return this.taxonBases;
2699
    }
2700

    
2701
    /**
2702
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2703
     * to the set of taxon bases using <i>this</i> taxon name.
2704
     *
2705
     * @param  taxonBase  the taxon base to be added
2706
     * @see 			  #getTaxonBases()
2707
     * @see 			  #removeTaxonBase(TaxonBase)
2708
     */
2709
    //TODO protected
2710
    @Override
2711
    public void addTaxonBase(TaxonBase taxonBase){
2712
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2713
        ReflectionUtils.makeAccessible(method);
2714
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2715
        taxonBases.add(taxonBase);
2716
    }
2717
    /**
2718
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2719
     *
2720
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2721
     * @see    				#getTaxonBases()
2722
     * @see    				#addTaxonBase(TaxonBase)
2723
     */
2724
    @Override
2725
    public void removeTaxonBase(TaxonBase taxonBase){
2726
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2727
        ReflectionUtils.makeAccessible(method);
2728
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2729

    
2730

    
2731
    }
2732

    
2733
    /**
2734
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2735
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2736
     * the set returned by getTaxonBases().
2737
     *
2738
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2739
     * @see	#getTaxonBases()
2740
     * @see	#getSynonyms()
2741
     */
2742
    @Override
2743
    @Transient
2744
    public Set<Taxon> getTaxa(){
2745
        Set<Taxon> result = new HashSet<>();
2746
        for (TaxonBase taxonBase : this.taxonBases){
2747
            if (taxonBase instanceof Taxon){
2748
                result.add((Taxon)taxonBase);
2749
            }
2750
        }
2751
        return result;
2752
    }
2753

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

    
2775
    //******* REGISTRATION *****************/
2776

    
2777
    @Override
2778
    public Set<Registration> getRegistrations() {
2779
        return this.registrations;
2780
    }
2781

    
2782

    
2783
// ************* RELATIONSHIPS *****************************/
2784

    
2785

    
2786
    /**
2787
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2788
     * by title cache of the related names.
2789
     * @see #getHybridParentRelations()
2790
     */
2791
    @Override
2792
    @Transient
2793
    public List<HybridRelationship> getOrderedChildRelationships(){
2794
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2795
        result.addAll(this.hybridChildRelations);
2796
        Collections.sort(result);
2797
        Collections.reverse(result);
2798
        return result;
2799

    
2800
    }
2801

    
2802

    
2803
    /**
2804
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2805
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2806
     * "is first/second parent" or "is male/female parent". By invoking this
2807
     * method <i>this</i> botanical name becomes a hybrid child of the parent
2808
     * botanical name.
2809
     *
2810
     * @param parentName      the botanical name of the parent for this new hybrid name relationship
2811
     * @param type            the type of this new name relationship
2812
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2813
     * @return
2814
     * @see                   #addHybridChild(BotanicalName, HybridRelationshipType,String )
2815
     * @see                   #getRelationsToThisName()
2816
     * @see                   #getNameRelations()
2817
     * @see                   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
2818
     * @see                   #addNameRelationship(NameRelationship)
2819
     */
2820
    @Override
2821
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2822
        return new HybridRelationship(this, parentName, type, ruleConsidered);
2823
    }
2824

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

    
2847
    @Override
2848
    public void removeHybridChild(INonViralName child) {
2849
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2850
        hybridRelationships.addAll(this.getHybridChildRelations());
2851
        hybridRelationships.addAll(this.getHybridParentRelations());
2852
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2853
            // remove name relationship from this side
2854
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2855
                this.removeHybridRelationship(hybridRelationship);
2856
            }
2857
        }
2858
    }
2859

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

    
2873

    
2874

    
2875
// *********** DESCRIPTIONS *************************************
2876

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

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

    
2927
// *********** HOMOTYPIC GROUP METHODS **************************************************
2928

    
2929
    @Override
2930
    @Transient
2931
    public void mergeHomotypicGroups(TaxonName name){
2932
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2933
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2934
        name.setHomotypicalGroup(this.homotypicalGroup);
2935
    }
2936

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

    
2963

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

    
2980
        // Check whether there are any other names in the group
2981
        if (typifiedNames.size() == 1) {
2982
                return false;
2983
        }
2984

    
2985
        for (TaxonName taxonName : typifiedNames) {
2986
                if (!taxonName.equals(this)) {
2987
                        if (! isBasionymFor(taxonName)) {
2988
                                return false;
2989
                        }
2990
                }
2991
        }
2992
        return true;
2993
    }
2994

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

    
3015
    /**
3016
     * Creates a basionym relationship to all other names in this names homotypical
3017
     * group.
3018
     *
3019
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3020
     */
3021
    @Override
3022
    @Transient
3023
    public void makeGroupsBasionym() {
3024
        this.homotypicalGroup.setGroupBasionym(this);
3025
    }
3026

    
3027

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

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

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

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

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

    
3168

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

    
3191

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

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

    
3218
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3219
        if (homotypicalGroup == null) {
3220
            return;
3221
        }
3222

    
3223
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3224
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3225

    
3226
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3227

    
3228
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3229

    
3230
            for(NameRelationship nameRelation : nameRelations){
3231
                relations.add(nameRelation);
3232
            }
3233
        }
3234

    
3235
        for (NameRelationship relation : relations) {
3236

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

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

    
3252
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3253
            if (!name.equals(this)) {
3254

    
3255
                // First check whether the relationship already exists
3256
                if (!this.isBasionymFor(name)) {
3257

    
3258
                    // Then create it
3259
                    name.addRelationshipFromName(this,
3260
                            NameRelationshipType.BASIONYM(), null);
3261
                }
3262
            }
3263
        }
3264
    }
3265

    
3266
    /**
3267
     * Removes 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 removeAsGroupsBasionym() {
3275

    
3276
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3277

    
3278
        if (homotypicalGroup == null) {
3279
            return;
3280
        }
3281

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

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

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

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

    
3294
        for (NameRelationship relation : relations) {
3295

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

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

    
3313

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

    
3337
    /**
3338
     * {@inheritDoc}
3339
     */
3340
    @Override
3341
    public boolean isHybridName() {
3342
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3343
    }
3344

    
3345
    /**
3346
     * {@inheritDoc}
3347
     */
3348
    @Override
3349
    public boolean isHybrid() {
3350
        return this.isHybridName() || this.isHybridFormula();
3351
    }
3352

    
3353
// ***************** COMPARE ********************************/
3354

    
3355
    @Override
3356
    public int compareToName(TaxonName otherName){
3357

    
3358
        int result = 0;
3359

    
3360
        if (otherName == null) {
3361
            throw new NullPointerException("Cannot compare to null.");
3362
        }
3363

    
3364
        //other
3365
        otherName = deproxy(otherName);
3366
        String otherNameCache = otherName.getNameCache();
3367
        String otherTitleCache = otherName.getTitleCache();
3368
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3369
        if (otherName.isAutonym()){
3370
            boolean isProtected = otherName.isProtectedNameCache();
3371
            String oldNameCache = otherName.getNameCache();
3372
            otherName.setProtectedNameCache(false);
3373
            otherName.setNameCache(null, false);
3374
            otherNameCache = otherName.getNameCache();
3375
            otherName.setNameCache(oldNameCache, isProtected);
3376
        }
3377

    
3378
        //this
3379
        String thisNameCache = this.getNameCache();
3380
        String thisTitleCache = this.getTitleCache();
3381

    
3382
        if (this.isAutonym()){
3383
            boolean isProtected = this.isProtectedNameCache();
3384
            String oldNameCache = this.getNameCache();
3385
            this.setProtectedNameCache(false);
3386
            this.setNameCache(null, false);
3387
            thisNameCache = this.getNameCache();
3388
            this.setNameCache(oldNameCache, isProtected);
3389
        }
3390

    
3391

    
3392
        // Compare name cache of taxon names
3393
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3394
            thisNameCache = normalizeName(thisNameCache);
3395
            otherNameCache = normalizeName(otherNameCache);
3396
            result = thisNameCache.compareTo(otherNameCache);
3397
        }
3398

    
3399
        // Compare title cache of taxon names
3400
        if (result == 0){
3401
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3402
                thisTitleCache = normalizeName(thisTitleCache);
3403
                otherTitleCache = normalizeName(otherTitleCache);
3404
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3405
            }
3406
        }
3407

    
3408
        return result;
3409
    }
3410

    
3411
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3412
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3413

    
3414
    /**
3415
     * @param thisNameCache
3416
     * @param HYBRID_SIGN
3417
     * @param QUOT_SIGN
3418
     * @return
3419
     */
3420
    private String normalizeName(String thisNameCache) {
3421
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3422
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3423
        return thisNameCache;
3424
    }
3425

    
3426
// ********************** INTERFACES ********************************************/
3427

    
3428
    /**
3429
     * Method to cast a interfaced name to a concrete name.
3430
     * The method includes a deproxy to guarantee that no
3431
     * class cast exception is thrown.
3432
     *
3433
     * @see #castAndDeproxy(Set)
3434
     * @param interfacedName
3435
     * @return
3436
     */
3437
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3438
        return deproxy(interfacedName, TaxonName.class);
3439
    }
3440

    
3441
    /**
3442
     * Method to cast a set of interfaced names to concrete namex.
3443
     * The method includes a deproxy to guarantee that no
3444
     * class cast exception is thrown.
3445
     *
3446
     * @see #castAndDeproxy(ITaxonNameBase)
3447
     * @param naminterfacedNames
3448
     * @return
3449
     */
3450
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3451
        Set<TaxonName> result = new HashSet<>();
3452
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3453
            result.add(castAndDeproxy(naminterfacedName));
3454
        }
3455
        return result;
3456
    }
3457

    
3458
//************************ isType ***********************************************/
3459

    
3460
    /**
3461
     * @return
3462
     */
3463
    @Override
3464
    public boolean isNonViral() {
3465
        return nameType.isNonViral();
3466
    }
3467

    
3468
    @Override
3469
    public boolean isZoological(){
3470
        return nameType.isZoological();
3471
    }
3472
    @Override
3473
    public boolean isBotanical() {
3474
        return nameType.isBotanical();
3475
    }
3476
    @Override
3477
    public boolean isCultivar() {
3478
        return nameType.isCultivar();
3479
    }
3480
    @Override
3481
    public boolean isBacterial() {
3482
        return nameType.isBacterial();
3483
    }
3484
    @Override
3485
    public boolean isViral() {
3486
        return nameType.isViral();
3487
    }
3488

    
3489

    
3490
//*********************** CLONE ********************************************************/
3491

    
3492
    /**
3493
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3494
     * a new instance that differs only slightly from <i>this</i> taxon name by
3495
     * modifying only some of the attributes.<BR><BR>
3496
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3497
     * <b>The name gets a newly created homotypical group</b><BR>
3498
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3499
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3500
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3501
     *
3502
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3503
     * @see java.lang.Object#clone()
3504
     */
3505
    @Override
3506
    public Object clone() {
3507
        TaxonName result;
3508
        try {
3509
            result = (TaxonName)super.clone();
3510

    
3511
            //taxonBases -> empty
3512
            result.taxonBases = new HashSet<>();
3513

    
3514
            //empty caches
3515
            if (! protectedFullTitleCache){
3516
                result.fullTitleCache = null;
3517
            }
3518

    
3519
            //descriptions
3520
            result.descriptions = new HashSet<>();
3521
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3522
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3523
                result.descriptions.add(newDescription);
3524
            }
3525

    
3526
            //status
3527
            result.status = new HashSet<>();
3528
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3529
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3530
                result.status.add(newStatus);
3531
            }
3532

    
3533

    
3534
            //to relations
3535
            result.relationsToThisName = new HashSet<>();
3536
            for (NameRelationship toRelationship : getRelationsToThisName()){
3537
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3538
                newRelationship.setRelatedTo(result);
3539
                result.relationsToThisName.add(newRelationship);
3540
            }
3541

    
3542
            //from relations
3543
            result.relationsFromThisName = new HashSet<>();
3544
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3545
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3546
                newRelationship.setRelatedFrom(result);
3547
                result.relationsFromThisName.add(newRelationship);
3548
            }
3549

    
3550
            //type designations
3551
            result.typeDesignations = new HashSet<>();
3552
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3553
                TypeDesignationBase<?> newDesignation = (TypeDesignationBase<?>)typeDesignation.clone();
3554
                this.removeTypeDesignation(newDesignation);
3555
                result.addTypeDesignation(newDesignation, false);
3556
            }
3557

    
3558
            //homotypicalGroup
3559
            //TODO still needs to be discussed
3560
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3561
            result.homotypicalGroup.addTypifiedName(this);
3562

    
3563

    
3564
            //HybridChildRelations
3565
            result.hybridChildRelations = new HashSet<>();
3566
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3567
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3568
                newChildRelationship.setRelatedTo(result);
3569
                result.hybridChildRelations.add(newChildRelationship);
3570
            }
3571

    
3572
            //HybridParentRelations
3573
            result.hybridParentRelations = new HashSet<>();
3574
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3575
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3576
                newParentRelationship.setRelatedFrom(result);
3577
                result.hybridParentRelations.add(newParentRelationship);
3578
            }
3579

    
3580
            //empty caches
3581
            if (! protectedNameCache){
3582
                result.nameCache = null;
3583
            }
3584

    
3585
            //empty caches
3586
            if (! protectedAuthorshipCache){
3587
                result.authorshipCache = null;
3588
            }
3589

    
3590
            //no changes to: appendedPharse, nomenclaturalReference,
3591
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3592
            //protectedFullTitleCache, rank
3593
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3594
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3595
            //protectedAuthorshipCache, protectedNameCache,
3596
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3597
            //acronym
3598
            //subGenusAuthorship, nameApprobation
3599
            //anamorphic
3600
            //cultivarName
3601
            return result;
3602
        } catch (CloneNotSupportedException e) {
3603
            logger.warn("Object does not implement cloneable");
3604
            e.printStackTrace();
3605
            return null;
3606
        }
3607

    
3608
    }
3609

    
3610

    
3611
}
3612

    
(29-29/36)