Project

General

Profile

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

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

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

    
163
    "acronym",
164

    
165
    "subGenusAuthorship",
166
    "nameApprobation",
167

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

    
172
    "anamorphic",
173

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

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

    
197

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
339
    @XmlElementWrapper(name = "Registrations")
340
    @XmlElement(name = "Registration")
341
    @XmlIDREF
342
    @XmlSchemaType(name = "IDREF")
343
    @OneToMany(mappedBy="name", fetch= FetchType.LAZY)
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 TaxonNameBase NewInstance(NomenclaturalCode code, Rank rank,
566
            HomotypicalGroup homotypicalGroup) {
567
        TaxonNameBase<?,?> result = new TaxonNameBase<>(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 TaxonNameBase 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
        TaxonNameBase result = new TaxonNameBase<>(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 #TaxonNameBase(Rank)
600
     * @see #TaxonNameBase(HomotypicalGroup)
601
     * @see #TaxonNameBase(Rank, HomotypicalGroup)
602
     */
603
    protected TaxonNameBase() {
604
        super();
605
        setNameCacheStrategy();
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.NonViralNameDefaultCacheStrategy 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    					 #TaxonNameBase()
620
     * @see    					 #TaxonNameBase(Rank)
621
     * @see    					 #TaxonNameBase(HomotypicalGroup)
622
     */
623
    protected TaxonNameBase(NomenclaturalCode type, Rank rank, HomotypicalGroup homotypicalGroup) {
624
        super();
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
        setNameCacheStrategy();
633
    }
634

    
635

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

    
679

    
680
    private void setNameCacheStrategy(){
681
        if (getNameType() == null){
682
            this.cacheStrategy = null;
683
        }else if (this.cacheStrategy != null){
684
           //
685
        }else if (getNameType() == NomenclaturalCode.NonViral){
686
            this.cacheStrategy = (S)NonViralNameDefaultCacheStrategy.NewInstance();
687
        }else if (getNameType().isBotanical()){
688
            this.cacheStrategy = (S)BotanicNameDefaultCacheStrategy.NewInstance();
689
        }else if (getNameType() == NomenclaturalCode.ICZN){
690
            this.cacheStrategy = (S)ZooNameDefaultCacheStrategy.NewInstance();
691
        }else if (getNameType() == NomenclaturalCode.ICNB){
692
            this.cacheStrategy = (S)BacterialNameDefaultCacheStrategy.NewInstance();;
693
        }else if (getNameType() == NomenclaturalCode.ICVCN){
694
            this.cacheStrategy = super.getCacheStrategy();
695
        }
696
    }
697

    
698

    
699
    @Override
700
    public void initListener(){
701
        PropertyChangeListener listener = new PropertyChangeListener() {
702
            @Override
703
            public void propertyChange(PropertyChangeEvent e) {
704
                boolean protectedByLowerCache = false;
705
                //authorship cache
706
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
707
                    if (protectedAuthorshipCache){
708
                        protectedByLowerCache = true;
709
                    }else{
710
                        authorshipCache = null;
711
                    }
712
                }
713

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

    
743
    private static Map<String, java.lang.reflect.Field> allFields = null;
744
    protected Map<String, java.lang.reflect.Field> getAllFields(){
745
        if (allFields == null){
746
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
747
        }
748
        return allFields;
749
    }
750

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

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

    
801
// ****************** GETTER / SETTER ****************************/
802

    
803
    @Override
804
    public NomenclaturalCode getNameType() {
805
        return nameType;
806
    }
807

    
808
    @Override
809
    public void setNameType(NomenclaturalCode nameType) {
810
        this.nameType = nameType;
811
        setNameCacheStrategy();
812
    }
813

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

    
827
    /**
828
     * @see     #isProtectedNameCache()
829
     */
830
    @Override
831
    public void setProtectedNameCache(boolean protectedNameCache) {
832
        this.protectedNameCache = protectedNameCache;
833
    }
834

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

    
849
    /**
850
     * @see  #getGenusOrUninomial()
851
     */
852
    @Override
853
    public void setGenusOrUninomial(String genusOrUninomial) {
854
        this.genusOrUninomial = StringUtils.isBlank(genusOrUninomial) ? null : genusOrUninomial;
855
    }
856

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

    
871
    /**
872
     * @see  #getInfraGenericEpithet()
873
     */
874
    @Override
875
    public void setInfraGenericEpithet(String infraGenericEpithet){
876
        this.infraGenericEpithet = StringUtils.isBlank(infraGenericEpithet)? null : infraGenericEpithet;
877
    }
878

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

    
892
    /**
893
     * @see  #getSpecificEpithet()
894
     */
895
    @Override
896
    public void setSpecificEpithet(String specificEpithet){
897
        this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
898
    }
899

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

    
914
    /**
915
     * @see  #getInfraSpecificEpithet()
916
     */
917
    @Override
918
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
919
        this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
920
    }
921

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

    
935
    /**
936
     * @see  #getCombinationAuthorship()
937
     */
938
    @Override
939
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
940
        this.combinationAuthorship = combinationAuthorship;
941
    }
942

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

    
971
    /**
972
     * @see  #getExCombinationAuthorship()
973
     */
974
    @Override
975
    public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
976
        this.exCombinationAuthorship = exCombinationAuthorship;
977
    }
978

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

    
995
    /**
996
     * @see  #getBasionymAuthorship()
997
     */
998
    @Override
999
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
1000
        this.basionymAuthorship = basionymAuthorship;
1001
    }
1002

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

    
1024
    /**
1025
     * @see  #getExBasionymAuthorship()
1026
     */
1027
    @Override
1028
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
1029
        this.exBasionymAuthorship = exBasionymAuthorship;
1030
    }
1031

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

    
1045
    /**
1046
     * @see     #isProtectedAuthorshipCache()
1047
     * @see     #getAuthorshipCache()
1048
     */
1049
    @Override
1050
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
1051
        this.protectedAuthorshipCache = protectedAuthorshipCache;
1052
    }
1053

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

    
1070
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1071
        this.hybridParentRelations = hybridParentRelations;
1072
    }
1073

    
1074

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

    
1091
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1092
        this.hybridChildRelations = hybridChildRelations;
1093
    }
1094

    
1095
    @Override
1096
    public boolean isProtectedFullTitleCache() {
1097
        return protectedFullTitleCache;
1098
    }
1099

    
1100
    @Override
1101
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1102
        this.protectedFullTitleCache = protectedFullTitleCache;
1103
    }
1104

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

    
1126
    /**
1127
     * @see  #isHybridFormula()
1128
     */
1129
    @Override
1130
    public void setHybridFormula(boolean hybridFormula){
1131
        this.hybridFormula = hybridFormula;
1132
    }
1133

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

    
1150
    /**
1151
     * @see  #isMonomHybrid()
1152
     * @see  #isBinomHybrid()
1153
     * @see  #isTrinomHybrid()
1154
     */
1155
    @Override
1156
    public void setMonomHybrid(boolean monomHybrid){
1157
        this.monomHybrid = monomHybrid;
1158
    }
1159

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

    
1176
    /**
1177
     * @see  #isBinomHybrid()
1178
     * @see  #isMonomHybrid()
1179
     * @see  #isTrinomHybrid()
1180
     */
1181
    @Override
1182
    public void setBinomHybrid(boolean binomHybrid){
1183
        this.binomHybrid = binomHybrid;
1184
    }
1185

    
1186
    @Override
1187
    public boolean isTrinomHybrid(){
1188
        return this.trinomHybrid;
1189
    }
