Project

General

Profile

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

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

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

    
163
    "acronym",
164

    
165
    "subGenusAuthorship",
166
    "nameApprobation",
167

    
168
    "breed",
169
    "publicationYear",
170
    "originalPublicationYear",
171
    "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 TermType type} of this term. Needs to be the same type in a {@link DefinedTermBase defined term}
203
     * and in it's {@link TermVocabulary vocabulary}.
204
     */
205
    @XmlAttribute(name ="NameType")
206
    @Column(name="nameType", length=15)
207
    @NotNull
208
    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
209
        parameters = {@org.hibernate.annotations.Parameter(name = "enumClass", value = "eu.etaxonomy.cdm.model.name.NomenclaturalCode")}
210
    )
211
    @Audited //needed ?
212
    private NomenclaturalCode nameType;
213

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
354

    
355

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

    
366
//****** Non-ViralName attributes ***************************************/
367

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

    
380
    @XmlElement(name = "ProtectedNameCache")
381
    @CacheUpdate(value="nameCache")
382
    protected boolean protectedNameCache;
383

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

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

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

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

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

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

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

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

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

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

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

    
490
    @XmlElement(name = "ProtectedAuthorshipCache")
491
    @CacheUpdate("authorshipCache")
492
    protected boolean protectedAuthorshipCache;
493

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

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

    
510

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

    
518
    @XmlElement(name ="IsMonomHybrid")
519
    @CacheUpdate("nameCache")
520
    private boolean monomHybrid = false;
521

    
522
    @XmlElement(name ="IsBinomHybrid")
523
    @CacheUpdate("nameCache")
524
    private boolean binomHybrid = false;
525

    
526
    @XmlElement(name ="IsTrinomHybrid")
527
    @CacheUpdate("nameCache")
528
    private boolean trinomHybrid = false;
529

    
530
// ViralName attributes ************************* /
531

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

    
539
// BacterialName attributes ***********************/
540

    
541
    //Author team and year of the subgenus name
542
    @XmlElement(name = "SubGenusAuthorship")
543
    @Field
544
    private String subGenusAuthorship;
545

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

    
551
    //ZOOLOGICAL NAME
552

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

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

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

    
572
    //Cultivar attribute(s)
573

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

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

    
586
// *************** FACTORY METHODS ********************************/
587

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

    
601

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

    
623

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

    
638

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

    
664

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

    
710

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

    
722

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

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

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

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

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

    
825
// ****************** GETTER / SETTER ****************************/
826

    
827
    @Override
828
    public NomenclaturalCode getNameType() {
829
        return nameType;
830
    }
831

    
832
    @Override
833
    public void setNameType(NomenclaturalCode nameType) {
834
        this.nameType = nameType;
835
    }
836

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1110
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1111
        this.hybridParentRelations = hybridParentRelations;
1112
    }
1113

    
1114

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

    
1131
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1132
        this.hybridChildRelations = hybridChildRelations;
1133
    }
1134

    
1135
    @Override
1136
    public boolean isProtectedFullTitleCache() {
1137
        return protectedFullTitleCache;
1138
    }
1139

    
1140
    @Override
1141
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1142
        this.protectedFullTitleCache = protectedFullTitleCache;
1143
    }
1144

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

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

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

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

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

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

    
1226
    @Override
1227
    public boolean isTrinomHybrid(){
1228
        return this.trinomHybrid;
1229
    }
1230

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

    
1241
    // ****************** VIRAL NAME ******************/
1242

    
1243
    @Override
1244
    public String getAcronym(){
1245
        return this.acronym;
1246
    }
1247

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

    
1256
    // ****************** BACTERIAL NAME ******************/
1257

    
1258
    @Override
1259
    public String getSubGenusAuthorship(){
1260
        return this.subGenusAuthorship;
1261
    }
1262

    
1263
    @Override
1264
    public void setSubGenusAuthorship(String subGenusAuthorship){
1265
        this.subGenusAuthorship = subGenusAuthorship;
1266
    }
1267

    
1268

    
1269
    @Override
1270
    public String getNameApprobation(){
1271
        return this.nameApprobation;
1272
    }
1273

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

    
1282
    //************ Zoological Name
1283

    
1284

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

    
1297

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

    
1310

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

    
1323
    // **** Cultivar Name ************
1324

    
1325

    
1326
    @Override
1327
    public String getCultivarName(){
1328
        return this.cultivarName;
1329
    }
1330

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

    
1339
    // **************** Fungus Name
1340
    @Override
1341
    public boolean isAnamorphic(){
1342
        return this.anamorphic;
1343
    }
1344

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

    
1353

    
1354
// **************** ADDER / REMOVE *************************/
1355

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

    
1385

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

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

    
1417
//********* METHODS **************************************/
1418

    
1419
    @Override
1420
    public INameCacheStrategy getCacheStrategy() {
1421
        rectifyNameCacheStrategy();
1422
        return this.cacheStrategy;
1423
    }
1424

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

    
1435

    
1436
    @Override
1437
    public void setFullTitleCache(String fullTitleCache){
1438
        setFullTitleCache(fullTitleCache, PROTECTED);
1439
    }
1440

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

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

    
1473

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

    
1481
    @Override
1482
    @Transient
1483
    public String getFullTitleCache(){
1484
        if (protectedFullTitleCache){
1485
            return this.fullTitleCache;
1486
        }
1487
        updateAuthorshipCache();
1488
        if (fullTitleCache == null ){
1489
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1490
        }
1491
        return fullTitleCache;
1492
    }
1493

    
1494

    
1495
    @Override
1496
    public String getTitleCache(){
1497
        if(!protectedTitleCache) {
1498
            updateAuthorshipCache();
1499
        }
1500
        return super.getTitleCache();
1501
    }
1502

    
1503
    @Override
1504
    public void setTitleCache(String titleCache, boolean protectCache){
1505
        super.setTitleCache(titleCache, protectCache);
1506
    }
1507

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

    
1531
        }
1532
        return authorshipCache;
1533
    }
1534

    
1535

    
1536

    
1537

    
1538

    
1539

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

    
1552

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

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

    
1586

    
1587

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

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

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

    
1618

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

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

    
1637
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1638
        if (author == null){
1639
            return null;
1640
        }else{
1641
            return author.getNomenclaturalTitle();
1642
        }
1643
    }
1644

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

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

    
1684
    /**
1685
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1686
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1687
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1688
     *
1689
     * @param toName		  the taxon name of the target for this new name relationship
1690
     * @param type			  the type of this new name relationship
1691
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1692
     * @return
1693
     * @see    				  #getRelationsToThisName()
1694
     * @see    				  #getNameRelations()
1695
     * @see    				  #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1696
     * @see    				  #addNameRelationship(NameRelationship)
1697
     */
1698
    @Override
1699
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1700
        if (toName == null){
1701
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1702
        }
1703
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered);
1704
        return rel;
1705
    }
1706

    
1707
    /**
1708
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1709
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1710
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1711
     *
1712
     * @param fromName		  the taxon name of the source for this new name relationship
1713
     * @param type			  the type of this new name relationship
1714
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1715
     * @param citation		  the reference in which this relation was described
1716
     * @param microCitation	  the reference detail for this relation (e.g. page)
1717
     * @see    				  #getRelationsFromThisName()
1718
     * @see    				  #getNameRelations()
1719
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1720
     * @see    				  #addNameRelationship(NameRelationship)
1721
     */
1722
    @Override
1723
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered){
1724
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1725
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
1726
    }
1727
    /**
1728
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1729
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1730
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1731
     *
1732
     * @param fromName		  the taxon name of the source for this new name relationship
1733
     * @param type			  the type of this new name relationship
1734
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1735
     * @param citation		  the reference in which this relation was described
1736
     * @param microCitation	  the reference detail for this relation (e.g. page)
1737
     * @see    				  #getRelationsFromThisName()
1738
     * @see    				  #getNameRelations()
1739
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1740
     * @see    				  #addNameRelationship(NameRelationship)
1741
     */
1742
    @Override
1743
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1744
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1745
    }
