Project

General

Profile

Download (140 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
    @Override
826
    public boolean hasUnprotectedCache(){
827
        return super.hasUnprotectedCache()
828
                || !this.protectedNameCache
829
                || !this.protectedAuthorshipCache
830
                || !this.protectedFullTitleCache;
831
    }
832

    
833
// ****************** GETTER / SETTER ****************************/
834

    
835
    @Override
836
    public NomenclaturalCode getNameType() {
837
        return nameType;
838
    }
839

    
840
    @Override
841
    public void setNameType(NomenclaturalCode nameType) {
842
        this.nameType = nameType;
843
    }
844

    
845
    /**
846
     * Returns the boolean value of the flag intended to protect (true)
847
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
848
     * string of <i>this</i> non viral taxon name.
849
     *
850
     * @return  the boolean value of the protectedNameCache flag
851
     * @see     #getNameCache()
852
     */
853
    @Override
854
    public boolean isProtectedNameCache() {
855
        return protectedNameCache;
856
    }
857

    
858
    /**
859
     * @see     #isProtectedNameCache()
860
     */
861
    @Override
862
    public void setProtectedNameCache(boolean protectedNameCache) {
863
        this.protectedNameCache = protectedNameCache;
864
    }
865

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

    
880
    /**
881
     * @see  #getGenusOrUninomial()
882
     */
883
    @Override
884
    public void setGenusOrUninomial(String genusOrUninomial) {
885
        this.genusOrUninomial = StringUtils.isBlank(genusOrUninomial) ? null : genusOrUninomial;
886
    }
887

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

    
902
    /**
903
     * @see  #getInfraGenericEpithet()
904
     */
905
    @Override
906
    public void setInfraGenericEpithet(String infraGenericEpithet){
907
        this.infraGenericEpithet = StringUtils.isBlank(infraGenericEpithet)? null : infraGenericEpithet;
908
    }
909

    
910
    /**
911
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
912
     * species aggregate or lower (bi- or trinomial). Species epithet strings
913
     * begin with a lower case letter.
914
     *
915
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
916
     * @see     #getNameCache()
917
     */
918
    @Override
919
    public String getSpecificEpithet(){
920
        return this.specificEpithet;
921
    }
922

    
923
    /**
924
     * @see  #getSpecificEpithet()
925
     */
926
    @Override
927
    public void setSpecificEpithet(String specificEpithet){
928
        this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
929
    }
930

    
931
    /**
932
     * Returns the species subdivision epithet string (infraspecific part) for
933
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
934
     * (lower than species: trinomial). Species subdivision epithet strings
935
     * begin with a lower case letter.
936
     *
937
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
938
     * @see     #getNameCache()
939
     */
940
    @Override
941
    public String getInfraSpecificEpithet(){
942
        return this.infraSpecificEpithet;
943
    }
944

    
945
    /**
946
     * @see  #getInfraSpecificEpithet()
947
     */
948
    @Override
949
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
950
        this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
951
    }
952

    
953
    /**
954
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
955
     * taxon name.
956
     *
957
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
958
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
959
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
960
     */
961
    @Override
962
    public TeamOrPersonBase<?> getCombinationAuthorship(){
963
        return this.combinationAuthorship;
964
    }
965

    
966
    /**
967
     * @see  #getCombinationAuthorship()
968
     */
969
    @Override
970
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
971
        this.combinationAuthorship = combinationAuthorship;
972
    }
973

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

    
1009
    @Override
1010
    public TeamOrPersonBase<?> getInCombinationAuthorship(){
1011
        return this.inCombinationAuthorship;
1012
    }
1013
    @Override
1014
    public void setInCombinationAuthorship(TeamOrPersonBase<?> inCombinationAuthorship) {
1015
        this.inCombinationAuthorship = inCombinationAuthorship;
1016
    }
1017

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

    
1034
    /**
1035
     * @see  #getBasionymAuthorship()
1036
     */
1037
    @Override
1038
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
1039
        this.basionymAuthorship = basionymAuthorship;
1040
    }
1041

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

    
1063
    /**
1064
     * @see  #getExBasionymAuthorship()
1065
     */
1066
    @Override
1067
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
1068
        this.exBasionymAuthorship = exBasionymAuthorship;
1069
    }
1070

    
1071
    @Override
1072
    public TeamOrPersonBase<?> getInBasionymAuthorship(){
1073
        return this.inBasionymAuthorship;
1074
    }
1075
    @Override
1076
    public void setInBasionymAuthorship(TeamOrPersonBase<?> inBasionymAuthorship) {
1077
        this.inBasionymAuthorship = inBasionymAuthorship;
1078
    }
1079

    
1080
    /**
1081
     * Returns the boolean value of the flag intended to protect (true)
1082
     * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
1083
     * of <i>this</i> non viral taxon name.
1084
     *
1085
     * @return  the boolean value of the protectedAuthorshipCache flag
1086
     * @see     #getAuthorshipCache()
1087
     */
1088
    @Override
1089
    public boolean isProtectedAuthorshipCache() {
1090
        return protectedAuthorshipCache;
1091
    }
1092

    
1093
    /**
1094
     * @see     #isProtectedAuthorshipCache()
1095
     * @see     #getAuthorshipCache()
1096
     */
1097
    @Override
1098
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
1099
        this.protectedAuthorshipCache = protectedAuthorshipCache;
1100
    }
1101

    
1102
    /**
1103
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1104
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
1105
     *
1106
     * @see    #getHybridRelationships()
1107
     * @see    #getChildRelationships()
1108
     * @see    HybridRelationshipType
1109
     */
1110
    @Override
1111
    public Set<HybridRelationship> getHybridParentRelations() {
1112
        if(hybridParentRelations == null) {
1113
            this.hybridParentRelations = new HashSet<>();
1114
        }
1115
        return hybridParentRelations;
1116
    }
1117

    
1118
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1119
        this.hybridParentRelations = hybridParentRelations;
1120
    }
1121

    
1122

    
1123
    /**
1124
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1125
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
1126
     *
1127
     * @see    #getHybridRelationships()
1128
     * @see    #getParentRelationships()
1129
     * @see    HybridRelationshipType
1130
     */
1131
    @Override
1132
    public Set<HybridRelationship> getHybridChildRelations() {
1133
        if(hybridChildRelations == null) {
1134
            this.hybridChildRelations = new HashSet<>();
1135
        }
1136
        return hybridChildRelations;
1137
    }
1138

    
1139
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1140
        this.hybridChildRelations = hybridChildRelations;
1141
    }
1142

    
1143
    @Override
1144
    public boolean isProtectedFullTitleCache() {
1145
        return protectedFullTitleCache;
1146
    }
1147

    
1148
    @Override
1149
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1150
        this.protectedFullTitleCache = protectedFullTitleCache;
1151
    }
1152

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

    
1174
    /**
1175
     * @see  #isHybridFormula()
1176
     */
1177
    @Override
1178
    public void setHybridFormula(boolean hybridFormula){
1179
        this.hybridFormula = hybridFormula;
1180
    }
1181

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

    
1198
    /**
1199
     * @see  #isMonomHybrid()
1200
     * @see  #isBinomHybrid()
1201
     * @see  #isTrinomHybrid()
1202
     */
1203
    @Override
1204
    public void setMonomHybrid(boolean monomHybrid){
1205
        this.monomHybrid = monomHybrid;
1206
    }
1207

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

    
1224
    /**
1225
     * @see  #isBinomHybrid()
1226
     * @see  #isMonomHybrid()
1227
     * @see  #isTrinomHybrid()
1228
     */
1229
    @Override
1230
    public void setBinomHybrid(boolean binomHybrid){
1231
        this.binomHybrid = binomHybrid;
1232
    }
1233

    
1234
    @Override
1235
    public boolean isTrinomHybrid(){
1236
        return this.trinomHybrid;
1237
    }
1238

    
1239
    /**
1240
     * @see  #isTrinomHybrid()
1241
     * @see  #isBinomHybrid()
1242
     * @see  #isMonomHybrid()
1243
     */
1244
    @Override
1245
    public void setTrinomHybrid(boolean trinomHybrid){
1246
        this.trinomHybrid = trinomHybrid;
1247
    }
1248

    
1249
    // ****************** VIRAL NAME ******************/
1250

    
1251
    @Override
1252
    public String getAcronym(){
1253
        return this.acronym;
1254
    }
1255

    
1256
    /**
1257
     * @see  #getAcronym()
1258
     */
1259
    @Override
1260
    public void setAcronym(String acronym){
1261
        this.acronym = StringUtils.isBlank(acronym)? null : acronym;
1262
    }
1263

    
1264
    // ****************** BACTERIAL NAME ******************/
1265

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

    
1271
    @Override
1272
    public void setSubGenusAuthorship(String subGenusAuthorship){
1273
        this.subGenusAuthorship = subGenusAuthorship;
1274
    }
1275

    
1276

    
1277
    @Override
1278
    public String getNameApprobation(){
1279
        return this.nameApprobation;
1280
    }
1281

    
1282
    /**
1283
     * @see  #getNameApprobation()
1284
     */
1285
    @Override
1286
    public void setNameApprobation(String nameApprobation){
1287
        this.nameApprobation = nameApprobation;
1288
    }
1289

    
1290
    //************ Zoological Name
1291

    
1292

    
1293
    @Override