1190

    
1191
    /**
1192
     * @see  #isTrinomHybrid()
1193
     * @see  #isBinomHybrid()
1194
     * @see  #isMonomHybrid()
1195
     */
1196
    @Override
1197
    public void setTrinomHybrid(boolean trinomHybrid){
1198
        this.trinomHybrid = trinomHybrid;
1199
    }
1200

    
1201
    // ****************** VIRAL NAME ******************/
1202

    
1203
    @Override
1204
    public String getAcronym(){
1205
        return this.acronym;
1206
    }
1207

    
1208
    /**
1209
     * @see  #getAcronym()
1210
     */
1211
    @Override
1212
    public void setAcronym(String acronym){
1213
        this.acronym = StringUtils.isBlank(acronym)? null : acronym;
1214
    }
1215

    
1216
    // ****************** BACTERIAL NAME ******************/
1217

    
1218
    @Override
1219
    public String getSubGenusAuthorship(){
1220
        return this.subGenusAuthorship;
1221
    }
1222

    
1223
    @Override
1224
    public void setSubGenusAuthorship(String subGenusAuthorship){
1225
        this.subGenusAuthorship = subGenusAuthorship;
1226
    }
1227

    
1228

    
1229
    @Override
1230
    public String getNameApprobation(){
1231
        return this.nameApprobation;
1232
    }
1233

    
1234
    /**
1235
     * @see  #getNameApprobation()
1236
     */
1237
    @Override
1238
    public void setNameApprobation(String nameApprobation){
1239
        this.nameApprobation = nameApprobation;
1240
    }
1241

    
1242
    //************ Zoological Name
1243

    
1244

    
1245
    @Override
1246
    public String getBreed(){
1247
        return this.breed;
1248
    }
1249
    /**
1250
     * @see  #getBreed()
1251
     */
1252
    @Override
1253
    public void setBreed(String breed){
1254
        this.breed = StringUtils.isBlank(breed) ? null : breed;
1255
    }
1256

    
1257

    
1258
    @Override
1259
    public Integer getPublicationYear() {
1260
        return publicationYear;
1261
    }
1262
    /**
1263
     * @see  #getPublicationYear()
1264
     */
1265
    @Override
1266
    public void setPublicationYear(Integer publicationYear) {
1267
        this.publicationYear = publicationYear;
1268
    }
1269

    
1270

    
1271
    @Override
1272
    public Integer getOriginalPublicationYear() {
1273
        return originalPublicationYear;
1274
    }
1275
    /**
1276
     * @see  #getOriginalPublicationYear()
1277
     */
1278
    @Override
1279
    public void setOriginalPublicationYear(Integer originalPublicationYear) {
1280
        this.originalPublicationYear = originalPublicationYear;
1281
    }
1282

    
1283
    // **** Cultivar Name ************
1284

    
1285

    
1286
    @Override
1287
    public String getCultivarName(){
1288
        return this.cultivarName;
1289
    }
1290

    
1291
    /**
1292
     * @see  #getCultivarName()
1293
     */
1294
    @Override
1295
    public void setCultivarName(String cultivarName){
1296
        this.cultivarName = StringUtils.isBlank(cultivarName) ? null : cultivarName;
1297
    }
1298

    
1299
    // **************** Fungus Name
1300
    @Override
1301
    public boolean isAnamorphic(){
1302
        return this.anamorphic;
1303
    }
1304

    
1305
    /**
1306
     * @see  #isAnamorphic()
1307
     */
1308
    @Override
1309
    public void setAnamorphic(boolean anamorphic){
1310
        this.anamorphic = anamorphic;
1311
    }
1312

    
1313

    
1314
// **************** ADDER / REMOVE *************************/
1315

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

    
1345

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

    
1361
        TaxonNameBase<?,?> parent = hybridRelation.getParentName();
1362
        TaxonNameBase<?,?> child = hybridRelation.getHybridName();
1363
        if (this.equals(parent)){
1364
            this.hybridParentRelations.remove(hybridRelation);
1365
            child.hybridChildRelations.remove(hybridRelation);
1366
            hybridRelation.setHybridName(null);
1367
            hybridRelation.setParentName(null);
1368
        }
1369
        if (this.equals(child)){
1370
            parent.hybridParentRelations.remove(hybridRelation);
1371
            this.hybridChildRelations.remove(hybridRelation);
1372
            hybridRelation.setHybridName(null);
1373
            hybridRelation.setParentName(null);
1374
        }
1375
    }
1376

    
1377
//********* METHODS **************************************/
1378

    
1379
    @Override
1380
    public S getCacheStrategy() {
1381
        setNameCacheStrategy();
1382
        return this.cacheStrategy;
1383
    }
1384

    
1385
    @Override
1386
    public String generateFullTitle(){
1387
        if (getCacheStrategy() == null){
1388
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1389
            return null;
1390
        }else{
1391
            return cacheStrategy.getFullTitleCache(this);
1392
        }
1393
    }
1394

    
1395

    
1396
    @Override
1397
    public void setFullTitleCache(String fullTitleCache){
1398
        setFullTitleCache(fullTitleCache, PROTECTED);
1399
    }
1400

    
1401
    @Override
1402
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1403
        fullTitleCache = getTruncatedCache(fullTitleCache);
1404
        this.fullTitleCache = fullTitleCache;
1405
        this.setProtectedFullTitleCache(protectCache);
1406
    }
1407

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

    
1433

    
1434
    @Override
1435
    @Transient
1436
    public List<TaggedText> getTaggedName(){
1437
        return getCacheStrategy().getTaggedTitle(this);
1438
    }
1439

    
1440
    @Override
1441
    @Transient
1442
    public String getFullTitleCache(){
1443
        if (protectedFullTitleCache){
1444
            return this.fullTitleCache;
1445
        }
1446
        updateAuthorshipCache();
1447
        if (fullTitleCache == null ){
1448
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1449
        }
1450
        return fullTitleCache;
1451
    }
1452

    
1453

    
1454
    @Override
1455
    public String getTitleCache(){
1456
        if(!protectedTitleCache) {
1457
            updateAuthorshipCache();
1458
        }
1459
        return super.getTitleCache();
1460
    }
1461

    
1462
    @Override
1463
    public void setTitleCache(String titleCache, boolean protectCache){
1464
        super.setTitleCache(titleCache, protectCache);
1465
    }
1466

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

    
1490
        }
1491
        return authorshipCache;
1492
    }
1493

    
1494

    
1495

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

    
1512

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

    
1525

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

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

    
1559

    
1560

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

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

    
1582
    /**
1583
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1584
     * @return
1585
     */
1586
    @Override
1587
    public String computeBasionymAuthorNomenclaturalTitle() {
1588
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1589
    }
1590

    
1591

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

    
1601
    /**
1602
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1603
     * @return
1604
     */
1605
    @Override
1606
    public String computeExBasionymAuthorNomenclaturalTitle() {
1607
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1608
    }
1609

    
1610
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1611
        if (author == null){
1612
            return null;
1613
        }else{
1614
            return author.getNomenclaturalTitle();
1615
        }
1616
    }
1617

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

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

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

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

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

    
1760
        TaxonNameBase fromName = nameRelation.getFromName();
1761
        TaxonNameBase toName = nameRelation.getToName();
1762

    
1763
        if (nameRelation != null) {
1764
            nameRelation.setToName(null);
1765
            nameRelation.setFromName(null);
1766
        }
1767

    
1768
        if (fromName != null) {
1769
            fromName.removeNameRelationship(nameRelation);
1770
        }