1746

    
1747
    /**
1748
     * Adds an existing {@link NameRelationship name relationship} either to the set of
1749
     * {@link #getRelationsToThisName() relations to <i>this</i> taxon name} or to the set of
1750
     * {@link #getRelationsFromThisName() relations from <i>this</i> taxon name}. If neither the
1751
     * source nor the target of the name relationship match with <i>this</i> taxon name
1752
     * no addition will be carried out.
1753
     *
1754
     * @param rel  the name relationship to be added to one of <i>this</i> taxon name's name relationships sets
1755
     * @see    	   #getNameRelations()
1756
     * @see    	   #addRelationshipToName(TaxonName, NameRelationshipType, String)
1757
     * @see    	   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1758
     */
1759
    protected void addNameRelationship(NameRelationship rel) {
1760
        if (rel != null ){
1761
            if (rel.getToName().equals(this)){
1762
                this.relationsToThisName.add(rel);
1763
            }else if(rel.getFromName().equals(this)){
1764
                this.relationsFromThisName.add(rel);
1765
            }
1766
            NameRelationshipType type = rel.getType();
1767
            if (type != null && ( type.isBasionymRelation() || type.isReplacedSynonymRelation() ) ){
1768
                rel.getFromName().mergeHomotypicGroups(rel.getToName());
1769
            }
1770
        }else{
1771
            throw new RuntimeException("NameRelationship is either null or the relationship does not reference this name");
1772
        }
1773
    }
1774
    /**
1775
     * Removes one {@link NameRelationship name relationship} from one of both sets of
1776
     * {@link #getNameRelations() name relationships} in which <i>this</i> taxon name is involved.
1777
     * The name relationship will also be removed from one of both sets belonging
1778
     * to the second taxon name involved. Furthermore the fromName and toName
1779
     * attributes of the name relationship object will be nullified.
1780
     *
1781
     * @param  nameRelation  the name relationship which should be deleted from one of both sets
1782
     * @see    				 #getNameRelations()
1783
     */
1784
    @Override
1785
    public void removeNameRelationship(NameRelationship nameRelation) {
1786

    
1787
        TaxonName fromName = nameRelation.getFromName();
1788
        TaxonName toName = nameRelation.getToName();
1789

    
1790
        if (nameRelation != null) {
1791
            nameRelation.setToName(null);
1792
            nameRelation.setFromName(null);
1793
        }
1794

    
1795
        if (fromName != null) {
1796
            fromName.removeNameRelationship(nameRelation);
1797
        }
1798

    
1799
        if (toName != null) {
1800
            toName.removeNameRelationship(nameRelation);
1801
        }
1802

    
1803
        this.relationsToThisName.remove(nameRelation);
1804
        this.relationsFromThisName.remove(nameRelation);
1805
    }
1806

    
1807
    @Override
1808
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1809
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1810
//		nameRelationships.addAll(this.getNameRelations());
1811
        nameRelationships.addAll(this.getRelationsFromThisName());
1812
        nameRelationships.addAll(this.getRelationsToThisName());
1813
        for(NameRelationship nameRelationship : nameRelationships) {
1814
            // remove name relationship from this side
1815
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1816
                this.removeNameRelationship(nameRelationship);
1817
            }
1818
        }
1819
    }
1820

    
1821
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1822

    
1823
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1824
        for(NameRelationship nameRelationship : tmpRels) {
1825
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1826
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1827
                if (type == null || type.equals(nameRelationship.getType())){
1828
                    this.removeNameRelationship(nameRelationship);
1829
                }
1830
            }
1831
        }
1832
    }
1833

    
1834

    
1835
    /**
1836
     * If relation is of type NameRelationship, addNameRelationship is called;
1837
     * if relation is of type HybridRelationship addHybridRelationship is called,
1838
     * otherwise an IllegalArgumentException is thrown.
1839
     *
1840
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1841
     * @see    	   		#addNameRelationship(NameRelationship)
1842
     * @see    	   		#getNameRelations()
1843
     * @see    	   		NameRelationship
1844
     * @see    	   		RelationshipBase
1845
     * @see             #addHybridRelationship(HybridRelationship)
1846

    
1847
     * @deprecated to be used by RelationshipBase only
1848
     */
1849
    @Deprecated
1850
    @Override
1851
    public void addRelationship(RelationshipBase relation) {
1852
        if (relation instanceof NameRelationship){
1853
            addNameRelationship((NameRelationship)relation);
1854

    
1855
        }else if (relation instanceof HybridRelationship){
1856
            addHybridRelationship((HybridRelationship)relation);
1857
        }else{
1858
            logger.warn("Relationship not of type NameRelationship!");
1859
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1860
        }
1861
    }
1862

    
1863
    /**
1864
     * Returns the set of all {@link NameRelationship name relationships}
1865
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1866
     *
1867
     * @see    #getNameRelations()
1868
     * @see    #getRelationsToThisName()
1869
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1870
     */
1871
    @Override
1872
    public Set<NameRelationship> getRelationsFromThisName() {
1873
        if(relationsFromThisName == null) {
1874
            this.relationsFromThisName = new HashSet<>();
1875
        }
1876
        return relationsFromThisName;
1877
    }
1878

    
1879
    /**
1880
     * Returns the set of all {@link NameRelationship name relationships}
1881
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1882
     *
1883
     * @see    #getNameRelations()
1884
     * @see    #getRelationsFromThisName()
1885
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1886
     */
1887
    @Override
1888
    public Set<NameRelationship> getRelationsToThisName() {
1889
        if(relationsToThisName == null) {
1890
            this.relationsToThisName = new HashSet<>();
1891
        }
1892
        return relationsToThisName;
1893
    }
1894

    
1895
    /**
1896
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1897
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1898
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1899
     * and the nomenclatural code rule considered.
1900
     *
1901
     * @see     NomenclaturalStatus
1902
     * @see     NomenclaturalStatusType
1903
     */
1904
    @Override
1905
    public Set<NomenclaturalStatus> getStatus() {
1906
        if(status == null) {
1907
            this.status = new HashSet<>();
1908
        }
1909
        return status;
1910
    }
1911

    
1912
    /**
1913
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1914
     * to <i>this</i> taxon name's set of nomenclatural status.
1915
     *
1916
     * @param  nomStatus  the nomenclatural status to be added
1917
     * @see 			  #getStatus()
1918
     */
1919
    @Override
1920
    public void addStatus(NomenclaturalStatus nomStatus) {
1921
        this.status.add(nomStatus);
1922
    }
1923
    @Override
1924
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1925
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1926
        this.status.add(newStatus);
1927
        return newStatus;
1928
    }
1929

    
1930
    /**
1931
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1932
     * Type and ruleConsidered attributes of the nomenclatural status object
1933
     * will be nullified.
1934
     *
1935
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1936
     * @see     		  #getStatus()
1937
     */
1938
    @Override
1939
    public void removeStatus(NomenclaturalStatus nomStatus) {
1940
        //TODO to be implemented?
1941
        logger.warn("not yet fully implemented?");
1942
        this.status.remove(nomStatus);
1943
    }
1944

    
1945

    
1946
    /**
1947
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1948
     * strings or year according to the strategy defined in
1949
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1950
     * The result might be stored in {@link #getNameCache() nameCache} if the
1951
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1952
     *
1953
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1954
     * @see     #getNameCache()
1955
     */
1956
    protected String generateNameCache(){
1957
        if (getCacheStrategy() == null){
1958
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1959
            return null;
1960
        }else{
1961
            return cacheStrategy.getNameCache(this);
1962
        }
1963
    }
1964

    
1965
    /**
1966
     * Returns or generates the nameCache (scientific name
1967
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1968
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1969
     * the string will be generated according to a defined strategy,
1970
     * otherwise the value of the actual nameCache string will be returned.
1971
     *
1972
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1973
     * @see     #generateNameCache()
1974
     */
1975
    @Override
1976
    @Transient
1977
    public String getNameCache() {
1978
        if (protectedNameCache){
1979
            return this.nameCache;
1980
        }
1981
        // is title dirty, i.e. equal NULL?
1982
        if (nameCache == null){
1983
            this.nameCache = generateNameCache();
1984
        }
1985
        return nameCache;
1986
    }
