Project

General

Profile

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

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

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

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

    
47
import org.apache.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.Type;
52
import org.hibernate.envers.Audited;
53
import org.hibernate.search.annotations.Analyze;
54
import org.hibernate.search.annotations.Analyzer;
55
import org.hibernate.search.annotations.Field;
56
import org.hibernate.search.annotations.Fields;
57
import org.hibernate.search.annotations.Index;
58
import org.hibernate.search.annotations.Indexed;
59
import org.hibernate.search.annotations.IndexedEmbedded;
60
import org.hibernate.search.annotations.Store;
61
import org.hibernate.validator.constraints.NotEmpty;
62
import org.springframework.util.ReflectionUtils;
63

    
64
import eu.etaxonomy.cdm.common.CdmUtils;
65
import eu.etaxonomy.cdm.common.UTF8;
66
import eu.etaxonomy.cdm.model.EntityCollectionSetterAdapter;
67
import eu.etaxonomy.cdm.model.EntityCollectionSetterAdapter.SetterAdapterException;
68
import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
69
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
70
import eu.etaxonomy.cdm.model.common.CdmBase;
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.Language;
76
import eu.etaxonomy.cdm.model.common.RelationshipBase;
77
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
78
import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
79
import eu.etaxonomy.cdm.model.description.IDescribable;
80
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
81
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
82
import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
83
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
84
import eu.etaxonomy.cdm.model.reference.Reference;
85
import eu.etaxonomy.cdm.model.taxon.Synonym;
86
import eu.etaxonomy.cdm.model.taxon.Taxon;
87
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
88
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
89
import eu.etaxonomy.cdm.strategy.cache.name.CacheUpdate;
90
import eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy;
91
import eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy;
92
import eu.etaxonomy.cdm.strategy.match.IMatchable;
93
import eu.etaxonomy.cdm.strategy.match.Match;
94
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
95
import eu.etaxonomy.cdm.strategy.match.MatchMode;
96
import eu.etaxonomy.cdm.strategy.merge.Merge;
97
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
98
import eu.etaxonomy.cdm.strategy.parser.ParserProblem;
99
import eu.etaxonomy.cdm.validation.Level2;
100
import eu.etaxonomy.cdm.validation.Level3;
101
import eu.etaxonomy.cdm.validation.annotation.CorrectEpithetsForRank;
102
import eu.etaxonomy.cdm.validation.annotation.NameMustFollowCode;
103
import eu.etaxonomy.cdm.validation.annotation.NameMustHaveAuthority;
104
import eu.etaxonomy.cdm.validation.annotation.NoDuplicateNames;
105
import eu.etaxonomy.cdm.validation.annotation.NullOrNotEmpty;
106
import eu.etaxonomy.cdm.validation.annotation.ValidTaxonomicYear;
107

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

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

    
163
    "acronym",
164

    
165
    "subGenusAuthorship",
166
    "nameApprobation",
167

    
168
    "breed",
169
    "publicationYear",
170
    "originalPublicationYear",
171
    "inCombinationAuthorship",
172
    "inBasionymAuthorship",
173

    
174
    "anamorphic",
175

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

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

    
200

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
333
    //#6581
334
    @XmlElement(name = "NomenclaturalReference")
335
    @XmlIDREF
336
    @XmlSchemaType(name = "IDREF")
337
    @ManyToOne(fetch = FetchType.LAZY)
338
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
339
    @CacheUpdate(noUpdate ="titleCache")
340
    @IndexedEmbedded
341
    private Reference nomenclaturalReference;
342

    
343
    //#6581
344
    @XmlElement(name = "NomenclaturalSource")
345
    @XmlIDREF
346
    @XmlSchemaType(name = "IDREF")
347
    @ManyToOne(fetch = FetchType.LAZY)
348
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
349
    @CacheUpdate(noUpdate ="titleCache")
350
    @IndexedEmbedded
351
    private DescriptionElementSource nomenclaturalSource;
352

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

    
363
//****** Non-ViralName attributes ***************************************/
364

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

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

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

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

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

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

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

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

    
436
    //#6943
437
    @XmlElement(name = "InCombinationAuthorship")
438
    @XmlIDREF
439
    @XmlSchemaType(name = "IDREF")
440
    @ManyToOne(fetch = FetchType.LAZY)
441
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
442
    @CacheUpdate("authorshipCache")
443
    @IndexedEmbedded
444
    private TeamOrPersonBase<?> inCombinationAuthorship;
445

    
446
    @XmlElement(name = "BasionymAuthorship")
447
    @XmlIDREF
448
    @XmlSchemaType(name = "IDREF")
449
    @ManyToOne(fetch = FetchType.LAZY)
450
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
451
    @CacheUpdate("authorshipCache")
452
    @IndexedEmbedded
453
    private TeamOrPersonBase<?> basionymAuthorship;
454

    
455
    @XmlElement(name = "ExBasionymAuthorship")
456
    @XmlIDREF
457
    @XmlSchemaType(name = "IDREF")
458
    @ManyToOne(fetch = FetchType.LAZY)
459
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
460
    @CacheUpdate("authorshipCache")
461
    @IndexedEmbedded
462
    private TeamOrPersonBase<?> exBasionymAuthorship;
463

    
464
    //#6943
465
    @XmlElement(name = "InBasionymAuthorship")
466
    @XmlIDREF
467
    @XmlSchemaType(name = "IDREF")
468
    @ManyToOne(fetch = FetchType.LAZY)
469
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
470
    @CacheUpdate("authorshipCache")
471
    @IndexedEmbedded
472
    private TeamOrPersonBase<?> inBasionymAuthorship;
473

    
474
    @XmlElement(name = "AuthorshipCache")
475
    @Fields({
476
        @Field(name = "authorshipCache_tokenized"),
477
        @Field(analyze = Analyze.NO)
478
    })
479
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
480
            cacheReplacedProperties={"combinationAuthorship", "basionymAuthorship", "exCombinationAuthorship", "exBasionymAuthorship"} )
481
    //TODO Val #3379
482
//    @NotNull
483
    @Column(length=255)
484
    @Pattern(regexp = "^[A-Za-z0-9 \\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-\\&\\,\\(\\)\\.]+$", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForAuthority.message}")
485
    private String authorshipCache;
486

    
487
    @XmlElement(name = "ProtectedAuthorshipCache")
488
    @CacheUpdate("authorshipCache")
489
    protected boolean protectedAuthorshipCache;
490

    
491
    @XmlElementWrapper(name = "HybridRelationsFromThisName")
492
    @XmlElement(name = "HybridRelationsFromThisName")
493
    @OneToMany(mappedBy="relatedFrom", fetch = FetchType.LAZY)
494
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
495
    @Merge(MergeMode.RELATION)
496
    @NotNull
497
    private Set<HybridRelationship> hybridParentRelations = new HashSet<>();
498

    
499
    @XmlElementWrapper(name = "HybridRelationsToThisName")
500
    @XmlElement(name = "HybridRelationsToThisName")
501
    @OneToMany(mappedBy="relatedTo", fetch = FetchType.LAZY, orphanRemoval=true) //a hybrid relation can be deleted automatically if the child is deleted.
502
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
503
    @Merge(MergeMode.RELATION)
504
    @NotNull
505
    private Set<HybridRelationship> hybridChildRelations = new HashSet<>();
506

    
507

    
508
    //if set: this name is a hybrid formula (a hybrid that does not have an own name) and no
509
    //other hybrid flags may be set. A
510
    //hybrid name  may not have either an authorteam nor other name components.
511
    @XmlElement(name ="IsHybridFormula")
512
    @CacheUpdate("nameCache")
513
    private boolean hybridFormula = false;
514

    
515
    @XmlElement(name ="IsMonomHybrid")
516
    @CacheUpdate("nameCache")
517
    private boolean monomHybrid = false;
518

    
519
    @XmlElement(name ="IsBinomHybrid")
520
    @CacheUpdate("nameCache")
521
    private boolean binomHybrid = false;
522

    
523
    @XmlElement(name ="IsTrinomHybrid")
524
    @CacheUpdate("nameCache")
525
    private boolean trinomHybrid = false;
526

    
527
// ViralName attributes ************************* /
528

    
529
    @XmlElement(name = "Acronym")
530
    @Field
531
    //TODO Val #3379
532
//  @NullOrNotEmpty
533
    @Column(length=255)
534
    private String acronym;
535

    
536
// BacterialName attributes ***********************/
537

    
538
    //Author team and year of the subgenus name
539
    @XmlElement(name = "SubGenusAuthorship")
540
    @Field
541
    private String subGenusAuthorship;
542

    
543
    //Approbation of name according to approved list, validation list, or validly published, paper in IJSB after 1980
544
    @XmlElement(name = "NameApprobation")
545
    @Field
546
    private String nameApprobation;
547

    
548
    //ZOOLOGICAL NAME
549

    
550
    //Name of the breed of an animal
551
    @XmlElement(name = "Breed")
552
    @Field
553
    @NullOrNotEmpty
554
    @Column(length=255)
555
    private String breed;
556

    
557
    @XmlElement(name = "PublicationYear")
558
    @Field(analyze = Analyze.NO)
559
    @CacheUpdate(value ="authorshipCache")
560
    @Min(0)
561
    private Integer publicationYear;
562

    
563
    @XmlElement(name = "OriginalPublicationYear")
564
    @Field(analyze = Analyze.NO)
565
    @CacheUpdate(value ="authorshipCache")
566
    @Min(0)
567
    private Integer originalPublicationYear;
568

    
569
    //Cultivar attribute(s)
570

    
571
    //the characteristical name of the cultivar
572
    @XmlElement(name = "CultivarName")
573
    //TODO Val #3379
574
    //@NullOrNotEmpty
575
    @Column(length=255)
576
    private String cultivarName;
577

    
578
    // ************** FUNGUS name attributes
579
    //to indicate that the type of the name is asexual or not
580
    @XmlElement(name ="IsAnamorphic")
581
    private boolean anamorphic = false;
582

    
583
// *************** FACTORY METHODS ********************************/
584

    
585
    //see TaxonNameFactory
586
    /**
587
     * @param code
588
     * @param rank
589
     * @param homotypicalGroup
590
     * @return
591
     */
592
    protected static TaxonName NewInstance(NomenclaturalCode code, Rank rank,
593
            HomotypicalGroup homotypicalGroup) {
594
        TaxonName result = new TaxonName(code, rank, homotypicalGroup);
595
        return result;
596
    }