1771

    
1772
        if (toName != null) {
1773
            toName.removeNameRelationship(nameRelation);
1774
        }
1775

    
1776
        this.relationsToThisName.remove(nameRelation);
1777
        this.relationsFromThisName.remove(nameRelation);
1778
    }
1779

    
1780
    @Override
1781
    public void removeRelationToTaxonName(TaxonNameBase toTaxonName) {
1782
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1783
//		nameRelationships.addAll(this.getNameRelations());
1784
        nameRelationships.addAll(this.getRelationsFromThisName());
1785
        nameRelationships.addAll(this.getRelationsToThisName());
1786
        for(NameRelationship nameRelationship : nameRelationships) {
1787
            // remove name relationship from this side
1788
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1789
                this.removeNameRelationship(nameRelationship);
1790
            }
1791
        }
1792
    }
1793

    
1794

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

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

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

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

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

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

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

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

    
1905

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

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

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

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

    
1975

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

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

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

    
2038
    /**
2039
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2040
     * The basionym of a taxon name is its epithet-bringing synonym.
2041
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2042
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2043
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2044
     */
2045
    @Override
2046
    @Transient
2047
    public Set<TaxonNameBase> getBasionyms(){
2048
        Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
2049
        Set<NameRelationship> rels = this.getRelationsToThisName();
2050
        for (NameRelationship rel : rels){
2051
            if (rel.getType()!= null && rel.getType().isBasionymRelation()){
2052
                TaxonNameBase<?,?> basionym = rel.getFromName();
2053
                result.add(basionym);
2054
            }
2055
        }
2056
        return result;
2057
    }
2058

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

    
2097
    /**
2098
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2099
     *
2100
     */
2101
    @Override
2102
    @Transient
2103
    public Set<TaxonNameBase> getReplacedSynonyms(){
2104
        Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
2105
        Set<NameRelationship> rels = this.getRelationsToThisName();
2106
        for (NameRelationship rel : rels){
2107
            if (rel.getType().isReplacedSynonymRelation()){
2108
                TaxonNameBase replacedSynonym = rel.getFromName();
2109
                result.add(replacedSynonym);
2110
            }
2111
        }
2112
        return result;
2113
    }
2114

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

    
2135
    /**
2136
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2137
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2138
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2139
     * previously used as basionym.
2140
     *
2141
     * @see   #getBasionym()
2142
     * @see   #addBasionym(TaxonNameBase)
2143
     */
2144
    @Override
2145
    public void removeBasionyms(){
2146
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
2147
        for (NameRelationship nameRelation : this.getRelationsToThisName()){
2148
            if (nameRelation.getType().isBasionymRelation()){
2149
                removeRelations.add(nameRelation);
2150
            }
2151
        }
2152
        // Removing relations from a set through which we are iterating causes a
2153
        // ConcurrentModificationException. Therefore, we delete the targeted
2154
        // relations in a second step.
2155
        for (NameRelationship relation : removeRelations){
2156
            this.removeNameRelationship(relation);
2157
        }
2158
    }
2159

    
2160
    /**
2161
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2162
     *
2163
     * @see 	Rank
2164
     */
2165
    @Override
2166
    public Rank getRank(){
2167
        return this.rank;
2168
    }
2169

    
2170
    /**
2171
     * @see  #getRank()
2172
     */
2173
    @Override
2174
    public void setRank(Rank rank){
2175
        this.rank = rank;
2176
    }
2177

    
2178
    /**
2179
     * Returns the {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} of <i>this</i> taxon name.
2180
     * The nomenclatural reference is here meant to be the one publication
2181
     * <i>this</i> taxon name was originally published in while fulfilling the formal
2182
     * requirements as specified by the corresponding {@link NomenclaturalCode nomenclatural code}.
2183
     *
2184
     * @see 	eu.etaxonomy.cdm.model.reference.INomenclaturalReference
2185
     * @see 	eu.etaxonomy.cdm.model.reference.Reference
2186
     */
2187
    @Override
2188
    public INomenclaturalReference getNomenclaturalReference(){
2189
        return this.nomenclaturalReference;
2190
    }
2191
    /**
2192
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2193
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2194
     * as it is obviously used for nomenclatural purposes.
2195
     *
2196
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2197
     * @see  #getNomenclaturalReference()
2198
     */
2199
    @Override
2200
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2201
        if(nomenclaturalReference != null){
2202
            if(!INomenclaturalReference.class.isAssignableFrom(nomenclaturalReference.getClass())){
2203
                throw new IllegalArgumentException("Parameter nomenclaturalReference is not assignable from INomenclaturalReference");
2204
            }
2205
            this.nomenclaturalReference = (Reference)nomenclaturalReference;
2206
        } else {
2207
            this.nomenclaturalReference = null;
2208
        }
2209
    }
2210

    
2211
    /**
2212
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2213
     * The appended phrase is a non-atomised addition to a name. It is
2214
     * not ruled by a nomenclatural code.
2215
     */
2216
    @Override
2217
    public String getAppendedPhrase(){
2218
        return this.appendedPhrase;
2219
    }
2220

    
2221
    /**
2222
     * @see  #getAppendedPhrase()
2223
     */
2224
    @Override
2225
    public void setAppendedPhrase(String appendedPhrase){
2226
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2227
    }
2228

    
2229
    /**
2230
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2231
     * to <i>this</i> taxon name. The details describe the exact localisation within
2232
     * the publication used as nomenclature reference. These are mostly
2233
     * (implicitly) pages but can also be figures or tables or any other
2234
     * element of a publication. A nomenclatural micro reference (details)
2235
     * requires the existence of a nomenclatural reference.
2236
     */
2237
    //Details of the nomenclatural reference (protologue).
2238
    @Override
2239
    public String getNomenclaturalMicroReference(){
2240
        return this.nomenclaturalMicroReference;
2241
    }
2242
    /**
2243
     * @see  #getNomenclaturalMicroReference()
2244
     */
2245
    @Override
2246
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2247
        this.nomenclaturalMicroReference = StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
2248
    }
2249

    
2250
    @Override
2251
    public int getParsingProblem(){
2252
        return this.parsingProblem;
2253
    }
2254

    
2255
    @Override
2256
    public void setParsingProblem(int parsingProblem){
2257
        this.parsingProblem = parsingProblem;
2258
    }
2259

    
2260
    @Override
2261
    public void addParsingProblem(ParserProblem problem){
2262
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2263
    }
2264

    
2265
    @Override
2266
    public void removeParsingProblem(ParserProblem problem) {
2267
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2268
    }
2269

    
2270
    /**
2271
     * @param warnings
2272
     */
2273
    @Override
2274
    public void addParsingProblems(int problems){
2275
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2276
    }
2277

    
2278
    @Override
2279
    public boolean hasProblem(){
2280
        return parsingProblem != 0;
2281
    }
2282

    
2283
    @Override
2284
    public boolean hasProblem(ParserProblem problem) {
2285
        return getParsingProblems().contains(problem);
2286
    }
2287

    
2288
    @Override
2289
    public int getProblemStarts(){
2290
        return this.problemStarts;
2291
    }
2292

    
2293
    @Override
2294
    public void setProblemStarts(int start) {
2295
        this.problemStarts = start;
2296
    }
2297

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

    
2303
    @Override
2304
    public void setProblemEnds(int end) {
2305
        this.problemEnds = end;
2306
    }
2307

    
2308
//*********************** TYPE DESIGNATION *********************************************//
2309

    
2310
    /**
2311
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2312
     * to <i>this</i> taxon name.
2313
     * @see     NameTypeDesignation
2314
     * @see     SpecimenTypeDesignation
2315
     */