1987

    
1988
    /**
1989
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1990
     * Sets the protectedNameCache flag to <code>true</code>.
1991
     *
1992
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1993
     * @see    #getNameCache()
1994
     */
1995
    @Override
1996
    public void setNameCache(String nameCache){
1997
        setNameCache(nameCache, true);
1998
    }
1999

    
2000
    /**
2001
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
2002
     * Sets the protectedNameCache flag to <code>true</code>.
2003
     *
2004
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
2005
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
2006
     * <code>false</code>
2007
     * @see    #getNameCache()
2008
     */
2009
    @Override
2010
    public void setNameCache(String nameCache, boolean protectedNameCache){
2011
        this.nameCache = nameCache;
2012
        this.setProtectedNameCache(protectedNameCache);
2013
    }
2014

    
2015

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

    
2037
    /**
2038
     * Indicates <i>this</i> taxon name is a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
2039
     * of any other taxon name. Returns "true", if a replaced
2040
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
2041
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
2042
     * homotypical group).
2043
     */
2044
    @Override
2045
    @Transient
2046
    public boolean isReplacedSynonym(){
2047
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
2048
        for (NameRelationship relation : relationsFromThisName) {
2049
            if (relation.getType().isReplacedSynonymRelation()) {
2050
                return true;
2051
            }
2052
        }
2053
        return false;
2054
    }
2055

    
2056
    /**
2057
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2058
     * The basionym of a taxon name is its epithet-bringing synonym.
2059
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2060
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2061
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2062
     *
2063
     * If more than one basionym exists one is choosen at radom.
2064
     *
2065
     * If no basionym exists null is returned.
2066
     */
2067
    @Override
2068
    @Transient
2069
    public TaxonName getBasionym(){
2070
        Set<TaxonName> basionyms = getBasionyms();
2071
        if (basionyms.size() == 0){
2072
            return null;
2073
        }else{
2074
            return basionyms.iterator().next();
2075
        }
2076
    }
2077

    
2078
    /**
2079
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2080
     * The basionym of a taxon name is its epithet-bringing synonym.
2081
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2082
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2083
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2084
     */
2085
    @Override
2086
    @Transient
2087
    public Set<TaxonName> getBasionyms(){
2088
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
2089
    }
2090

    
2091
    /**
2092
     *
2093
     * @param direction
2094
     * @param type
2095
     * @return
2096
     */
2097
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
2098
        return getRelatedNames(relationsWithThisName(direction), type);
2099
    }
2100

    
2101
    /**
2102
     * @param rels
2103
     * @param type
2104
     * @return
2105
     */
2106
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2107
        Set<TaxonName> result = new HashSet<>();
2108
        for (NameRelationship rel : rels){
2109
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2110
                TaxonName basionym = rel.getFromName();
2111
                result.add(basionym);
2112
            }
2113
        }
2114
        return result;
2115
    }
2116

    
2117
    /**
2118
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2119
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2120
     * and to the basionym. The basionym cannot have itself as a basionym.
2121
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2122
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2123
     *
2124
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2125
     * @see  				#getBasionym()
2126
     * @see  				#addBasionym(TaxonName, String)
2127
     */
2128
    @Override
2129
    public void addBasionym(TaxonName basionym){
2130
        addBasionym(basionym, null, null, null);
2131
    }
2132
    /**
2133
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2134
     * and keeps the nomenclatural rule considered for it. The basionym
2135
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2136
     * The basionym cannot have itself as a basionym.
2137
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2138
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2139
     *
2140
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2141
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2142
     * @return
2143
     * @see  					#getBasionym()
2144
     * @see  					#addBasionym(TaxonName)
2145
     */
2146
    @Override
2147
    public NameRelationship addBasionym(TaxonName basionym, Reference citation, String microcitation, String ruleConsidered){
2148
        if (basionym != null){
2149
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
2150
        }else{
2151
            return null;
2152
        }
2153
    }
2154

    
2155
    /**
2156
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2157
     *
2158
     */
2159
    @Override
2160
    @Transient
2161
    public Set<TaxonName> getReplacedSynonyms(){
2162

    
2163
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2164
    }
2165

    
2166
    /**
2167
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2168
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2169
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2170
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2171
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2172
     *
2173
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2174
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2175
     * @see  					#getBasionym()
2176
     * @see  					#addBasionym(TaxonName)
2177
     */
2178
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2179
    @Override
2180
    public void addReplacedSynonym(TaxonName replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
2181
        if (replacedSynonym != null){
2182
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
2183
        }
2184
    }
2185

    
2186
    /**
2187
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2188
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2189
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2190
     * previously used as basionym.
2191
     *
2192
     * @see   #getBasionym()
2193
     * @see   #addBasionym(TaxonName)
2194
     */
2195
    @Override
2196
    public void removeBasionyms(){
2197
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2198
    }
2199

    
2200

    
2201
    /**
2202
     * Removes all {@link NameRelationship relationships} of the given <code>type</code> from the set of
2203
     * relations in the specified <code>direction</code> direction wich are related from or to this
2204
     * <i>this</i> taxon name. The same relationship will be removed from the set of
2205
     * reverse relations of the other taxon name.
2206
     *
2207
     * @param direction
2208
     * @param type
2209
     */
2210
    public void removeNameRelations(Direction direction, NameRelationshipType type) {
2211
        Set<NameRelationship> relationsWithThisName = relationsWithThisName(direction);
2212
        Set<NameRelationship> removeRelations = new HashSet<>();
2213
        for (NameRelationship nameRelation : relationsWithThisName){
2214
            if (nameRelation.getType().isRelationshipType(type)){
2215
                removeRelations.add(nameRelation);
2216
            }
2217
        }
2218
        // Removing relations from a set through which we are iterating causes a
2219
        // ConcurrentModificationException. Therefore, we delete the targeted
2220
        // relations in a second step.
2221
        for (NameRelationship relation : removeRelations){
2222
            this.removeNameRelationship(relation);
2223
        }
2224
    }
2225

    
2226

    
2227
    /**
2228
     * @param direction
2229
     * @return
2230
     */
2231
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2232

    
2233
        switch(direction) {
2234
            case relatedTo:
2235
                return this.getRelationsToThisName();
2236
            case relatedFrom:
2237
                return this.getRelationsFromThisName();
2238
            default: throw new RuntimeException("invalid Direction:" + direction);
2239
        }
2240
    }
2241

    
2242
    /**
2243
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2244
     *
2245
     * @see 	Rank
2246
     */
2247
    @Override
2248
    public Rank getRank(){
2249
        return this.rank;
2250
    }
2251

    
2252
    /**
2253
     * @see  #getRank()
2254
     */
2255
    @Override
2256
    public void setRank(Rank rank){
2257
        this.rank = rank;
2258
    }
2259

    
2260

    
2261
    @Override
2262
    public Reference getNomenclaturalReference(){
2263
        //#6581
2264
        return this.nomenclaturalReference;
2265
//        if (this.nomenclaturalSource == null){
2266
//            return null;
2267
//        }
2268
//        return this.nomenclaturalSource.getCitation();
2269
    }
2270

    
2271
    @Override
2272
    public DescriptionElementSource getNomenclaturalSource(){
2273
        return this.nomenclaturalSource;
2274
    }
2275

    
2276
    protected DescriptionElementSource getNomenclaturalSource(boolean createIfNotExist){
2277
        if (this.nomenclaturalSource == null){
2278
            if (!createIfNotExist){
2279
                return null;
2280
            }
2281
            this.nomenclaturalSource = DescriptionElementSource.NewInstance(OriginalSourceType.NomenclaturalReference);
2282
        }
2283
        return this.nomenclaturalSource;
2284
    }
2285

    
2286
    /**
2287
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2288
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2289
     * as it is obviously used for nomenclatural purposes.
2290
     *
2291
     * Shortcut to set the nomenclatural reference.
2292
     *
2293
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2294
     * @see  #getNomenclaturalReference()
2295
     */
2296

    
2297
    @Override
2298
    public void setNomenclaturalReference(Reference nomenclaturalReference){
2299
        //#6581
2300
        this.nomenclaturalReference = nomenclaturalReference;
2301
//        getNomenclaturalSource(true).setCitation(nomenclaturalReference);
2302
//        checkNullSource();
2303
    }