1294
    public String getBreed(){
1295
        return this.breed;
1296
    }
1297
    /**
1298
     * @see  #getBreed()
1299
     */
1300
    @Override
1301
    public void setBreed(String breed){
1302
        this.breed = StringUtils.isBlank(breed) ? null : breed;
1303
    }
1304

    
1305

    
1306
    @Override
1307
    public Integer getPublicationYear() {
1308
        return publicationYear;
1309
    }
1310
    /**
1311
     * @see  #getPublicationYear()
1312
     */
1313
    @Override
1314
    public void setPublicationYear(Integer publicationYear) {
1315
        this.publicationYear = publicationYear;
1316
    }
1317

    
1318

    
1319
    @Override
1320
    public Integer getOriginalPublicationYear() {
1321
        return originalPublicationYear;
1322
    }
1323
    /**
1324
     * @see  #getOriginalPublicationYear()
1325
     */
1326
    @Override
1327
    public void setOriginalPublicationYear(Integer originalPublicationYear) {
1328
        this.originalPublicationYear = originalPublicationYear;
1329
    }
1330

    
1331
    // **** Cultivar Name ************
1332

    
1333

    
1334
    @Override
1335
    public String getCultivarName(){
1336
        return this.cultivarName;
1337
    }
1338

    
1339
    /**
1340
     * @see  #getCultivarName()
1341
     */
1342
    @Override
1343
    public void setCultivarName(String cultivarName){
1344
        this.cultivarName = StringUtils.isBlank(cultivarName) ? null : cultivarName;
1345
    }
1346

    
1347
    // **************** Fungus Name
1348
    @Override
1349
    public boolean isAnamorphic(){
1350
        return this.anamorphic;
1351
    }
1352

    
1353
    /**
1354
     * @see  #isAnamorphic()
1355
     */
1356
    @Override
1357
    public void setAnamorphic(boolean anamorphic){
1358
        this.anamorphic = anamorphic;
1359
    }
1360

    
1361

    
1362
// **************** ADDER / REMOVE *************************/
1363

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

    
1393

    
1394
    /**
1395
     * Removes one {@link HybridRelationship hybrid relationship} from the set of
1396
     * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1397
     * is involved. The hybrid relationship will also be removed from the set
1398
     * belonging to the second botanical taxon name involved.
1399
     *
1400
     * @param  relationship  the hybrid relationship which should be deleted from the corresponding sets
1401
     * @see                  #getHybridRelationships()
1402
     */
1403
    @Override
1404
    public void removeHybridRelationship(HybridRelationship hybridRelation) {
1405
        if (hybridRelation == null) {
1406
            return;
1407
        }
1408

    
1409
        TaxonName parent = hybridRelation.getParentName();
1410
        TaxonName child = hybridRelation.getHybridName();
1411
        if (this.equals(parent)){
1412
            this.hybridParentRelations.remove(hybridRelation);
1413
            child.hybridChildRelations.remove(hybridRelation);
1414
            hybridRelation.setHybridName(null);
1415
            hybridRelation.setParentName(null);
1416
        }
1417
        if (this.equals(child)){
1418
            parent.hybridParentRelations.remove(hybridRelation);
1419
            this.hybridChildRelations.remove(hybridRelation);
1420
            hybridRelation.setHybridName(null);
1421
            hybridRelation.setParentName(null);
1422
        }
1423
    }
1424

    
1425
//********* METHODS **************************************/
1426

    
1427
    @Override
1428
    public INameCacheStrategy getCacheStrategy() {
1429
        rectifyNameCacheStrategy();
1430
        return this.cacheStrategy;
1431
    }
1432

    
1433
    @Override
1434
    public String generateFullTitle(){
1435
        if (getCacheStrategy() == null){
1436
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1437
            return null;
1438
        }else{
1439
            return cacheStrategy.getFullTitleCache(this);
1440
        }
1441
    }
1442

    
1443

    
1444
    @Override
1445
    public void setFullTitleCache(String fullTitleCache){
1446
        setFullTitleCache(fullTitleCache, PROTECTED);
1447
    }
1448

    
1449
    @Override
1450
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1451
        fullTitleCache = getTruncatedCache(fullTitleCache);
1452
        this.fullTitleCache = fullTitleCache;
1453
        this.setProtectedFullTitleCache(protectCache);
1454
    }
1455

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

    
1481

    
1482
    @Override
1483
    @Transient
1484
    public List<TaggedText> getTaggedName(){
1485
        INameCacheStrategy strat = getCacheStrategy();
1486
        return strat.getTaggedTitle(this);
1487
    }
1488

    
1489
    @Override
1490
    @Transient
1491
    public String getFullTitleCache(){
1492
        if (protectedFullTitleCache){
1493
            return this.fullTitleCache;
1494
        }
1495
        updateAuthorshipCache();
1496
        if (fullTitleCache == null ){
1497
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1498
        }
1499
        return fullTitleCache;
1500
    }
1501

    
1502

    
1503
    @Override
1504
    public String getTitleCache(){
1505
        if(!protectedTitleCache) {
1506
            updateAuthorshipCache();
1507
        }
1508
        return super.getTitleCache();
1509
    }
1510

    
1511
    @Override
1512
    public void setTitleCache(String titleCache, boolean protectCache){
1513
        super.setTitleCache(titleCache, protectCache);
1514
    }
1515

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

    
1539
        }
1540
        return authorshipCache;
1541
    }
1542

    
1543

    
1544

    
1545
    /**
1546
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
1547
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
1548
     * @return
1549
     */
1550
    private void updateAuthorshipCache() {
1551
        //updates the authorship cache if necessary and via the listener updates all higher caches
1552
        if (protectedAuthorshipCache == false){
1553
            String oldCache = this.authorshipCache;
1554
            String newCache = this.getAuthorshipCache();
1555
            if ( (oldCache == null && newCache != null)  ||  CdmUtils.nullSafeEqual(oldCache,newCache)){
1556
                this.setAuthorshipCache(this.getAuthorshipCache(), false);
1557
            }
1558
        }
1559
    }
1560

    
1561

    
1562
    /**
1563
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1564
     * flag to <code>true</code>.
1565
     *
1566
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1567
     * @see    #getAuthorshipCache()
1568
     */
1569
    @Override
1570
    public void setAuthorshipCache(String authorshipCache) {
1571
        setAuthorshipCache(authorshipCache, true);
1572
    }
1573

    
1574

    
1575
    /**
1576
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1577
     *
1578
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1579
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1580
     * the flag is set to <code>false</code>.
1581
     * @see    #getAuthorshipCache()
1582
     */
1583
    @Override
1584
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1585
        this.authorshipCache = authorshipCache;
1586
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1587
    }
1588

    
1589
    /**
1590
     * Generates and returns a concatenated and formated authorteams string
1591
     * including basionym and combination authors of <i>this</i> non viral taxon name
1592
     * according to the strategy defined in
1593
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName) INonViralNameCacheStrategy}.
1594
     *
1595
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1596
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName)
1597
     */
1598
    @Override
1599
    public String generateAuthorship(){
1600
        if (getCacheStrategy() == null){
1601
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1602
            return null;
1603
        }else{
1604
            return cacheStrategy.getAuthorshipCache(this);
1605
        }
1606
    }
1607

    
1608

    
1609

    
1610
    /**
1611
     * Tests if the given name has any authors.
1612
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1613
     */
1614
    @Override
1615
    public boolean hasAuthors() {
1616
        return (this.getCombinationAuthorship() != null ||
1617
                this.getExCombinationAuthorship() != null ||
1618
                this.getBasionymAuthorship() != null ||
1619
                this.getExBasionymAuthorship() != null);
1620
    }
1621

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

    
1631
    /**
1632
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1633
     * @return
1634
     */
1635
    @Override
1636
    public String computeBasionymAuthorNomenclaturalTitle() {
1637
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1638
    }
1639

    
1640

    
1641
    /**
1642
     * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1643
     * @return
1644
     */
1645
    @Override
1646
    public String computeExCombinationAuthorNomenclaturalTitle() {
1647
        return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1648
    }
1649

    
1650
    /**
1651
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1652
     * @return
1653
     */
1654
    @Override
1655
    public String computeExBasionymAuthorNomenclaturalTitle() {
1656
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1657
    }
1658

    
1659
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1660
        if (author == null){
1661
            return null;
1662
        }else{
1663
            return author.getNomenclaturalTitle();
1664
        }
1665
    }
1666

    
1667
    /**
1668
     * Returns the set of all {@link NameRelationship name relationships}
1669
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1670
     * in some name relationships or target in some others.
1671
     *
1672
     * @see    #getRelationsToThisName()
1673
     * @see    #getRelationsFromThisName()
1674
     * @see    #addNameRelationship(NameRelationship)
1675
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1676
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1677
     */
1678
    @Override
1679
    @Transient
1680
    public Set<NameRelationship> getNameRelations() {
1681
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1682
        rels.addAll(getRelationsFromThisName());
1683
        rels.addAll(getRelationsToThisName());
1684
        return rels;
1685
    }
1686

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

    
1706
    /**
1707
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1708
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1709
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1710
     *
1711
     * @param toName		  the taxon name of the target for this new name relationship
1712
     * @param type			  the type of this new name relationship
1713
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1714
     * @return
1715
     * @see    				  #getRelationsToThisName()
1716
     * @see    				  #getNameRelations()
1717
     * @see    				  #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1718
     * @see    				  #addNameRelationship(NameRelationship)
1719
     */