2316
    @Override
2317
    public Set<TypeDesignationBase> getTypeDesignations() {
2318
        if(typeDesignations == null) {
2319
            this.typeDesignations = new HashSet<TypeDesignationBase>();
2320
        }
2321
        return typeDesignations;
2322
    }
2323

    
2324
    /**
2325
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2326
     * <i>this</i> taxon name. The type designation itself will be nullified.
2327
     *
2328
     * @param  typeDesignation  the type designation which should be deleted
2329
     */
2330
    @Override
2331
    @SuppressWarnings("deprecation")
2332
    public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2333
        this.typeDesignations.remove(typeDesignation);
2334
        typeDesignation.removeTypifiedName(this);
2335
    }
2336

    
2337
    /**
2338
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2339
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2340
     * "species" or below. The specimen type designations include all the
2341
     * specimens on which the typification of this name is based (which are
2342
     * exclusively used to typify taxon names belonging to the same
2343
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2344
     * belongs) and eventually the status of these designations.
2345
     *
2346
     * @see     SpecimenTypeDesignation
2347
     * @see     NameTypeDesignation
2348
     * @see     HomotypicalGroup
2349
     */
2350
    @Override
2351
    @Transient
2352
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2353
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2354
    }
2355

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

    
2358
    /**
2359
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2360
     * to <i>this</i> taxon name the rank of which must be above "species".
2361
     * The name type designations include all the taxon names used to typify
2362
     * <i>this</i> taxon name and eventually the rejected or conserved status
2363
     * of these designations.
2364
     *
2365
     * @see     NameTypeDesignation
2366
     * @see     SpecimenTypeDesignation
2367
     */
2368
    @Override
2369
    @Transient
2370
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2371
        Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2372
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2373
            if (typeDesignation instanceof NameTypeDesignation){
2374
                result.add((NameTypeDesignation)typeDesignation);
2375
            }
2376
        }
2377
        return result;
2378
    }
2379

    
2380
    /**
2381
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2382
     * to <i>this</i> taxon name's set of type designations.
2383
     *
2384
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2385
     * @param  citation					the reference for this new designation
2386
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2387
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2388
     * @param  isRejectedType			the boolean status for a rejected name type designation
2389
     * @param  isConservedType			the boolean status for a conserved name type designation
2390
     * @param  isLectoType				the boolean status for a lectotype name type designation
2391
     * @param  isNotDesignated			the boolean status for a name type designation without name type
2392
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2393
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2394
     * @return
2395
     * @see 			  				#getNameTypeDesignations()
2396
     * @see 			  				NameTypeDesignation
2397
     * @see 			  				TypeDesignationBase#isNotDesignated()
2398
     */
2399
    @Override
2400
    public NameTypeDesignation addNameTypeDesignation(TaxonNameBase typeSpecies,
2401
                Reference citation,
2402
                String citationMicroReference,
2403
                String originalNameString,
2404
                NameTypeDesignationStatus status,
2405
                boolean isRejectedType,
2406
                boolean isConservedType,
2407
                /*boolean isLectoType, */
2408
                boolean isNotDesignated,
2409
                boolean addToAllHomotypicNames) {
2410
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalNameString, status, isRejectedType, isConservedType, isNotDesignated);
2411
        //nameTypeDesignation.setLectoType(isLectoType);
2412
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2413
        return nameTypeDesignation;
2414
    }
2415

    
2416
    /**
2417
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2418
     * to <i>this</i> taxon name's set of type designations.
2419
     *
2420
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2421
     * @param  citation					the reference for this new designation
2422
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2423
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2424
     * @param  status                   the name type designation status
2425
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2426
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2427
     * @return
2428
     * @see 			  				#getNameTypeDesignations()
2429
     * @see 			  				NameTypeDesignation
2430
     * @see 			  				TypeDesignationBase#isNotDesignated()
2431
     */
2432
    @Override
2433
    public NameTypeDesignation addNameTypeDesignation(TaxonNameBase typeSpecies,
2434
                Reference citation,
2435
                String citationMicroReference,
2436
                String originalNameString,
2437
                NameTypeDesignationStatus status,
2438
                boolean addToAllHomotypicNames) {
2439
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2440
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2441
        return nameTypeDesignation;
2442
    }
2443

    
2444
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2445

    
2446
    /**
2447
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2448
     * that typify <i>this</i> taxon name.
2449
     */
2450
    @Override
2451
    @Transient
2452
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2453
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2454
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2455
            if (typeDesignation instanceof SpecimenTypeDesignation){
2456
                result.add((SpecimenTypeDesignation)typeDesignation);
2457
            }
2458
        }
2459
        return result;
2460
    }
2461

    
2462

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

    
2495
    //used by merge strategy
2496
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2497
        return addTypeDesignation(typeDesignation, true);
2498
    }
2499

    
2500
    /**
2501
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2502
     *
2503
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2504
     * @param addToAllNames				the boolean indicating whether the type designation should be
2505
     * 									added to all taxon names of the homotypical group the typified
2506
     * 									taxon name belongs to
2507
     * @return							true if the operation was succesful
2508
     *
2509
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2510
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2511
     *
2512
     */
2513
    @Override
2514
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2515
        //currently typeDesignations are not persisted with the homotypical group
2516
        //so explicit adding to the homotypical group is not necessary.
2517
        if (typeDesignation != null){
2518
            checkHomotypicalGroup(typeDesignation);
2519
            this.typeDesignations.add(typeDesignation);
2520
            typeDesignation.addTypifiedName(this);
2521

    
2522
            if (addToAllNames){
2523
                for (TaxonNameBase taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2524
                    if (taxonName != this){
2525
                        taxonName.addTypeDesignation(typeDesignation, false);
2526
                    }
2527
                }
2528
            }
2529
        }
2530
        return true;
2531
    }
2532

    
2533
    /**
2534
     * Throws an Exception this type designation already has typified names from another homotypical group.
2535
     * @param typeDesignation
2536
     */
2537
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2538
        if(typeDesignation.getTypifiedNames().size() > 0){
2539
            Set<HomotypicalGroup> groups = new HashSet<HomotypicalGroup>();
2540
            Set<TaxonNameBase> names = typeDesignation.getTypifiedNames();
2541
            for (TaxonNameBase taxonName: names){
2542
                groups.add(taxonName.getHomotypicalGroup());
2543
            }
2544
            if (groups.size() > 1){
2545
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2546
            }
2547
        }
2548
    }
2549

    
2550

    
2551

    
2552
//*********************** HOMOTYPICAL GROUP *********************************************//
2553

    
2554

    
2555
    /**
2556
     * Returns the {@link HomotypicalGroup homotypical group} to which
2557
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2558
     * that share the same types.
2559
     *
2560
     * @see 	HomotypicalGroup
2561
     */
2562

    
2563
    @Override
2564
    public HomotypicalGroup getHomotypicalGroup() {
2565
        if (homotypicalGroup == null){
2566
            homotypicalGroup = new HomotypicalGroup();
2567
            homotypicalGroup.typifiedNames.add(this);
2568
        }
2569
    	return homotypicalGroup;
2570
    }
2571

    
2572
    /**
2573
     * @see #getHomotypicalGroup()
2574
     */
2575
    @Override
2576
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2577
        if (homotypicalGroup == null){
2578
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2579
        }
2580
        /*if (this.homotypicalGroup != null){
2581
        	this.homotypicalGroup.removeTypifiedName(this, false);
2582
        }*/