2304
    @Override
2305
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2306
        setNomenclaturalReference(CdmBase.deproxy(nomenclaturalReference, Reference.class));
2307
    }
2308

    
2309
    //#6581
2310
    private void checkNullSource() {
2311
        if (this.nomenclaturalSource == null){
2312
            return;
2313
        }else if (this.nomenclaturalSource.getCitation() != null
2314
           || this.nomenclaturalSource.getCitationMicroReference() != null
2315
           || this.nomenclaturalSource.getNameUsedInSource() != null
2316
           || isBlank(this.nomenclaturalSource.getOriginalNameString())){
2317
            //TODO what about supplemental data?
2318
                return;
2319
        }else{
2320
            this.nomenclaturalSource = null;
2321
        }
2322
    }
2323

    
2324

    
2325
    @Override
2326
    public void setNomenclaturalSource(DescriptionElementSource nomenclaturalSource) throws IllegalArgumentException {
2327
        if (!OriginalSourceType.NomenclaturalReference.equals(nomenclaturalSource.getType()) ){
2328
            throw new IllegalArgumentException("Nomenclatural source must be of type " + OriginalSourceType.NomenclaturalReference.getMessage());
2329
        }
2330
        this.nomenclaturalSource = nomenclaturalSource;
2331
    }
2332

    
2333
    /**
2334
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2335
     * The appended phrase is a non-atomised addition to a name. It is
2336
     * not ruled by a nomenclatural code.
2337
     */
2338
    @Override
2339
    public String getAppendedPhrase(){
2340
        return this.appendedPhrase;
2341
    }
2342

    
2343
    /**
2344
     * @see  #getAppendedPhrase()
2345
     */
2346
    @Override
2347
    public void setAppendedPhrase(String appendedPhrase){
2348
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2349
    }
2350

    
2351
    /**
2352
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2353
     * to <i>this</i> taxon name. The details describe the exact localisation within
2354
     * the publication used as nomenclature reference. These are mostly
2355
     * (implicitly) pages but can also be figures or tables or any other
2356
     * element of a publication. A nomenclatural micro reference (details)
2357
     * requires the existence of a nomenclatural reference.
2358
     */
2359
    //Details of the nomenclatural reference (protologue).
2360
    @Override
2361
    public String getNomenclaturalMicroReference(){
2362
        //#6581
2363
        return this.nomenclaturalMicroReference;
2364
//        if (this.nomenclaturalSource == null){
2365
//            return null;
2366
//        }
2367
//        return this.nomenclaturalSource.getCitationMicroReference();
2368
    }
2369
    /**
2370
     * @see  #getNomenclaturalMicroReference()
2371
     */
2372
    @Override
2373
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2374
        //#6581
2375
        this.nomenclaturalMicroReference = nomenclaturalMicroReference;
2376
//        this.getNomenclaturalSource(true).setCitationMicroReference(StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference);
2377
//        checkNullSource();
2378
    }
2379

    
2380
    @Override
2381
    public int getParsingProblem(){
2382
        return this.parsingProblem;
2383
    }
2384

    
2385
    @Override
2386
    public void setParsingProblem(int parsingProblem){
2387
        this.parsingProblem = parsingProblem;
2388
    }
2389

    
2390
    @Override
2391
    public void addParsingProblem(ParserProblem problem){
2392
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2393
    }
2394

    
2395
    @Override
2396
    public void removeParsingProblem(ParserProblem problem) {
2397
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2398
    }
2399

    
2400
    /**
2401
     * @param warnings
2402
     */
2403
    @Override
2404
    public void addParsingProblems(int problems){
2405
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2406
    }
2407

    
2408
    @Override
2409
    public boolean hasProblem(){
2410
        return parsingProblem != 0;
2411
    }
2412

    
2413
    @Override
2414
    public boolean hasProblem(ParserProblem problem) {
2415
        return getParsingProblems().contains(problem);
2416
    }
2417

    
2418
    @Override
2419
    public int getProblemStarts(){
2420
        return this.problemStarts;
2421
    }
2422

    
2423
    @Override
2424
    public void setProblemStarts(int start) {
2425
        this.problemStarts = start;
2426
    }
2427

    
2428
    @Override
2429
    public int getProblemEnds(){
2430
        return this.problemEnds;
2431
    }
2432

    
2433
    @Override
2434
    public void setProblemEnds(int end) {
2435
        this.problemEnds = end;
2436
    }
2437

    
2438
//*********************** TYPE DESIGNATION *********************************************//
2439

    
2440
    /**
2441
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2442
     * to <i>this</i> taxon name.
2443
     * @see     NameTypeDesignation
2444
     * @see     SpecimenTypeDesignation
2445
     */
2446
    @Override
2447
    public Set<TypeDesignationBase> getTypeDesignations() {
2448
        if(typeDesignations == null) {
2449
            this.typeDesignations = new HashSet<>();
2450
        }
2451
        return typeDesignations;
2452
    }
2453

    
2454
    /**
2455
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2456
     * <i>this</i> taxon name. The type designation itself will be nullified.
2457
     *
2458
     * @param  typeDesignation  the type designation which should be deleted
2459
     */
2460
    @Override
2461
    @SuppressWarnings("deprecation")
2462
    public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2463
        this.typeDesignations.remove(typeDesignation);
2464
        typeDesignation.removeTypifiedName(this);
2465
    }
2466

    
2467
    /**
2468
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2469
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2470
     * "species" or below. The specimen type designations include all the
2471
     * specimens on which the typification of this name is based (which are
2472
     * exclusively used to typify taxon names belonging to the same
2473
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2474
     * belongs) and eventually the status of these designations.
2475
     *
2476
     * @see     SpecimenTypeDesignation
2477
     * @see     NameTypeDesignation
2478
     * @see     HomotypicalGroup
2479
     */
2480
    @Override
2481
    @Transient
2482
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2483
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2484
    }
2485

    
2486
//*********************** NAME TYPE DESIGNATION *********************************************//
2487

    
2488
    /**
2489
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2490
     * to <i>this</i> taxon name the rank of which must be above "species".
2491
     * The name type designations include all the taxon names used to typify
2492
     * <i>this</i> taxon name and eventually the rejected or conserved status
2493
     * of these designations.
2494
     *
2495
     * @see     NameTypeDesignation
2496
     * @see     SpecimenTypeDesignation
2497
     */
2498
    @Override
2499
    @Transient
2500
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2501
        Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2502
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2503
            if (typeDesignation instanceof NameTypeDesignation){
2504
                result.add((NameTypeDesignation)typeDesignation);
2505
            }
2506
        }
2507
        return result;
2508
    }
2509

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

    
2546
    /**
2547
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2548
     * to <i>this</i> taxon name's set of type designations.
2549
     *
2550
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2551
     * @param  citation					the reference for this new designation
2552
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2553
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2554
     * @param  status                   the name type designation status
2555
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2556
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2557
     * @return
2558
     * @see 			  				#getNameTypeDesignations()
2559
     * @see 			  				NameTypeDesignation
2560
     * @see 			  				TypeDesignationBase#isNotDesignated()
2561
     */
2562
    @Override
2563
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2564
                Reference citation,
2565
                String citationMicroReference,
2566
                String originalNameString,
2567
                NameTypeDesignationStatus status,
2568
                boolean addToAllHomotypicNames) {
2569
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2570
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2571
        return nameTypeDesignation;
2572
    }
2573

    
2574
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2575

    
2576
    /**
2577
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2578
     * that typify <i>this</i> taxon name.
2579
     */
2580
    @Override
2581
    @Transient
2582
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2583
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2584
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2585
            if (typeDesignation instanceof SpecimenTypeDesignation){
2586
                result.add((SpecimenTypeDesignation)typeDesignation);
2587
            }
2588
        }
2589
        return result;
2590
    }