1720
    @Override
1721
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1722
        if (toName == null){
1723
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1724
        }
1725
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered);
1726
        return rel;
1727
    }
1728

    
1729
    /**
1730
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1731
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1732
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1733
     *
1734
     * @param fromName		  the taxon name of the source for this new name relationship
1735
     * @param type			  the type of this new name relationship
1736
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1737
     * @param citation		  the reference in which this relation was described
1738
     * @param microCitation	  the reference detail for this relation (e.g. page)
1739
     * @see    				  #getRelationsFromThisName()
1740
     * @see    				  #getNameRelations()
1741
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1742
     * @see    				  #addNameRelationship(NameRelationship)
1743
     */
1744
    @Override
1745
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered){
1746
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1747
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
1748
    }
1749
    /**
1750
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1751
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1752
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1753
     *
1754
     * @param fromName		  the taxon name of the source for this new name relationship
1755
     * @param type			  the type of this new name relationship
1756
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1757
     * @param citation		  the reference in which this relation was described
1758
     * @param microCitation	  the reference detail for this relation (e.g. page)
1759
     * @see    				  #getRelationsFromThisName()
1760
     * @see    				  #getNameRelations()
1761
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1762
     * @see    				  #addNameRelationship(NameRelationship)
1763
     */
1764
    @Override
1765
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1766
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1767
    }
1768

    
1769
    /**
1770
     * Adds an existing {@link NameRelationship name relationship} either to the set of
1771
     * {@link #getRelationsToThisName() relations to <i>this</i> taxon name} or to the set of
1772
     * {@link #getRelationsFromThisName() relations from <i>this</i> taxon name}. If neither the
1773
     * source nor the target of the name relationship match with <i>this</i> taxon name
1774
     * no addition will be carried out.
1775
     *
1776
     * @param rel  the name relationship to be added to one of <i>this</i> taxon name's name relationships sets
1777
     * @see    	   #getNameRelations()
1778
     * @see    	   #addRelationshipToName(TaxonName, NameRelationshipType, String)
1779
     * @see    	   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1780
     */
1781
    protected void addNameRelationship(NameRelationship rel) {
1782
        if (rel != null ){
1783
            if (rel.getToName().equals(this)){
1784
                this.relationsToThisName.add(rel);
1785
            }else if(rel.getFromName().equals(this)){
1786
                this.relationsFromThisName.add(rel);
1787
            }
1788
            NameRelationshipType type = rel.getType();
1789
            if (type != null && ( type.isBasionymRelation() || type.isReplacedSynonymRelation() ) ){
1790
                rel.getFromName().mergeHomotypicGroups(rel.getToName());
1791
            }
1792
        }else{
1793
            throw new RuntimeException("NameRelationship is either null or the relationship does not reference this name");
1794
        }
1795
    }
1796
    /**
1797
     * Removes one {@link NameRelationship name relationship} from one of both sets of
1798
     * {@link #getNameRelations() name relationships} in which <i>this</i> taxon name is involved.
1799
     * The name relationship will also be removed from one of both sets belonging
1800
     * to the second taxon name involved. Furthermore the fromName and toName
1801
     * attributes of the name relationship object will be nullified.
1802
     *
1803
     * @param  nameRelation  the name relationship which should be deleted from one of both sets
1804
     * @see    				 #getNameRelations()
1805
     */
1806
    @Override
1807
    public void removeNameRelationship(NameRelationship nameRelation) {
1808

    
1809
        TaxonName fromName = nameRelation.getFromName();
1810
        TaxonName toName = nameRelation.getToName();
1811

    
1812
        if (nameRelation != null) {
1813
            nameRelation.setToName(null);
1814
            nameRelation.setFromName(null);
1815
        }
1816

    
1817
        if (fromName != null) {
1818
            fromName.removeNameRelationship(nameRelation);
1819
        }
1820

    
1821
        if (toName != null) {
1822
            toName.removeNameRelationship(nameRelation);
1823
        }
1824

    
1825
        this.relationsToThisName.remove(nameRelation);
1826
        this.relationsFromThisName.remove(nameRelation);
1827
    }
1828

    
1829
    @Override
1830
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1831
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1832
//		nameRelationships.addAll(this.getNameRelations());
1833
        nameRelationships.addAll(this.getRelationsFromThisName());
1834
        nameRelationships.addAll(this.getRelationsToThisName());
1835
        for(NameRelationship nameRelationship : nameRelationships) {
1836
            // remove name relationship from this side
1837
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1838
                this.removeNameRelationship(nameRelationship);
1839
            }
1840
        }
1841
    }
1842

    
1843
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1844

    
1845
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1846
        for(NameRelationship nameRelationship : tmpRels) {
1847
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1848
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1849
                if (type == null || type.equals(nameRelationship.getType())){
1850
                    this.removeNameRelationship(nameRelationship);
1851
                }
1852
            }
1853
        }
1854
    }
1855

    
1856

    
1857
    /**
1858
     * If relation is of type NameRelationship, addNameRelationship is called;
1859
     * if relation is of type HybridRelationship addHybridRelationship is called,
1860
     * otherwise an IllegalArgumentException is thrown.
1861
     *
1862
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1863
     * @see    	   		#addNameRelationship(NameRelationship)
1864
     * @see    	   		#getNameRelations()
1865
     * @see    	   		NameRelationship
1866
     * @see    	   		RelationshipBase
1867
     * @see             #addHybridRelationship(HybridRelationship)
1868

    
1869
     * @deprecated to be used by RelationshipBase only
1870
     */
1871
    @Deprecated
1872
    @Override
1873
    public void addRelationship(RelationshipBase relation) {
1874
        if (relation instanceof NameRelationship){
1875
            addNameRelationship((NameRelationship)relation);
1876

    
1877
        }else if (relation instanceof HybridRelationship){
1878
            addHybridRelationship((HybridRelationship)relation);
1879
        }else{
1880
            logger.warn("Relationship not of type NameRelationship!");
1881
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1882
        }
1883
    }
1884

    
1885
    /**
1886
     * Returns the set of all {@link NameRelationship name relationships}
1887
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1888
     *
1889
     * @see    #getNameRelations()
1890
     * @see    #getRelationsToThisName()
1891
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1892
     */
1893
    @Override
1894
    public Set<NameRelationship> getRelationsFromThisName() {
1895
        if(relationsFromThisName == null) {
1896
            this.relationsFromThisName = new HashSet<>();
1897
        }
1898
        return relationsFromThisName;
1899
    }
1900

    
1901
    /**
1902
     * Returns the set of all {@link NameRelationship name relationships}
1903
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1904
     *
1905
     * @see    #getNameRelations()
1906
     * @see    #getRelationsFromThisName()
1907
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1908
     */
1909
    @Override
1910
    public Set<NameRelationship> getRelationsToThisName() {
1911
        if(relationsToThisName == null) {
1912
            this.relationsToThisName = new HashSet<>();
1913
        }
1914
        return relationsToThisName;
1915
    }
1916

    
1917
    /**
1918
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1919
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1920
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1921
     * and the nomenclatural code rule considered.
1922
     *
1923
     * @see     NomenclaturalStatus
1924
     * @see     NomenclaturalStatusType
1925
     */
1926
    @Override
1927
    public Set<NomenclaturalStatus> getStatus() {
1928
        if(status == null) {
1929
            this.status = new HashSet<>();
1930
        }
1931
        return status;
1932
    }
1933

    
1934
    /**
1935
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1936
     * to <i>this</i> taxon name's set of nomenclatural status.
1937
     *
1938
     * @param  nomStatus  the nomenclatural status to be added
1939
     * @see 			  #getStatus()
1940
     */
1941
    @Override
1942
    public void addStatus(NomenclaturalStatus nomStatus) {
1943
        this.status.add(nomStatus);
1944
    }
1945
    @Override
1946
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1947
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1948
        this.status.add(newStatus);
1949
        return newStatus;
1950
    }
1951

    
1952
    /**
1953
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1954
     * Type and ruleConsidered attributes of the nomenclatural status object
1955
     * will be nullified.
1956
     *
1957
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1958
     * @see     		  #getStatus()
1959
     */
1960
    @Override
1961
    public void removeStatus(NomenclaturalStatus nomStatus) {
1962
        //TODO to be implemented?
1963
        logger.warn("not yet fully implemented?");
1964
        this.status.remove(nomStatus);
1965
    }
1966

    
1967

    
1968
    /**
1969
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1970
     * strings or year according to the strategy defined in
1971
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1972
     * The result might be stored in {@link #getNameCache() nameCache} if the
1973
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1974
     *
1975
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1976
     * @see     #getNameCache()
1977
     */
1978
    protected String generateNameCache(){
1979
        if (getCacheStrategy() == null){
1980
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1981
            return null;
1982
        }else{
1983
            return cacheStrategy.getNameCache(this);
1984
        }
1985
    }
1986

    
1987
    /**
1988
     * Returns or generates the nameCache (scientific name
1989
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1990
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1991
     * the string will be generated according to a defined strategy,
1992
     * otherwise the value of the actual nameCache string will be returned.
1993
     *
1994
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1995
     * @see     #generateNameCache()
1996
     */