2583
        this.homotypicalGroup = homotypicalGroup;
2584
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2585
        	 this.homotypicalGroup.addTypifiedName(this);
2586
        }
2587
    }
2588

    
2589

    
2590

    
2591
// *************************************************************************//
2592

    
2593
    /**
2594
     * Returns the complete string containing the
2595
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2596
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2597
     *
2598
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2599
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2600
     * @see		#getNomenclaturalReference()
2601
     * @see		#getNomenclaturalMicroReference()
2602
     */
2603
    @Override
2604
    @Transient
2605
    public String getCitationString(){
2606
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2607
    }
2608

    
2609
    /**
2610
     * Returns the parsing problems
2611
     * @return
2612
     */
2613
    @Override
2614
    public List<ParserProblem> getParsingProblems(){
2615
        return ParserProblem.warningList(this.parsingProblem);
2616
    }
2617

    
2618
    /**
2619
     * Returns the string containing the publication date (generally only year)
2620
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2621
     * no nomenclatural reference.
2622
     *
2623
     * @return  the string containing the publication date of <i>this</i> taxon name
2624
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2625
     */
2626
    @Override
2627
    @Transient
2628
    @ValidTaxonomicYear(groups=Level3.class)
2629
    public String getReferenceYear(){
2630
        if (this.getNomenclaturalReference() != null ){
2631
            return this.getNomenclaturalReference().getYear();
2632
        }else{
2633
            return null;
2634
        }
2635
    }
2636

    
2637
    /**
2638
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2639
     * In this context a taxon base means the use of a taxon name by a reference
2640
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2641
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2642
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2643
     * within a taxonomic treatment (identified by one reference).
2644
     *
2645
     * @see	#getTaxa()
2646
     * @see	#getSynonyms()
2647
     */
2648
    @Override
2649
    public Set<TaxonBase> getTaxonBases() {
2650
        if(taxonBases == null) {
2651
            this.taxonBases = new HashSet<TaxonBase>();
2652
        }
2653
        return this.taxonBases;
2654
    }
2655

    
2656
    /**
2657
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2658
     * to the set of taxon bases using <i>this</i> taxon name.
2659
     *
2660
     * @param  taxonBase  the taxon base to be added
2661
     * @see 			  #getTaxonBases()
2662
     * @see 			  #removeTaxonBase(TaxonBase)
2663
     */
2664
    //TODO protected
2665
    @Override
2666
    public void addTaxonBase(TaxonBase taxonBase){
2667
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonNameBase.class});
2668
        ReflectionUtils.makeAccessible(method);
2669
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2670
        taxonBases.add(taxonBase);
2671
    }
2672
    /**
2673
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2674
     *
2675
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2676
     * @see    				#getTaxonBases()
2677
     * @see    				#addTaxonBase(TaxonBase)
2678
     */
2679
    @Override
2680
    public void removeTaxonBase(TaxonBase taxonBase){
2681
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonNameBase.class});
2682
        ReflectionUtils.makeAccessible(method);
2683
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2684

    
2685

    
2686
    }
2687

    
2688
    /**
2689
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2690
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2691
     * the set returned by getTaxonBases().
2692
     *
2693
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2694
     * @see	#getTaxonBases()
2695
     * @see	#getSynonyms()
2696
     */
2697
    @Override
2698
    @Transient
2699
    public Set<Taxon> getTaxa(){
2700
        Set<Taxon> result = new HashSet<>();
2701
        for (TaxonBase taxonBase : this.taxonBases){
2702
            if (taxonBase instanceof Taxon){
2703
                result.add((Taxon)taxonBase);
2704
            }
2705
        }
2706
        return result;
2707
    }
2708

    
2709
    /**
2710
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2711
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2712
     * the set returned by getTaxonBases().
2713
     *
2714
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2715
     * @see	#getTaxonBases()
2716
     * @see	#getTaxa()
2717
     */
2718
    @Override
2719
    @Transient
2720
    public Set<Synonym> getSynonyms() {
2721
        Set<Synonym> result = new HashSet<>();
2722
        for (TaxonBase taxonBase : this.taxonBases){
2723
            if (taxonBase instanceof Synonym){
2724
                result.add((Synonym)taxonBase);
2725
            }
2726
        }
2727
        return result;
2728
    }
2729

    
2730
    //******* REGISTRATION *****************/
2731

    
2732
    @Override
2733
    public Set<Registration> getRegistrations() {
2734
        return this.registrations;
2735
    }
2736

    
2737

    
2738
// ************* RELATIONSHIPS *****************************/
2739

    
2740

    
2741
    /**
2742
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2743
     * by title cache of the related names.
2744
     * @see #getHybridParentRelations()
2745
     */
2746
    @Override
2747
    @Transient
2748
    public List<HybridRelationship> getOrderedChildRelationships(){
2749
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2750
        result.addAll(this.hybridChildRelations);
2751
        Collections.sort(result);
2752
        Collections.reverse(result);
2753
        return result;
2754

    
2755
    }
2756

    
2757

    
2758
    /**
2759
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2760
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2761
     * "is first/second parent" or "is male/female parent". By invoking this
2762
     * method <i>this</i> botanical name becomes a hybrid child of the parent
2763
     * botanical name.
2764
     *
2765
     * @param parentName      the botanical name of the parent for this new hybrid name relationship
2766
     * @param type            the type of this new name relationship
2767
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2768
     * @return
2769
     * @see                   #addHybridChild(BotanicalName, HybridRelationshipType,String )
2770
     * @see                   #getRelationsToThisName()
2771
     * @see                   #getNameRelations()
2772
     * @see                   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
2773
     * @see                   #addNameRelationship(NameRelationship)
2774
     */
2775
    @Override
2776
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2777
        return new HybridRelationship(this, parentName, type, ruleConsidered);
2778
    }
2779

    
2780
    /**
2781
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2782
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2783
     * "is first/second parent" or "is male/female parent". By invoking this
2784
     * method <i>this</i> botanical name becomes a parent of the hybrid child
2785
     * botanical name.
2786
     *
2787
     * @param childName       the botanical name of the child for this new hybrid name relationship
2788
     * @param type            the type of this new name relationship
2789
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2790
     * @return
2791
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
2792
     * @see                   #getRelationsToThisName()
2793
     * @see                   #getNameRelations()
2794
     * @see                   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
2795
     * @see                   #addNameRelationship(NameRelationship)
2796
     */
2797
    @Override
2798
    public HybridRelationship addHybridChild(INonViralName childName, HybridRelationshipType type, String ruleConsidered){
2799
        return new HybridRelationship(childName, this, type, ruleConsidered);
2800
    }
2801

    
2802
    @Override
2803
    public void removeHybridChild(INonViralName child) {
2804
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2805
        hybridRelationships.addAll(this.getHybridChildRelations());
2806
        hybridRelationships.addAll(this.getHybridParentRelations());
2807
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2808
            // remove name relationship from this side
2809
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2810
                this.removeHybridRelationship(hybridRelationship);
2811
            }
2812
        }
2813
    }
2814

    
2815
    @Override
2816
    public void removeHybridParent(INonViralName parent) {
2817
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2818
        hybridRelationships.addAll(this.getHybridChildRelations());
2819
        hybridRelationships.addAll(this.getHybridParentRelations());
2820
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2821
            // remove name relationship from this side
2822
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2823
                this.removeHybridRelationship(hybridRelationship);
2824
            }
2825
        }
2826
    }