2591

    
2592

    
2593
    /**
2594
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2595
     * to <i>this</i> taxon name's set of type designations.
2596
     *
2597
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2598
     * @param  status					the specimen type designation status
2599
     * @param  citation					the reference for this new specimen type designation
2600
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2601
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2602
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2603
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2604
     * 									added to all taxon names of the homotypical group the typified
2605
     * 									taxon name belongs to
2606
     * @return
2607
     * @see 			  				#getSpecimenTypeDesignations()
2608
     * @see 			  				SpecimenTypeDesignationStatus
2609
     * @see 			  				SpecimenTypeDesignation
2610
     * @see 			  				TypeDesignationBase#isNotDesignated()
2611
     */
2612
    @Override
2613
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2614
                SpecimenTypeDesignationStatus status,
2615
                Reference citation,
2616
                String citationMicroReference,
2617
                String originalNameString,
2618
                boolean isNotDesignated,
2619
                boolean addToAllHomotypicNames) {
2620
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2621
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2622
        return specimenTypeDesignation;
2623
    }
2624

    
2625
    //used by merge strategy
2626
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2627
        return addTypeDesignation(typeDesignation, true);
2628
    }
2629

    
2630
    /**
2631
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2632
     *
2633
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2634
     * @param addToAllNames				the boolean indicating whether the type designation should be
2635
     * 									added to all taxon names of the homotypical group the typified
2636
     * 									taxon name belongs to
2637
     * @return							true if the operation was successful
2638
     *
2639
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2640
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2641
     *
2642
     */
2643
    @Override
2644
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2645
        //currently typeDesignations are not persisted with the homotypical group
2646
        //so explicit adding to the homotypical group is not necessary.
2647
        if (typeDesignation != null){
2648
            checkHomotypicalGroup(typeDesignation);
2649
            this.typeDesignations.add(typeDesignation);
2650
            typeDesignation.addTypifiedName(this);
2651

    
2652
            if (addToAllNames){
2653
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2654
                    if (taxonName != this){
2655
                        taxonName.addTypeDesignation(typeDesignation, false);
2656
                    }
2657
                }
2658
            }
2659
        }
2660
        return true;
2661
    }
2662

    
2663
    /**
2664
     * Throws an Exception this type designation already has typified names from another homotypical group.
2665
     * @param typeDesignation
2666
     */
2667
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2668
        if(typeDesignation.getTypifiedNames().size() > 0){
2669
            Set<HomotypicalGroup> groups = new HashSet<>();
2670
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2671
            for (TaxonName taxonName: names){
2672
                groups.add(taxonName.getHomotypicalGroup());
2673
            }
2674
            if (groups.size() > 1){
2675
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2676
            }
2677
        }
2678
    }
2679

    
2680

    
2681

    
2682
//*********************** HOMOTYPICAL GROUP *********************************************//
2683

    
2684

    
2685
    /**
2686
     * Returns the {@link HomotypicalGroup homotypical group} to which
2687
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2688
     * that share the same types.
2689
     *
2690
     * @see 	HomotypicalGroup
2691
     */
2692

    
2693
    @Override
2694
    public HomotypicalGroup getHomotypicalGroup() {
2695
        if (homotypicalGroup == null){
2696
            homotypicalGroup = new HomotypicalGroup();
2697
            homotypicalGroup.typifiedNames.add(this);
2698
        }
2699
    	return homotypicalGroup;
2700
    }
2701

    
2702
    /**
2703
     * @see #getHomotypicalGroup()
2704
     */
2705
    @Override
2706
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2707
        if (homotypicalGroup == null){
2708
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2709
        }
2710
        /*if (this.homotypicalGroup != null){
2711
        	this.homotypicalGroup.removeTypifiedName(this, false);
2712
        }*/
2713
        this.homotypicalGroup = homotypicalGroup;
2714
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2715
        	 this.homotypicalGroup.addTypifiedName(this);
2716
        }
2717
    }
2718

    
2719

    
2720

    
2721
// *************************************************************************//
2722

    
2723
    /**
2724
     * Returns the complete string containing the
2725
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2726
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2727
     *
2728
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2729
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2730
     * @see		#getNomenclaturalReference()
2731
     * @see		#getNomenclaturalMicroReference()
2732
     */
2733
    @Override
2734
    @Transient
2735
    public String getCitationString(){
2736
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2737
    }
2738

    
2739
    /**
2740
     * Returns the parsing problems
2741
     * @return
2742
     */
2743
    @Override
2744
    public List<ParserProblem> getParsingProblems(){
2745
        return ParserProblem.warningList(this.parsingProblem);
2746
    }
2747

    
2748
    /**
2749
     * Returns the string containing the publication date (generally only year)
2750
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2751
     * no nomenclatural reference.
2752
     *
2753
     * @return  the string containing the publication date of <i>this</i> taxon name
2754
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2755
     */
2756
    @Override
2757
    @Transient
2758
    @ValidTaxonomicYear(groups=Level3.class)
2759
    public String getReferenceYear(){
2760
        if (this.getNomenclaturalReference() != null ){
2761
            return this.getNomenclaturalReference().getYear();
2762
        }else{
2763
            return null;
2764
        }
2765
    }
2766

    
2767
    /**
2768
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2769
     * In this context a taxon base means the use of a taxon name by a reference
2770
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2771
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2772
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2773
     * within a taxonomic treatment (identified by one reference).
2774
     *
2775
     * @see	#getTaxa()
2776
     * @see	#getSynonyms()
2777
     */
2778
    @Override
2779
    public Set<TaxonBase> getTaxonBases() {
2780
        if(taxonBases == null) {
2781
            this.taxonBases = new HashSet<TaxonBase>();
2782
        }
2783
        return this.taxonBases;
2784
    }
2785

    
2786
    /**
2787
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2788
     * to the set of taxon bases using <i>this</i> taxon name.
2789
     *
2790
     * @param  taxonBase  the taxon base to be added
2791
     * @see 			  #getTaxonBases()
2792
     * @see 			  #removeTaxonBase(TaxonBase)
2793
     */
2794
    //TODO protected
2795
    @Override
2796
    public void addTaxonBase(TaxonBase taxonBase){
2797
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2798
        ReflectionUtils.makeAccessible(method);
2799
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2800
        taxonBases.add(taxonBase);
2801
    }
2802
    /**
2803
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2804
     *
2805
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2806
     * @see    				#getTaxonBases()
2807
     * @see    				#addTaxonBase(TaxonBase)
2808
     */
2809
    @Override
2810
    public void removeTaxonBase(TaxonBase taxonBase){
2811
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2812
        ReflectionUtils.makeAccessible(method);
2813
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2814

    
2815

    
2816
    }
2817

    
2818
    /**
2819
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2820
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2821
     * the set returned by getTaxonBases().
2822
     *
2823
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2824
     * @see	#getTaxonBases()
2825
     * @see	#getSynonyms()
2826
     */
2827
    @Override
2828
    @Transient
2829
    public Set<Taxon> getTaxa(){
2830
        Set<Taxon> result = new HashSet<>();
2831
        for (TaxonBase taxonBase : this.taxonBases){
2832
            if (taxonBase instanceof Taxon){
2833
                result.add((Taxon)taxonBase);
2834
            }
2835
        }
2836
        return result;
2837
    }
2838

    
2839
    /**
2840
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2841
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2842
     * the set returned by getTaxonBases().
2843
     *
2844
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2845
     * @see	#getTaxonBases()
2846
     * @see	#getTaxa()
2847
     */
2848
    @Override
2849
    @Transient
2850
    public Set<Synonym> getSynonyms() {
2851
        Set<Synonym> result = new HashSet<>();
2852
        for (TaxonBase taxonBase : this.taxonBases){
2853
            if (taxonBase instanceof Synonym){
2854
                result.add((Synonym)taxonBase);
2855
            }
2856
        }
2857
        return result;
2858
    }
2859

    
2860
    //******* REGISTRATION *****************/
2861

    
2862
    @Override
2863
    public Set<Registration> getRegistrations() {
2864
        return this.registrations;
2865
    }
2866

    
2867

    
2868
// ************* RELATIONSHIPS *****************************/
2869

    
2870

    
2871
    /**
2872
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2873
     * by title cache of the related names.
2874
     * @see #getHybridParentRelations()
2875
     */
2876
    @Override
2877
    @Transient