1997
    @Override
1998
    @Transient
1999
    public String getNameCache() {
2000
        if (protectedNameCache){
2001
            return this.nameCache;
2002
        }
2003
        // is title dirty, i.e. equal NULL?
2004
        if (nameCache == null){
2005
            this.nameCache = generateNameCache();
2006
        }
2007
        return nameCache;
2008
    }
2009

    
2010
    /**
2011
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
2012
     * Sets the protectedNameCache flag to <code>true</code>.
2013
     *
2014
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
2015
     * @see    #getNameCache()
2016
     */
2017
    @Override
2018
    public void setNameCache(String nameCache){
2019
        setNameCache(nameCache, true);
2020
    }
2021

    
2022
    /**
2023
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
2024
     * Sets the protectedNameCache flag to <code>true</code>.
2025
     *
2026
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
2027
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
2028
     * <code>false</code>
2029
     * @see    #getNameCache()
2030
     */
2031
    @Override
2032
    public void setNameCache(String nameCache, boolean protectedNameCache){
2033
        this.nameCache = nameCache;
2034
        this.setProtectedNameCache(protectedNameCache);
2035
    }
2036

    
2037

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

    
2059
    /**
2060
     * Indicates <i>this</i> taxon name is a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
2061
     * of any other taxon name. Returns "true", if a replaced
2062
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
2063
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
2064
     * homotypical group).
2065
     */
2066
    @Override
2067
    @Transient
2068
    public boolean isReplacedSynonym(){
2069
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
2070
        for (NameRelationship relation : relationsFromThisName) {
2071
            if (relation.getType().isReplacedSynonymRelation()) {
2072
                return true;
2073
            }
2074
        }
2075
        return false;
2076
    }
2077

    
2078
    /**
2079
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} 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
     * If more than one basionym exists one is choosen at radom.
2086
     *
2087
     * If no basionym exists null is returned.
2088
     */
2089
    @Override
2090
    @Transient
2091
    public TaxonName getBasionym(){
2092
        Set<TaxonName> basionyms = getBasionyms();
2093
        if (basionyms.size() == 0){
2094
            return null;
2095
        }else{
2096
            return basionyms.iterator().next();
2097
        }
2098
    }
2099

    
2100
    /**
2101
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2102
     * The basionym of a taxon name is its epithet-bringing synonym.
2103
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2104
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2105
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2106
     */
2107
    @Override
2108
    @Transient
2109
    public Set<TaxonName> getBasionyms(){
2110
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
2111
    }
2112

    
2113
    /**
2114
     *
2115
     * @param direction
2116
     * @param type
2117
     * @return
2118
     */
2119
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
2120
        return getRelatedNames(relationsWithThisName(direction), type);
2121
    }
2122

    
2123
    /**
2124
     * @param rels
2125
     * @param type
2126
     * @return
2127
     */
2128
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2129
        Set<TaxonName> result = new HashSet<>();
2130
        for (NameRelationship rel : rels){
2131
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2132
                TaxonName basionym = rel.getFromName();
2133
                result.add(basionym);
2134
            }
2135
        }
2136
        return result;
2137
    }
2138

    
2139
    /**
2140
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2141
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2142
     * and to the basionym. The basionym cannot have itself as a basionym.
2143
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2144
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2145
     *
2146
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2147
     * @see  				#getBasionym()
2148
     * @see  				#addBasionym(TaxonName, String)
2149
     */
2150
    @Override
2151
    public void addBasionym(TaxonName basionym){
2152
        addBasionym(basionym, null, null, null);
2153
    }
2154
    /**
2155
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2156
     * and keeps the nomenclatural rule considered for it. The basionym
2157
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2158
     * The basionym cannot have itself as a basionym.
2159
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2160
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2161
     *
2162
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2163
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2164
     * @return
2165
     * @see  					#getBasionym()
2166
     * @see  					#addBasionym(TaxonName)
2167
     */
2168
    @Override
2169
    public NameRelationship addBasionym(TaxonName basionym, Reference citation, String microcitation, String ruleConsidered){
2170
        if (basionym != null){
2171
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
2172
        }else{
2173
            return null;
2174
        }
2175
    }
2176

    
2177
    /**
2178
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2179
     *
2180
     */
2181
    @Override
2182
    @Transient
2183
    public Set<TaxonName> getReplacedSynonyms(){
2184

    
2185
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2186
    }
2187

    
2188
    /**
2189
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2190
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2191
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2192
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2193
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2194
     *
2195
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2196
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2197
     * @see  					#getBasionym()
2198
     * @see  					#addBasionym(TaxonName)
2199
     */
2200
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2201
    @Override
2202
    public void addReplacedSynonym(TaxonName replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
2203
        if (replacedSynonym != null){
2204
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
2205
        }
2206
    }
2207

    
2208
    /**
2209
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2210
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2211
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2212
     * previously used as basionym.
2213
     *
2214
     * @see   #getBasionym()
2215
     * @see   #addBasionym(TaxonName)
2216
     */
2217
    @Override
2218
    public void removeBasionyms(){
2219
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2220
    }
2221

    
2222

    
2223
    /**
2224
     * Removes all {@link NameRelationship relationships} of the given <code>type</code> from the set of
2225
     * relations in the specified <code>direction</code> direction wich are related from or to this
2226
     * <i>this</i> taxon name. The same relationship will be removed from the set of
2227
     * reverse relations of the other taxon name.
2228
     *
2229
     * @param direction
2230
     * @param type
2231
     */
2232
    public void removeNameRelations(Direction direction, NameRelationshipType type) {
2233
        Set<NameRelationship> relationsWithThisName = relationsWithThisName(direction);
2234
        Set<NameRelationship> removeRelations = new HashSet<>();
2235
        for (NameRelationship nameRelation : relationsWithThisName){
2236
            if (nameRelation.getType().isRelationshipType(type)){
2237
                removeRelations.add(nameRelation);
2238
            }
2239
        }
2240
        // Removing relations from a set through which we are iterating causes a
2241
        // ConcurrentModificationException. Therefore, we delete the targeted
2242
        // relations in a second step.
2243
        for (NameRelationship relation : removeRelations){
2244
            this.removeNameRelationship(relation);
2245
        }
2246
    }
2247

    
2248

    
2249
    /**
2250
     * @param direction
2251
     * @return
2252
     */
2253
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2254

    
2255
        switch(direction) {
2256
            case relatedTo:
2257
                return this.getRelationsToThisName();
2258
            case relatedFrom:
2259
                return this.getRelationsFromThisName();
2260
            default: throw new RuntimeException("invalid Direction:" + direction);
2261
        }
2262
    }
2263

    
2264
    /**
2265
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2266
     *
2267
     * @see 	Rank
2268
     */
2269
    @Override
2270
    public Rank getRank(){
2271
        return this.rank;
2272
    }
2273

    
2274
    /**
2275
     * @see  #getRank()
2276
     */
2277
    @Override
2278
    public void setRank(Rank rank){
2279
        this.rank = rank;
2280
    }
2281

    
2282

    
2283
    @Override
2284
    public Reference getNomenclaturalReference(){
2285
        //#6581
2286
        return this.nomenclaturalReference;
2287
//        if (this.nomenclaturalSource == null){
2288
//            return null;
2289
//        }
2290
//        return this.nomenclaturalSource.getCitation();
2291
    }
2292

    
2293
    @Override
2294
    public DescriptionElementSource getNomenclaturalSource(){
2295
        return this.nomenclaturalSource;
2296
    }
2297

    
2298
    protected DescriptionElementSource getNomenclaturalSource(boolean createIfNotExist){
2299
        if (this.nomenclaturalSource == null){
2300
            if (!createIfNotExist){
2301
                return null;
2302
            }
2303
            this.nomenclaturalSource = DescriptionElementSource.NewInstance(OriginalSourceType.NomenclaturalReference);
2304
        }
2305
        return this.nomenclaturalSource;
2306
    }
2307

    
2308
    /**
2309
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2310
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2311
     * as it is obviously used for nomenclatural purposes.
2312
     *
2313
     * Shortcut to set the nomenclatural reference.
2314
     *
2315
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2316
     * @see  #getNomenclaturalReference()
2317
     */
2318

    
2319
    @Override
2320
    public void setNomenclaturalReference(Reference nomenclaturalReference){
2321
        //#6581
2322
        this.nomenclaturalReference = nomenclaturalReference;
2323
//        getNomenclaturalSource(true).setCitation(nomenclaturalReference);
2324
//        checkNullSource();
2325
    }
2326
    @Override
2327
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2328
        setNomenclaturalReference(CdmBase.deproxy(nomenclaturalReference, Reference.class));
2329
    }
2330

    
2331
    //#6581
2332
    private void checkNullSource() {
2333
        if (this.nomenclaturalSource == null){
2334
            return;
2335
        }else if (this.nomenclaturalSource.getCitation() != null
2336
           || this.nomenclaturalSource.getCitationMicroReference() != null
2337
           || this.nomenclaturalSource.getNameUsedInSource() != null
2338
           || isBlank(this.nomenclaturalSource.getOriginalNameString())){
2339
            //TODO what about supplemental data?
2340
                return;
2341
        }else{
2342
            this.nomenclaturalSource = null;
2343
        }
2344
    }