2827

    
2828

    
2829

    
2830
// *********** DESCRIPTIONS *************************************
2831

    
2832
    /**
2833
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2834
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2835
     * concerning the taxon name like for instance the content of its first
2836
     * publication (protolog) or a picture of this publication.
2837
     *
2838
     * @see	#addDescription(TaxonNameDescription)
2839
     * @see	#removeDescription(TaxonNameDescription)
2840
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2841
     */
2842
    @Override
2843
    public Set<TaxonNameDescription> getDescriptions() {
2844
        return descriptions;
2845
    }
2846

    
2847
    /**
2848
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2849
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2850
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2851
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2852
     *
2853
     * @param  description  the taxon name description to be added
2854
     * @see					#getDescriptions()
2855
     * @see 			  	#removeDescription(TaxonNameDescription)
2856
     */
2857
    @Override
2858
    public void addDescription(TaxonNameDescription description) {
2859
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonNameBase.class);
2860
        ReflectionUtils.makeAccessible(field);
2861
        ReflectionUtils.setField(field, description, this);
2862
        descriptions.add(description);
2863
    }
2864
    /**
2865
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2866
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2867
     * of the description itself will be set to "null".
2868
     *
2869
     * @param  description  the taxon name description which should be removed
2870
     * @see     		  	#getDescriptions()
2871
     * @see     		  	#addDescription(TaxonNameDescription)
2872
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
2873
     */
2874
    @Override
2875
    public void removeDescription(TaxonNameDescription description) {
2876
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonNameBase.class);
2877
        ReflectionUtils.makeAccessible(field);
2878
        ReflectionUtils.setField(field, description, null);
2879
        descriptions.remove(description);
2880
    }
2881

    
2882
// *********** HOMOTYPIC GROUP METHODS **************************************************
2883

    
2884
    @Override
2885
    @Transient
2886
    public void mergeHomotypicGroups(TaxonNameBase name){
2887
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2888
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2889
        name.setHomotypicalGroup(this.homotypicalGroup);
2890
    }
2891

    
2892
    /**
2893
     * Returns the boolean value indicating whether a given taxon name belongs
2894
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
2895
     * or not (false). Returns "true" only if the homotypical groups of both
2896
     * taxon names exist and if they are identical.
2897
     *
2898
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
2899
     * @return  			   the boolean value of the check
2900
     * @see     			   HomotypicalGroup
2901
     */
2902
    @Override
2903
    @Transient
2904
    public boolean isHomotypic(TaxonNameBase homoTypicName) {
2905
        if (homoTypicName == null) {
2906
            return false;
2907
        }
2908
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
2909
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
2910
            return false;
2911
        }
2912
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
2913
            return true;
2914
        }
2915
        return false;
2916
    }
2917

    
2918

    
2919
    /**
2920
     * Checks whether name is a basionym for ALL names
2921
     * in its homotypical group.
2922
     * Returns <code>false</code> if there are no other names in the group
2923
     * @param name
2924
     * @return
2925
     */
2926
    @Override
2927
    @Transient
2928
    public boolean isGroupsBasionym() {
2929
    	if (homotypicalGroup == null){
2930
    		homotypicalGroup = HomotypicalGroup.NewInstance();
2931
    		homotypicalGroup.addTypifiedName(this);
2932
    	}
2933
        Set<TaxonNameBase> typifiedNames = homotypicalGroup.getTypifiedNames();
2934

    
2935
        // Check whether there are any other names in the group
2936
        if (typifiedNames.size() == 1) {
2937
                return false;
2938
        }
2939

    
2940
        for (TaxonNameBase<?,?> taxonName : typifiedNames) {
2941
                if (!taxonName.equals(this)) {
2942
                        if (! isBasionymFor(taxonName)) {
2943
                                return false;
2944
                        }
2945
                }
2946
        }
2947
        return true;
2948
    }
2949

    
2950
    /**
2951
     * Checks whether a basionym relationship exists between fromName and toName.
2952
     *
2953
     * @param fromName
2954
     * @param toName
2955
     * @return
2956
     */
2957
    @Override
2958
    @Transient
2959
    public boolean isBasionymFor(TaxonNameBase newCombinationName) {
2960
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
2961
            for (NameRelationship relation : relations) {
2962
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
2963
                                    relation.getFromName().equals(this)) {
2964
                            return true;
2965
                    }
2966
            }
2967
            return false;
2968
    }
2969

    
2970
    /**
2971
     * Creates a basionym relationship to all other names in this names homotypical
2972
     * group.
2973
     *
2974
     * @see HomotypicalGroup.setGroupBasionym(TaxonNameBase basionymName)
2975
     */
2976
    @Override
2977
    @Transient
2978
    public void makeGroupsBasionym() {
2979
        this.homotypicalGroup.setGroupBasionym(this);
2980
    }
2981

    
2982

    
2983
//*********  Rank comparison shortcuts   ********************//
2984
    /**
2985
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2986
     * taxon name is higher than the genus rank (true) or not (false).
2987
     * Suprageneric non viral names are monomials.
2988
     * Returns false if rank is null.
2989
     *
2990
     * @see  #isGenus()
2991
     * @see  #isInfraGeneric()
2992
     * @see  #isSpecies()
2993
     * @see  #isInfraSpecific()
2994
     */
2995
    @Override
2996
    @Transient
2997
    public boolean isSupraGeneric() {
2998
        if (rank == null){
2999
            return false;
3000
        }
3001
        return getRank().isSupraGeneric();
3002
    }
3003
    /**
3004
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3005
     * taxon name is the genus rank (true) or not (false). Non viral names with
3006
     * genus rank are monomials. Returns false if rank is null.
3007
     *
3008
     * @see  #isSupraGeneric()
3009
     * @see  #isInfraGeneric()
3010
     * @see  #isSpecies()
3011
     * @see  #isInfraSpecific()
3012
     */
3013
    @Override
3014
    @Transient
3015
    public boolean isGenus() {
3016
        if (rank == null){
3017
            return false;
3018
        }
3019
        return getRank().isGenus();
3020
    }
3021

    
3022
    @Override
3023
    @Transient
3024
    public boolean isGenusOrSupraGeneric() {
3025
        return isGenus()|| isSupraGeneric();
3026
    }
3027
    /**
3028
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3029
     * taxon name is higher than the species rank and lower than the
3030
     * genus rank (true) or not (false). Infrageneric non viral names are
3031
     * binomials. Returns false if rank is null.
3032
     *
3033
     * @see  #isSupraGeneric()
3034
     * @see  #isGenus()
3035
     * @see  #isSpecies()
3036
     * @see  #isInfraSpecific()
3037
     */
3038
    @Override
3039
    @Transient
3040
    public boolean isInfraGeneric() {
3041
        if (rank == null){
3042
            return false;
3043
        }
3044
        return getRank().isInfraGeneric();
3045
    }
3046

    
3047
    /**
3048
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3049
     * taxon name is higher than the species rank (true) or not (false).
3050
     * Returns false if rank is null.
3051
     *
3052
     * @see  #isGenus()
3053
     * @see  #isInfraGeneric()
3054
     * @see  #isSpecies()
3055
     * @see  #isInfraSpecific()
3056
     */
3057
    @Override
3058
    @Transient
3059
    public boolean isSupraSpecific(){
3060
        if (rank == null) {
3061
            return false;
3062
        }
3063
        return getRank().isHigher(Rank.SPECIES());
3064
    }