597

    
598

    
599
    /**
600
     * @param icnafp
601
     * @param rank2
602
     * @param genusOrUninomial2
603
     * @param infraGenericEpithet2
604
     * @param specificEpithet2
605
     * @param infraSpecificEpithet2
606
     * @param combinationAuthorship2
607
     * @param nomenclaturalReference2
608
     * @param nomenclMicroRef
609
     * @param homotypicalGroup2
610
     * @return
611
     */
612
    public static TaxonName NewInstance(NomenclaturalCode code, Rank rank, String genusOrUninomial,
613
            String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet,
614
            TeamOrPersonBase combinationAuthorship, Reference nomenclaturalReference,
615
            String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
616
        TaxonName result = new TaxonName(code, rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
617
        return result;
618
    }
619

    
620

    
621
// ************* CONSTRUCTORS *************/
622
    /**
623
     * Class constructor: creates a new empty taxon name.
624
     * @param code
625
     *
626
     * @see #TaxonName(Rank)
627
     * @see #TaxonName(HomotypicalGroup)
628
     * @see #TaxonName(Rank, HomotypicalGroup)
629
     */
630
    protected TaxonName() {
631
        super();
632
        rectifyNameCacheStrategy();
633
    }
634

    
635

    
636
    /**
637
     * Class constructor: creates a new taxon name instance
638
     * only containing its {@link Rank rank} and
639
     * its {@link HomotypicalGroup homotypical group} and
640
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy default cache strategy}.
641
     * The new taxon name will be also added to the set of taxon names
642
     * belonging to this homotypical group.
643
     *
644
     * @param  rank  			 the rank to be assigned to <i>this</i> taxon name
645
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
646
     * @see    					 #TaxonName()
647
     * @see    					 #TaxonName(Rank)
648
     * @see    					 #TaxonName(HomotypicalGroup)
649
     */
650
    protected TaxonName(NomenclaturalCode type, Rank rank, HomotypicalGroup homotypicalGroup) {
651
        this();
652
        setNameType(type);
653
        this.setRank(rank);
654
        if (homotypicalGroup == null){
655
            homotypicalGroup = HomotypicalGroup.NewInstance();
656
        }
657
        homotypicalGroup.addTypifiedName(this);
658
        this.homotypicalGroup = homotypicalGroup;
659
    }
660

    
661

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

    
707

    
708
    /**
709
     * This method was originally needed to distinguish cache strategies
710
     * depending on the name type. Now we have a unified cache strategy
711
     * which does not require this anymore. Maybe we could even further remove this method.
712
     */
713
    private void rectifyNameCacheStrategy(){
714
        if (this.cacheStrategy == null){
715
            this.cacheStrategy = TaxonNameDefaultCacheStrategy.NewInstance();
716
        }
717
    }
718

    
719

    
720
    @Override
721
    public void initListener(){
722
        PropertyChangeListener listener = new PropertyChangeListener() {
723
            @Override
724
            public void propertyChange(PropertyChangeEvent e) {
725
                boolean protectedByLowerCache = false;
726
                //authorship cache
727
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
728
                    if (protectedAuthorshipCache){
729
                        protectedByLowerCache = true;
730
                    }else{
731
                        authorshipCache = null;
732
                    }
733
                }
734

    
735
                //nameCache
736
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "nameCache")){
737
                    if (protectedNameCache){
738
                        protectedByLowerCache = true;
739
                    }else{
740
                        nameCache = null;
741
                    }
742
                }
743
                //title cache
744
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "titleCache")){
745
                    if (isProtectedTitleCache()|| protectedByLowerCache == true ){
746
                        protectedByLowerCache = true;
747
                    }else{
748
                        titleCache = null;
749
                    }
750
                }
751
                //full title cache
752
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "fullTitleCache")){
753
                    if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
754
                        protectedByLowerCache = true;
755
                    }else{
756
                        fullTitleCache = null;
757
                    }
758
                }
759
            }
760
        };
761
        addPropertyChangeListener(listener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
762
    }
763

    
764
    private static Map<String, java.lang.reflect.Field> allFields = null;
765
    protected Map<String, java.lang.reflect.Field> getAllFields(){
766
        if (allFields == null){
767
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
768
        }
769
        return allFields;
770
    }
771

    
772
    /**
773
     * @param propertyName
774
     * @param string
775
     * @return
776
     */
777
    private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
778
        java.lang.reflect.Field field;
779
        try {
780
            field = getAllFields().get(propertyName);
781
            if (field != null){
782
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
783
                if (updateAnnotation != null){
784
                    for (String value : updateAnnotation.value()){
785
                        if (cacheName.equals(value)){
786
                            return true;
787
                        }
788
                    }
789
                }
790
            }
791
            return false;
792
        } catch (SecurityException e1) {
793
            throw e1;
794
        }
795
    }
796

    
797
    private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
798
        java.lang.reflect.Field field;
799
        //do not update fields with the same name
800
        if (cacheName.equals(propertyName)){
801
            return true;
802
        }
803
        //evaluate annotation
804
        try {
805
            field = getAllFields().get(propertyName);
806
            if (field != null){
807
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
808
                if (updateAnnotation != null){
809
                    for (String value : updateAnnotation.noUpdate()){
810
                        if (cacheName.equals(value)){
811
                            return true;
812
                        }
813
                    }
814
                }
815
            }
816
            return false;
817
        } catch (SecurityException e1) {
818
            throw e1;
819
        }
820
    }
821

    
822
// ****************** GETTER / SETTER ****************************/
823

    
824
    @Override
825
    public NomenclaturalCode getNameType() {
826
        return nameType;
827
    }
828

    
829
    @Override
830
    public void setNameType(NomenclaturalCode nameType) {
831
        this.nameType = nameType;
832
    }
833

    
834
    /**
835
     * Returns the boolean value of the flag intended to protect (true)
836
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
837
     * string of <i>this</i> non viral taxon name.
838
     *
839
     * @return  the boolean value of the protectedNameCache flag
840
     * @see     #getNameCache()
841
     */
842
    @Override
843
    public boolean isProtectedNameCache() {
844
        return protectedNameCache;
845
    }
846

    
847
    /**
848
     * @see     #isProtectedNameCache()
849
     */
850
    @Override
851
    public void setProtectedNameCache(boolean protectedNameCache) {
852
        this.protectedNameCache = protectedNameCache;
853
    }
854

    
855
    /**
856
     * Returns either the scientific name string (without authorship) for <i>this</i>
857
     * non viral taxon name if its rank is genus or higher (monomial) or the string for
858
     * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
859
     * Genus or uninomial strings begin with an upper case letter.
860
     *
861
     * @return  the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
862
     * @see     #getNameCache()
863
     */
864
    @Override
865
    public String getGenusOrUninomial() {
866
        return genusOrUninomial;
867
    }
868

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

    
877
    /**
878
     * Returns the genus subdivision epithet string (infrageneric part) for
879
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
880
     * higher than species aggregate: binomial). Genus subdivision epithet
881
     * strings begin with an upper case letter.
882
     *
883
     * @return  the string containing the infrageneric part of <i>this</i> non viral taxon name
884
     * @see     #getNameCache()
885
     */
886
    @Override
887
    public String getInfraGenericEpithet(){
888
        return this.infraGenericEpithet;
889
    }
890

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

    
899
    /**
900
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
901
     * species aggregate or lower (bi- or trinomial). Species epithet strings
902
     * begin with a lower case letter.
903
     *
904
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
905
     * @see     #getNameCache()
906
     */
907
    @Override
908
    public String getSpecificEpithet(){
909
        return this.specificEpithet;
910
    }
911

    
912
    /**
913
     * @see  #getSpecificEpithet()
914
     */
915
    @Override
916
    public void setSpecificEpithet(String specificEpithet){
917
        this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
918
    }
919

    
920
    /**
921
     * Returns the species subdivision epithet string (infraspecific part) for
922
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
923
     * (lower than species: trinomial). Species subdivision epithet strings
924
     * begin with a lower case letter.
925
     *
926
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
927
     * @see     #getNameCache()
928
     */
929
    @Override
930
    public String getInfraSpecificEpithet(){
931
        return this.infraSpecificEpithet;
932
    }
933

    
934
    /**
935
     * @see  #getInfraSpecificEpithet()
936
     */
937
    @Override
938
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
939
        this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
940
    }
941

    
942
    /**
943
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
944
     * taxon name.
945
     *
946
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
947
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
948
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
949
     */
950
    @Override
951
    public TeamOrPersonBase<?> getCombinationAuthorship(){
952
        return this.combinationAuthorship;
953
    }
954

    
955
    /**
956
     * @see  #getCombinationAuthorship()
957
     */
958
    @Override
959
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
960
        this.combinationAuthorship = combinationAuthorship;
961
    }
962

    
963
    /**
964
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
965
     * the publication of <i>this</i> non viral taxon name as generally stated by
966
     * the {@link #getCombinationAuthorship() combination author (team)} itself.<BR>
967
     * An ex-author(-team) is an author(-team) to whom a taxon name was ascribed
968
     * although it is not the author(-team) of a valid publication (for instance
969
     * without the validating description or diagnosis in case of a name for a
970
     * new taxon). The name of this ascribed authorship, followed by "ex", may
971
     * be inserted before the name(s) of the publishing author(s) of the validly
972
     * published name:<BR>
973
     * <i>Lilium tianschanicum</i> was described by Grubov (1977) as a new species and
974
     * its name was ascribed to Ivanova; since there is no indication that
975
     * Ivanova provided the validating description, the name may be cited as
976
     * <i>Lilium tianschanicum</i> N. A. Ivanova ex Grubov or <i>Lilium tianschanicum</i> Grubov.
977
     * <P>
978
     * The presence of an author (team) of <i>this</i> non viral taxon name is a
979
     * condition for the existence of an ex author (team) for <i>this</i> same name.
980
     *
981
     * @return  the nomenclatural ex author (team) of <i>this</i> non viral taxon name
982
     * @see     #getCombinationAuthorship()
983
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
984
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
985
     */
986
    @Override
987
    public TeamOrPersonBase<?> getExCombinationAuthorship(){
988
        return this.exCombinationAuthorship;
989
    }
990
    /**
991
     * @see  #getExCombinationAuthorship()
992
     */
993
    @Override
994
    public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
995
        this.exCombinationAuthorship = exCombinationAuthorship;
996
    }
997

    
998
    @Override
999
    public TeamOrPersonBase<?> getInCombinationAuthorship(){
1000
        return this.inCombinationAuthorship;
1001
    }
1002
    @Override
1003
    public void setInCombinationAuthorship(TeamOrPersonBase<?> inCombinationAuthorship) {
1004
        this.inCombinationAuthorship = inCombinationAuthorship;
1005
    }