2345

    
2346

    
2347
    @Override
2348
    public void setNomenclaturalSource(DescriptionElementSource nomenclaturalSource) throws IllegalArgumentException {
2349
        if (!OriginalSourceType.NomenclaturalReference.equals(nomenclaturalSource.getType()) ){
2350
            throw new IllegalArgumentException("Nomenclatural source must be of type " + OriginalSourceType.NomenclaturalReference.getMessage());
2351
        }
2352
        this.nomenclaturalSource = nomenclaturalSource;
2353
    }
2354

    
2355
    /**
2356
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2357
     * The appended phrase is a non-atomised addition to a name. It is
2358
     * not ruled by a nomenclatural code.
2359
     */
2360
    @Override
2361
    public String getAppendedPhrase(){
2362
        return this.appendedPhrase;
2363
    }
2364

    
2365
    /**
2366
     * @see  #getAppendedPhrase()
2367
     */
2368
    @Override
2369
    public void setAppendedPhrase(String appendedPhrase){
2370
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2371
    }
2372

    
2373
    /**
2374
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2375
     * to <i>this</i> taxon name. The details describe the exact localisation within
2376
     * the publication used as nomenclature reference. These are mostly
2377
     * (implicitly) pages but can also be figures or tables or any other
2378
     * element of a publication. A nomenclatural micro reference (details)
2379
     * requires the existence of a nomenclatural reference.
2380
     */
2381
    //Details of the nomenclatural reference (protologue).
2382
    @Override
2383
    public String getNomenclaturalMicroReference(){
2384
        //#6581
2385
        return this.nomenclaturalMicroReference;
2386
//        if (this.nomenclaturalSource == null){
2387
//            return null;
2388
//        }
2389
//        return this.nomenclaturalSource.getCitationMicroReference();
2390
    }
2391
    /**
2392
     * @see  #getNomenclaturalMicroReference()
2393
     */
2394
    @Override
2395
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2396
        //#6581
2397
        this.nomenclaturalMicroReference = nomenclaturalMicroReference;
2398
//        this.getNomenclaturalSource(true).setCitationMicroReference(StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference);
2399
//        checkNullSource();
2400
    }
2401

    
2402
    @Override
2403
    public int getParsingProblem(){
2404
        return this.parsingProblem;
2405
    }
2406

    
2407
    @Override
2408
    public void setParsingProblem(int parsingProblem){
2409
        this.parsingProblem = parsingProblem;
2410
    }
2411

    
2412
    @Override
2413
    public void addParsingProblem(ParserProblem problem){
2414
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2415
    }
2416

    
2417
    @Override
2418
    public void removeParsingProblem(ParserProblem problem) {
2419
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2420
    }
2421

    
2422
    /**
2423
     * @param warnings
2424
     */
2425
    @Override
2426
    public void addParsingProblems(int problems){
2427
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2428
    }
2429

    
2430
    @Override
2431
    public boolean hasProblem(){
2432
        return parsingProblem != 0;
2433
    }
2434

    
2435
    @Override
2436
    public boolean hasProblem(ParserProblem problem) {
2437
        return getParsingProblems().contains(problem);
2438
    }
2439

    
2440
    @Override
2441
    public int getProblemStarts(){
2442
        return this.problemStarts;
2443
    }
2444

    
2445
    @Override
2446
    public void setProblemStarts(int start) {
2447
        this.problemStarts = start;
2448
    }
2449

    
2450
    @Override
2451
    public int getProblemEnds(){
2452
        return this.problemEnds;
2453
    }
2454

    
2455
    @Override
2456
    public void setProblemEnds(int end) {
2457
        this.problemEnds = end;
2458
    }
2459

    
2460
//*********************** TYPE DESIGNATION *********************************************//
2461

    
2462
    /**
2463
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2464
     * to <i>this</i> taxon name.
2465
     * @see     NameTypeDesignation
2466
     * @see     SpecimenTypeDesignation
2467
     */
2468
    @Override
2469
    public Set<TypeDesignationBase> getTypeDesignations() {
2470
        if(typeDesignations == null) {
2471
            this.typeDesignations = new HashSet<>();
2472
        }
2473
        return typeDesignations;
2474
    }
2475

    
2476
    /**
2477
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2478
     * <i>this</i> taxon name. The type designation itself will be nullified.
2479
     *
2480
     * @param  typeDesignation  the type designation which should be deleted
2481
     */
2482
    @Override
2483
    @SuppressWarnings("deprecation")
2484
    public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2485
        this.typeDesignations.remove(typeDesignation);
2486
        typeDesignation.removeTypifiedName(this);
2487
    }
2488

    
2489
    /**
2490
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2491
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2492
     * "species" or below. The specimen type designations include all the
2493
     * specimens on which the typification of this name is based (which are
2494
     * exclusively used to typify taxon names belonging to the same
2495
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2496
     * belongs) and eventually the status of these designations.
2497
     *
2498
     * @see     SpecimenTypeDesignation
2499
     * @see     NameTypeDesignation
2500
     * @see     HomotypicalGroup
2501
     */
2502
    @Override
2503
    @Transient
2504
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2505
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2506
    }
2507

    
2508
//*********************** NAME TYPE DESIGNATION *********************************************//
2509

    
2510
    /**
2511
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2512
     * to <i>this</i> taxon name the rank of which must be above "species".
2513
     * The name type designations include all the taxon names used to typify
2514
     * <i>this</i> taxon name and eventually the rejected or conserved status
2515
     * of these designations.
2516
     *
2517
     * @see     NameTypeDesignation
2518
     * @see     SpecimenTypeDesignation
2519
     */
2520
    @Override
2521
    @Transient
2522
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2523
        Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2524
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2525
            if (typeDesignation instanceof NameTypeDesignation){
2526
                result.add((NameTypeDesignation)typeDesignation);
2527
            }
2528
        }
2529
        return result;
2530
    }
2531

    
2532
    /**
2533
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2534
     * to <i>this</i> taxon name's set of type designations.
2535
     *
2536
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2537
     * @param  citation					the reference for this new designation
2538
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2539
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2540
     * @param  isRejectedType			the boolean status for a rejected name type designation
2541
     * @param  isConservedType			the boolean status for a conserved name type designation
2542
     * @param  isLectoType				the boolean status for a lectotype name type designation
2543
     * @param  isNotDesignated			the boolean status for a name type designation without name type
2544
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2545
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2546
     * @return
2547
     * @see 			  				#getNameTypeDesignations()
2548
     * @see 			  				NameTypeDesignation
2549
     * @see 			  				TypeDesignationBase#isNotDesignated()
2550
     */
2551
    @Override
2552
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2553
                Reference citation,
2554
                String citationMicroReference,
2555
                String originalNameString,
2556
                NameTypeDesignationStatus status,
2557
                boolean isRejectedType,
2558
                boolean isConservedType,
2559
                /*boolean isLectoType, */
2560
                boolean isNotDesignated,
2561
                boolean addToAllHomotypicNames) {
2562
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalNameString, status, isRejectedType, isConservedType, isNotDesignated);
2563
        //nameTypeDesignation.setLectoType(isLectoType);
2564
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2565
        return nameTypeDesignation;
2566
    }
2567

    
2568
    /**
2569
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2570
     * to <i>this</i> taxon name's set of type designations.
2571
     *
2572
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2573
     * @param  citation					the reference for this new designation
2574
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2575
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2576
     * @param  status                   the name type designation status
2577
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2578
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2579
     * @return
2580
     * @see 			  				#getNameTypeDesignations()
2581
     * @see 			  				NameTypeDesignation
2582
     * @see 			  				TypeDesignationBase#isNotDesignated()
2583
     */
2584
    @Override
2585
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2586
                Reference citation,
2587
                String citationMicroReference,
2588
                String originalNameString,
2589
                NameTypeDesignationStatus status,
2590
                boolean addToAllHomotypicNames) {
2591
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2592
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2593
        return nameTypeDesignation;
2594
    }
2595

    
2596
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2597

    
2598
    /**
2599
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2600
     * that typify <i>this</i> taxon name.
2601
     */
2602
    @Override
2603
    @Transient
2604
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2605
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2606
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2607
            if (typeDesignation instanceof SpecimenTypeDesignation){
2608
                result.add((SpecimenTypeDesignation)typeDesignation);
2609
            }
2610
        }
2611
        return result;
2612
    }
2613

    
2614

    
2615
    /**
2616
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2617
     * to <i>this</i> taxon name's set of type designations.
2618
     *
2619
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2620
     * @param  status					the specimen type designation status
2621
     * @param  citation					the reference for this new specimen type designation
2622
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2623
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2624
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2625
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2626
     * 									added to all taxon names of the homotypical group the typified
2627
     * 									taxon name belongs to
2628
     * @return
2629
     * @see 			  				#getSpecimenTypeDesignations()
2630
     * @see 			  				SpecimenTypeDesignationStatus
2631
     * @see 			  				SpecimenTypeDesignation
2632
     * @see 			  				TypeDesignationBase#isNotDesignated()
2633
     */
2634
    @Override
2635
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2636
                SpecimenTypeDesignationStatus status,
2637
                Reference citation,
2638
                String citationMicroReference,
2639
                String originalNameString,
2640
                boolean isNotDesignated,