3065

    
3066
    /**
3067
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3068
     * taxon name is the species rank (true) or not (false). Non viral names
3069
     * with species rank are binomials.
3070
     * Returns false if rank is null.
3071
     *
3072
     * @see  #isSupraGeneric()
3073
     * @see  #isGenus()
3074
     * @see  #isInfraGeneric()
3075
     * @see  #isInfraSpecific()
3076
     */
3077
    @Override
3078
    @Transient
3079
    public boolean isSpecies() {
3080
        if (rank == null){
3081
            return false;
3082
        }
3083
        return getRank().isSpecies();
3084
    }
3085
    /**
3086
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3087
     * taxon name is lower than the species rank (true) or not (false).
3088
     * Infraspecific non viral names are trinomials.
3089
     * Returns false if rank is null.
3090
     *
3091
     * @see  #isSupraGeneric()
3092
     * @see  #isGenus()
3093
     * @see  #isInfraGeneric()
3094
     * @see  #isSpecies()
3095
     */
3096
    @Override
3097
    @Transient
3098
    public boolean isInfraSpecific() {
3099
        if (rank == null){
3100
            return false;
3101
        }
3102
        return getRank().isInfraSpecific();
3103
    }
3104

    
3105
    /**
3106
     * Returns true if this name's rank indicates a rank that aggregates species like species
3107
     * aggregates or species groups, false otherwise. This methods currently returns false
3108
     * for all user defined ranks.
3109
     *
3110
     *@see Rank#isSpeciesAggregate()
3111
     *
3112
     * @return
3113
     */
3114
    @Override
3115
    @Transient
3116
    public boolean isSpeciesAggregate() {
3117
        if (rank == null){
3118
            return false;
3119
        }
3120
        return getRank().isSpeciesAggregate();
3121
    }
3122

    
3123

    
3124
    /**
3125
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3126
     * the construction of <i>this</i> taxon name since there is no specific
3127
     * nomenclatural code defined. The real implementention takes place in the
3128
     * subclasses {@link IBacterialName BacterialName},
3129
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3130
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3131
     * and only one nomenclatural code.
3132
     *
3133
     * @return  null
3134
     * @see  	#isCodeCompliant()
3135
     * @see  	#getHasProblem()
3136
     * @deprecated use {@link #getNameType()} instead
3137
     */
3138
    @Override
3139
    @Deprecated
3140
    @Transient
3141
    @java.beans.Transient
3142
    public NomenclaturalCode getNomenclaturalCode() {
3143
        return nameType;
3144
    }
3145

    
3146

    
3147
    /**
3148
     * Generates and returns the string with the scientific name of <i>this</i>
3149
     * taxon name (only non viral taxon names can be generated from their
3150
     * components). This string may be stored in the inherited
3151
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3152
     * This method overrides the generic and inherited
3153
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3154
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3155
     *
3156
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3157
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3158
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3159
     */
3160
//	@Override
3161
//	public abstract String generateTitle();
3162

    
3163
    /**
3164
     * Creates a basionym relationship between this name and
3165
     * 	each name in its homotypic group.
3166
     *
3167
     * @param basionymName
3168
     */
3169
    @Override
3170
    @Transient
3171
    public void setAsGroupsBasionym() {
3172

    
3173
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3174
        if (homotypicalGroup == null) {
3175
            return;
3176
        }
3177

    
3178
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3179
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3180

    
3181
        for(TaxonNameBase<?, ?> typifiedName : homotypicalGroup.getTypifiedNames()){
3182

    
3183
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3184

    
3185
            for(NameRelationship nameRelation : nameRelations){
3186
                relations.add(nameRelation);
3187
            }
3188
        }
3189

    
3190
        for (NameRelationship relation : relations) {
3191

    
3192
            // If this is a basionym relation, and toName is in the homotypical group,
3193
            //	remove the relationship.
3194
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3195
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3196
                removeRelations.add(relation);
3197
            }
3198
        }
3199

    
3200
        // Removing relations from a set through which we are iterating causes a
3201
        //	ConcurrentModificationException. Therefore, we delete the targeted
3202
        //	relations in a second step.
3203
        for (NameRelationship relation : removeRelations) {
3204
            this.removeNameRelationship(relation);
3205
        }
3206

    
3207
        for (TaxonNameBase<?, ?> name : homotypicalGroup.getTypifiedNames()) {
3208
            if (!name.equals(this)) {
3209

    
3210
                // First check whether the relationship already exists
3211
                if (!this.isBasionymFor(name)) {
3212

    
3213
                    // Then create it
3214
                    name.addRelationshipFromName(this,
3215
                            NameRelationshipType.BASIONYM(), null);
3216
                }
3217
            }
3218
        }
3219
    }
3220

    
3221
    /**
3222
     * Removes basionym relationship between this name and
3223
     * 	each name in its homotypic group.
3224
     *
3225
     * @param basionymName
3226
     */
3227
    @Override
3228
    @Transient
3229
    public void removeAsGroupsBasionym() {
3230

    
3231
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3232

    
3233
        if (homotypicalGroup == null) {
3234
            return;
3235
        }
3236

    
3237
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3238
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3239

    
3240
        for(TaxonNameBase<?, ?> typifiedName : homotypicalGroup.getTypifiedNames()){
3241

    
3242
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3243

    
3244
            for(NameRelationship nameRelation : nameRelations){
3245
                relations.add(nameRelation);
3246
            }
3247
        }
3248

    
3249
        for (NameRelationship relation : relations) {
3250

    
3251
            // If this is a basionym relation, and toName is in the homotypical group,
3252
            //	and fromName is basionymName, remove the relationship.
3253
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3254
                    relation.getFromName().equals(this) &&
3255
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3256
                removeRelations.add(relation);
3257
            }
3258
        }
3259

    
3260
        // Removing relations from a set through which we are iterating causes a
3261
        //	ConcurrentModificationException. Therefore, we delete the targeted
3262
        //	relations in a second step.
3263
        for (NameRelationship relation : removeRelations) {
3264
            this.removeNameRelationship(relation);
3265
        }
3266
    }
3267

    
3268

    
3269
    /**
3270
     * Defines the last part of the name.
3271
     * This is for infraspecific taxa, the infraspecific epithet,
3272
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3273
     * else the genusOrUninomial.
3274
     * However, the result does not depend on the rank (which may be not correctly set
3275
     * in case of dirty data) but returns the first name part which is not blank
3276
     * considering the above order.
3277
     * @return the first not blank name part in reverse order
3278
     */
3279
    @Override
3280
    public String getLastNamePart() {
3281
        String result =
3282
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3283
                    this.getInfraSpecificEpithet() :
3284
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3285
                    this.getSpecificEpithet():
3286
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3287
                    this.getInfraGenericEpithet():
3288
                this.getGenusOrUninomial();
3289
        return result;
3290
    }
3291

    
3292
    /**
3293
     * {@inheritDoc}
3294
     */
3295
    @Override
3296
    public boolean isHybridName() {
3297
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3298
    }
3299

    
3300
    /**
3301
     * {@inheritDoc}
3302
     */
3303
    @Override
3304
    public boolean isHybrid() {
3305
        return this.isHybridName() || this.isHybridFormula();
3306
    }
3307

    
3308
// ***************** COMPARE ********************************/
3309

    
3310
    @Override