1006

    
1007
    /**
1008
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
1009
     * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
1010
     * author (team) can only exist if <i>this</i> non viral taxon name is a new
1011
     * combination due to a taxonomical revision.
1012
     *
1013
     * @return  the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
1014
     * @see     #getCombinationAuthorship()
1015
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
1016
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
1017
     */
1018
    @Override
1019
    public TeamOrPersonBase<?> getBasionymAuthorship(){
1020
        return basionymAuthorship;
1021
    }
1022

    
1023
    /**
1024
     * @see  #getBasionymAuthorship()
1025
     */
1026
    @Override
1027
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
1028
        this.basionymAuthorship = basionymAuthorship;
1029
    }
1030

    
1031
    /**
1032
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
1033
     * the publication of the original combination <i>this</i> non viral taxon name is
1034
     * based on. This should have been generally stated by
1035
     * the {@link #getBasionymAuthorship() basionym author (team)} itself.
1036
     * The presence of a basionym author (team) of <i>this</i> non viral taxon name is a
1037
     * condition for the existence of an ex basionym author (team)
1038
     * for <i>this</i> same name.
1039
     *
1040
     * @return  the nomenclatural ex basionym author (team) of <i>this</i> non viral taxon name
1041
     * @see     #getBasionymAuthorship()
1042
     * @see     #getExCombinationAuthorship()
1043
     * @see     #getCombinationAuthorship()
1044
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
1045
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
1046
     */
1047
    @Override
1048
    public TeamOrPersonBase<?> getExBasionymAuthorship(){
1049
        return exBasionymAuthorship;
1050
    }
1051

    
1052
    /**
1053
     * @see  #getExBasionymAuthorship()
1054
     */
1055
    @Override
1056
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
1057
        this.exBasionymAuthorship = exBasionymAuthorship;
1058
    }
1059

    
1060
    @Override
1061
    public TeamOrPersonBase<?> getInBasionymAuthorship(){
1062
        return this.inBasionymAuthorship;
1063
    }
1064
    @Override
1065
    public void setInBasionymAuthorship(TeamOrPersonBase<?> inBasionymAuthorship) {
1066
        this.inBasionymAuthorship = inBasionymAuthorship;
1067
    }
1068

    
1069
    /**
1070
     * Returns the boolean value of the flag intended to protect (true)
1071
     * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
1072
     * of <i>this</i> non viral taxon name.
1073
     *
1074
     * @return  the boolean value of the protectedAuthorshipCache flag
1075
     * @see     #getAuthorshipCache()
1076
     */
1077
    @Override
1078
    public boolean isProtectedAuthorshipCache() {
1079
        return protectedAuthorshipCache;
1080
    }
1081

    
1082
    /**
1083
     * @see     #isProtectedAuthorshipCache()
1084
     * @see     #getAuthorshipCache()
1085
     */
1086
    @Override
1087
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
1088
        this.protectedAuthorshipCache = protectedAuthorshipCache;
1089
    }
1090

    
1091
    /**
1092
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1093
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
1094
     *
1095
     * @see    #getHybridRelationships()
1096
     * @see    #getChildRelationships()
1097
     * @see    HybridRelationshipType
1098
     */
1099
    @Override
1100
    public Set<HybridRelationship> getHybridParentRelations() {
1101
        if(hybridParentRelations == null) {
1102
            this.hybridParentRelations = new HashSet<>();
1103
        }
1104
        return hybridParentRelations;
1105
    }
1106

    
1107
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1108
        this.hybridParentRelations = hybridParentRelations;
1109
    }
1110

    
1111

    
1112
    /**
1113
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1114
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
1115
     *
1116
     * @see    #getHybridRelationships()
1117
     * @see    #getParentRelationships()
1118
     * @see    HybridRelationshipType
1119
     */
1120
    @Override
1121
    public Set<HybridRelationship> getHybridChildRelations() {
1122
        if(hybridChildRelations == null) {
1123
            this.hybridChildRelations = new HashSet<>();
1124
        }
1125
        return hybridChildRelations;
1126
    }
1127

    
1128
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1129
        this.hybridChildRelations = hybridChildRelations;
1130
    }
1131

    
1132
    @Override
1133
    public boolean isProtectedFullTitleCache() {
1134
        return protectedFullTitleCache;
1135
    }
1136

    
1137
    @Override
1138
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1139
        this.protectedFullTitleCache = protectedFullTitleCache;
1140
    }
1141

    
1142
    /**
1143
     * Returns the boolean value of the flag indicating whether the name of <i>this</i>
1144
     * botanical taxon name is a hybrid formula (true) or not (false). A hybrid
1145
     * named by a hybrid formula (composed with its parent names by placing the
1146
     * multiplication sign between them) does not have an own published name
1147
     * and therefore has neither an {@link NonViralName#getAuthorshipCache() autorship}
1148
     * nor other name components. If this flag is set no other hybrid flags may
1149
     * be set.
1150
     *
1151
     * @return  the boolean value of the isHybridFormula flag
1152
     * @see     #isMonomHybrid()
1153
     * @see     #isBinomHybrid()
1154
     * @see     #isTrinomHybrid()
1155
     */
1156
    @Override
1157
    @Transient
1158
    @java.beans.Transient
1159
    public boolean isHybridFormula(){
1160
        return this.hybridFormula;
1161
    }
1162

    
1163
    /**
1164
     * @see  #isHybridFormula()
1165
     */
1166
    @Override
1167
    public void setHybridFormula(boolean hybridFormula){
1168
        this.hybridFormula = hybridFormula;
1169
    }
1170

    
1171
    /**
1172
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1173
     * taxon name is the name of an intergeneric hybrid (true) or not (false).
1174
     * In this case the multiplication sign is placed before the scientific
1175
     * name. If this flag is set no other hybrid flags may be set.
1176
     *
1177
     * @return  the boolean value of the isMonomHybrid flag
1178
     * @see     #isHybridFormula()
1179
     * @see     #isBinomHybrid()
1180
     * @see     #isTrinomHybrid()
1181
     */
1182
    @Override
1183
    public boolean isMonomHybrid(){
1184
        return this.monomHybrid;
1185
    }
1186

    
1187
    /**
1188
     * @see  #isMonomHybrid()
1189
     * @see  #isBinomHybrid()
1190
     * @see  #isTrinomHybrid()
1191
     */
1192
    @Override
1193
    public void setMonomHybrid(boolean monomHybrid){
1194
        this.monomHybrid = monomHybrid;
1195
    }
1196

    
1197
    /**
1198
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1199
     * taxon name is the name of an interspecific hybrid (true) or not (false).
1200
     * In this case the multiplication sign is placed before the species
1201
     * epithet. If this flag is set no other hybrid flags may be set.
1202
     *
1203
     * @return  the boolean value of the isBinomHybrid flag
1204
     * @see     #isHybridFormula()
1205
     * @see     #isMonomHybrid()
1206
     * @see     #isTrinomHybrid()
1207
     */
1208
    @Override
1209
    public boolean isBinomHybrid(){
1210
        return this.binomHybrid;
1211
    }
1212

    
1213
    /**
1214
     * @see  #isBinomHybrid()
1215
     * @see  #isMonomHybrid()
1216
     * @see  #isTrinomHybrid()
1217
     */
1218
    @Override
1219
    public void setBinomHybrid(boolean binomHybrid){
1220
        this.binomHybrid = binomHybrid;
1221
    }
1222

    
1223
    @Override
1224
    public boolean isTrinomHybrid(){
1225
        return this.trinomHybrid;
1226
    }
1227

    
1228
    /**
1229
     * @see  #isTrinomHybrid()
1230
     * @see  #isBinomHybrid()
1231
     * @see  #isMonomHybrid()
1232
     */
1233
    @Override
1234
    public void setTrinomHybrid(boolean trinomHybrid){
1235
        this.trinomHybrid = trinomHybrid;
1236
    }
1237

    
1238
    // ****************** VIRAL NAME ******************/
1239

    
1240
    @Override
1241
    public String getAcronym(){
1242
        return this.acronym;
1243
    }
1244

    
1245
    /**
1246
     * @see  #getAcronym()
1247
     */
1248
    @Override
1249
    public void setAcronym(String acronym){
1250
        this.acronym = StringUtils.isBlank(acronym)? null : acronym;
1251
    }
1252

    
1253
    // ****************** BACTERIAL NAME ******************/
1254

    
1255
    @Override
1256
    public String getSubGenusAuthorship(){
1257
        return this.subGenusAuthorship;
1258
    }
1259

    
1260
    @Override
1261
    public void setSubGenusAuthorship(String subGenusAuthorship){
1262
        this.subGenusAuthorship = subGenusAuthorship;
1263
    }
1264

    
1265

    
1266
    @Override
1267
    public String getNameApprobation(){
1268
        return this.nameApprobation;
1269
    }
1270

    
1271
    /**
1272
     * @see  #getNameApprobation()
1273
     */
1274
    @Override
1275
    public void setNameApprobation(String nameApprobation){
1276
        this.nameApprobation = nameApprobation;
1277
    }
1278

    
1279
    //************ Zoological Name
1280

    
1281

    
1282
    @Override
1283
    public String getBreed(){
1284
        return this.breed;
1285
    }
1286
    /**
1287
     * @see  #getBreed()
1288
     */
1289
    @Override
1290
    public void setBreed(String breed){
1291
        this.breed = StringUtils.isBlank(breed) ? null : breed;
1292
    }
1293

    
1294

    
1295
    @Override
1296
    public Integer getPublicationYear() {
1297
        return publicationYear;
1298
    }
1299
    /**
1300
     * @see  #getPublicationYear()
1301
     */
1302
    @Override
1303
    public void setPublicationYear(Integer publicationYear) {
1304
        this.publicationYear = publicationYear;
1305
    }
1306

    
1307

    
1308
    @Override
1309
    public Integer getOriginalPublicationYear() {
1310
        return originalPublicationYear;
1311
    }
1312
    /**
1313
     * @see  #getOriginalPublicationYear()
1314
     */
1315
    @Override
1316
    public void setOriginalPublicationYear(Integer originalPublicationYear) {
1317
        this.originalPublicationYear = originalPublicationYear;
1318
    }
1319

    
1320
    // **** Cultivar Name ************
1321

    
1322

    
1323
    @Override
1324
    public String getCultivarName(){
1325
        return this.cultivarName;
1326
    }
1327

    
1328
    /**
1329
     * @see  #getCultivarName()
1330
     */
1331
    @Override
1332
    public void setCultivarName(String cultivarName){
1333
        this.cultivarName = StringUtils.isBlank(cultivarName) ? null : cultivarName;
1334
    }
1335

    
1336
    // **************** Fungus Name
1337
    @Override