2641
                boolean addToAllHomotypicNames) {
2642
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2643
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2644
        return specimenTypeDesignation;
2645
    }
2646

    
2647
    //used by merge strategy
2648
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2649
        return addTypeDesignation(typeDesignation, true);
2650
    }
2651

    
2652
    /**
2653
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2654
     *
2655
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2656
     * @param addToAllNames				the boolean indicating whether the type designation should be
2657
     * 									added to all taxon names of the homotypical group the typified
2658
     * 									taxon name belongs to
2659
     * @return							true if the operation was successful
2660
     *
2661
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2662
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2663
     *
2664
     */
2665
    @Override
2666
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2667
        //currently typeDesignations are not persisted with the homotypical group
2668
        //so explicit adding to the homotypical group is not necessary.
2669
        if (typeDesignation != null){
2670
            checkHomotypicalGroup(typeDesignation);
2671
            this.typeDesignations.add(typeDesignation);
2672
            typeDesignation.addTypifiedName(this);
2673

    
2674
            if (addToAllNames){
2675
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2676
                    if (taxonName != this){
2677
                        taxonName.addTypeDesignation(typeDesignation, false);
2678
                    }
2679
                }
2680
            }
2681
        }
2682
        return true;
2683
    }
2684

    
2685
    /**
2686
     * Throws an Exception this type designation already has typified names from another homotypical group.
2687
     * @param typeDesignation
2688
     */
2689
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2690
        if(typeDesignation.getTypifiedNames().size() > 0){
2691
            Set<HomotypicalGroup> groups = new HashSet<>();
2692
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2693
            for (TaxonName taxonName: names){
2694
                groups.add(taxonName.getHomotypicalGroup());
2695
            }
2696
            if (groups.size() > 1){
2697
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2698
            }
2699
        }
2700
    }
2701

    
2702

    
2703

    
2704
//*********************** HOMOTYPICAL GROUP *********************************************//
2705

    
2706

    
2707
    /**
2708
     * Returns the {@link HomotypicalGroup homotypical group} to which
2709
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2710
     * that share the same types.
2711
     *
2712
     * @see 	HomotypicalGroup
2713
     */
2714

    
2715
    @Override
2716
    public HomotypicalGroup getHomotypicalGroup() {
2717
        if (homotypicalGroup == null){
2718
            homotypicalGroup = new HomotypicalGroup();
2719
            homotypicalGroup.typifiedNames.add(this);
2720
        }
2721
    	return homotypicalGroup;
2722
    }
2723

    
2724
    /**
2725
     * @see #getHomotypicalGroup()
2726
     */
2727
    @Override
2728
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2729
        if (homotypicalGroup == null){
2730
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2731
        }
2732
        /*if (this.homotypicalGroup != null){
2733
        	this.homotypicalGroup.removeTypifiedName(this, false);
2734
        }*/
2735
        this.homotypicalGroup = homotypicalGroup;
2736
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2737
        	 this.homotypicalGroup.addTypifiedName(this);
2738
        }
2739
    }
2740

    
2741

    
2742

    
2743
// *************************************************************************//
2744

    
2745
    /**
2746
     * Returns the complete string containing the
2747
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2748
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2749
     *
2750
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2751
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2752
     * @see		#getNomenclaturalReference()
2753
     * @see		#getNomenclaturalMicroReference()
2754
     */
2755
    @Override
2756
    @Transient
2757
    public String getCitationString(){
2758
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2759
    }
2760

    
2761
    /**
2762
     * Returns the parsing problems
2763
     * @return
2764
     */
2765
    @Override
2766
    public List<ParserProblem> getParsingProblems(){
2767
        return ParserProblem.warningList(this.parsingProblem);
2768
    }
2769

    
2770
    /**
2771
     * Returns the string containing the publication date (generally only year)
2772
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2773
     * no nomenclatural reference.
2774
     *
2775
     * @return  the string containing the publication date of <i>this</i> taxon name
2776
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2777
     */
2778
    @Override
2779
    @Transient
2780
    @ValidTaxonomicYear(groups=Level3.class)
2781
    public String getReferenceYear(){
2782
        if (this.getNomenclaturalReference() != null ){
2783
            return this.getNomenclaturalReference().getYear();
2784
        }else{
2785
            return null;
2786
        }
2787
    }
2788

    
2789
    /**
2790
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2791
     * In this context a taxon base means the use of a taxon name by a reference
2792
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2793
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2794
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2795
     * within a taxonomic treatment (identified by one reference).
2796
     *
2797
     * @see	#getTaxa()
2798
     * @see	#getSynonyms()
2799
     */
2800
    @Override
2801
    public Set<TaxonBase> getTaxonBases() {
2802
        if(taxonBases == null) {
2803
            this.taxonBases = new HashSet<TaxonBase>();
2804
        }
2805
        return this.taxonBases;
2806
    }
2807

    
2808
    /**
2809
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2810
     * to the set of taxon bases using <i>this</i> taxon name.
2811
     *
2812
     * @param  taxonBase  the taxon base to be added
2813
     * @see 			  #getTaxonBases()
2814
     * @see 			  #removeTaxonBase(TaxonBase)
2815
     */
2816
    //TODO protected
2817
    @Override
2818
    public void addTaxonBase(TaxonBase taxonBase){
2819
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2820
        ReflectionUtils.makeAccessible(method);
2821
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2822
        taxonBases.add(taxonBase);
2823
    }
2824
    /**
2825
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2826
     *
2827
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2828
     * @see    				#getTaxonBases()
2829
     * @see    				#addTaxonBase(TaxonBase)
2830
     */
2831
    @Override
2832
    public void removeTaxonBase(TaxonBase taxonBase){
2833
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2834
        ReflectionUtils.makeAccessible(method);
2835
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2836

    
2837

    
2838
    }
2839

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

    
2861
    /**
2862
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2863
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2864
     * the set returned by getTaxonBases().
2865
     *
2866
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2867
     * @see	#getTaxonBases()
2868
     * @see	#getTaxa()
2869
     */
2870
    @Override
2871
    @Transient
2872
    public Set<Synonym> getSynonyms() {
2873
        Set<Synonym> result = new HashSet<>();
2874
        for (TaxonBase taxonBase : this.taxonBases){
2875
            if (taxonBase instanceof Synonym){
2876
                result.add((Synonym)taxonBase);
2877
            }
2878
        }
2879
        return result;
2880
    }
2881

    
2882
    //******* REGISTRATION *****************/
2883

    
2884
    @Override
2885
    public Set<Registration> getRegistrations() {
2886
        return this.registrations;
2887
    }
2888

    
2889

    
2890
// ************* RELATIONSHIPS *****************************/
2891

    
2892

    
2893
    /**
2894
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2895
     * by title cache of the related names.
2896
     * @see #getHybridParentRelations()
2897
     */
2898
    @Override
2899
    @Transient
2900
    public List<HybridRelationship> getOrderedChildRelationships(){
2901
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2902
        result.addAll(this.hybridChildRelations);
2903
        Collections.sort(result);
2904
        Collections.reverse(result);
2905
        return result;
2906

    
2907
    }
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 hybrid child of the parent
2915
     * botanical name.
2916
     *
2917
     * @param parentName      the botanical name of the parent 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                   #addHybridChild(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 addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2929
        return new HybridRelationship(this, parentName, type, ruleConsidered);
2930
    }
2931

    
2932
    /**
2933
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2934
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2935
     * "is first/second parent" or "is male/female parent". By invoking this
2936
     * method <i>this</i> botanical name becomes a parent of the hybrid child
2937
     * botanical name.
2938
     *
2939
     * @param childName       the botanical name of the child for this new hybrid name relationship
2940
     * @param type            the type of this new name relationship
2941
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2942
     * @return
2943
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
2944
     * @see                   #getRelationsToThisName()
2945
     * @see                   #getNameRelations()
2946
     * @see                   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
2947
     * @see                   #addNameRelationship(NameRelationship)
2948
     */
2949
    @Override
2950
    public HybridRelationship addHybridChild(INonViralName childName, HybridRelationshipType type, String ruleConsidered){
2951
        return new HybridRelationship(childName, this, type, ruleConsidered);
2952
    }
2953

    
2954
    @Override
2955
    public void removeHybridChild(INonViralName child) {
2956
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2957
        hybridRelationships.addAll(this.getHybridChildRelations());
2958
        hybridRelationships.addAll(this.getHybridParentRelations());
2959
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2960
            // remove name relationship from this side
2961
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2962
                this.removeHybridRelationship(hybridRelationship);
2963
            }
2964
        }
2965
    }
2966

    
2967
    @Override
2968
    public void removeHybridParent(INonViralName parent) {
2969
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2970
        hybridRelationships.addAll(this.getHybridChildRelations());
2971
        hybridRelationships.addAll(this.getHybridParentRelations());
2972
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2973
            // remove name relationship from this side
2974
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2975
                this.removeHybridRelationship(hybridRelationship);
2976
            }
2977
        }
2978
    }
2979

    
2980

    
2981

    
2982
// *********** DESCRIPTIONS *************************************
2983

    
2984
    /**
2985
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2986
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2987
     * concerning the taxon name like for instance the content of its first
2988
     * publication (protolog) or a picture of this publication.
2989
     *
2990
     * @see	#addDescription(TaxonNameDescription)
2991
     * @see	#removeDescription(TaxonNameDescription)
2992
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2993
     */