2878
    public List<HybridRelationship> getOrderedChildRelationships(){
2879
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2880
        result.addAll(this.hybridChildRelations);
2881
        Collections.sort(result);
2882
        Collections.reverse(result);
2883
        return result;
2884

    
2885
    }
2886

    
2887

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

    
2910
    /**
2911
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2912
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2913
     * "is first/second parent" or "is male/female parent". By invoking this
2914
     * method <i>this</i> botanical name becomes a parent of the hybrid child
2915
     * botanical name.
2916
     *
2917
     * @param childName       the botanical name of the child for this new hybrid name relationship
2918
     * @param type            the type of this new name relationship
2919
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2920
     * @return
2921
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
2922
     * @see                   #getRelationsToThisName()
2923
     * @see                   #getNameRelations()
2924
     * @see                   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
2925
     * @see                   #addNameRelationship(NameRelationship)
2926
     */
2927
    @Override
2928
    public HybridRelationship addHybridChild(INonViralName childName, HybridRelationshipType type, String ruleConsidered){
2929
        return new HybridRelationship(childName, this, type, ruleConsidered);
2930
    }
2931

    
2932
    @Override
2933
    public void removeHybridChild(INonViralName child) {
2934
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2935
        hybridRelationships.addAll(this.getHybridChildRelations());
2936
        hybridRelationships.addAll(this.getHybridParentRelations());
2937
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2938
            // remove name relationship from this side
2939
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2940
                this.removeHybridRelationship(hybridRelationship);
2941
            }
2942
        }
2943
    }
2944

    
2945
    @Override
2946
    public void removeHybridParent(INonViralName parent) {
2947
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2948
        hybridRelationships.addAll(this.getHybridChildRelations());
2949
        hybridRelationships.addAll(this.getHybridParentRelations());
2950
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2951
            // remove name relationship from this side
2952
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2953
                this.removeHybridRelationship(hybridRelationship);
2954
            }
2955
        }
2956
    }
2957

    
2958

    
2959

    
2960
// *********** DESCRIPTIONS *************************************
2961

    
2962
    /**
2963
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2964
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2965
     * concerning the taxon name like for instance the content of its first
2966
     * publication (protolog) or a picture of this publication.
2967
     *
2968
     * @see	#addDescription(TaxonNameDescription)
2969
     * @see	#removeDescription(TaxonNameDescription)
2970
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2971
     */
2972
    @Override
2973
    public Set<TaxonNameDescription> getDescriptions() {
2974
        return descriptions;
2975
    }
2976

    
2977
    /**
2978
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2979
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2980
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2981
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2982
     *
2983
     * @param  description  the taxon name description to be added
2984
     * @see					#getDescriptions()
2985
     * @see 			  	#removeDescription(TaxonNameDescription)
2986
     */
2987
    @Override
2988
    public void addDescription(TaxonNameDescription description) {
2989
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2990
        ReflectionUtils.makeAccessible(field);
2991
        ReflectionUtils.setField(field, description, this);
2992
        descriptions.add(description);
2993
    }
2994
    /**
2995
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2996
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2997
     * of the description itself will be set to "null".
2998
     *
2999
     * @param  description  the taxon name description which should be removed
3000
     * @see     		  	#getDescriptions()
3001
     * @see     		  	#addDescription(TaxonNameDescription)
3002
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
3003
     */
3004
    @Override
3005
    public void removeDescription(TaxonNameDescription description) {
3006
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
3007
        ReflectionUtils.makeAccessible(field);
3008
        ReflectionUtils.setField(field, description, null);
3009
        descriptions.remove(description);
3010
    }
3011

    
3012
// *********** HOMOTYPIC GROUP METHODS **************************************************
3013

    
3014
    @Override
3015
    @Transient
3016
    public void mergeHomotypicGroups(TaxonName name){
3017
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
3018
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
3019
        name.setHomotypicalGroup(this.homotypicalGroup);
3020
    }
3021

    
3022
    /**
3023
     * Returns the boolean value indicating whether a given taxon name belongs
3024
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
3025
     * or not (false). Returns "true" only if the homotypical groups of both
3026
     * taxon names exist and if they are identical.
3027
     *
3028
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
3029
     * @return  			   the boolean value of the check
3030
     * @see     			   HomotypicalGroup
3031
     */
3032
    @Override
3033
    @Transient
3034
    public boolean isHomotypic(TaxonName homoTypicName) {
3035
        if (homoTypicName == null) {
3036
            return false;
3037
        }
3038
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
3039
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
3040
            return false;
3041
        }
3042
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
3043
            return true;
3044
        }
3045
        return false;
3046
    }
3047

    
3048

    
3049
    /**
3050
     * Checks whether name is a basionym for ALL names
3051
     * in its homotypical group.
3052
     * Returns <code>false</code> if there are no other names in the group
3053
     * @param name
3054
     * @return
3055
     */
3056
    @Override
3057
    @Transient
3058
    public boolean isGroupsBasionym() {
3059
    	if (homotypicalGroup == null){
3060
    		homotypicalGroup = HomotypicalGroup.NewInstance();
3061
    		homotypicalGroup.addTypifiedName(this);
3062
    	}
3063
        Set<TaxonName> typifiedNames = homotypicalGroup.getTypifiedNames();
3064

    
3065
        // Check whether there are any other names in the group
3066
        if (typifiedNames.size() == 1) {
3067
                return false;
3068
        }
3069

    
3070
        for (TaxonName taxonName : typifiedNames) {
3071
                if (!taxonName.equals(this)) {
3072
                        if (! isBasionymFor(taxonName)) {
3073
                                return false;
3074
                        }
3075
                }
3076
        }
3077
        return true;
3078
    }
3079

    
3080
    /**
3081
     * Checks whether a basionym relationship exists between fromName and toName.
3082
     *
3083
     * @param fromName
3084
     * @param toName
3085
     * @return
3086
     */
3087
    @Override
3088
    @Transient
3089
    public boolean isBasionymFor(TaxonName newCombinationName) {
3090
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3091
            for (NameRelationship relation : relations) {
3092
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3093
                                    relation.getFromName().equals(this)) {
3094
                            return true;
3095
                    }
3096
            }
3097
            return false;
3098
    }
3099

    
3100
    /**
3101
     * Creates a basionym relationship to all other names in this names homotypical
3102
     * group.
3103
     *
3104
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3105
     */
3106
    @Override
3107
    @Transient
3108
    public void makeGroupsBasionym() {
3109
        this.homotypicalGroup.setGroupBasionym(this);
3110
    }
3111

    
3112

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

    
3152
    @Override
3153
    @Transient
3154
    public boolean isGenusOrSupraGeneric() {
3155
        return isGenus()|| isSupraGeneric();
3156
    }
3157
    /**
3158
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3159
     * taxon name is higher than the species rank and lower than the
3160
     * genus rank (true) or not (false). Infrageneric non viral names are
3161
     * binomials. Returns false if rank is null.
3162
     *
3163
     * @see  #isSupraGeneric()
3164
     * @see  #isGenus()
3165
     * @see  #isSpecies()
3166
     * @see  #isInfraSpecific()
3167
     */
3168
    @Override
3169
    @Transient
3170
    public boolean isInfraGeneric() {
3171
        if (rank == null){
3172
            return false;
3173
        }
3174
        return getRank().isInfraGeneric();
3175
    }
3176

    
3177
    /**
3178
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3179
     * taxon name is higher than the species rank (true) or not (false).
3180
     * Returns false if rank is null.
3181
     *
3182
     * @see  #isGenus()
3183
     * @see  #isInfraGeneric()
3184
     * @see  #isSpecies()
3185
     * @see  #isInfraSpecific()
3186
     */
3187
    @Override
3188
    @Transient
3189
    public boolean isSupraSpecific(){
3190
        if (rank == null) {
3191
            return false;
3192
        }
3193
        return getRank().isHigher(Rank.SPECIES());
3194
    }
3195

    
3196
    /**
3197
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3198
     * taxon name is the species rank (true) or not (false). Non viral names
3199
     * with species rank are binomials.
3200
     * Returns false if rank is null.
3201
     *
3202
     * @see  #isSupraGeneric()
3203
     * @see  #isGenus()
3204
     * @see  #isInfraGeneric()
3205
     * @see  #isInfraSpecific()
3206
     */