1338
    public boolean isAnamorphic(){
1339
        return this.anamorphic;
1340
    }
1341

    
1342
    /**
1343
     * @see  #isAnamorphic()
1344
     */
1345
    @Override
1346
    public void setAnamorphic(boolean anamorphic){
1347
        this.anamorphic = anamorphic;
1348
    }
1349

    
1350

    
1351
// **************** ADDER / REMOVE *************************/
1352

    
1353
    /**
1354
     * Adds the given {@link HybridRelationship hybrid relationship} to the set
1355
     * of {@link #getHybridRelationships() hybrid relationships} of both non-viral names
1356
     * involved in this hybrid relationship. One of both non-viral names
1357
     * must be <i>this</i> non-viral name otherwise no addition will be carried
1358
     * out. The {@link eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedTo() child
1359
     * non viral taxon name} must be a hybrid, which means that one of its four hybrid flags must be set.
1360
     *
1361
     * @param relationship  the hybrid relationship to be added
1362
     * @see                 #isHybridFormula()
1363
     * @see                 #isMonomHybrid()
1364
     * @see                 #isBinomHybrid()
1365
     * @see                 #isTrinomHybrid()
1366
     * @see                 #getHybridRelationships()
1367
     * @see                 #getParentRelationships()
1368
     * @see                 #getChildRelationships()
1369
     * @see                 #addRelationship(RelationshipBase)
1370
     * @throws              IllegalArgumentException
1371
     */
1372
    protected void addHybridRelationship(HybridRelationship rel) {
1373
        if (rel!=null && rel.getHybridName().equals(this)){
1374
            this.hybridChildRelations.add(rel);
1375
        }else if(rel!=null && rel.getParentName().equals(this)){
1376
            this.hybridParentRelations.add(rel);
1377
        }else{
1378
            throw new IllegalArgumentException("Hybrid relationship is either null or the relationship does not reference this name");
1379
        }
1380
    }
1381

    
1382

    
1383
    /**
1384
     * Removes one {@link HybridRelationship hybrid relationship} from the set of
1385
     * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1386
     * is involved. The hybrid relationship will also be removed from the set
1387
     * belonging to the second botanical taxon name involved.
1388
     *
1389
     * @param  relationship  the hybrid relationship which should be deleted from the corresponding sets
1390
     * @see                  #getHybridRelationships()
1391
     */
1392
    @Override
1393
    public void removeHybridRelationship(HybridRelationship hybridRelation) {
1394
        if (hybridRelation == null) {
1395
            return;
1396
        }
1397

    
1398
        TaxonName parent = hybridRelation.getParentName();
1399
        TaxonName child = hybridRelation.getHybridName();
1400
        if (this.equals(parent)){
1401
            this.hybridParentRelations.remove(hybridRelation);
1402
            child.hybridChildRelations.remove(hybridRelation);
1403
            hybridRelation.setHybridName(null);
1404
            hybridRelation.setParentName(null);
1405
        }
1406
        if (this.equals(child)){
1407
            parent.hybridParentRelations.remove(hybridRelation);
1408
            this.hybridChildRelations.remove(hybridRelation);
1409
            hybridRelation.setHybridName(null);
1410
            hybridRelation.setParentName(null);
1411
        }
1412
    }
1413

    
1414
//********* METHODS **************************************/
1415

    
1416
    @Override
1417
    public INameCacheStrategy getCacheStrategy() {
1418
        rectifyNameCacheStrategy();
1419
        return this.cacheStrategy;
1420
    }
1421

    
1422
    @Override
1423
    public String generateFullTitle(){
1424
        if (getCacheStrategy() == null){
1425
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1426
            return null;
1427
        }else{
1428
            return cacheStrategy.getFullTitleCache(this);
1429
        }
1430
    }
1431

    
1432

    
1433
    @Override
1434
    public void setFullTitleCache(String fullTitleCache){
1435
        setFullTitleCache(fullTitleCache, PROTECTED);
1436
    }
1437

    
1438
    @Override
1439
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1440
        fullTitleCache = getTruncatedCache(fullTitleCache);
1441
        this.fullTitleCache = fullTitleCache;
1442
        this.setProtectedFullTitleCache(protectCache);
1443
    }
1444

    
1445
   /** Checks if this name is an autonym.<BR>
1446
    * An autonym is a taxon name that has equal specific and infra specific epithets.<BR>
1447
    * {@link http://ibot.sav.sk/icbn/frameset/0010Ch2Sec1a006.htm#6.8. Vienna Code §6.8}
1448
    * or a taxon name that has equal generic and infrageneric epithets (A22.2).<BR>
1449
    * Only relevant for botanical names.
1450
    * @return true, if name has Rank, Rank is below species and species epithet equals infraSpeciesEpithtet, else false
1451
    */
1452
    @Override
1453
    @Transient
1454
    public boolean isAutonym(){
1455
        if (isBotanical()){
1456
            if (this.getRank() != null && this.getSpecificEpithet() != null && this.getInfraSpecificEpithet() != null &&
1457
                this.isInfraSpecific() && this.getSpecificEpithet().trim().equals(this.getInfraSpecificEpithet().trim())){
1458
                return true;
1459
            }else if (this.getRank() != null && this.getGenusOrUninomial() != null && this.getInfraGenericEpithet() != null &&
1460
                    this.isInfraGeneric() && this.getGenusOrUninomial().trim().equals(this.getInfraGenericEpithet().trim())){
1461
                return true;
1462
            }else{
1463
                return false;
1464
            }
1465
        }else{
1466
            return false;
1467
        }
1468
    }
1469

    
1470

    
1471
    @Override
1472
    @Transient
1473
    public List<TaggedText> getTaggedName(){
1474
        INameCacheStrategy strat = getCacheStrategy();
1475
        return strat.getTaggedTitle(this);
1476
    }
1477

    
1478
    @Override
1479
    @Transient
1480
    public List<TaggedText> getTaggedFullTitle() {
1481
        INameCacheStrategy strat = getCacheStrategy();
1482
        return strat.getTaggedFullTitle(this);
1483
    }
1484

    
1485
    @Override
1486
    @Transient
1487
    public String getFullTitleCache(){
1488
        if (protectedFullTitleCache){
1489
            return this.fullTitleCache;
1490
        }
1491
        updateAuthorshipCache();
1492
        if (fullTitleCache == null ){
1493
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1494
        }
1495
        return fullTitleCache;
1496
    }
1497

    
1498

    
1499
    @Override
1500
    public String getTitleCache(){
1501
        if(!protectedTitleCache) {
1502
            updateAuthorshipCache();
1503
        }
1504
        return super.getTitleCache();
1505
    }
1506

    
1507
    @Override
1508
    public void setTitleCache(String titleCache, boolean protectCache){
1509
        super.setTitleCache(titleCache, protectCache);
1510
    }
1511

    
1512
    /**
1513
     * Returns the concatenated and formated authorteams string including
1514
     * basionym and combination authors of <i>this</i> non viral taxon name.
1515
     * If the protectedAuthorshipCache flag is set this method returns the
1516
     * string stored in the the authorshipCache attribute, otherwise it
1517
     * generates the complete authorship string, returns it and stores it in
1518
     * the authorshipCache attribute.
1519
     *
1520
     * @return  the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
1521
     * @see     #generateAuthorship()
1522
     */
1523
    @Override
1524
    @Transient
1525
    public String getAuthorshipCache() {
1526
        if (protectedAuthorshipCache){
1527
            return this.authorshipCache;
1528
        }
1529
        if (this.authorshipCache == null ){
1530
            this.authorshipCache = generateAuthorship();
1531
        }else{
1532
            //TODO get isDirty of authors, make better if possible
1533
            this.setAuthorshipCache(generateAuthorship(), protectedAuthorshipCache); //throw change event to inform higher caches
1534

    
1535
        }
1536
        return authorshipCache;
1537
    }
1538

    
1539

    
1540

    
1541

    
1542

    
1543

    
1544
    /**
1545
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1546
     * flag to <code>true</code>.
1547
     *
1548
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1549
     * @see    #getAuthorshipCache()
1550
     */
1551
    @Override
1552
    public void setAuthorshipCache(String authorshipCache) {
1553
        setAuthorshipCache(authorshipCache, true);
1554
    }
1555

    
1556

    
1557
    /**
1558
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1559
     *
1560
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1561
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1562
     * the flag is set to <code>false</code>.
1563
     * @see    #getAuthorshipCache()
1564
     */
1565
    @Override
1566
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1567
        this.authorshipCache = authorshipCache;
1568
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1569
    }
1570

    
1571
    /**
1572
     * Generates and returns a concatenated and formated author team string
1573
     * including basionym and combination authors of <i>this</i> taxon name
1574
     * according to the strategy defined in
1575
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName) INonViralNameCacheStrategy}.
1576
     *
1577
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1578
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName)
1579
     */
1580
    @Override
1581
    public String generateAuthorship(){
1582
        if (getCacheStrategy() == null){
1583
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1584
            return null;
1585
        }else{
1586
            return cacheStrategy.getAuthorshipCache(this);
1587
        }
1588
    }
1589

    
1590

    
1591

    
1592
    /**
1593
     * Tests if the given name has any authors.
1594
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1595
     */
1596
    @Override
1597
    public boolean hasAuthors() {
1598
        return (this.getCombinationAuthorship() != null ||
1599
                this.getExCombinationAuthorship() != null ||
1600
                this.getBasionymAuthorship() != null ||
1601
                this.getExBasionymAuthorship() != null);
1602
    }
1603

    
1604
    /**
1605
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1606
     * @return
1607
     */
1608
    @Override
1609
    public String computeCombinationAuthorNomenclaturalTitle() {
1610
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1611
    }
1612

    
1613
    /**
1614
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1615
     * @return
1616
     */
1617
    @Override
1618
    public String computeBasionymAuthorNomenclaturalTitle() {
1619
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1620
    }
1621

    
1622

    
1623
    /**
1624
     * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1625
     * @return
1626
     */
1627
    @Override
1628
    public String computeExCombinationAuthorNomenclaturalTitle() {
1629
        return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1630
    }
1631

    
1632
    /**
1633
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1634
     * @return
1635
     */
1636
    @Override
1637
    public String computeExBasionymAuthorNomenclaturalTitle() {
1638
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1639
    }
1640

    
1641
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1642
        if (author == null){
1643
            return null;
1644
        }else{
1645
            return author.getNomenclaturalTitle();
1646
        }
1647
    }
1648

    
1649
    /**
1650
     * Returns the set of all {@link NameRelationship name relationships}
1651
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1652
     * in some name relationships or target in some others.
1653
     *
1654
     * @see    #getRelationsToThisName()
1655
     * @see    #getRelationsFromThisName()
1656
     * @see    #addNameRelationship(NameRelationship)
1657
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1658
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1659
     */