2994
    @Override
2995
    public Set<TaxonNameDescription> getDescriptions() {
2996
        return descriptions;
2997
    }
2998

    
2999
    /**
3000
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
3001
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
3002
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
3003
     * taxon name description itself will be replaced with <i>this</i> taxon name.
3004
     *
3005
     * @param  description  the taxon name description to be added
3006
     * @see					#getDescriptions()
3007
     * @see 			  	#removeDescription(TaxonNameDescription)
3008
     */
3009
    @Override
3010
    public void addDescription(TaxonNameDescription description) {
3011
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
3012
        ReflectionUtils.makeAccessible(field);
3013
        ReflectionUtils.setField(field, description, this);
3014
        descriptions.add(description);
3015
    }
3016
    /**
3017
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
3018
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
3019
     * of the description itself will be set to "null".
3020
     *
3021
     * @param  description  the taxon name description which should be removed
3022
     * @see     		  	#getDescriptions()
3023
     * @see     		  	#addDescription(TaxonNameDescription)
3024
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
3025
     */
3026
    @Override
3027
    public void removeDescription(TaxonNameDescription description) {
3028
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
3029
        ReflectionUtils.makeAccessible(field);
3030
        ReflectionUtils.setField(field, description, null);
3031
        descriptions.remove(description);
3032
    }
3033

    
3034
// *********** HOMOTYPIC GROUP METHODS **************************************************
3035

    
3036
    @Override
3037
    @Transient
3038
    public void mergeHomotypicGroups(TaxonName name){
3039
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
3040
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
3041
        name.setHomotypicalGroup(this.homotypicalGroup);
3042
    }
3043

    
3044
    /**
3045
     * Returns the boolean value indicating whether a given taxon name belongs
3046
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
3047
     * or not (false). Returns "true" only if the homotypical groups of both
3048
     * taxon names exist and if they are identical.
3049
     *
3050
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
3051
     * @return  			   the boolean value of the check
3052
     * @see     			   HomotypicalGroup
3053
     */
3054
    @Override
3055
    @Transient
3056
    public boolean isHomotypic(TaxonName homoTypicName) {
3057
        if (homoTypicName == null) {
3058
            return false;
3059
        }
3060
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
3061
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
3062
            return false;
3063
        }
3064
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
3065
            return true;
3066
        }
3067
        return false;
3068
    }
3069

    
3070

    
3071
    /**
3072
     * Checks whether name is a basionym for ALL names
3073
     * in its homotypical group.
3074
     * Returns <code>false</code> if there are no other names in the group
3075
     * @param name
3076
     * @return
3077
     */
3078
    @Override
3079
    @Transient
3080
    public boolean isGroupsBasionym() {
3081
    	if (homotypicalGroup == null){
3082
    		homotypicalGroup = HomotypicalGroup.NewInstance();
3083
    		homotypicalGroup.addTypifiedName(this);
3084
    	}
3085
        Set<TaxonName> typifiedNames = homotypicalGroup.getTypifiedNames();
3086

    
3087
        // Check whether there are any other names in the group
3088
        if (typifiedNames.size() == 1) {
3089
                return false;
3090
        }
3091

    
3092
        for (TaxonName taxonName : typifiedNames) {
3093
                if (!taxonName.equals(this)) {
3094
                        if (! isBasionymFor(taxonName)) {
3095
                                return false;
3096
                        }
3097
                }
3098
        }
3099
        return true;
3100
    }
3101

    
3102
    /**
3103
     * Checks whether a basionym relationship exists between fromName and toName.
3104
     *
3105
     * @param fromName
3106
     * @param toName
3107
     * @return
3108
     */
3109
    @Override
3110
    @Transient
3111
    public boolean isBasionymFor(TaxonName newCombinationName) {
3112
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3113
            for (NameRelationship relation : relations) {
3114
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3115
                                    relation.getFromName().equals(this)) {
3116
                            return true;
3117
                    }
3118
            }
3119
            return false;
3120
    }
3121

    
3122
    /**
3123
     * Creates a basionym relationship to all other names in this names homotypical
3124
     * group.
3125
     *
3126
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3127
     */
3128
    @Override
3129
    @Transient
3130
    public void makeGroupsBasionym() {
3131
        this.homotypicalGroup.setGroupBasionym(this);
3132
    }
3133

    
3134

    
3135
//*********  Rank comparison shortcuts   ********************//
3136
    /**
3137
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3138
     * taxon name is higher than the genus rank (true) or not (false).
3139
     * Suprageneric non viral names are monomials.
3140
     * Returns false if rank is null.
3141
     *
3142
     * @see  #isGenus()
3143
     * @see  #isInfraGeneric()
3144
     * @see  #isSpecies()
3145
     * @see  #isInfraSpecific()
3146
     */
3147
    @Override
3148
    @Transient
3149
    public boolean isSupraGeneric() {
3150
        if (rank == null){
3151
            return false;
3152
        }
3153
        return getRank().isSupraGeneric();
3154
    }
3155
    /**
3156
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3157
     * taxon name is the genus rank (true) or not (false). Non viral names with
3158
     * genus rank are monomials. Returns false if rank is null.
3159
     *
3160
     * @see  #isSupraGeneric()
3161
     * @see  #isInfraGeneric()
3162
     * @see  #isSpecies()
3163
     * @see  #isInfraSpecific()
3164
     */
3165
    @Override
3166
    @Transient
3167
    public boolean isGenus() {
3168
        if (rank == null){
3169
            return false;
3170
        }
3171
        return getRank().isGenus();
3172
    }
3173

    
3174
    @Override
3175
    @Transient
3176
    public boolean isGenusOrSupraGeneric() {
3177
        return isGenus()|| isSupraGeneric();
3178
    }
3179
    /**
3180
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3181
     * taxon name is higher than the species rank and lower than the
3182
     * genus rank (true) or not (false). Infrageneric non viral names are
3183
     * binomials. Returns false if rank is null.
3184
     *
3185
     * @see  #isSupraGeneric()
3186
     * @see  #isGenus()
3187
     * @see  #isSpecies()
3188
     * @see  #isInfraSpecific()
3189
     */
3190
    @Override
3191
    @Transient
3192
    public boolean isInfraGeneric() {
3193
        if (rank == null){
3194
            return false;
3195
        }
3196
        return getRank().isInfraGeneric();
3197
    }
3198

    
3199
    /**
3200
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3201
     * taxon name is higher than the species rank (true) or not (false).
3202
     * Returns false if rank is null.
3203
     *
3204
     * @see  #isGenus()
3205
     * @see  #isInfraGeneric()
3206
     * @see  #isSpecies()
3207
     * @see  #isInfraSpecific()
3208
     */
3209
    @Override
3210
    @Transient
3211
    public boolean isSupraSpecific(){
3212
        if (rank == null) {
3213
            return false;
3214
        }
3215
        return getRank().isHigher(Rank.SPECIES());
3216
    }
3217

    
3218
    /**
3219
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3220
     * taxon name is the species rank (true) or not (false). Non viral names
3221
     * with species rank are binomials.
3222
     * Returns false if rank is null.
3223
     *
3224
     * @see  #isSupraGeneric()
3225
     * @see  #isGenus()
3226
     * @see  #isInfraGeneric()
3227
     * @see  #isInfraSpecific()
3228
     */
3229
    @Override
3230
    @Transient
3231
    public boolean isSpecies() {
3232
        if (rank == null){
3233
            return false;
3234
        }
3235
        return getRank().isSpecies();
3236
    }
3237
    /**
3238
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3239
     * taxon name is lower than the species rank (true) or not (false).
3240
     * Infraspecific non viral names are trinomials.
3241
     * Returns false if rank is null.
3242
     *
3243
     * @see  #isSupraGeneric()
3244
     * @see  #isGenus()
3245
     * @see  #isInfraGeneric()
3246
     * @see  #isSpecies()
3247
     */
3248
    @Override
3249
    @Transient
3250
    public boolean isInfraSpecific() {
3251
        if (rank == null){
3252
            return false;
3253
        }
3254
        return getRank().isInfraSpecific();
3255
    }
3256

    
3257
    /**
3258
     * Returns true if this name's rank indicates a rank that aggregates species like species
3259
     * aggregates or species groups, false otherwise. This methods currently returns false
3260
     * for all user defined ranks.
3261
     *
3262
     *@see Rank#isSpeciesAggregate()
3263
     *
3264
     * @return
3265
     */
3266
    @Override
3267
    @Transient
3268
    public boolean isSpeciesAggregate() {
3269
        if (rank == null){
3270
            return false;
3271
        }
3272
        return getRank().isSpeciesAggregate();
3273
    }
3274

    
3275

    
3276
    /**
3277
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3278
     * the construction of <i>this</i> taxon name since there is no specific
3279
     * nomenclatural code defined. The real implementention takes place in the
3280
     * subclasses {@link IBacterialName BacterialName},
3281
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3282
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3283
     * and only one nomenclatural code.
3284
     *
3285
     * @return  null
3286
     * @see  	#isCodeCompliant()
3287
     * @see  	#getHasProblem()
3288
     * @deprecated use {@link #getNameType()} instead
3289
     */
3290
    @Override
3291
    @Deprecated
3292
    @Transient
3293
    @java.beans.Transient