3311
    public int compareToName(TaxonNameBase<?,?> otherName){
3312

    
3313
        int result = 0;
3314

    
3315
        if (otherName == null) {
3316
            throw new NullPointerException("Cannot compare to null.");
3317
        }
3318

    
3319
        //other
3320
        otherName = deproxy(otherName);
3321
        String otherNameCache = otherName.getNameCache();
3322
        String otherTitleCache = otherName.getTitleCache();
3323
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3324
        if (otherName.isAutonym()){
3325
            boolean isProtected = otherName.isProtectedNameCache();
3326
            String oldNameCache = otherName.getNameCache();
3327
            otherName.setProtectedNameCache(false);
3328
            otherName.setNameCache(null, false);
3329
            otherNameCache = otherName.getNameCache();
3330
            otherName.setNameCache(oldNameCache, isProtected);
3331
        }
3332

    
3333
        //this
3334
        String thisNameCache = this.getNameCache();
3335
        String thisTitleCache = this.getTitleCache();
3336

    
3337
        if (this.isAutonym()){
3338
            boolean isProtected = this.isProtectedNameCache();
3339
            String oldNameCache = this.getNameCache();
3340
            this.setProtectedNameCache(false);
3341
            this.setNameCache(null, false);
3342
            thisNameCache = this.getNameCache();
3343
            this.setNameCache(oldNameCache, isProtected);
3344
        }
3345

    
3346

    
3347
        // Compare name cache of taxon names
3348
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3349
            thisNameCache = normalizeName(thisNameCache);
3350
            otherNameCache = normalizeName(otherNameCache);
3351
            result = thisNameCache.compareTo(otherNameCache);
3352
        }
3353

    
3354
        // Compare title cache of taxon names
3355
        if (result == 0){
3356
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3357
                thisTitleCache = normalizeName(thisTitleCache);
3358
                otherTitleCache = normalizeName(otherTitleCache);
3359
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3360
            }
3361
        }
3362

    
3363
        return result;
3364
    }
3365

    
3366
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3367
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3368

    
3369
    /**
3370
     * @param thisNameCache
3371
     * @param HYBRID_SIGN
3372
     * @param QUOT_SIGN
3373
     * @return
3374
     */
3375
    private String normalizeName(String thisNameCache) {
3376
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3377
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3378
        return thisNameCache;
3379
    }
3380

    
3381
// ********************** INTERFACES ********************************************/
3382

    
3383
    /**
3384
     * Method to cast a interfaced name to a concrete name.
3385
     * The method includes a deproxy to guarantee that no
3386
     * class cast exception is thrown.
3387
     *
3388
     * @see #castAndDeproxy(Set)
3389
     * @param interfacedName
3390
     * @return
3391
     */
3392
    public static TaxonNameBase castAndDeproxy(ITaxonNameBase interfacedName){
3393
        return deproxy(interfacedName, TaxonNameBase.class);
3394
    }
3395

    
3396
    /**
3397
     * Method to cast a set of interfaced names to concrete namex.
3398
     * The method includes a deproxy to guarantee that no
3399
     * class cast exception is thrown.
3400
     *
3401
     * @see #castAndDeproxy(ITaxonNameBase)
3402
     * @param naminterfacedNames
3403
     * @return
3404
     */
3405
    public static Set<TaxonNameBase> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3406
        Set<TaxonNameBase> result = new HashSet<>();
3407
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3408
            result.add(castAndDeproxy(naminterfacedName));
3409
        }
3410
        return result;
3411
    }
3412

    
3413

    
3414
//*********************** CLONE ********************************************************/
3415

    
3416
    /**
3417
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3418
     * a new instance that differs only slightly from <i>this</i> taxon name by
3419
     * modifying only some of the attributes.<BR><BR>
3420
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3421
     * <b>The name gets a newly created homotypical group</b><BR>
3422
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3423
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3424
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3425
     *
3426
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3427
     * @see java.lang.Object#clone()
3428
     */
3429
    @Override
3430
    public Object clone() {
3431
        TaxonNameBase<?,?> result;
3432
        try {
3433
            result = (TaxonNameBase)super.clone();
3434

    
3435
            //taxonBases -> empty
3436
            result.taxonBases = new HashSet<>();
3437

    
3438
            //empty caches
3439
            if (! protectedFullTitleCache){
3440
                result.fullTitleCache = null;
3441
            }
3442

    
3443
            //descriptions
3444
            result.descriptions = new HashSet<>();
3445
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3446
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3447
                result.descriptions.add(newDescription);
3448
            }
3449

    
3450
            //status
3451
            result.status = new HashSet<>();
3452
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3453
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3454
                result.status.add(newStatus);
3455
            }
3456

    
3457

    
3458
            //To Relations
3459
            result.relationsToThisName = new HashSet<>();
3460
            for (NameRelationship toRelationship : getRelationsToThisName()){
3461
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3462
                newRelationship.setRelatedTo(result);
3463
                result.relationsToThisName.add(newRelationship);
3464
            }
3465

    
3466
            //From Relations
3467
            result.relationsFromThisName = new HashSet<>();
3468
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3469
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3470
                newRelationship.setRelatedFrom(result);
3471
                result.relationsFromThisName.add(newRelationship);
3472
            }
3473

    
3474
            //type designations
3475
            result.typeDesignations = new HashSet<>();
3476
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3477
                TypeDesignationBase<?> newDesignation = (TypeDesignationBase<?>)typeDesignation.clone();
3478
                result.typeDesignations.add(newDesignation);
3479
                newDesignation.addTypifiedName(result);
3480
            }
3481

    
3482
            //homotypicalGroup
3483
            //TODO still needs to be discussed
3484
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3485
            result.homotypicalGroup.addTypifiedName(this);
3486

    
3487

    
3488
            //HybridChildRelations
3489
            result.hybridChildRelations = new HashSet<HybridRelationship>();
3490
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3491
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3492
                newChildRelationship.setRelatedTo(result);
3493
                result.hybridChildRelations.add(newChildRelationship);
3494
            }
3495

    
3496
            //HybridParentRelations
3497
            result.hybridParentRelations = new HashSet<HybridRelationship>();
3498
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3499
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3500
                newParentRelationship.setRelatedFrom(result);
3501
                result.hybridParentRelations.add(newParentRelationship);
3502
            }
3503

    
3504
            //empty caches
3505
            if (! protectedNameCache){
3506
                result.nameCache = null;
3507
            }
3508

    
3509
            //empty caches
3510
            if (! protectedAuthorshipCache){
3511
                result.authorshipCache = null;
3512
            }
3513

    
3514
            //no changes to: appendedPharse, nomenclaturalReference,
3515
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3516
            //protectedFullTitleCache, rank
3517
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3518
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3519
            //protectedAuthorshipCache, protectedNameCache,
3520
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3521
            //acronym
3522
            //subGenusAuthorship, nameApprobation
3523
            //anamorphic
3524
            //cultivarName
3525
            return result;
3526
        } catch (CloneNotSupportedException e) {
3527
            logger.warn("Object does not implement cloneable");
3528
            e.printStackTrace();
3529
            return null;
3530
        }
3531

    
3532
    }
3533

    
3534
    /**
3535
     * @return
3536
     */
3537
    @Override
3538
    public boolean isNonViral() {
3539
        return nameType.isNonViral();
3540
    }
3541

    
3542
    @Override
3543
    public boolean isZoological(){
3544
        return nameType.isZoological();
3545
    }
3546
    @Override
3547
    public boolean isBotanical() {
3548
        return nameType.isBotanical();
3549
    }
3550
    @Override
3551
    public boolean isCultivar() {
3552
        return nameType.isCultivar();
3553
    }
3554
    @Override
3555
    public boolean isBacterial() {
3556
        return nameType.isBacterial();
3557
    }
3558
    @Override
3559
    public boolean isViral() {
3560
        return nameType.isViral();
3561
    }
3562

    
3563
}
(33-33/42)