1660
    @Override
1661
    @Transient
1662
    public Set<NameRelationship> getNameRelations() {
1663
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1664
        rels.addAll(getRelationsFromThisName());
1665
        rels.addAll(getRelationsToThisName());
1666
        return rels;
1667
    }
1668

    
1669
    @Override
1670
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1671
        return addRelationshipToName(toName, type, null, null, ruleConsidered, codeEdition);
1672
    }
1673

    
1674
    @Override
1675
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type){
1676
        return addRelationshipToName(toName, type, null, null, null, null);
1677
    }
1678

    
1679
    @Override
1680
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1681
        if (toName == null){
1682
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1683
        }
1684
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered, codeEdition);
1685
        return rel;
1686
    }
1687

    
1688
    @Override
1689
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1690
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1691
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered, codeEdition);
1692
    }
1693

    
1694
    @Override
1695
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1696
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered, codeEdition);
1697
    }
1698

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

    
1739
        TaxonName fromName = nameRelation.getFromName();
1740
        TaxonName toName = nameRelation.getToName();
1741

    
1742
        if (nameRelation != null) {
1743
            nameRelation.setToName(null);
1744
            nameRelation.setFromName(null);
1745
        }
1746

    
1747
        if (fromName != null) {
1748
            fromName.removeNameRelationship(nameRelation);
1749
        }
1750

    
1751
        if (toName != null) {
1752
            toName.removeNameRelationship(nameRelation);
1753
        }
1754

    
1755
        this.relationsToThisName.remove(nameRelation);
1756
        this.relationsFromThisName.remove(nameRelation);
1757
    }
1758

    
1759
    @Override
1760
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1761
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1762
//		nameRelationships.addAll(this.getNameRelations());
1763
        nameRelationships.addAll(this.getRelationsFromThisName());
1764
        nameRelationships.addAll(this.getRelationsToThisName());
1765
        for(NameRelationship nameRelationship : nameRelationships) {
1766
            // remove name relationship from this side
1767
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1768
                this.removeNameRelationship(nameRelationship);
1769
            }
1770
        }
1771
    }
1772

    
1773
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1774

    
1775
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1776
        for(NameRelationship nameRelationship : tmpRels) {
1777
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1778
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1779
                if (type == null || type.equals(nameRelationship.getType())){
1780
                    this.removeNameRelationship(nameRelationship);
1781
                }
1782
            }
1783
        }
1784
    }
1785

    
1786

    
1787
    /**
1788
     * If relation is of type NameRelationship, addNameRelationship is called;
1789
     * if relation is of type HybridRelationship addHybridRelationship is called,
1790
     * otherwise an IllegalArgumentException is thrown.
1791
     *
1792
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1793
     * @see    	   		#addNameRelationship(NameRelationship)
1794
     * @see    	   		#getNameRelations()
1795
     * @see    	   		NameRelationship
1796
     * @see    	   		RelationshipBase
1797
     * @see             #addHybridRelationship(HybridRelationship)
1798

    
1799
     * @deprecated to be used by RelationshipBase only
1800
     */
1801
    @Deprecated
1802
    @Override
1803
    public void addRelationship(RelationshipBase relation) {
1804
        if (relation instanceof NameRelationship){
1805
            addNameRelationship((NameRelationship)relation);
1806

    
1807
        }else if (relation instanceof HybridRelationship){
1808
            addHybridRelationship((HybridRelationship)relation);
1809
        }else{
1810
            logger.warn("Relationship not of type NameRelationship!");
1811
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1812
        }
1813
    }
1814

    
1815
    /**
1816
     * Returns the set of all {@link NameRelationship name relationships}
1817
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1818
     *
1819
     * @see    #getNameRelations()
1820
     * @see    #getRelationsToThisName()
1821
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1822
     */
1823
    @Override
1824
    public Set<NameRelationship> getRelationsFromThisName() {
1825
        if(relationsFromThisName == null) {
1826
            this.relationsFromThisName = new HashSet<>();
1827
        }
1828
        return relationsFromThisName;
1829
    }
1830

    
1831
    /**
1832
     * Returns the set of all {@link NameRelationship name relationships}
1833
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1834
     *
1835
     * @see    #getNameRelations()
1836
     * @see    #getRelationsFromThisName()
1837
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1838
     */
1839
    @Override
1840
    public Set<NameRelationship> getRelationsToThisName() {
1841
        if(relationsToThisName == null) {
1842
            this.relationsToThisName = new HashSet<>();
1843
        }
1844
        return relationsToThisName;
1845
    }
1846

    
1847
    /**
1848
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1849
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1850
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1851
     * and the nomenclatural code rule considered.
1852
     *
1853
     * @see     NomenclaturalStatus
1854
     * @see     NomenclaturalStatusType
1855
     */
1856
    @Override
1857
    public Set<NomenclaturalStatus> getStatus() {
1858
        if(status == null) {
1859
            this.status = new HashSet<>();
1860
        }
1861
        return status;
1862
    }
1863

    
1864
    /**
1865
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1866
     * to <i>this</i> taxon name's set of nomenclatural status.
1867
     *
1868
     * @param  nomStatus  the nomenclatural status to be added
1869
     * @see 			  #getStatus()
1870
     */
1871
    @Override
1872
    public void addStatus(NomenclaturalStatus nomStatus) {
1873
        this.status.add(nomStatus);
1874
    }
1875
    @Override
1876
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1877
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1878
        this.status.add(newStatus);
1879
        return newStatus;
1880
    }
1881

    
1882
    /**
1883
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1884
     * Type and ruleConsidered attributes of the nomenclatural status object
1885
     * will be nullified.
1886
     *
1887
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1888
     * @see     		  #getStatus()
1889
     */
1890
    @Override
1891
    public void removeStatus(NomenclaturalStatus nomStatus) {
1892
        //TODO to be implemented?
1893
        logger.warn("not yet fully implemented?");
1894
        this.status.remove(nomStatus);
1895
    }
1896

    
1897
    public void setStatus(Set<NomenclaturalStatus> nomStatus) throws SetterAdapterException {
1898
        new EntityCollectionSetterAdapter<TaxonName, NomenclaturalStatus>(TaxonName.class, NomenclaturalStatus.class, "status", "addStatus", "removeStatus").setCollection(this, status);
1899
   }
1900

    
1901

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

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

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

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

    
1971

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

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

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

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

    
2047
    /**
2048
     *
2049
     * @param direction
2050
     * @param type
2051
     * @return
2052
     */
2053
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
2054
        return getRelatedNames(relationsWithThisName(direction), type);
2055
    }
2056

    
2057
    /**
2058
     * @param rels
2059
     * @param type
2060
     * @return
2061
     */
2062
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2063
        Set<TaxonName> result = new HashSet<>();
2064
        for (NameRelationship rel : rels){
2065
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2066
                TaxonName basionym = rel.getFromName();
2067
                result.add(basionym);
2068
            }
2069
        }
2070
        return result;
2071
    }
2072

    
2073
    @Override
2074
    @Deprecated
2075
    public NameRelationship addOriginalSpelling(TaxonName originalSpelling, Reference citation, String microcitation){
2076
        if (originalSpelling != null){
2077
            return originalSpelling.addRelationshipToName(this, NameRelationshipType.ORIGINAL_SPELLING(), citation, microcitation, null, null);
2078
        }else{
2079
            return null;
2080
        }
2081
    }
2082

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

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

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

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

    
2166

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

    
2192

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

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

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

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

    
2226
//*************** nom ref/source *******************/
2227

    
2228
    @Override
2229
    public Reference getNomenclaturalReference(){
2230
        //#6581
2231
        return this.nomenclaturalReference;
2232
//        if (this.nomenclaturalSource == null){
2233
//            return null;
2234
//        }
2235
//        return this.nomenclaturalSource.getCitation();
2236
    }
2237

    
2238
    @Override
2239
    public DescriptionElementSource getNomenclaturalSource(){
2240
        return this.nomenclaturalSource;
2241
    }
2242

    
2243
    protected DescriptionElementSource getNomenclaturalSource(boolean createIfNotExist){
2244
        if (this.nomenclaturalSource == null){
2245
            if (!createIfNotExist){
2246
                return null;
2247
            }
2248
            this.nomenclaturalSource = DescriptionElementSource.NewInstance(OriginalSourceType.NomenclaturalReference);
2249
        }
2250
        return this.nomenclaturalSource;
2251
    }
2252

    
2253
    /**
2254
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2255
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2256
     * as it is obviously used for nomenclatural purposes.
2257
     *
2258
     * Shortcut to set the nomenclatural reference.
2259
     *
2260
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2261
     * @see  #getNomenclaturalReference()
2262
     */
2263

    
2264
    @Override
2265
    public void setNomenclaturalReference(Reference nomenclaturalReference){
2266
        //#6581
2267
        this.nomenclaturalReference = nomenclaturalReference;
2268
//        getNomenclaturalSource(true).setCitation(nomenclaturalReference);
2269
//        checkNullSource();
2270
    }
2271

    
2272
    @Override
2273
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2274
        setNomenclaturalReference(CdmBase.deproxy(nomenclaturalReference, Reference.class));
2275
    }
2276

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

    
2306
    //#6581
2307
    private void checkNullSource() {
2308
        if (this.nomenclaturalSource != null && this.nomenclaturalSource.checkEmpty()){
2309
            this.nomenclaturalSource = null;
2310
        }
2311
    }
2312

    
2313

    
2314
    @Override
2315
    public void setNomenclaturalSource(DescriptionElementSource nomenclaturalSource) throws IllegalArgumentException {
2316
        if (nomenclaturalSource != null && !OriginalSourceType.NomenclaturalReference.equals(nomenclaturalSource.getType()) ){
2317
            throw new IllegalArgumentException("Nomenclatural source must be of type " + OriginalSourceType.NomenclaturalReference.getMessage());
2318
        }
2319
        this.nomenclaturalSource = nomenclaturalSource;
2320
    }
2321

    
2322
    /**
2323
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2324
     * The appended phrase is a non-atomised addition to a name. It is
2325
     * not ruled by a nomenclatural code.
2326
     */
2327
    @Override
2328
    public String getAppendedPhrase(){
2329
        return this.appendedPhrase;
2330
    }
2331

    
2332
    /**
2333
     * @see  #getAppendedPhrase()
2334
     */
2335
    @Override
2336
    public void setAppendedPhrase(String appendedPhrase){
2337
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2338
    }
2339

    
2340
    @Override
