Project

General

Profile

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

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

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

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

    
47
import org.apache.commons.lang.StringUtils;
48
import org.apache.log4j.Logger;
49
import org.hibernate.annotations.Cascade;
50
import org.hibernate.annotations.CascadeType;
51
import org.hibernate.annotations.Type;
52
import org.hibernate.envers.Audited;
53
import org.hibernate.search.annotations.Analyze;
54
import org.hibernate.search.annotations.Analyzer;
55
import org.hibernate.search.annotations.Field;
56
import org.hibernate.search.annotations.Fields;
57
import org.hibernate.search.annotations.Index;
58
import org.hibernate.search.annotations.Indexed;
59
import org.hibernate.search.annotations.IndexedEmbedded;
60
import org.hibernate.search.annotations.Store;
61
import org.hibernate.validator.constraints.NotEmpty;
62
import org.springframework.util.ReflectionUtils;
63

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

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

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

    
160
    "acronym",
161

    
162
    "subGenusAuthorship",
163
    "nameApprobation",
164

    
165
    "breed",
166
    "publicationYear",
167
    "originalPublicationYear",
168
    "inCombinationAuthorship",
169
    "inBasionymAuthorship",
170

    
171
    "anamorphic",
172

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

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

    
197

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
351

    
352

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
507

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

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

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

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

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

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

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

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

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

    
548
    //ZOOLOGICAL NAME
549

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

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

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

    
569
    //Cultivar attribute(s)
570

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

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

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

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

    
598

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

    
620

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

    
635

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

    
661

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

    
707

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

    
719

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1111

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1265

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

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

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

    
1281

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

    
1294

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

    
1307

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

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

    
1322

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

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

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

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

    
1350

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

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

    
1382

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

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

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

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

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

    
1432

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

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

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

    
1470

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

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

    
1491

    
1492
    @Override
1493
    public String getTitleCache(){
1494
        if(!protectedTitleCache) {
1495
            updateAuthorshipCache();
1496
        }
1497
        return super.getTitleCache();
1498
    }
1499

    
1500
    @Override
1501
    public void setTitleCache(String titleCache, boolean protectCache){
1502
        super.setTitleCache(titleCache, protectCache);
1503
    }
1504

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

    
1528
        }
1529
        return authorshipCache;
1530
    }
1531

    
1532

    
1533

    
1534

    
1535

    
1536

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

    
1549

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

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

    
1583

    
1584

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

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

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

    
1615

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

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

    
1634
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1635
        if (author == null){
1636
            return null;
1637
        }else{
1638
            return author.getNomenclaturalTitle();
1639
        }
1640
    }
1641

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

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

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

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

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

    
1784
        TaxonName fromName = nameRelation.getFromName();
1785
        TaxonName toName = nameRelation.getToName();
1786

    
1787
        if (nameRelation != null) {
1788
            nameRelation.setToName(null);
1789
            nameRelation.setFromName(null);
1790
        }
1791

    
1792
        if (fromName != null) {
1793
            fromName.removeNameRelationship(nameRelation);
1794
        }
1795

    
1796
        if (toName != null) {
1797
            toName.removeNameRelationship(nameRelation);
1798
        }
1799

    
1800
        this.relationsToThisName.remove(nameRelation);
1801
        this.relationsFromThisName.remove(nameRelation);
1802
    }
1803

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

    
1818
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1819

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

    
1831

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

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

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

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

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

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

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

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

    
1942

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

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

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

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

    
2012

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

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

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

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

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

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

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

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

    
2160
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2161
    }
2162

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

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

    
2197

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

    
2223

    
2224
    /**
2225
     * @param direction
2226
     * @return
2227
     */
2228
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2229

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

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

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

    
2257

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

    
2268
    @Override
2269
    public DescriptionElementSource getNomenclaturalSource(){
2270
        return this.nomenclaturalSource;
2271
    }
2272

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

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

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

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

    
2321

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

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

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

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

    
2377
    @Override
2378
    public int getParsingProblem(){
2379
        return this.parsingProblem;
2380
    }
2381

    
2382
    @Override
2383
    public void setParsingProblem(int parsingProblem){
2384
        this.parsingProblem = parsingProblem;
2385
    }
2386

    
2387
    @Override
2388
    public void addParsingProblem(ParserProblem problem){
2389
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2390
    }
2391

    
2392
    @Override
2393
    public void removeParsingProblem(ParserProblem problem) {
2394
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2395
    }
2396

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

    
2405
    @Override