3207
    @Override
3208
    @Transient
3209
    public boolean isSpecies() {
3210
        if (rank == null){
3211
            return false;
3212
        }
3213
        return getRank().isSpecies();
3214
    }
3215
    /**
3216
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3217
     * taxon name is lower than the species rank (true) or not (false).
3218
     * Infraspecific non viral names are trinomials.
3219
     * Returns false if rank is null.
3220
     *
3221
     * @see  #isSupraGeneric()
3222
     * @see  #isGenus()
3223
     * @see  #isInfraGeneric()
3224
     * @see  #isSpecies()
3225
     */
3226
    @Override
3227
    @Transient
3228
    public boolean isInfraSpecific() {
3229
        if (rank == null){
3230
            return false;
3231
        }
3232
        return getRank().isInfraSpecific();
3233
    }
3234

    
3235
    /**
3236
     * Returns true if this name's rank indicates a rank that aggregates species like species
3237
     * aggregates or species groups, false otherwise. This methods currently returns false
3238
     * for all user defined ranks.
3239
     *
3240
     *@see Rank#isSpeciesAggregate()
3241
     *
3242
     * @return
3243
     */
3244
    @Override
3245
    @Transient
3246
    public boolean isSpeciesAggregate() {
3247
        if (rank == null){
3248
            return false;
3249
        }
3250
        return getRank().isSpeciesAggregate();
3251
    }
3252

    
3253

    
3254
    /**
3255
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3256
     * the construction of <i>this</i> taxon name since there is no specific
3257
     * nomenclatural code defined. The real implementention takes place in the
3258
     * subclasses {@link IBacterialName BacterialName},
3259
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3260
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3261
     * and only one nomenclatural code.
3262
     *
3263
     * @return  null
3264
     * @see  	#isCodeCompliant()
3265
     * @see  	#getHasProblem()
3266
     * @deprecated use {@link #getNameType()} instead
3267
     */
3268
    @Override
3269
    @Deprecated
3270
    @Transient
3271
    @java.beans.Transient
3272
    public NomenclaturalCode getNomenclaturalCode() {
3273
        return nameType;
3274
    }
3275

    
3276

    
3277
    /**
3278
     * Generates and returns the string with the scientific name of <i>this</i>
3279
     * taxon name (only non viral taxon names can be generated from their
3280
     * components). This string may be stored in the inherited
3281
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3282
     * This method overrides the generic and inherited
3283
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3284
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3285
     *
3286
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3287
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3288
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3289
     */
3290
//	@Override
3291
//	public abstract String generateTitle();
3292

    
3293
    /**
3294
     * Creates a basionym relationship between this name and
3295
     * 	each name in its homotypic group.
3296
     *
3297
     * @param basionymName
3298
     */
3299
    @Override
3300
    @Transient
3301
    public void setAsGroupsBasionym() {
3302

    
3303
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3304
        if (homotypicalGroup == null) {
3305
            return;
3306
        }
3307

    
3308
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3309
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3310

    
3311
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3312

    
3313
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3314

    
3315
            for(NameRelationship nameRelation : nameRelations){
3316
                relations.add(nameRelation);
3317
            }
3318
        }
3319

    
3320
        for (NameRelationship relation : relations) {
3321

    
3322
            // If this is a basionym relation, and toName is in the homotypical group,
3323
            //	remove the relationship.
3324
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3325
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3326
                removeRelations.add(relation);
3327
            }
3328
        }
3329

    
3330
        // Removing relations from a set through which we are iterating causes a
3331
        //	ConcurrentModificationException. Therefore, we delete the targeted
3332
        //	relations in a second step.
3333
        for (NameRelationship relation : removeRelations) {
3334
            this.removeNameRelationship(relation);
3335
        }
3336

    
3337
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3338
            if (!name.equals(this)) {
3339

    
3340
                // First check whether the relationship already exists
3341
                if (!this.isBasionymFor(name)) {
3342

    
3343
                    // Then create it
3344
                    name.addRelationshipFromName(this,
3345
                            NameRelationshipType.BASIONYM(), null);
3346
                }
3347
            }
3348
        }
3349
    }
3350

    
3351
    /**
3352
     * Removes basionym relationship between this name and
3353
     * 	each name in its homotypic group.
3354
     *
3355
     * @param basionymName
3356
     */
3357
    @Override
3358
    @Transient
3359
    public void removeAsGroupsBasionym() {
3360

    
3361
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3362

    
3363
        if (homotypicalGroup == null) {
3364
            return;
3365
        }
3366

    
3367
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3368
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3369

    
3370
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3371

    
3372
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3373

    
3374
            for(NameRelationship nameRelation : nameRelations){
3375
                relations.add(nameRelation);
3376
            }
3377
        }
3378

    
3379
        for (NameRelationship relation : relations) {
3380

    
3381
            // If this is a basionym relation, and toName is in the homotypical group,
3382
            //	and fromName is basionymName, remove the relationship.
3383
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3384
                    relation.getFromName().equals(this) &&
3385
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3386
                removeRelations.add(relation);
3387
            }
3388
        }
3389

    
3390
        // Removing relations from a set through which we are iterating causes a
3391
        //	ConcurrentModificationException. Therefore, we delete the targeted
3392
        //	relations in a second step.
3393
        for (NameRelationship relation : removeRelations) {
3394
            this.removeNameRelationship(relation);
3395
        }
3396
    }
3397

    
3398

    
3399
    /**
3400
     * Defines the last part of the name.
3401
     * This is for infraspecific taxa, the infraspecific epithet,
3402
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3403
     * else the genusOrUninomial.
3404
     * However, the result does not depend on the rank (which may be not correctly set
3405
     * in case of dirty data) but returns the first name part which is not blank
3406
     * considering the above order.
3407
     * @return the first not blank name part in reverse order
3408
     */
3409
    @Override
3410
    public String getFamilyNamePart() {
3411
        String result =
3412
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3413
                    this.getInfraSpecificEpithet() :
3414
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3415
                    this.getSpecificEpithet():
3416
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3417
                    this.getInfraGenericEpithet():
3418
                this.getGenusOrUninomial();
3419
        return result;
3420
    }
3421

    
3422
    /**
3423
     * {@inheritDoc}
3424
     */
3425
    @Override
3426
    public boolean isHybridName() {
3427
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3428
    }
3429

    
3430
    /**
3431
     * {@inheritDoc}
3432
     */
3433
    @Override
3434
    public boolean isHybrid() {
3435
        return this.isHybridName() || this.isHybridFormula();
3436
    }
3437

    
3438
// ***************** COMPARE ********************************/
3439

    
3440
    @Override
3441
    public int compareToName(TaxonName otherName){
3442

    
3443
        int result = 0;
3444

    
3445
        if (otherName == null) {
3446
            throw new NullPointerException("Cannot compare to null.");
3447
        }
3448

    
3449
        //other
3450
        otherName = deproxy(otherName);
3451
        String otherNameCache = otherName.getNameCache();
3452
        String otherTitleCache = otherName.getTitleCache();
3453
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3454
        if (otherName.isAutonym()){
3455
            boolean isProtected = otherName.isProtectedNameCache();
3456
            String oldNameCache = otherName.getNameCache();
3457
            otherName.setProtectedNameCache(false);
3458
            otherName.setNameCache(null, false);
3459
            otherNameCache = otherName.getNameCache();
3460
            otherName.setNameCache(oldNameCache, isProtected);
3461
        }
3462

    
3463
        //this
3464
        String thisNameCache = this.getNameCache();
3465
        String thisTitleCache = this.getTitleCache();
3466

    
3467
        if (this.isAutonym()){
3468
            boolean isProtected = this.isProtectedNameCache();
3469
            String oldNameCache = this.getNameCache();
3470
            this.setProtectedNameCache(false);
3471
            this.setNameCache(null, false);
3472
            thisNameCache = this.getNameCache();
3473
            this.setNameCache(oldNameCache, isProtected);
3474
        }
3475

    
3476

    
3477
        // Compare name cache of taxon names
3478
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3479
            thisNameCache = normalizeName(thisNameCache);
3480
            otherNameCache = normalizeName(otherNameCache);
3481
            result = thisNameCache.compareTo(otherNameCache);
3482
        }