3294
    public NomenclaturalCode getNomenclaturalCode() {
3295
        return nameType;
3296
    }
3297

    
3298

    
3299
    /**
3300
     * Generates and returns the string with the scientific name of <i>this</i>
3301
     * taxon name (only non viral taxon names can be generated from their
3302
     * components). This string may be stored in the inherited
3303
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3304
     * This method overrides the generic and inherited
3305
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3306
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3307
     *
3308
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3309
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3310
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3311
     */
3312
//	@Override
3313
//	public abstract String generateTitle();
3314

    
3315
    /**
3316
     * Creates a basionym relationship between this name and
3317
     * 	each name in its homotypic group.
3318
     *
3319
     * @param basionymName
3320
     */
3321
    @Override
3322
    @Transient
3323
    public void setAsGroupsBasionym() {
3324

    
3325
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3326
        if (homotypicalGroup == null) {
3327
            return;
3328
        }
3329

    
3330
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3331
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3332

    
3333
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3334

    
3335
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3336

    
3337
            for(NameRelationship nameRelation : nameRelations){
3338
                relations.add(nameRelation);
3339
            }
3340
        }
3341

    
3342
        for (NameRelationship relation : relations) {
3343

    
3344
            // If this is a basionym relation, and toName is in the homotypical group,
3345
            //	remove the relationship.
3346
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3347
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3348
                removeRelations.add(relation);
3349
            }
3350
        }
3351

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

    
3359
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3360
            if (!name.equals(this)) {
3361

    
3362
                // First check whether the relationship already exists
3363
                if (!this.isBasionymFor(name)) {
3364

    
3365
                    // Then create it
3366
                    name.addRelationshipFromName(this,
3367
                            NameRelationshipType.BASIONYM(), null);
3368
                }
3369
            }
3370
        }
3371
    }
3372

    
3373
    /**
3374
     * Removes basionym relationship between this name and
3375
     * 	each name in its homotypic group.
3376
     *
3377
     * @param basionymName
3378
     */
3379
    @Override
3380
    @Transient
3381
    public void removeAsGroupsBasionym() {
3382

    
3383
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3384

    
3385
        if (homotypicalGroup == null) {
3386
            return;
3387
        }
3388

    
3389
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3390
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3391

    
3392
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3393

    
3394
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3395

    
3396
            for(NameRelationship nameRelation : nameRelations){
3397
                relations.add(nameRelation);
3398
            }
3399
        }
3400

    
3401
        for (NameRelationship relation : relations) {
3402

    
3403
            // If this is a basionym relation, and toName is in the homotypical group,
3404
            //	and fromName is basionymName, remove the relationship.
3405
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3406
                    relation.getFromName().equals(this) &&
3407
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3408
                removeRelations.add(relation);
3409
            }
3410
        }
3411

    
3412
        // Removing relations from a set through which we are iterating causes a
3413
        //	ConcurrentModificationException. Therefore, we delete the targeted
3414
        //	relations in a second step.
3415
        for (NameRelationship relation : removeRelations) {
3416
            this.removeNameRelationship(relation);
3417
        }
3418
    }
3419

    
3420

    
3421
    /**
3422
     * Defines the last part of the name.
3423
     * This is for infraspecific taxa, the infraspecific epithet,
3424
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3425
     * else the genusOrUninomial.
3426
     * However, the result does not depend on the rank (which may be not correctly set
3427
     * in case of dirty data) but returns the first name part which is not blank
3428
     * considering the above order.
3429
     * @return the first not blank name part in reverse order
3430
     */
3431
    @Override
3432
    public String getFamilyNamePart() {
3433
        String result =
3434
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3435
                    this.getInfraSpecificEpithet() :
3436
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3437
                    this.getSpecificEpithet():
3438
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3439
                    this.getInfraGenericEpithet():
3440
                this.getGenusOrUninomial();
3441
        return result;
3442
    }
3443

    
3444
    /**
3445
     * {@inheritDoc}
3446
     */
3447
    @Override
3448
    public boolean isHybridName() {
3449
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3450
    }
3451

    
3452
    /**
3453
     * {@inheritDoc}
3454
     */
3455
    @Override
3456
    public boolean isHybrid() {
3457
        return this.isHybridName() || this.isHybridFormula();
3458
    }
3459

    
3460
// ***************** COMPARE ********************************/
3461

    
3462
    @Override
3463
    public int compareToName(TaxonName otherName){
3464

    
3465
        int result = 0;
3466

    
3467
        if (otherName == null) {
3468
            throw new NullPointerException("Cannot compare to null.");
3469
        }
3470

    
3471
        //other
3472
        otherName = deproxy(otherName);
3473
        String otherNameCache = otherName.getNameCache();
3474
        String otherTitleCache = otherName.getTitleCache();
3475
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3476
        if (otherName.isAutonym()){
3477
            boolean isProtected = otherName.isProtectedNameCache();
3478
            String oldNameCache = otherName.getNameCache();
3479
            otherName.setProtectedNameCache(false);
3480
            otherName.setNameCache(null, false);
3481
            otherNameCache = otherName.getNameCache();
3482
            otherName.setNameCache(oldNameCache, isProtected);
3483
        }
3484

    
3485
        //this
3486
        String thisNameCache = this.getNameCache();
3487
        String thisTitleCache = this.getTitleCache();
3488

    
3489
        if (this.isAutonym()){
3490
            boolean isProtected = this.isProtectedNameCache();
3491
            String oldNameCache = this.getNameCache();
3492
            this.setProtectedNameCache(false);
3493
            this.setNameCache(null, false);
3494
            thisNameCache = this.getNameCache();
3495
            this.setNameCache(oldNameCache, isProtected);
3496
        }
3497

    
3498

    
3499
        // Compare name cache of taxon names
3500
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3501
            thisNameCache = normalizeName(thisNameCache);
3502
            otherNameCache = normalizeName(otherNameCache);
3503
            result = thisNameCache.compareTo(otherNameCache);
3504
        }
3505

    
3506
        // Compare title cache of taxon names
3507
        if (result == 0){
3508
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3509
                thisTitleCache = normalizeName(thisTitleCache);
3510
                otherTitleCache = normalizeName(otherTitleCache);
3511
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3512
            }
3513
        }
3514

    
3515
        return result;
3516
    }
3517

    
3518
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3519
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3520

    
3521
    /**
3522
     * @param thisNameCache
3523
     * @param HYBRID_SIGN
3524
     * @param QUOT_SIGN
3525
     * @return
3526
     */
3527
    private String normalizeName(String thisNameCache) {
3528
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3529
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3530
        return thisNameCache;
3531
    }
3532

    
3533
// ********************** INTERFACES ********************************************/
3534

    
3535
    /**
3536
     * Method to cast a interfaced name to a concrete name.
3537
     * The method includes a deproxy to guarantee that no
3538
     * class cast exception is thrown.
3539
     *
3540
     * @see #castAndDeproxy(Set)
3541
     * @param interfacedName
3542
     * @return
3543
     */
3544
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3545
        return deproxy(interfacedName, TaxonName.class);
3546
    }
3547

    
3548
    /**
3549
     * Method to cast a set of interfaced names to concrete namex.
3550
     * The method includes a deproxy to guarantee that no
3551
     * class cast exception is thrown.
3552
     *
3553
     * @see #castAndDeproxy(ITaxonNameBase)
3554
     * @param naminterfacedNames
3555
     * @return
3556
     */
3557
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3558
        Set<TaxonName> result = new HashSet<>();
3559
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3560
            result.add(castAndDeproxy(naminterfacedName));
3561
        }
3562
        return result;
3563
    }
3564

    
3565
//************************ isType ***********************************************/
3566

    
3567
    /**
3568
     * @return
3569
     */
3570
    @Override
3571
    public boolean isNonViral() {
3572
        return nameType.isNonViral();
3573
    }
3574

    
3575
    @Override
3576
    public boolean isZoological(){
3577
        return nameType.isZoological();
3578
    }
3579
    @Override
3580
    public boolean isBotanical() {
3581
        return nameType.isBotanical();
3582
    }
3583
    @Override
3584
    public boolean isCultivar() {
3585
        return nameType.isCultivar();
3586
    }
3587
    @Override
3588
    public boolean isBacterial() {
3589
        return nameType.isBacterial();
3590
    }
3591
    @Override
3592
    public boolean isViral() {
3593
        return nameType.isViral();
3594
    }
3595

    
3596

    
3597
//*********************** CLONE ********************************************************/
3598

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

    
3618
            //taxonBases -> empty
3619
            result.taxonBases = new HashSet<>();
3620

    
3621
            //empty caches
3622
            if (! protectedFullTitleCache){
3623
                result.fullTitleCache = null;
3624
            }
3625

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

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

    
3640

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

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

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

    
3665
            //homotypicalGroup
3666
            //TODO still needs to be discussed
3667
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3668
            result.homotypicalGroup.addTypifiedName(this);
3669

    
3670

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

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

    
3687
            //empty caches
3688
            if (! protectedNameCache){
3689
                result.nameCache = null;
3690
            }
3691

    
3692
            //empty caches
3693
            if (! protectedAuthorshipCache){
3694
                result.authorshipCache = null;
3695
            }
3696

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

    
3715
    }
3716

    
3717

    
3718
}
3719

    
(29-29/36)