2341
    public int getParsingProblem(){
2342
        return this.parsingProblem;
2343
    }
2344

    
2345
    @Override
2346
    public void setParsingProblem(int parsingProblem){
2347
        this.parsingProblem = parsingProblem;
2348
    }
2349

    
2350
    @Override
2351
    public void addParsingProblem(ParserProblem problem){
2352
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2353
    }
2354

    
2355
    @Override
2356
    public void removeParsingProblem(ParserProblem problem) {
2357
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2358
    }
2359

    
2360
    /**
2361
     * @param warnings
2362
     */
2363
    @Override
2364
    public void addParsingProblems(int problems){
2365
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2366
    }
2367

    
2368
    @Override
2369
    public boolean hasProblem(){
2370
        return parsingProblem != 0;
2371
    }
2372

    
2373
    @Override
2374
    public boolean hasProblem(ParserProblem problem) {
2375
        return getParsingProblems().contains(problem);
2376
    }
2377

    
2378
    @Override
2379
    public int getProblemStarts(){
2380
        return this.problemStarts;
2381
    }
2382

    
2383
    @Override
2384
    public void setProblemStarts(int start) {
2385
        this.problemStarts = start;
2386
    }
2387

    
2388
    @Override
2389
    public int getProblemEnds(){
2390
        return this.problemEnds;
2391
    }
2392

    
2393
    @Override
2394
    public void setProblemEnds(int end) {
2395
        this.problemEnds = end;
2396
    }
2397

    
2398
//*********************** TYPE DESIGNATION *********************************************//
2399

    
2400
    /**
2401
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2402
     * to <i>this</i> taxon name.
2403
     * @see     NameTypeDesignation
2404
     * @see     SpecimenTypeDesignation
2405
     */
2406
    @Override
2407
    public Set<TypeDesignationBase> getTypeDesignations() {
2408
        if(typeDesignations == null) {
2409
            this.typeDesignations = new HashSet<>();
2410
        }
2411
        return typeDesignations;
2412
    }
2413

    
2414
    /**
2415
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2416
     * <i>this</i> taxon name. The type designation itself will be nullified.
2417
     *
2418
     * @param  typeDesignation  the type designation which should be deleted
2419
     */
2420
    @Override
2421
    @SuppressWarnings("deprecation")
2422
    public void removeTypeDesignation(TypeDesignationBase<?> typeDesignation) {
2423
        this.typeDesignations.remove(typeDesignation);
2424
        typeDesignation.removeTypifiedName(this);
2425
    }
2426

    
2427
    /**
2428
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2429
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2430
     * "species" or below. The specimen type designations include all the
2431
     * specimens on which the typification of this name is based (which are
2432
     * exclusively used to typify taxon names belonging to the same
2433
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2434
     * belongs) and eventually the status of these designations.
2435
     *
2436
     * @see     SpecimenTypeDesignation
2437
     * @see     NameTypeDesignation
2438
     * @see     HomotypicalGroup
2439
     */
2440
    @Override
2441
    @Transient
2442
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2443
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2444
    }
2445

    
2446
//*********************** NAME TYPE DESIGNATION *********************************************//
2447

    
2448
    /**
2449
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2450
     * to <i>this</i> taxon name the rank of which must be above "species".
2451
     * The name type designations include all the taxon names used to typify
2452
     * <i>this</i> taxon name and eventually the rejected or conserved status
2453
     * of these designations.
2454
     *
2455
     * @see     NameTypeDesignation
2456
     * @see     SpecimenTypeDesignation
2457
     */
2458
    @Override
2459
    @Transient
2460
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2461
        Set<NameTypeDesignation> result = new HashSet<>();
2462
        for (TypeDesignationBase<?> typeDesignation : this.typeDesignations){
2463
            if (typeDesignation.isInstanceOf(NameTypeDesignation.class)){
2464
                result.add(CdmBase.deproxy(typeDesignation, NameTypeDesignation.class));
2465
            }
2466
        }
2467
        return result;
2468
    }
2469

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

    
2506
    /**
2507
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2508
     * to <i>this</i> taxon name's set of type designations.
2509
     *
2510
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2511
     * @param  citation					the reference for this new designation
2512
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2513
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2514
     * @param  status                   the name type designation status
2515
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2516
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2517
     * @return
2518
     * @see 			  				#getNameTypeDesignations()
2519
     * @see 			  				NameTypeDesignation
2520
     * @see 			  				TypeDesignationBase#isNotDesignated()
2521
     */
2522
    @Override
2523
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2524
                Reference citation,
2525
                String citationMicroReference,
2526
                String originalNameString,
2527
                NameTypeDesignationStatus status,
2528
                boolean addToAllHomotypicNames) {
2529
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2530
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2531
        return nameTypeDesignation;
2532
    }
2533

    
2534
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2535

    
2536
    /**
2537
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2538
     * that typify <i>this</i> taxon name.
2539
     */
2540
    @Override
2541
    @Transient
2542
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2543
        Set<SpecimenTypeDesignation> result = new HashSet<>();
2544
        for (TypeDesignationBase<?> typeDesignation : this.typeDesignations){
2545
            if (typeDesignation.isInstanceOf(SpecimenTypeDesignation.class)){
2546
                result.add(CdmBase.deproxy(typeDesignation, SpecimenTypeDesignation.class));
2547
            }
2548
        }
2549
        return result;
2550
    }
2551

    
2552

    
2553
    /**
2554
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2555
     * to <i>this</i> taxon name's set of type designations.
2556
     *
2557
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2558
     * @param  status					the specimen type designation status
2559
     * @param  citation					the reference for this new specimen type designation
2560
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2561
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2562
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2563
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2564
     * 									added to all taxon names of the homotypical group the typified
2565
     * 									taxon name belongs to
2566
     * @return
2567
     * @see 			  				#getSpecimenTypeDesignations()
2568
     * @see 			  				SpecimenTypeDesignationStatus
2569
     * @see 			  				SpecimenTypeDesignation
2570
     * @see 			  				TypeDesignationBase#isNotDesignated()
2571
     */
2572
    @Override
2573
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2574
                SpecimenTypeDesignationStatus status,
2575
                Reference citation,
2576
                String citationMicroReference,
2577
                String originalNameString,
2578
                boolean isNotDesignated,
2579
                boolean addToAllHomotypicNames) {
2580
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2581
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2582
        return specimenTypeDesignation;
2583
    }
2584

    
2585
    @Override
2586
    public TextualTypeDesignation addTextualTypeDesignation(
2587
                String text,
2588
                Language language,
2589
                boolean isVerbatim,
2590
                Reference citation,
2591
                String citationMicroReference,
2592
                String originalNameString,
2593
                boolean addToAllHomotypicNames) {
2594
        TextualTypeDesignation textualTypeDesignation = TextualTypeDesignation.NewInstance(text, language, isVerbatim, citation, citationMicroReference, originalNameString);
2595
        addTypeDesignation(textualTypeDesignation, addToAllHomotypicNames);
2596
        return textualTypeDesignation;
2597
    }
2598

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

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

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

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

    
2654

    
2655

    
2656
//*********************** HOMOTYPICAL GROUP *********************************************//
2657

    
2658

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

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

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

    
2693

    
2694

    
2695
// *************************************************************************//
2696

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

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

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

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

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

    
2789

    
2790
    }
2791

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

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

    
2834
//***************** REGISTRATION *****************/
2835

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

    
2841

    
2842
// ************* RELATIONSHIPS *****************************/
2843

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

    
2859
    @Override
2860
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2861
        return addHybridParent(parentName, type, null, null, ruleConsidered, null);
2862
    }
2863

    
2864
    @Override
2865
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, Reference reference,
2866
            String microReference, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
2867
        return new HybridRelationship(this, parentName, type, reference, microReference, ruleConsidered, codeEdition);
2868
    }
2869

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

    
2892
    @Override
2893
    public void removeHybridChild(INonViralName child) {
2894
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2895
        hybridRelationships.addAll(this.getHybridChildRelations());
2896
        hybridRelationships.addAll(this.getHybridParentRelations());
2897
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2898
            // remove name relationship from this side
2899
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2900
                this.removeHybridRelationship(hybridRelationship);
2901
            }
2902
        }
2903
    }
2904

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

    
2918

    
2919

    
2920
// *********** DESCRIPTIONS *************************************
2921

    
2922
    /**
2923
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2924
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2925
     * concerning the taxon name like for instance the content of its first
2926
     * publication (protolog) or a picture of this publication.
2927
     *
2928
     * @see	#addDescription(TaxonNameDescription)
2929
     * @see	#removeDescription(TaxonNameDescription)
2930
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2931
     */
2932
    @Override
2933
    public Set<TaxonNameDescription> getDescriptions() {
2934
        return descriptions;
2935
    }
2936

    
2937
    /**
2938
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2939
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2940
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2941
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2942
     *
2943
     * @param  description  the taxon name description to be added
2944
     * @see					#getDescriptions()
2945
     * @see 			  	#removeDescription(TaxonNameDescription)
2946
     */
2947
    @Override
2948
    public void addDescription(TaxonNameDescription description) {
2949
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2950
        ReflectionUtils.makeAccessible(field);
2951
        ReflectionUtils.setField(field, description, this);
2952
        descriptions.add(description);
2953
    }
2954
    /**
2955
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2956
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2957
     * of the description itself will be set to "null".
2958
     *
2959
     * @param  description  the taxon name description which should be removed
2960
     * @see     		  	#getDescriptions()
2961
     * @see     		  	#addDescription(TaxonNameDescription)
2962
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
2963
     */
2964
    @Override
2965
    public void removeDescription(TaxonNameDescription description) {
2966
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2967
        ReflectionUtils.makeAccessible(field);
2968
        ReflectionUtils.setField(field, description, null);
2969
        descriptions.remove(description);
2970
    }
2971

    
2972
// *********** HOMOTYPIC GROUP METHODS **************************************************
2973

    
2974
    @Override
2975
    @Transient
2976
    public void mergeHomotypicGroups(TaxonName name){
2977
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2978
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2979
        name.setHomotypicalGroup(this.homotypicalGroup);
2980
    }
2981

    
2982
    /**
2983
     * Returns the boolean value indicating whether a given taxon name belongs
2984
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
2985
     * or not (false). Returns "true" only if the homotypical groups of both
2986
     * taxon names exist and if they are identical.
2987
     *
2988
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
2989
     * @return  			   the boolean value of the check
2990
     * @see     			   HomotypicalGroup
2991
     */
2992
    @Override
2993
    @Transient
2994
    public boolean isHomotypic(TaxonName homoTypicName) {
2995
        if (homoTypicName == null) {
2996
            return false;
2997
        }
2998
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
2999
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
3000
            return false;
3001
        }
