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
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1788
        for(NameRelationship nameRelationship : tmpRels) {
1789
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1790
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1791
                if (type == null || type.equals(nameRelationship.getType())){
1792
                    this.removeNameRelationship(nameRelationship);
1793
                }
1794
            }
1795
        }
1796
    }
1797

    
1798

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

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

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

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

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

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

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

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

    
1909

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

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

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

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

    
1979

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

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

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

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

    
2053
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
2054
    }
2055

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

    
2064
        return getRelatedNames(relationsWithThisName(direction), type);
2065
    }
2066

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

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

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

    
2129
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2130
    }
2131

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

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

    
2166

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

    
2192

    
2193
    /**
2194
     * @param direction
2195
     * @return
2196
     */
2197
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2198

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2356
//*********************** TYPE DESIGNATION *********************************************//
2357

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

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

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

    
2404
//*********************** NAME TYPE DESIGNATION *********************************************//
2405

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

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

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

    
2492
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2493

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

    
2510

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

    
2543
    //used by merge strategy
2544
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2545
        return addTypeDesignation(typeDesignation, true);
2546
    }
2547

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

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

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

    
2598

    
2599

    
2600
//*********************** HOMOTYPICAL GROUP *********************************************//
2601

    
2602

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

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

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

    
2637

    
2638

    
2639
// *************************************************************************//
2640

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

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

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

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

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

    
2733

    
2734
    }
2735

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

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

    
2778
    //******* REGISTRATION *****************/
2779

    
2780
    @Override
2781
    public Set<Registration> getRegistrations() {
2782
        return this.registrations;
2783
    }
2784

    
2785

    
2786
// ************* RELATIONSHIPS *****************************/
2787

    
2788

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

    
2803
    }
2804

    
2805

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

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

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

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

    
2876

    
2877

    
2878
// *********** DESCRIPTIONS *************************************
2879

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

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

    
2930
// *********** HOMOTYPIC GROUP METHODS **************************************************
2931

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

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

    
2966

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

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

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

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

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

    
3030

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

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

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

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

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

    
3171

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

    
3194

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

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

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

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

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

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

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

    
3238
        for (NameRelationship relation : relations) {
3239

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

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

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

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

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

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

    
3279
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3280

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

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

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

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

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

    
3297
        for (NameRelationship relation : relations) {
3298

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

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

    
3316

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

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

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

    
3356
// ***************** COMPARE ********************************/
3357

    
3358
    @Override
3359
    public int compareToName(TaxonName otherName){
3360

    
3361
        int result = 0;
3362

    
3363
        if (otherName == null) {
3364
            throw new NullPointerException("Cannot compare to null.");
3365
        }
3366

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

    
3381
        //this
3382
        String thisNameCache = this.getNameCache();
3383
        String thisTitleCache = this.getTitleCache();
3384

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

    
3394

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

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

    
3411
        return result;
3412
    }
3413

    
3414
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3415
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3416

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

    
3429
// ********************** INTERFACES ********************************************/
3430

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

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

    
3461
//************************ isType ***********************************************/
3462

    
3463
    /**
3464
     * @return
3465
     */
3466
    @Override
3467
    public boolean isNonViral() {
3468
        return nameType.isNonViral();
3469
    }
3470

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

    
3492

    
3493
//*********************** CLONE ********************************************************/
3494

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

    
3514
            //taxonBases -> empty
3515
            result.taxonBases = new HashSet<>();
3516

    
3517
            //empty caches
3518
            if (! protectedFullTitleCache){
3519
                result.fullTitleCache = null;
3520
            }
3521

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

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

    
3536

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

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

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

    
3561
            //homotypicalGroup
3562
            //TODO still needs to be discussed
3563
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3564
            result.homotypicalGroup.addTypifiedName(this);
3565

    
3566

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

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

    
3583
            //empty caches
3584
            if (! protectedNameCache){
3585
                result.nameCache = null;
3586
            }
3587

    
3588
            //empty caches
3589
            if (! protectedAuthorshipCache){
3590
                result.authorshipCache = null;
3591
            }
3592

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

    
3611
    }
3612

    
3613

    
3614
}
3615

    
(29-29/36)