2406
    public boolean hasProblem(){
2407
        return parsingProblem != 0;
2408
    }
2409

    
2410
    @Override
2411
    public boolean hasProblem(ParserProblem problem) {
2412
        return getParsingProblems().contains(problem);
2413
    }
2414

    
2415
    @Override
2416
    public int getProblemStarts(){
2417
        return this.problemStarts;
2418
    }
2419

    
2420
    @Override
2421
    public void setProblemStarts(int start) {
2422
        this.problemStarts = start;
2423
    }
2424

    
2425
    @Override
2426
    public int getProblemEnds(){
2427
        return this.problemEnds;
2428
    }
2429

    
2430
    @Override
2431
    public void setProblemEnds(int end) {
2432
        this.problemEnds = end;
2433
    }
2434

    
2435
//*********************** TYPE DESIGNATION *********************************************//
2436

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

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

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

    
2483
//*********************** NAME TYPE DESIGNATION *********************************************//
2484

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

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

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

    
2571
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2572

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

    
2589

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

    
2622
    //used by merge strategy
2623
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2624
        return addTypeDesignation(typeDesignation, true);
2625
    }
2626

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

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

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

    
2677

    
2678

    
2679
//*********************** HOMOTYPICAL GROUP *********************************************//
2680

    
2681

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

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

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

    
2716

    
2717

    
2718
// *************************************************************************//
2719

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

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

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

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

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

    
2812

    
2813
    }
2814

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

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

    
2857
    //******* REGISTRATION *****************/
2858

    
2859
    @Override
2860
    public Set<Registration> getRegistrations() {
2861
        return this.registrations;
2862
    }
2863

    
2864

    
2865
// ************* RELATIONSHIPS *****************************/
2866

    
2867

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

    
2882
    }
2883

    
2884

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

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

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

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

    
2955

    
2956

    
2957
// *********** DESCRIPTIONS *************************************
2958

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

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

    
3009
// *********** HOMOTYPIC GROUP METHODS **************************************************
3010

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

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

    
3045

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

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

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

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

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

    
3109

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

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

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

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

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

    
3250

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

    
3273

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

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

    
3300
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3301
        if (homotypicalGroup == null) {
3302
            return;
3303
        }
3304

    
3305
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3306
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3307

    
3308
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3309

    
3310
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3311

    
3312
            for(NameRelationship nameRelation : nameRelations){
3313
                relations.add(nameRelation);
3314
            }
3315
        }
3316

    
3317
        for (NameRelationship relation : relations) {
3318

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

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

    
3334
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3335
            if (!name.equals(this)) {
3336

    
3337
                // First check whether the relationship already exists
3338
                if (!this.isBasionymFor(name)) {
3339

    
3340
                    // Then create it
3341
                    name.addRelationshipFromName(this,
3342
                            NameRelationshipType.BASIONYM(), null);
3343
                }
3344
            }
3345
        }
3346
    }
3347

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

    
3358
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3359

    
3360
        if (homotypicalGroup == null) {
3361
            return;
3362
        }
3363

    
3364
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3365
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3366

    
3367
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3368

    
3369
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3370

    
3371
            for(NameRelationship nameRelation : nameRelations){
3372
                relations.add(nameRelation);
3373
            }
3374
        }
3375

    
3376
        for (NameRelationship relation : relations) {
3377

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

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

    
3395

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

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

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

    
3435
// ***************** COMPARE ********************************/
3436

    
3437
    @Override
3438
    public int compareToName(TaxonName otherName){
3439

    
3440
        int result = 0;
3441

    
3442
        if (otherName == null) {
3443
            throw new NullPointerException("Cannot compare to null.");
3444
        }
3445

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

    
3460
        //this
3461
        String thisNameCache = this.getNameCache();
3462
        String thisTitleCache = this.getTitleCache();
3463

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

    
3473

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

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

    
3490
        return result;
3491
    }
3492

    
3493
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3494
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3495

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

    
3508
// ********************** INTERFACES ********************************************/
3509

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

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

    
3540
//************************ isType ***********************************************/
3541

    
3542
    /**
3543
     * @return
3544
     */
3545
    @Override
3546
    public boolean isNonViral() {
3547
        return nameType.isNonViral();
3548
    }
3549

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

    
3571
// *********************** CACHES ***************************************************/
3572

    
3573

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

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

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

    
3619

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

    
3636

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

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

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

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

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

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

    
3680

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

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

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

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

    
3710

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

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

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

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

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

    
3755
    }
3756

    
3757

    
3758
}
3759

    
(29-29/36)