3002
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
3003
            return true;
3004
        }
3005
        return false;
3006
    }
3007

    
3008

    
3009
    /**
3010
     * Checks whether name is a basionym for ALL names
3011
     * in its homotypical group.
3012
     * Returns <code>false</code> if there are no other names in the group
3013
     * @param name
3014
     * @return
3015
     */
3016
    @Override
3017
    @Transient
3018
    public boolean isGroupsBasionym() {
3019
    	if (homotypicalGroup == null){
3020
    		homotypicalGroup = HomotypicalGroup.NewInstance();
3021
    		homotypicalGroup.addTypifiedName(this);
3022
    	}
3023
        Set<TaxonName> typifiedNames = homotypicalGroup.getTypifiedNames();
3024

    
3025
        // Check whether there are any other names in the group
3026
        if (typifiedNames.size() == 1) {
3027
                return false;
3028
        }
3029

    
3030
        for (TaxonName taxonName : typifiedNames) {
3031
                if (!taxonName.equals(this)) {
3032
                        if (! isBasionymFor(taxonName)) {
3033
                                return false;
3034
                        }
3035
                }
3036
        }
3037
        return true;
3038
    }
3039

    
3040
    /**
3041
     * Checks whether a basionym relationship exists between fromName and toName.
3042
     *
3043
     * @param fromName
3044
     * @param toName
3045
     * @return
3046
     */
3047
    @Override
3048
    @Transient
3049
    public boolean isBasionymFor(TaxonName newCombinationName) {
3050
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3051
            for (NameRelationship relation : relations) {
3052
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3053
                                    relation.getFromName().equals(this)) {
3054
                            return true;
3055
                    }
3056
            }
3057
            return false;
3058
    }
3059

    
3060
    /**
3061
     * Creates a basionym relationship to all other names in this names homotypical
3062
     * group.
3063
     *
3064
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3065
     */
3066
    @Override
3067
    @Transient
3068
    public void makeGroupsBasionym() {
3069
        this.homotypicalGroup.setGroupBasionym(this);
3070
    }
3071

    
3072

    
3073
//*********  Rank comparison shortcuts   ********************//
3074
    /**
3075
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3076
     * taxon name is higher than the genus rank (true) or not (false).
3077
     * Suprageneric non viral names are monomials.
3078
     * Returns false if rank is null.
3079
     *
3080
     * @see  #isGenus()
3081
     * @see  #isInfraGeneric()
3082
     * @see  #isSpecies()
3083
     * @see  #isInfraSpecific()
3084
     */
3085
    @Override
3086
    @Transient
3087
    public boolean isSupraGeneric() {
3088
        if (rank == null){
3089
            return false;
3090
        }
3091
        return getRank().isSupraGeneric();
3092
    }
3093
    /**
3094
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3095
     * taxon name is the genus rank (true) or not (false). Non viral names with
3096
     * genus rank are monomials. Returns false if rank is null.
3097
     *
3098
     * @see  #isSupraGeneric()
3099
     * @see  #isInfraGeneric()
3100
     * @see  #isSpecies()
3101
     * @see  #isInfraSpecific()
3102
     */
3103
    @Override
3104
    @Transient
3105
    public boolean isGenus() {
3106
        if (rank == null){
3107
            return false;
3108
        }
3109
        return getRank().isGenus();
3110
    }
3111

    
3112
    @Override
3113
    @Transient
3114
    public boolean isGenusOrSupraGeneric() {
3115
        return isGenus()|| isSupraGeneric();
3116
    }
3117
    /**
3118
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3119
     * taxon name is higher than the species rank and lower than the
3120
     * genus rank (true) or not (false). Infrageneric non viral names are
3121
     * binomials. Returns false if rank is null.
3122
     *
3123
     * @see  #isSupraGeneric()
3124
     * @see  #isGenus()
3125
     * @see  #isSpecies()
3126
     * @see  #isInfraSpecific()
3127
     */
3128
    @Override
3129
    @Transient
3130
    public boolean isInfraGeneric() {
3131
        if (rank == null){
3132
            return false;
3133
        }
3134
        return getRank().isInfraGeneric();
3135
    }
3136

    
3137
    /**
3138
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3139
     * taxon name is higher than the species rank (true) or not (false).
3140
     * Returns false if rank is null.
3141
     *
3142
     * @see  #isGenus()
3143
     * @see  #isInfraGeneric()
3144
     * @see  #isSpecies()
3145
     * @see  #isInfraSpecific()
3146
     */
3147
    @Override
3148
    @Transient
3149
    public boolean isSupraSpecific(){
3150
        if (rank == null) {
3151
            return false;
3152
        }
3153
        return getRank().isHigher(Rank.SPECIES());
3154
    }
3155

    
3156
    /**
3157
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3158
     * taxon name is the species rank (true) or not (false). Non viral names
3159
     * with species rank are binomials.
3160
     * Returns false if rank is null.
3161
     *
3162
     * @see  #isSupraGeneric()
3163
     * @see  #isGenus()
3164
     * @see  #isInfraGeneric()
3165
     * @see  #isInfraSpecific()
3166
     */
3167
    @Override
3168
    @Transient
3169
    public boolean isSpecies() {
3170
        if (rank == null){
3171
            return false;
3172
        }
3173
        return getRank().isSpecies();
3174
    }
3175
    /**
3176
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3177
     * taxon name is lower than the species rank (true) or not (false).
3178
     * Infraspecific non viral names are trinomials.
3179
     * Returns false if rank is null.
3180
     *
3181
     * @see  #isSupraGeneric()
3182
     * @see  #isGenus()
3183
     * @see  #isInfraGeneric()
3184
     * @see  #isSpecies()
3185
     */
3186
    @Override
3187
    @Transient
3188
    public boolean isInfraSpecific() {
3189
        if (rank == null){
3190
            return false;
3191
        }
3192
        return getRank().isInfraSpecific();
3193
    }
3194

    
3195
    /**
3196
     * Returns true if this name's rank indicates a rank that aggregates species like species
3197
     * aggregates or species groups, false otherwise. This methods currently returns false
3198
     * for all user defined ranks.
3199
     *
3200
     *@see Rank#isSpeciesAggregate()
3201
     *
3202
     * @return
3203
     */
3204
    @Override
3205
    @Transient
3206
    public boolean isSpeciesAggregate() {
3207
        if (rank == null){
3208
            return false;
3209
        }
3210
        return getRank().isSpeciesAggregate();
3211
    }
3212

    
3213

    
3214
    /**
3215
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3216
     * the construction of <i>this</i> taxon name since there is no specific
3217
     * nomenclatural code defined. The real implementention takes place in the
3218
     * subclasses {@link IBacterialName BacterialName},
3219
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3220
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3221
     * and only one nomenclatural code.
3222
     *
3223
     * @return  null
3224
     * @see  	#isCodeCompliant()
3225
     * @see  	#getHasProblem()
3226
     * @deprecated use {@link #getNameType()} instead
3227
     */
3228
    @Deprecated
3229
    @Transient
3230
    @java.beans.Transient
3231
    public NomenclaturalCode getNomenclaturalCode() {
3232
        return nameType;
3233
    }
3234

    
3235
    /**
3236
     * Generates and returns the string with the scientific name of <i>this</i>
3237
     * taxon name (only non viral taxon names can be generated from their
3238
     * components). This string may be stored in the inherited
3239
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3240
     * This method overrides the generic and inherited
3241
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3242
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3243
     *
3244
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3245
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3246
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3247
     */
3248
//	@Override
3249
//	public abstract String generateTitle();
3250

    
3251
    /**
3252
     * Creates a basionym relationship between this name and
3253
     * 	each name in its homotypic group.
3254
     *
3255
     * @param basionymName
3256
     */
3257
    @Override
3258
    @Transient
3259
    public void setAsGroupsBasionym() {
3260

    
3261
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3262
        if (homotypicalGroup == null) {
3263
            return;
3264
        }
3265

    
3266
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3267
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3268

    
3269
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3270

    
3271
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3272

    
3273
            for(NameRelationship nameRelation : nameRelations){
3274
                relations.add(nameRelation);
3275
            }
3276
        }
3277

    
3278
        for (NameRelationship relation : relations) {
3279

    
3280
            // If this is a basionym relation, and toName is in the homotypical group,
3281
            //	remove the relationship.
3282
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3283
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3284
                removeRelations.add(relation);
3285
            }
3286
        }
3287

    
3288
        // Removing relations from a set through which we are iterating causes a
3289
        //	ConcurrentModificationException. Therefore, we delete the targeted
3290
        //	relations in a second step.
3291
        for (NameRelationship relation : removeRelations) {
3292
            this.removeNameRelationship(relation);
3293
        }
3294

    
3295
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3296
            if (!name.equals(this)) {
3297

    
3298
                // First check whether the relationship already exists
3299
                if (!this.isBasionymFor(name)) {
3300

    
3301
                    // Then create it
3302
                    name.addRelationshipFromName(this,
3303
                            NameRelationshipType.BASIONYM(), null, null);
3304
                }
3305
            }
3306
        }
3307
    }
3308

    
3309
    /**
3310
     * Removes basionym relationship between this name and
3311
     * 	each name in its homotypic group.
3312
     *
3313
     * @param basionymName
3314
     */
3315
    @Override
3316
    @Transient
3317
    public void removeAsGroupsBasionym() {
3318

    
3319
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3320

    
3321
        if (homotypicalGroup == null) {
3322
            return;
3323
        }
3324

    
3325
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3326
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3327

    
3328
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3329

    
3330
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3331

    
3332
            for(NameRelationship nameRelation : nameRelations){
3333
                relations.add(nameRelation);
3334
            }
3335
        }
3336

    
3337
        for (NameRelationship relation : relations) {
3338

    
3339
            // If this is a basionym relation, and toName is in the homotypical group,
3340
            //	and fromName is basionymName, remove the relationship.
3341
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3342
                    relation.getFromName().equals(this) &&
3343
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3344
                removeRelations.add(relation);
3345
            }
3346
        }
3347

    
3348
        // Removing relations from a set through which we are iterating causes a
3349
        //	ConcurrentModificationException. Therefore, we delete the targeted
3350
        //	relations in a second step.
3351
        for (NameRelationship relation : removeRelations) {
3352
            this.removeNameRelationship(relation);
3353
        }
3354
    }
3355

    
3356

    
3357
    /**
3358
     * Defines the last part of the name.
3359
     * This is for infraspecific taxa, the infraspecific epithet,
3360
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3361
     * else the genusOrUninomial.
3362
     * However, the result does not depend on the rank (which may be not correctly set
3363
     * in case of dirty data) but returns the first name part which is not blank
3364
     * considering the above order.
3365
     * @return the first not blank name part in reverse order
3366
     */