3483

    
3484
        // Compare title cache of taxon names
3485
        if (result == 0){
3486
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3487
                thisTitleCache = normalizeName(thisTitleCache);
3488
                otherTitleCache = normalizeName(otherTitleCache);
3489
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3490
            }
3491
        }
3492

    
3493
        return result;
3494
    }
3495

    
3496
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3497
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3498

    
3499
    /**
3500
     * @param thisNameCache
3501
     * @param HYBRID_SIGN
3502
     * @param QUOT_SIGN
3503
     * @return
3504
     */
3505
    private String normalizeName(String thisNameCache) {
3506
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3507
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3508
        return thisNameCache;
3509
    }
3510

    
3511
// ********************** INTERFACES ********************************************/
3512

    
3513
    /**
3514
     * Method to cast a interfaced name to a concrete name.
3515
     * The method includes a deproxy to guarantee that no
3516
     * class cast exception is thrown.
3517
     *
3518
     * @see #castAndDeproxy(Set)
3519
     * @param interfacedName
3520
     * @return
3521
     */
3522
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3523
        return deproxy(interfacedName, TaxonName.class);
3524
    }
3525

    
3526
    /**
3527
     * Method to cast a set of interfaced names to concrete namex.
3528
     * The method includes a deproxy to guarantee that no
3529
     * class cast exception is thrown.
3530
     *
3531
     * @see #castAndDeproxy(ITaxonNameBase)
3532
     * @param naminterfacedNames
3533
     * @return
3534
     */
3535
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3536
        Set<TaxonName> result = new HashSet<>();
3537
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3538
            result.add(castAndDeproxy(naminterfacedName));
3539
        }
3540
        return result;
3541
    }
3542

    
3543
//************************ isType ***********************************************/
3544

    
3545
    /**
3546
     * @return
3547
     */
3548
    @Override
3549
    public boolean isNonViral() {
3550
        return nameType.isNonViral();
3551
    }
3552

    
3553
    @Override
3554
    public boolean isZoological(){
3555
        return nameType.isZoological();
3556
    }
3557
    @Override
3558
    public boolean isBotanical() {
3559
        return nameType.isBotanical();
3560
    }
3561
    @Override
3562
    public boolean isCultivar() {
3563
        return nameType.isCultivar();
3564
    }
3565
    @Override
3566
    public boolean isBacterial() {
3567
        return nameType.isBacterial();
3568
    }
3569
    @Override
3570
    public boolean isViral() {
3571
        return nameType.isViral();
3572
    }
3573

    
3574
// *********************** CACHES ***************************************************/
3575

    
3576

    
3577
    @Override
3578
    public boolean updateCaches() {
3579
        boolean result = updateAuthorshipCache();
3580
        result |= updateNameCache();
3581
        result |= super.updateCaches();
3582
        result |= updateFullTitleCache();
3583
        return result;
3584
    }
3585

    
3586
    /**
3587
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
3588
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened.
3589
     * @return <code>true</code> if something changed
3590
     */
3591
    private boolean updateAuthorshipCache() {
3592
        //updates the authorship cache if necessary and via the listener updates all higher caches
3593
        if (protectedAuthorshipCache == false){
3594
            String oldCache = this.authorshipCache;
3595
            String newCache = this.getAuthorshipCache();
3596
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3597
                this.setAuthorshipCache(this.getAuthorshipCache(), false);
3598
                return true;
3599
            }
3600
        }
3601
        return false;
3602
    }
3603

    
3604
    /**
3605
     * @return
3606
     */
3607
    private boolean updateNameCache() {
3608
        //updates the name cache if necessary and via the listener updates all higher caches
3609
        if (protectedNameCache == false){
3610
            String oldCache = this.nameCache;
3611
            String newCache = this.getNameCache();
3612
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3613
                this.setNameCache(this.getNameCache(), false);
3614
                return true;
3615
            }
3616
        }
3617
        return false;
3618
    }
3619

    
3620

    
3621
    /**
3622
     * @return
3623
     */
3624
    private boolean updateFullTitleCache() {
3625
        if (protectedFullTitleCache == false){
3626
            String oldCache = this.fullTitleCache;
3627
            String newCache = this.getFullTitleCache();
3628
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3629
                this.setFullTitleCache(this.getFullTitleCache(), false);
3630
                return true;
3631
            }
3632
        }
3633
        return false;
3634
    }
3635

    
3636

    
3637
//*********************** CLONE ********************************************************/
3638

    
3639
    /**
3640
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3641
     * a new instance that differs only slightly from <i>this</i> taxon name by
3642
     * modifying only some of the attributes.<BR><BR>
3643
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3644
     * <b>The name gets a newly created homotypical group</b><BR>
3645
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3646
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3647
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3648
     *
3649
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3650
     * @see java.lang.Object#clone()
3651
     */
3652
    @Override
3653
    public Object clone() {
3654
        TaxonName result;
3655
        try {
3656
            result = (TaxonName)super.clone();
3657

    
3658
            //taxonBases -> empty
3659
            result.taxonBases = new HashSet<>();
3660

    
3661
            //empty caches
3662
            if (! protectedFullTitleCache){
3663
                result.fullTitleCache = null;
3664
            }
3665

    
3666
            //descriptions
3667
            result.descriptions = new HashSet<>();
3668
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3669
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3670
                result.descriptions.add(newDescription);
3671
            }
3672

    
3673
            //status
3674
            result.status = new HashSet<>();
3675
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3676
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3677
                result.status.add(newStatus);
3678
            }
3679

    
3680

    
3681
            //to relations
3682
            result.relationsToThisName = new HashSet<>();
3683
            for (NameRelationship toRelationship : getRelationsToThisName()){
3684
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3685
                newRelationship.setRelatedTo(result);
3686
                result.relationsToThisName.add(newRelationship);
3687
            }
3688

    
3689
            //from relations
3690
            result.relationsFromThisName = new HashSet<>();
3691
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3692
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3693
                newRelationship.setRelatedFrom(result);
3694
                result.relationsFromThisName.add(newRelationship);
3695
            }
3696

    
3697
            //type designations
3698
            result.typeDesignations = new HashSet<>();
3699
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3700
                TypeDesignationBase<?> newDesignation = (TypeDesignationBase<?>)typeDesignation.clone();
3701
                this.removeTypeDesignation(newDesignation);
3702
                result.addTypeDesignation(newDesignation, false);
3703
            }
3704

    
3705
            //homotypicalGroup
3706
            //TODO still needs to be discussed
3707
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3708
            result.homotypicalGroup.addTypifiedName(this);
3709

    
3710

    
3711
            //HybridChildRelations
3712
            result.hybridChildRelations = new HashSet<>();
3713
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3714
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3715
                newChildRelationship.setRelatedTo(result);
3716
                result.hybridChildRelations.add(newChildRelationship);
3717
            }
3718

    
3719
            //HybridParentRelations
3720
            result.hybridParentRelations = new HashSet<>();
3721
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3722
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3723
                newParentRelationship.setRelatedFrom(result);
3724
                result.hybridParentRelations.add(newParentRelationship);
3725
            }
3726

    
3727
            //empty caches
3728
            if (! protectedNameCache){
3729
                result.nameCache = null;
3730
            }
3731

    
3732
            //empty caches
3733
            if (! protectedAuthorshipCache){
3734
                result.authorshipCache = null;
3735
            }
3736

    
3737
            //no changes to: appendedPharse, nomenclaturalReference,
3738
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3739
            //protectedFullTitleCache, rank
3740
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3741
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3742
            //protectedAuthorshipCache, protectedNameCache,
3743
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3744
            //acronym
3745
            //subGenusAuthorship, nameApprobation
3746
            //anamorphic
3747
            //cultivarName
3748
            return result;
3749
        } catch (CloneNotSupportedException e) {
3750
            logger.warn("Object does not implement cloneable");
3751
            e.printStackTrace();
3752
            return null;
3753
        }
3754

    
3755
    }
3756

    
3757

    
3758
}
3759

    
(29-29/36)