3367
    @Override
3368
    public String getLastNamePart() {
3369
        String result =
3370
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3371
                    this.getInfraSpecificEpithet() :
3372
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3373
                    this.getSpecificEpithet():
3374
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3375
                    this.getInfraGenericEpithet():
3376
                this.getGenusOrUninomial();
3377
        return result;
3378
    }
3379

    
3380
    /**
3381
     * {@inheritDoc}
3382
     */
3383
    @Override
3384
    public boolean isHybridName() {
3385
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3386
    }
3387

    
3388
    /**
3389
     * {@inheritDoc}
3390
     */
3391
    @Override
3392
    public boolean isHybrid() {
3393
        return this.isHybridName() || this.isHybridFormula();
3394
    }
3395

    
3396
// ***************** COMPARE ********************************/
3397

    
3398
    @Override
3399
    public int compareToName(TaxonName otherName){
3400

    
3401
        int result = 0;
3402

    
3403
        if (otherName == null) {
3404
            throw new NullPointerException("Cannot compare to null.");
3405
        }
3406

    
3407
        //other
3408
        otherName = deproxy(otherName);
3409
        String otherNameCache = otherName.getNameCache();
3410
        String otherTitleCache = otherName.getTitleCache();
3411
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3412
        if (otherName.isAutonym()){
3413
            boolean isProtected = otherName.isProtectedNameCache();
3414
            String oldNameCache = otherName.getNameCache();
3415
            otherName.setProtectedNameCache(false);
3416
            otherName.setNameCache(null, false);
3417
            otherNameCache = otherName.getNameCache();
3418
            otherName.setNameCache(oldNameCache, isProtected);
3419
        }
3420

    
3421
        //this
3422
        String thisNameCache = this.getNameCache();
3423
        String thisTitleCache = this.getTitleCache();
3424

    
3425
        if (this.isAutonym()){
3426
            boolean isProtected = this.isProtectedNameCache();
3427
            String oldNameCache = this.getNameCache();
3428
            this.setProtectedNameCache(false);
3429
            this.setNameCache(null, false);
3430
            thisNameCache = this.getNameCache();
3431
            this.setNameCache(oldNameCache, isProtected);
3432
        }
3433

    
3434

    
3435
        // Compare name cache of taxon names
3436
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3437
            thisNameCache = normalizeName(thisNameCache);
3438
            otherNameCache = normalizeName(otherNameCache);
3439
            result = thisNameCache.compareTo(otherNameCache);
3440
        }
3441

    
3442
        // Compare title cache of taxon names
3443
        if (result == 0){
3444
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3445
                thisTitleCache = normalizeName(thisTitleCache);
3446
                otherTitleCache = normalizeName(otherTitleCache);
3447
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3448
            }
3449
        }
3450

    
3451
        return result;
3452
    }
3453

    
3454
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3455
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3456

    
3457
    /**
3458
     * @param thisNameCache
3459
     * @param HYBRID_SIGN
3460
     * @param QUOT_SIGN
3461
     * @return
3462
     */
3463
    private String normalizeName(String thisNameCache) {
3464
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3465
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3466
        return thisNameCache;
3467
    }
3468

    
3469
// ********************** INTERFACES ********************************************/
3470

    
3471
    /**
3472
     * Method to cast a interfaced name to a concrete name.
3473
     * The method includes a deproxy to guarantee that no
3474
     * class cast exception is thrown.
3475
     *
3476
     * @see #castAndDeproxy(Set)
3477
     * @param interfacedName
3478
     * @return
3479
     */
3480
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3481
        return deproxy(interfacedName, TaxonName.class);
3482
    }
3483

    
3484
    /**
3485
     * Method to cast a set of interfaced names to concrete namex.
3486
     * The method includes a deproxy to guarantee that no
3487
     * class cast exception is thrown.
3488
     *
3489
     * @see #castAndDeproxy(ITaxonNameBase)
3490
     * @param naminterfacedNames
3491
     * @return
3492
     */
3493
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3494
        Set<TaxonName> result = new HashSet<>();
3495
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3496
            result.add(castAndDeproxy(naminterfacedName));
3497
        }
3498
        return result;
3499
    }
3500

    
3501
//************************ isType ***********************************************/
3502

    
3503
    /**
3504
     * @return
3505
     */
3506
    @Override
3507
    public boolean isNonViral() {
3508
        return nameType.isNonViral();
3509
    }
3510

    
3511
    @Override
3512
    public boolean isZoological(){
3513
        return nameType.isZoological();
3514
    }
3515
    @Override
3516
    public boolean isBotanical() {
3517
        return nameType.isBotanical();
3518
    }
3519
    @Override
3520
    public boolean isCultivar() {
3521
        return nameType.isCultivar();
3522
    }
3523
    @Override
3524
    public boolean isBacterial() {
3525
        return nameType.isBacterial();
3526
    }
3527
    @Override
3528
    public boolean isViral() {
3529
        return nameType.isViral();
3530
    }
3531

    
3532
// *********************** CACHES ***************************************************/
3533

    
3534
    @Override
3535
    public boolean updateCaches() {
3536
        boolean result = updateAuthorshipCache();
3537
        result |= updateNameCache();
3538
        result |= super.updateCaches();
3539
        result |= updateFullTitleCache();
3540
        return result;
3541
    }
3542

    
3543
    /**
3544
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
3545
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened.
3546
     * @return <code>true</code> if something changed
3547
     */
3548
    private boolean updateAuthorshipCache() {
3549
        //updates the authorship cache if necessary and via the listener updates all higher caches
3550
        if (protectedAuthorshipCache == false){
3551
            String oldCache = this.authorshipCache;
3552
            String newCache = cacheStrategy.getAuthorshipCache(this);
3553
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3554
                this.setAuthorshipCache(null, false);
3555
                this.getAuthorshipCache();
3556
                return true;
3557
            }
3558
        }
3559
        return false;
3560
    }
3561

    
3562
    private boolean updateNameCache() {
3563
        //updates the name cache if necessary and via the listener updates all higher caches
3564
        if (protectedNameCache == false){
3565
            String oldCache = this.nameCache;
3566
            String newCache = cacheStrategy.getNameCache(this);
3567
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3568
                this.setNameCache(null, false);
3569
                this.getNameCache();
3570
                return true;
3571
            }
3572
        }
3573
        return false;
3574
    }
3575

    
3576

    
3577
    /**
3578
     * @return
3579
     */
3580
    private boolean updateFullTitleCache() {
3581
        if (protectedFullTitleCache == false){
3582
            String oldCache = this.fullTitleCache;
3583
            String newCache = getTruncatedCache(cacheStrategy.getFullTitleCache(this));
3584
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3585
                this.setFullTitleCache(null, false);
3586
                this.getFullTitleCache();
3587
                return true;
3588
            }
3589
        }
3590
        return false;
3591
    }
3592

    
3593

    
3594
//*********************** CLONE ********************************************************/
3595

    
3596
    /**
3597
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3598
     * a new instance that differs only slightly from <i>this</i> taxon name by
3599
     * modifying only some of the attributes.<BR><BR>
3600
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3601
     * <b>The name gets a newly created homotypical group</b><BR>
3602
     * (CAUTION: this behavior needs to be discussed and may change in future).<BR><BR>
3603
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3604
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3605
     *
3606
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3607
     * @see java.lang.Object#clone()
3608
     */
3609
    @Override
3610
    public Object clone() {
3611
        TaxonName result;
3612
        try {
3613
            result = (TaxonName)super.clone();
3614

    
3615
            //taxonBases -> empty
3616
            result.taxonBases = new HashSet<>();
3617

    
3618
            //empty caches
3619
            if (! protectedFullTitleCache){
3620
                result.fullTitleCache = null;
3621
            }
3622

    
3623
            //descriptions
3624
            result.descriptions = new HashSet<>();
3625
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3626
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3627
                result.descriptions.add(newDescription);
3628
            }
3629

    
3630
            //status
3631
            result.status = new HashSet<>();
3632
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3633
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3634
                result.status.add(newStatus);
3635
            }
3636

    
3637

    
3638
            //to relations
3639
            result.relationsToThisName = new HashSet<>();
3640
            for (NameRelationship toRelationship : getRelationsToThisName()){
3641
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3642
                newRelationship.setRelatedTo(result);
3643
                result.relationsToThisName.add(newRelationship);
3644
            }
3645

    
3646
            //from relations
3647
            result.relationsFromThisName = new HashSet<>();
3648
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3649
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3650
                newRelationship.setRelatedFrom(result);
3651
                result.relationsFromThisName.add(newRelationship);
3652
            }
3653

    
3654
            //type designations
3655
            result.typeDesignations = new HashSet<>();
3656
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3657
                TypeDesignationBase<?> newDesignation = (TypeDesignationBase<?>)typeDesignation.clone();
3658
                this.removeTypeDesignation(newDesignation);
3659
                result.addTypeDesignation(newDesignation, false);
3660
            }
3661

    
3662
            //homotypicalGroup
3663
            //TODO still needs to be discussed
3664
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3665
            result.homotypicalGroup.addTypifiedName(this);
3666

    
3667

    
3668
            //HybridChildRelations
3669
            result.hybridChildRelations = new HashSet<>();
3670
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3671
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3672
                newChildRelationship.setRelatedTo(result);
3673
                result.hybridChildRelations.add(newChildRelationship);
3674
            }
3675

    
3676
            //HybridParentRelations
3677
            result.hybridParentRelations = new HashSet<>();
3678
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3679
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3680
                newParentRelationship.setRelatedFrom(result);
3681
                result.hybridParentRelations.add(newParentRelationship);
3682
            }
3683

    
3684
            //empty caches
3685
            if (! protectedNameCache){
3686
                result.nameCache = null;
3687
            }
3688

    
3689
            //empty caches
3690
            if (! protectedAuthorshipCache){
3691
                result.authorshipCache = null;
3692
            }
3693

    
3694
            //no changes to: appendedPharse, nomenclaturalReference,
3695
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3696
            //protectedFullTitleCache, rank
3697
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3698
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3699
            //protectedAuthorshipCache, protectedNameCache,
3700
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3701
            //acronym
3702
            //subGenusAuthorship, nameApprobation
3703
            //anamorphic
3704
            //cultivarName
3705
            return result;
3706
        } catch (CloneNotSupportedException e) {
3707
            logger.warn("Object does not implement cloneable");
3708
            e.printStackTrace();
3709
            return null;
3710
        }
3711
    }
3712
}
(32-32/40)