Project

General

Profile

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

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

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

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

    
47
import org.apache.commons.lang.StringUtils;
48
import org.apache.log4j.Logger;
49
import org.hibernate.annotations.Cascade;
50
import org.hibernate.annotations.CascadeType;
51
import org.hibernate.annotations.Table;
52
import org.hibernate.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.IndexedEmbedded;
59
import org.hibernate.search.annotations.Store;
60
import org.hibernate.validator.constraints.NotEmpty;
61
import org.springframework.util.ReflectionUtils;
62

    
63
import eu.etaxonomy.cdm.common.CdmUtils;
64
import eu.etaxonomy.cdm.common.UTF8;
65
import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
66
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
67
import eu.etaxonomy.cdm.model.common.CdmBase;
68
import eu.etaxonomy.cdm.model.common.IParsable;
69
import eu.etaxonomy.cdm.model.common.IRelated;
70
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
71
import eu.etaxonomy.cdm.model.common.RelationshipBase;
72
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
73
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
74
import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
75
import eu.etaxonomy.cdm.model.reference.Reference;
76
import eu.etaxonomy.cdm.model.taxon.Synonym;
77
import eu.etaxonomy.cdm.model.taxon.Taxon;
78
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
79
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
80
import eu.etaxonomy.cdm.strategy.cache.name.CacheUpdate;
81
import eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy;
82
import eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy;
83
import eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy;
84
import eu.etaxonomy.cdm.strategy.match.IMatchable;
85
import eu.etaxonomy.cdm.strategy.match.Match;
86
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
87
import eu.etaxonomy.cdm.strategy.match.MatchMode;
88
import eu.etaxonomy.cdm.strategy.merge.Merge;
89
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
90
import eu.etaxonomy.cdm.strategy.parser.ParserProblem;
91
import eu.etaxonomy.cdm.validation.Level2;
92
import eu.etaxonomy.cdm.validation.Level3;
93
import eu.etaxonomy.cdm.validation.annotation.NullOrNotEmpty;
94
import eu.etaxonomy.cdm.validation.annotation.ValidTaxonomicYear;
95

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

    
129
    "nameCache",
130
    "genusOrUninomial",
131
    "infraGenericEpithet",
132
    "specificEpithet",
133
    "infraSpecificEpithet",
134
    "combinationAuthorship",
135
    "exCombinationAuthorship",
136
    "basionymAuthorship",
137
    "exBasionymAuthorship",
138
    "authorshipCache",
139
    "protectedAuthorshipCache",
140
    "protectedNameCache",
141
    "hybridParentRelations",
142
    "hybridChildRelations",
143
    "hybridFormula",
144
    "monomHybrid",
145
    "binomHybrid",
146
    "trinomHybrid",
147

    
148
    "acronym",
149

    
150
    "subGenusAuthorship",
151
    "nameApprobation",
152

    
153
    "breed",
154
    "publicationYear",
155
    "originalPublicationYear",
156

    
157
//    "anamorphic",
158

    
159
    "cultivarName"
160
})
161
@XmlRootElement(name = "TaxonNameBase")
162
@Entity
163
@Audited
164
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
165
@Table(appliesTo="TaxonNameBase", indexes = { @org.hibernate.annotations.Index(name = "taxonNameBaseTitleCacheIndex", columnNames = { "titleCache" }),  @org.hibernate.annotations.Index(name = "taxonNameBaseNameCacheIndex", columnNames = { "nameCache" }) })
166
public abstract class TaxonNameBase<T extends TaxonNameBase<?,?>, S extends INameCacheStrategy>
167
            extends IdentifiableEntity<S>
168
            implements ITaxonNameBase, INonViralName, IViralName, IBacterialName, IZoologicalName,
169
                IBotanicalName, ICultivarPlantName,
170
                IParsable, IRelated, IMatchable, Cloneable {
171

    
172
    private static final long serialVersionUID = -791164269603409712L;
173
    private static final Logger logger = Logger.getLogger(TaxonNameBase.class);
174

    
175

    
176
    @XmlElement(name = "FullTitleCache")
177
    @Column(length=800, name="fullTitleCache")  //see #1592
178
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
179
    @CacheUpdate(noUpdate ="titleCache")
180
    @NotEmpty(groups = Level2.class)
181
    protected String fullTitleCache;
182

    
183
    //if true titleCache will not be automatically generated/updated
184
    @XmlElement(name = "ProtectedFullTitleCache")
185
    @CacheUpdate(value ="fullTitleCache", noUpdate ="titleCache")
186
    private boolean protectedFullTitleCache;
187

    
188
    @XmlElementWrapper(name = "Descriptions")
189
    @XmlElement(name = "Description")
190
    @OneToMany(mappedBy="taxonName", fetch= FetchType.LAZY, orphanRemoval=true)
191
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
192
    @NotNull
193
    private Set<TaxonNameDescription> descriptions = new HashSet<TaxonNameDescription>();
194

    
195
    @XmlElement(name = "AppendedPhrase")
196
    @Field
197
    @CacheUpdate(value ="nameCache")
198
    //TODO Val #3379
199
//    @NullOrNotEmpty
200
    @Column(length=255)
201
    private String appendedPhrase;
202

    
203
    @XmlElement(name = "NomenclaturalMicroReference")
204
    @Field
205
    @CacheUpdate(noUpdate ="titleCache")
206
    //TODO Val #3379
207
//    @NullOrNotEmpty
208
    @Column(length=255)
209
    private String nomenclaturalMicroReference;
210

    
211
    @XmlAttribute
212
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
213
    private int parsingProblem = 0;
214

    
215
    @XmlAttribute
216
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
217
    private int problemStarts = -1;
218

    
219
    @XmlAttribute
220
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
221
    private int problemEnds = -1;
222

    
223
    @XmlElementWrapper(name = "TypeDesignations")
224
    @XmlElement(name = "TypeDesignation")
225
    @XmlIDREF
226
    @XmlSchemaType(name = "IDREF")
227
    @ManyToMany(fetch = FetchType.LAZY)
228
    @JoinTable(
229
        name="TaxonNameBase_TypeDesignationBase",
230
        joinColumns=@javax.persistence.JoinColumn(name="TaxonNameBase_id"),
231
        inverseJoinColumns=@javax.persistence.JoinColumn(name="typedesignations_id")
232
    )
233
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
234
    @NotNull
235
    private Set<TypeDesignationBase> typeDesignations = new HashSet<>();
236

    
237
    @XmlElement(name = "HomotypicalGroup")
238
    @XmlIDREF
239
    @XmlSchemaType(name = "IDREF")
240
    @ManyToOne(fetch = FetchType.LAZY)
241
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
242
    @Match(MatchMode.IGNORE)
243
    @CacheUpdate(noUpdate ="titleCache")
244
    //TODO Val #3379
245
//    @NotNull
246
    private HomotypicalGroup homotypicalGroup;
247

    
248
    @XmlElementWrapper(name = "RelationsFromThisName")
249
    @XmlElement(name = "RelationFromThisName")
250
    @OneToMany(mappedBy="relatedFrom", fetch= FetchType.LAZY, orphanRemoval=true)
251
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
252
    @Merge(MergeMode.RELATION)
253
    @NotNull
254
    @Valid
255
    private Set<NameRelationship> relationsFromThisName = new HashSet<>();
256

    
257
    @XmlElementWrapper(name = "RelationsToThisName")
258
    @XmlElement(name = "RelationToThisName")
259
    @XmlIDREF
260
    @XmlSchemaType(name = "IDREF")
261
    @OneToMany(mappedBy="relatedTo", fetch= FetchType.LAZY, orphanRemoval=true)
262
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
263
    @Merge(MergeMode.RELATION)
264
    @NotNull
265
    @Valid
266
    private Set<NameRelationship> relationsToThisName = new HashSet<>();
267

    
268
    @XmlElementWrapper(name = "NomenclaturalStatuses")
269
    @XmlElement(name = "NomenclaturalStatus")
270
    @OneToMany(fetch= FetchType.LAZY, orphanRemoval=true)
271
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE,CascadeType.DELETE})
272
    @NotNull
273
    @IndexedEmbedded(depth=1)
274
    private Set<NomenclaturalStatus> status = new HashSet<>();
275

    
276
    @XmlElementWrapper(name = "TaxonBases")
277
    @XmlElement(name = "TaxonBase")
278
    @XmlIDREF
279
    @XmlSchemaType(name = "IDREF")
280
    @OneToMany(mappedBy="name", fetch= FetchType.LAZY)
281
    @NotNull
282
    @IndexedEmbedded(depth=1)
283
    private Set<TaxonBase> taxonBases = new HashSet<>();
284

    
285
    @XmlElement(name = "Rank")
286
    @XmlIDREF
287
    @XmlSchemaType(name = "IDREF")
288
    @ManyToOne(fetch = FetchType.EAGER)
289
    @CacheUpdate(value ="nameCache")
290
    //TODO Val #3379, handle maybe as groups = Level2.class ??
291
//    @NotNull
292
    @IndexedEmbedded(depth=1)
293
    private Rank rank;
294

    
295
    @XmlElement(name = "NomenclaturalReference")
296
    @XmlIDREF
297
    @XmlSchemaType(name = "IDREF")
298
    @ManyToOne(fetch = FetchType.LAZY)
299
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
300
    @CacheUpdate(noUpdate ="titleCache")
301
    @IndexedEmbedded
302
    private Reference nomenclaturalReference;
303

    
304
//****** Non-ViralName attributes ***************************************/
305

    
306
    @XmlElement(name = "NameCache")
307
    @Fields({
308
        @Field(name = "nameCache_tokenized"),
309
        @Field(store = Store.YES, index = Index.YES, analyze = Analyze.YES)
310
    })
311
    @Analyzer(impl = org.apache.lucene.analysis.core.KeywordAnalyzer.class)
312
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
313
            cacheReplacedProperties={"genusOrUninomial", "infraGenericEpithet", "specificEpithet", "infraSpecificEpithet"} )
314
    @NotEmpty(groups = Level2.class) // implicitly NotNull
315
    @Column(length=255)
316
    private String nameCache;
317

    
318
    @XmlElement(name = "ProtectedNameCache")
319
    @CacheUpdate(value="nameCache")
320
    protected boolean protectedNameCache;
321

    
322
    @XmlElement(name = "GenusOrUninomial")
323
    @Field(analyze = Analyze.YES, indexNullAs=Field.DEFAULT_NULL_TOKEN)
324
    @Match(MatchMode.EQUAL_REQUIRED)
325
    @CacheUpdate("nameCache")
326
    @Column(length=255)
327
    @Pattern(regexp = "[A-Z][a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForUninomial.message}")
328
    @NullOrNotEmpty
329
    @NotNull(groups = Level2.class)
330
    private String genusOrUninomial;
331

    
332
    @XmlElement(name = "InfraGenericEpithet")
333
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
334
    @CacheUpdate("nameCache")
335
    //TODO Val #3379
336
//    @NullOrNotEmpty
337
    @Column(length=255)
338
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class,message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
339
    private String infraGenericEpithet;
340

    
341
    @XmlElement(name = "SpecificEpithet")
342
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
343
    @CacheUpdate("nameCache")
344
    //TODO Val #3379
345
//    @NullOrNotEmpty
346
    @Column(length=255)
347
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
348
    private String specificEpithet;
349

    
350
    @XmlElement(name = "InfraSpecificEpithet")
351
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
352
    @CacheUpdate("nameCache")
353
    //TODO Val #3379
354
//    @NullOrNotEmpty
355
    @Column(length=255)
356
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
357
    private String infraSpecificEpithet;
358

    
359
    @XmlElement(name = "CombinationAuthorship", type = TeamOrPersonBase.class)
360
    @XmlIDREF
361
    @XmlSchemaType(name = "IDREF")
362
    @ManyToOne(fetch = FetchType.LAZY)
363
//    @Target(TeamOrPersonBase.class)
364
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
365
    @JoinColumn(name="combinationAuthorship_id")
366
    @CacheUpdate("authorshipCache")
367
    @IndexedEmbedded
368
    private TeamOrPersonBase<?> combinationAuthorship;
369

    
370
    @XmlElement(name = "ExCombinationAuthorship", type = TeamOrPersonBase.class)
371
    @XmlIDREF
372
    @XmlSchemaType(name = "IDREF")
373
    @ManyToOne(fetch = FetchType.LAZY)
374
//    @Target(TeamOrPersonBase.class)
375
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
376
    @JoinColumn(name="exCombinationAuthorship_id")
377
    @CacheUpdate("authorshipCache")
378
    @IndexedEmbedded
379
    private TeamOrPersonBase<?> exCombinationAuthorship;
380

    
381
    @XmlElement(name = "BasionymAuthorship", type = TeamOrPersonBase.class)
382
    @XmlIDREF
383
    @XmlSchemaType(name = "IDREF")
384
    @ManyToOne(fetch = FetchType.LAZY)
385
//    @Target(TeamOrPersonBase.class)
386
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
387
    @JoinColumn(name="basionymAuthorship_id")
388
    @CacheUpdate("authorshipCache")
389
    @IndexedEmbedded
390
    private TeamOrPersonBase<?> basionymAuthorship;
391

    
392
    @XmlElement(name = "ExBasionymAuthorship", type = TeamOrPersonBase.class)
393
    @XmlIDREF
394
    @XmlSchemaType(name = "IDREF")
395
    @ManyToOne(fetch = FetchType.LAZY)
396
//    @Target(TeamOrPersonBase.class)
397
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
398
    @JoinColumn(name="exBasionymAuthorship_id")
399
    @CacheUpdate("authorshipCache")
400
    @IndexedEmbedded
401
    private TeamOrPersonBase<?> exBasionymAuthorship;
402

    
403
    @XmlElement(name = "AuthorshipCache")
404
    @Fields({
405
        @Field(name = "authorshipCache_tokenized"),
406
        @Field(analyze = Analyze.NO)
407
    })
408
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
409
            cacheReplacedProperties={"combinationAuthorship", "basionymAuthorship", "exCombinationAuthorship", "exBasionymAuthorship"} )
410
    //TODO Val #3379
411
//    @NotNull
412
    @Column(length=255)
413
    @Pattern(regexp = "^[A-Za-z0-9 \\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-\\&\\,\\(\\)\\.]+$", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForAuthority.message}")
414
    private String authorshipCache;
415

    
416
    @XmlElement(name = "ProtectedAuthorshipCache")
417
    @CacheUpdate("authorshipCache")
418
    protected boolean protectedAuthorshipCache;
419

    
420
    @XmlElementWrapper(name = "HybridRelationsFromThisName")
421
    @XmlElement(name = "HybridRelationsFromThisName")
422
    @OneToMany(mappedBy="relatedFrom", fetch = FetchType.LAZY)
423
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
424
    @Merge(MergeMode.RELATION)
425
    @NotNull
426
    private Set<HybridRelationship> hybridParentRelations = new HashSet<>();
427

    
428
    @XmlElementWrapper(name = "HybridRelationsToThisName")
429
    @XmlElement(name = "HybridRelationsToThisName")
430
    @OneToMany(mappedBy="relatedTo", fetch = FetchType.LAZY, orphanRemoval=true) //a hybrid relation can be deleted automatically if the child is deleted.
431
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
432
    @Merge(MergeMode.RELATION)
433
    @NotNull
434
    private Set<HybridRelationship> hybridChildRelations = new HashSet<>();
435

    
436

    
437
    //if set: this name is a hybrid formula (a hybrid that does not have an own name) and no
438
    //other hybrid flags may be set. A
439
    //hybrid name  may not have either an authorteam nor other name components.
440
    @XmlElement(name ="IsHybridFormula")
441
    @CacheUpdate("nameCache")
442
    private boolean hybridFormula = false;
443

    
444
    @XmlElement(name ="IsMonomHybrid")
445
    @CacheUpdate("nameCache")
446
    private boolean monomHybrid = false;
447

    
448
    @XmlElement(name ="IsBinomHybrid")
449
    @CacheUpdate("nameCache")
450
    private boolean binomHybrid = false;
451

    
452
    @XmlElement(name ="IsTrinomHybrid")
453
    @CacheUpdate("nameCache")
454
    private boolean trinomHybrid = false;
455

    
456
// ViralName attributes ************************* /
457

    
458
    @XmlElement(name = "Acronym")
459
    @Field
460
    //TODO Val #3379
461
//  @NullOrNotEmpty
462
    @Column(length=255)
463
    private String acronym;
464

    
465
// BacterialName attributes ***********************/
466

    
467
    //Author team and year of the subgenus name
468
    @XmlElement(name = "SubGenusAuthorship")
469
    @Field
470
    private String subGenusAuthorship;
471

    
472
    //Approbation of name according to approved list, validation list, or validly published, paper in IJSB after 1980
473
    @XmlElement(name = "NameApprobation")
474
    @Field
475
    private String nameApprobation;
476

    
477
    //ZOOLOGICAL NAME
478

    
479
    //Name of the breed of an animal
480
    @XmlElement(name = "Breed")
481
    @Field
482
    @NullOrNotEmpty
483
    @Column(length=255)
484
    private String breed;
485

    
486
    @XmlElement(name = "PublicationYear")
487
    @Field(analyze = Analyze.NO)
488
    @CacheUpdate(value ="authorshipCache")
489
    @Min(0)
490
    private Integer publicationYear;
491

    
492
    @XmlElement(name = "OriginalPublicationYear")
493
    @Field(analyze = Analyze.NO)
494
    @CacheUpdate(value ="authorshipCache")
495
    @Min(0)
496
    private Integer originalPublicationYear;
497

    
498
    //Cultivar attribute(s)
499

    
500
    //the characteristical name of the cultivar
501
    @XmlElement(name = "CultivarName", required = true)
502
    //TODO Val #3379
503
    //@NullOrNotEmpty
504
    @Column(length=255)
505
    private String cultivarName;
506

    
507

    
508
// *************** FACTORY METHODS ********************************/
509

    
510
    //see TaxonNameFactory
511

    
512
// *********************** PARSER STATIC *******************************/
513

    
514

    
515

    
516
// ************* CONSTRUCTORS *************/
517
    /**
518
     * Class constructor: creates a new empty taxon name.
519
     *
520
     * @see #TaxonNameBase(Rank)
521
     * @see #TaxonNameBase(HomotypicalGroup)
522
     * @see #TaxonNameBase(Rank, HomotypicalGroup)
523
     */
524
    protected TaxonNameBase() {
525
        super();
526
        setNameCacheStrategy();
527
    }
528
    /**
529
     * Class constructor: creates a new taxon name
530
     * only containing its {@link Rank rank}.
531
     *
532
     * @param  rank  the rank to be assigned to <i>this</i> taxon name
533
     * @see    		 #TaxonNameBase()
534
     * @see    		 #TaxonNameBase(HomotypicalGroup)
535
     * @see    		 #TaxonNameBase(Rank, HomotypicalGroup)
536
     */
537
    protected TaxonNameBase(Rank rank) {
538
        this(rank, null);
539
    }
540
    /**
541
     * Class constructor: creates a new taxon name instance
542
     * only containing its {@link HomotypicalGroup homotypical group}.
543
     * The new taxon name will be also added to the set of taxon names
544
     * belonging to this homotypical group.
545
     *
546
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
547
     * @see    					 #TaxonNameBase()
548
     * @see    					 #TaxonNameBase(Rank)
549
     * @see    					 #TaxonNameBase(Rank, HomotypicalGroup)
550
     */
551
    protected TaxonNameBase(HomotypicalGroup homotypicalGroup) {
552
        this(null, homotypicalGroup);
553
    }
554

    
555
    /**
556
     * Class constructor: creates a new taxon name instance
557
     * only containing its {@link Rank rank} and
558
     * its {@link HomotypicalGroup homotypical group} and
559
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
560
     * The new taxon name will be also added to the set of taxon names
561
     * belonging to this homotypical group.
562
     *
563
     * @param  rank  			 the rank to be assigned to <i>this</i> taxon name
564
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
565
     * @see    					 #TaxonNameBase()
566
     * @see    					 #TaxonNameBase(Rank)
567
     * @see    					 #TaxonNameBase(HomotypicalGroup)
568
     */
569
    protected TaxonNameBase(Rank rank, HomotypicalGroup homotypicalGroup) {
570
        super();
571
        this.setRank(rank);
572
        if (homotypicalGroup == null){
573
            homotypicalGroup = new HomotypicalGroup();
574
        }
575
        homotypicalGroup.addTypifiedName(this);
576
        this.homotypicalGroup = homotypicalGroup;
577
        setNameCacheStrategy();
578
    }
579

    
580

    
581
    /**
582
     * Class constructor: creates a new non viral taxon name instance
583
     * containing its {@link Rank rank},
584
     * its {@link HomotypicalGroup homotypical group},
585
     * its scientific name components, its {@link eu.etaxonomy.cdm.model.agent.TeamOrPersonBase author(team)},
586
     * its {@link eu.etaxonomy.cdm.model.reference.Reference nomenclatural reference} and
587
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
588
     * The new non viral taxon name instance will be also added to the set of
589
     * non viral taxon names belonging to this homotypical group.
590
     *
591
     * @param   rank  the rank to be assigned to <i>this</i> non viral taxon name
592
     * @param   genusOrUninomial the string for <i>this</i> non viral taxon name
593
     *          if its rank is genus or higher or for the genus part
594
     *          if its rank is lower than genus
595
     * @param   infraGenericEpithet  the string for the first epithet of
596
     *          <i>this</i> non viral taxon name if its rank is lower than genus
597
     *          and higher than species aggregate
598
     * @param   specificEpithet  the string for the first epithet of
599
     *          <i>this</i> non viral taxon name if its rank is species aggregate or lower
600
     * @param   infraSpecificEpithet  the string for the second epithet of
601
     *          <i>this</i> non viral taxon name if its rank is lower than species
602
     * @param   combinationAuthorship  the author or the team who published <i>this</i> non viral taxon name
603
     * @param   nomenclaturalReference  the nomenclatural reference where <i>this</i> non viral taxon name was published
604
     * @param   nomenclMicroRef  the string with the details for precise location within the nomenclatural reference
605
     * @param   homotypicalGroup  the homotypical group to which <i>this</i> non viral taxon name belongs
606
     * @see     #NonViralName()
607
     * @see     #NonViralName(Rank, HomotypicalGroup)
608
     * @see     #NewInstance(Rank, HomotypicalGroup)
609
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
610
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
611
     * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
612
     */
613
    protected TaxonNameBase(Rank rank, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, TeamOrPersonBase combinationAuthorship, INomenclaturalReference nomenclaturalReference, String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
614
        this(rank, homotypicalGroup);
615
        setGenusOrUninomial(genusOrUninomial);
616
        setInfraGenericEpithet (infraGenericEpithet);
617
        setSpecificEpithet(specificEpithet);
618
        setInfraSpecificEpithet(infraSpecificEpithet);
619
        setCombinationAuthorship(combinationAuthorship);
620
        setNomenclaturalReference(nomenclaturalReference);
621
        this.setNomenclaturalMicroReference(nomenclMicroRef);
622
    }
623

    
624

    
625
    private void setNameCacheStrategy(){
626
        if (getClass() == NonViralName.class){
627
            this.cacheStrategy = (S)NonViralNameDefaultCacheStrategy.NewInstance();
628
        }
629
    }
630

    
631

    
632
    @Override
633
    public void initListener(){
634
        PropertyChangeListener listener = new PropertyChangeListener() {
635
            @Override
636
            public void propertyChange(PropertyChangeEvent e) {
637
                boolean protectedByLowerCache = false;
638
                //authorship cache
639
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
640
                    if (protectedAuthorshipCache){
641
                        protectedByLowerCache = true;
642
                    }else{
643
                        authorshipCache = null;
644
                    }
645
                }
646

    
647
                //nameCache
648
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "nameCache")){
649
                    if (protectedNameCache){
650
                        protectedByLowerCache = true;
651
                    }else{
652
                        nameCache = null;
653
                    }
654
                }
655
                //title cache
656
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "titleCache")){
657
                    if (isProtectedTitleCache()|| protectedByLowerCache == true ){
658
                        protectedByLowerCache = true;
659
                    }else{
660
                        titleCache = null;
661
                    }
662
                }
663
                //full title cache
664
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "fullTitleCache")){
665
                    if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
666
                        protectedByLowerCache = true;
667
                    }else{
668
                        fullTitleCache = null;
669
                    }
670
                }
671
            }
672
        };
673
        addPropertyChangeListener(listener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
674
    }
675

    
676
    private static Map<String, java.lang.reflect.Field> allFields = null;
677
    protected Map<String, java.lang.reflect.Field> getAllFields(){
678
        if (allFields == null){
679
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
680
        }
681
        return allFields;
682
    }
683

    
684
    /**
685
     * @param propertyName
686
     * @param string
687
     * @return
688
     */
689
    private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
690
        java.lang.reflect.Field field;
691
        try {
692
            field = getAllFields().get(propertyName);
693
            if (field != null){
694
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
695
                if (updateAnnotation != null){
696
                    for (String value : updateAnnotation.value()){
697
                        if (cacheName.equals(value)){
698
                            return true;
699
                        }
700
                    }
701
                }
702
            }
703
            return false;
704
        } catch (SecurityException e1) {
705
            throw e1;
706
        }
707
    }
708

    
709
    private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
710
        java.lang.reflect.Field field;
711
        //do not update fields with the same name
712
        if (cacheName.equals(propertyName)){
713
            return true;
714
        }
715
        //evaluate annotation
716
        try {
717
            field = getAllFields().get(propertyName);
718
            if (field != null){
719
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
720
                if (updateAnnotation != null){
721
                    for (String value : updateAnnotation.noUpdate()){
722
                        if (cacheName.equals(value)){
723
                            return true;
724
                        }
725
                    }
726
                }
727
            }
728
            return false;
729
        } catch (SecurityException e1) {
730
            throw e1;
731
        }
732
    }
733

    
734
// ****************** GETTER / SETTER ****************************/
735

    
736
    /**
737
     * Returns the boolean value of the flag intended to protect (true)
738
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
739
     * string of <i>this</i> non viral taxon name.
740
     *
741
     * @return  the boolean value of the protectedNameCache flag
742
     * @see     #getNameCache()
743
     */
744
    @Override
745
    public boolean isProtectedNameCache() {
746
        return protectedNameCache;
747
    }
748

    
749
    /**
750
     * @see     #isProtectedNameCache()
751
     */
752
    @Override
753
    public void setProtectedNameCache(boolean protectedNameCache) {
754
        this.protectedNameCache = protectedNameCache;
755
    }
756

    
757
    /**
758
     * Returns either the scientific name string (without authorship) for <i>this</i>
759
     * non viral taxon name if its rank is genus or higher (monomial) or the string for
760
     * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
761
     * Genus or uninomial strings begin with an upper case letter.
762
     *
763
     * @return  the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
764
     * @see     #getNameCache()
765
     */
766
    @Override
767
    public String getGenusOrUninomial() {
768
        return genusOrUninomial;
769
    }
770

    
771
    /**
772
     * @see  #getGenusOrUninomial()
773
     */
774
    @Override
775
    public void setGenusOrUninomial(String genusOrUninomial) {
776
        this.genusOrUninomial = StringUtils.isBlank(genusOrUninomial) ? null : genusOrUninomial;
777
    }
778

    
779
    /**
780
     * Returns the genus subdivision epithet string (infrageneric part) for
781
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
782
     * higher than species aggregate: binomial). Genus subdivision epithet
783
     * strings begin with an upper case letter.
784
     *
785
     * @return  the string containing the infrageneric part of <i>this</i> non viral taxon name
786
     * @see     #getNameCache()
787
     */
788
    @Override
789
    public String getInfraGenericEpithet(){
790
        return this.infraGenericEpithet;
791
    }
792

    
793
    /**
794
     * @see  #getInfraGenericEpithet()
795
     */
796
    @Override
797
    public void setInfraGenericEpithet(String infraGenericEpithet){
798
        this.infraGenericEpithet = StringUtils.isBlank(infraGenericEpithet)? null : infraGenericEpithet;
799
    }
800

    
801
    /**
802
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
803
     * species aggregate or lower (bi- or trinomial). Species epithet strings
804
     * begin with a lower case letter.
805
     *
806
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
807
     * @see     #getNameCache()
808
     */
809
    @Override
810
    public String getSpecificEpithet(){
811
        return this.specificEpithet;
812
    }
813

    
814
    /**
815
     * @see  #getSpecificEpithet()
816
     */
817
    @Override
818
    public void setSpecificEpithet(String specificEpithet){
819
        this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
820
    }
821

    
822
    /**
823
     * Returns the species subdivision epithet string (infraspecific part) for
824
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
825
     * (lower than species: trinomial). Species subdivision epithet strings
826
     * begin with a lower case letter.
827
     *
828
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
829
     * @see     #getNameCache()
830
     */
831
    @Override
832
    public String getInfraSpecificEpithet(){
833
        return this.infraSpecificEpithet;
834
    }
835

    
836
    /**
837
     * @see  #getInfraSpecificEpithet()
838
     */
839
    @Override
840
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
841
        this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
842
    }
843

    
844
    /**
845
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
846
     * taxon name.
847
     *
848
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
849
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
850
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
851
     */
852
    @Override
853
    public TeamOrPersonBase<?> getCombinationAuthorship(){
854
        return this.combinationAuthorship;
855
    }
856

    
857
    /**
858
     * @see  #getCombinationAuthorship()
859
     */
860
    @Override
861
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
862
        this.combinationAuthorship = combinationAuthorship;
863
    }
864

    
865
    /**
866
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
867
     * the publication of <i>this</i> non viral taxon name as generally stated by
868
     * the {@link #getCombinationAuthorship() combination author (team)} itself.<BR>
869
     * An ex-author(-team) is an author(-team) to whom a taxon name was ascribed
870
     * although it is not the author(-team) of a valid publication (for instance
871
     * without the validating description or diagnosis in case of a name for a
872
     * new taxon). The name of this ascribed authorship, followed by "ex", may
873
     * be inserted before the name(s) of the publishing author(s) of the validly
874
     * published name:<BR>
875
     * <i>Lilium tianschanicum</i> was described by Grubov (1977) as a new species and
876
     * its name was ascribed to Ivanova; since there is no indication that
877
     * Ivanova provided the validating description, the name may be cited as
878
     * <i>Lilium tianschanicum</i> N. A. Ivanova ex Grubov or <i>Lilium tianschanicum</i> Grubov.
879
     * <P>
880
     * The presence of an author (team) of <i>this</i> non viral taxon name is a
881
     * condition for the existence of an ex author (team) for <i>this</i> same name.
882
     *
883
     * @return  the nomenclatural ex author (team) of <i>this</i> non viral taxon name
884
     * @see     #getCombinationAuthorship()
885
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
886
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
887
     */
888
    @Override
889
    public TeamOrPersonBase<?> getExCombinationAuthorship(){
890
        return this.exCombinationAuthorship;
891
    }
892

    
893
    /**
894
     * @see  #getExCombinationAuthorship()
895
     */
896
    @Override
897
    public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
898
        this.exCombinationAuthorship = exCombinationAuthorship;
899
    }
900

    
901
    /**
902
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
903
     * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
904
     * author (team) can only exist if <i>this</i> non viral taxon name is a new
905
     * combination due to a taxonomical revision.
906
     *
907
     * @return  the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
908
     * @see     #getCombinationAuthorship()
909
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
910
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
911
     */
912
    @Override
913
    public TeamOrPersonBase<?> getBasionymAuthorship(){
914
        return basionymAuthorship;
915
    }
916

    
917
    /**
918
     * @see  #getBasionymAuthorship()
919
     */
920
    @Override
921
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
922
        this.basionymAuthorship = basionymAuthorship;
923
    }
924

    
925
    /**
926
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
927
     * the publication of the original combination <i>this</i> non viral taxon name is
928
     * based on. This should have been generally stated by
929
     * the {@link #getBasionymAuthorship() basionym author (team)} itself.
930
     * The presence of a basionym author (team) of <i>this</i> non viral taxon name is a
931
     * condition for the existence of an ex basionym author (team)
932
     * for <i>this</i> same name.
933
     *
934
     * @return  the nomenclatural ex basionym author (team) of <i>this</i> non viral taxon name
935
     * @see     #getBasionymAuthorship()
936
     * @see     #getExCombinationAuthorship()
937
     * @see     #getCombinationAuthorship()
938
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
939
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
940
     */
941
    @Override
942
    public TeamOrPersonBase<?> getExBasionymAuthorship(){
943
        return exBasionymAuthorship;
944
    }
945

    
946
    /**
947
     * @see  #getExBasionymAuthorship()
948
     */
949
    @Override
950
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
951
        this.exBasionymAuthorship = exBasionymAuthorship;
952
    }
953

    
954
    /**
955
     * Returns the boolean value of the flag intended to protect (true)
956
     * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
957
     * of <i>this</i> non viral taxon name.
958
     *
959
     * @return  the boolean value of the protectedAuthorshipCache flag
960
     * @see     #getAuthorshipCache()
961
     */
962
    @Override
963
    public boolean isProtectedAuthorshipCache() {
964
        return protectedAuthorshipCache;
965
    }
966

    
967
    /**
968
     * @see     #isProtectedAuthorshipCache()
969
     * @see     #getAuthorshipCache()
970
     */
971
    @Override
972
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
973
        this.protectedAuthorshipCache = protectedAuthorshipCache;
974
    }
975

    
976
    /**
977
     * Returns the set of all {@link HybridRelationship hybrid relationships}
978
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
979
     *
980
     * @see    #getHybridRelationships()
981
     * @see    #getChildRelationships()
982
     * @see    HybridRelationshipType
983
     */
984
    @Override
985
    public Set<HybridRelationship> getHybridParentRelations() {
986
        if(hybridParentRelations == null) {
987
            this.hybridParentRelations = new HashSet<>();
988
        }
989
        return hybridParentRelations;
990
    }
991

    
992
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
993
        this.hybridParentRelations = hybridParentRelations;
994
    }
995

    
996

    
997
    /**
998
     * Returns the set of all {@link HybridRelationship hybrid relationships}
999
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
1000
     *
1001
     * @see    #getHybridRelationships()
1002
     * @see    #getParentRelationships()
1003
     * @see    HybridRelationshipType
1004
     */
1005
    @Override
1006
    public Set<HybridRelationship> getHybridChildRelations() {
1007
        if(hybridChildRelations == null) {
1008
            this.hybridChildRelations = new HashSet<>();
1009
        }
1010
        return hybridChildRelations;
1011
    }
1012

    
1013
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1014
        this.hybridChildRelations = hybridChildRelations;
1015
    }
1016

    
1017
    @Override
1018
    public boolean isProtectedFullTitleCache() {
1019
        return protectedFullTitleCache;
1020
    }
1021

    
1022
    @Override
1023
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1024
        this.protectedFullTitleCache = protectedFullTitleCache;
1025
    }
1026

    
1027
    /**
1028
     * Returns the boolean value of the flag indicating whether the name of <i>this</i>
1029
     * botanical taxon name is a hybrid formula (true) or not (false). A hybrid
1030
     * named by a hybrid formula (composed with its parent names by placing the
1031
     * multiplication sign between them) does not have an own published name
1032
     * and therefore has neither an {@link NonViralName#getAuthorshipCache() autorship}
1033
     * nor other name components. If this flag is set no other hybrid flags may
1034
     * be set.
1035
     *
1036
     * @return  the boolean value of the isHybridFormula flag
1037
     * @see     #isMonomHybrid()
1038
     * @see     #isBinomHybrid()
1039
     * @see     #isTrinomHybrid()
1040
     */
1041
    @Override
1042
    public boolean isHybridFormula(){
1043
        return this.hybridFormula;
1044
    }
1045

    
1046
    /**
1047
     * @see  #isHybridFormula()
1048
     */
1049
    @Override
1050
    public void setHybridFormula(boolean hybridFormula){
1051
        this.hybridFormula = hybridFormula;
1052
    }
1053

    
1054
    /**
1055
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1056
     * taxon name is the name of an intergeneric hybrid (true) or not (false).
1057
     * In this case the multiplication sign is placed before the scientific
1058
     * name. If this flag is set no other hybrid flags may be set.
1059
     *
1060
     * @return  the boolean value of the isMonomHybrid flag
1061
     * @see     #isHybridFormula()
1062
     * @see     #isBinomHybrid()
1063
     * @see     #isTrinomHybrid()
1064
     */
1065
    @Override
1066
    public boolean isMonomHybrid(){
1067
        return this.monomHybrid;
1068
    }
1069

    
1070
    /**
1071
     * @see  #isMonomHybrid()
1072
     * @see  #isBinomHybrid()
1073
     * @see  #isTrinomHybrid()
1074
     */
1075
    @Override
1076
    public void setMonomHybrid(boolean monomHybrid){
1077
        this.monomHybrid = monomHybrid;
1078
    }
1079

    
1080
    /**
1081
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1082
     * taxon name is the name of an interspecific hybrid (true) or not (false).
1083
     * In this case the multiplication sign is placed before the species
1084
     * epithet. If this flag is set no other hybrid flags may be set.
1085
     *
1086
     * @return  the boolean value of the isBinomHybrid flag
1087
     * @see     #isHybridFormula()
1088
     * @see     #isMonomHybrid()
1089
     * @see     #isTrinomHybrid()
1090
     */
1091
    @Override
1092
    public boolean isBinomHybrid(){
1093
        return this.binomHybrid;
1094
    }
1095

    
1096
    /**
1097
     * @see  #isBinomHybrid()
1098
     * @see  #isMonomHybrid()
1099
     * @see  #isTrinomHybrid()
1100
     */
1101
    @Override
1102
    public void setBinomHybrid(boolean binomHybrid){
1103
        this.binomHybrid = binomHybrid;
1104
    }
1105

    
1106
    /**
1107
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1108
     * taxon name is the name of an infraspecific hybrid (true) or not (false).
1109
     * In this case the term "notho-" (optionally abbreviated "n-") is used as
1110
     * a prefix to the term denoting the infraspecific rank of <i>this</i> botanical
1111
     * taxon name. If this flag is set no other hybrid flags may be set.
1112
     *
1113
     * @return  the boolean value of the isTrinomHybrid flag
1114
     * @see     #isHybridFormula()
1115
     * @see     #isMonomHybrid()
1116
     * @see     #isBinomHybrid()
1117
     */
1118
    @Override
1119
    public boolean isTrinomHybrid(){
1120
        return this.trinomHybrid;
1121
    }
1122

    
1123
    /**
1124
     * @see  #isTrinomHybrid()
1125
     * @see  #isBinomHybrid()
1126
     * @see  #isMonomHybrid()
1127
     */
1128
    @Override
1129
    public void setTrinomHybrid(boolean trinomHybrid){
1130
        this.trinomHybrid = trinomHybrid;
1131
    }
1132

    
1133
    // ****************** VIRAL NAME ******************/
1134

    
1135
    /**
1136
     * Returns the accepted acronym (an assigned abbreviation) string for <i>this</i>
1137
     * viral taxon name. For instance PCV stays for Peanut Clump Virus.
1138
     *
1139
     * @return  the string containing the accepted acronym of <i>this</i> viral taxon name
1140
     */
1141
    @Override
1142
    public String getAcronym(){
1143
        return this.acronym;
1144
    }
1145

    
1146
    /**
1147
     * @see  #getAcronym()
1148
     */
1149
    @Override
1150
    public void setAcronym(String acronym){
1151
        this.acronym = StringUtils.isBlank(acronym)? null : acronym;
1152
    }
1153

    
1154
    // ****************** BACTERIAL NAME ******************/
1155

    
1156
    /**
1157
     * Returns the string containing the authorship with the year and details
1158
     * of the reference in which the subgenus included in the scientific name
1159
     * of <i>this</i> bacterial taxon name was published.
1160
     * For instance if the bacterial taxon name is
1161
     * 'Bacillus (subgen. Aerobacillus Donker 1926, 128) polymyxa' the subgenus
1162
     * authorship string is 'Donker 1926, 128'.
1163
     *
1164
     * @return  the string containing the complete subgenus' authorship
1165
     *          included in <i>this</i> bacterial taxon name
1166
     */
1167
    @Override
1168
    public String getSubGenusAuthorship(){
1169
        return this.subGenusAuthorship;
1170
    }
1171

    
1172
    /**
1173
     * @see  #getSubGenusAuthorship()
1174
     */
1175
    @Override
1176
    public void setSubGenusAuthorship(String subGenusAuthorship){
1177
        this.subGenusAuthorship = subGenusAuthorship;
1178
    }
1179

    
1180
    /**
1181
     * Returns the string representing the reason for the approbation of <i>this</i>
1182
     * bacterial taxon name. Bacterial taxon names are valid or approved
1183
     * according to:
1184
     * <ul>
1185
     * <li>the approved list, c.f.r. IJSB 1980 (AL)
1186
     * <li>the validation list, in IJSB after 1980 (VL)
1187
     * </ul>
1188
     * or
1189
     * <ul>
1190
     * <li>are validly published as paper in IJSB after 1980 (VP).
1191
     * </ul>
1192
     * IJSB is the acronym for International Journal of Systematic Bacteriology.
1193
     *
1194
     * @return  the string with the source of the approbation for <i>this</i> bacterial taxon name
1195
     */
1196
    @Override
1197
    public String getNameApprobation(){
1198
        return this.nameApprobation;
1199
    }
1200

    
1201
    /**
1202
     * @see  #getNameApprobation()
1203
     */
1204
    @Override
1205
    public void setNameApprobation(String nameApprobation){
1206
        this.nameApprobation = nameApprobation;
1207
    }
1208

    
1209
    //************ Zoological Name
1210

    
1211
    /**
1212
     * Returns the breed name string for <i>this</i> animal (zoological taxon name).
1213
     *
1214
     * @return  the string containing the breed name for <i>this</i> zoological taxon name
1215
     */
1216
    @Override
1217
    public String getBreed(){
1218
        return this.breed;
1219
    }
1220
    /**
1221
     * @see  #getBreed()
1222
     */
1223
    @Override
1224
    public void setBreed(String breed){
1225
        this.breed = StringUtils.isBlank(breed) ? null : breed;
1226
    }
1227

    
1228
    /**
1229
     * Returns the publication year (as an integer) for <i>this</i> zoological taxon
1230
     * name. If the publicationYear attribute is null and a nomenclatural
1231
     * reference exists the year could be computed from the
1232
     * {@link eu.etaxonomy.cdm.reference.INomenclaturalReference nomenclatural reference}.
1233
     *
1234
     * @return  the integer representing the publication year for <i>this</i> zoological taxon name
1235
     * @see     #getOriginalPublicationYear()
1236
     */
1237
    @Override
1238
    public Integer getPublicationYear() {
1239
        return publicationYear;
1240
    }
1241
    /**
1242
     * @see  #getPublicationYear()
1243
     */
1244
    @Override
1245
    public void setPublicationYear(Integer publicationYear) {
1246
        this.publicationYear = publicationYear;
1247
    }
1248

    
1249
    /**
1250
     * Returns the publication year (as an integer) of the original validly
1251
     * published species epithet for <i>this</i> zoological taxon name. This only
1252
     * applies for zoological taxon names that are no {@link TaxonNameBase#isOriginalCombination() original combinations}.
1253
     * If the originalPublicationYear attribute is null the year could be taken
1254
     * from the publication year of the corresponding original name (basionym)
1255
     * or from the {@link eu.etaxonomy.cdm.reference.INomenclaturalReference nomenclatural reference} of the basionym
1256
     * if it exists.
1257
     *
1258
     * @return  the integer representing the publication year of the original
1259
     *          species epithet corresponding to <i>this</i> zoological taxon name
1260
     * @see     #getPublicationYear()
1261
     */
1262
    @Override
1263
    public Integer getOriginalPublicationYear() {
1264
        return originalPublicationYear;
1265
    }
1266
    /**
1267
     * @see  #getOriginalPublicationYear()
1268
     */
1269
    @Override
1270
    public void setOriginalPublicationYear(Integer originalPublicationYear) {
1271
        this.originalPublicationYear = originalPublicationYear;
1272
    }
1273

    
1274
    // **** Cultivar Name ************
1275

    
1276
    /**
1277
     * Returns the characteristical cultivar name part string assigned to <i>this</i>
1278
     * cultivar taxon name. In the scientific name "Clematis alpina 'Ruby'" for
1279
     * instance this characteristical string is "Ruby". This part of the name is
1280
     * governed by the International Code for the Nomenclature of Cultivated
1281
     * Plants and the string should include neither quotes nor + signs
1282
     * (these elements of the name cache string will be generated by the
1283
     * {@link eu.etaxonomy.cdm.strategy.cache.name.BotanicNameDefaultCacheStrategy default cache strategy}).
1284
     */
1285
    @Override
1286
    public String getCultivarName(){
1287
        return this.cultivarName;
1288
    }
1289

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

    
1298

    
1299
// **************** ADDER / REMOVE *************************/
1300

    
1301
    /**
1302
     * Adds the given {@link HybridRelationship hybrid relationship} to the set
1303
     * of {@link #getHybridRelationships() hybrid relationships} of both non-viral names
1304
     * involved in this hybrid relationship. One of both non-viral names
1305
     * must be <i>this</i> non-viral name otherwise no addition will be carried
1306
     * out. The {@link eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedTo() child
1307
     * non viral taxon name} must be a hybrid, which means that one of its four hybrid flags must be set.
1308
     *
1309
     * @param relationship  the hybrid relationship to be added
1310
     * @see                 #isHybridFormula()
1311
     * @see                 #isMonomHybrid()
1312
     * @see                 #isBinomHybrid()
1313
     * @see                 #isTrinomHybrid()
1314
     * @see                 #getHybridRelationships()
1315
     * @see                 #getParentRelationships()
1316
     * @see                 #getChildRelationships()
1317
     * @see                 #addRelationship(RelationshipBase)
1318
     * @throws              IllegalArgumentException
1319
     */
1320
    protected void addHybridRelationship(HybridRelationship rel) {
1321
        if (rel!=null && rel.getHybridName().equals(this)){
1322
            this.hybridChildRelations.add(rel);
1323
        }else if(rel!=null && rel.getParentName().equals(this)){
1324
            this.hybridParentRelations.add(rel);
1325
        }else{
1326
            throw new IllegalArgumentException("Hybrid relationship is either null or the relationship does not reference this name");
1327
        }
1328
    }
1329

    
1330

    
1331
    /**
1332
     * Removes one {@link HybridRelationship hybrid relationship} from the set of
1333
     * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1334
     * is involved. The hybrid relationship will also be removed from the set
1335
     * belonging to the second botanical taxon name involved.
1336
     *
1337
     * @param  relationship  the hybrid relationship which should be deleted from the corresponding sets
1338
     * @see                  #getHybridRelationships()
1339
     */
1340
    @Override
1341
    public void removeHybridRelationship(HybridRelationship hybridRelation) {
1342
        if (hybridRelation == null) {
1343
            return;
1344
        }
1345

    
1346
        TaxonNameBase<?,?> parent = hybridRelation.getParentName();
1347
        TaxonNameBase<?,?> child = hybridRelation.getHybridName();
1348
        if (this.equals(parent)){
1349
            this.hybridParentRelations.remove(hybridRelation);
1350
            child.hybridChildRelations.remove(hybridRelation);
1351
            hybridRelation.setHybridName(null);
1352
            hybridRelation.setParentName(null);
1353
        }
1354
        if (this.equals(child)){
1355
            parent.hybridParentRelations.remove(hybridRelation);
1356
            this.hybridChildRelations.remove(hybridRelation);
1357
            hybridRelation.setHybridName(null);
1358
            hybridRelation.setParentName(null);
1359
        }
1360
    }
1361

    
1362
//********* METHODS **************************************/
1363

    
1364
    @Override
1365
    public String generateFullTitle(){
1366
        if (cacheStrategy == null){
1367
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1368
            return null;
1369
        }else{
1370
            return cacheStrategy.getFullTitleCache(this);
1371
        }
1372
    }
1373

    
1374

    
1375
    @Override
1376
    public void setFullTitleCache(String fullTitleCache){
1377
        setFullTitleCache(fullTitleCache, PROTECTED);
1378
    }
1379

    
1380
    @Override
1381
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1382
        fullTitleCache = getTruncatedCache(fullTitleCache);
1383
        this.fullTitleCache = fullTitleCache;
1384
        this.setProtectedFullTitleCache(protectCache);
1385
    }
1386

    
1387
    /**
1388
      * Needs to be implemented by those classes that handle autonyms (e.g. botanical names).
1389
      **/
1390
    @Override
1391
    @Transient
1392
    public boolean isAutonym(){
1393
        return false;
1394
    }
1395

    
1396
    @Override
1397
    @Transient
1398
    public List<TaggedText> getTaggedName(){
1399
        return getCacheStrategy().getTaggedTitle(this);
1400
    }
1401

    
1402
    @Override
1403
    @Transient
1404
    public String getFullTitleCache(){
1405
        if (protectedFullTitleCache){
1406
            return this.fullTitleCache;
1407
        }
1408
        updateAuthorshipCache();
1409
        if (fullTitleCache == null ){
1410
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1411
        }
1412
        return fullTitleCache;
1413
    }
1414

    
1415

    
1416
    @Override
1417
    public String getTitleCache(){
1418
        if(!protectedTitleCache) {
1419
            updateAuthorshipCache();
1420
        }
1421
        return super.getTitleCache();
1422
    }
1423

    
1424
    @Override
1425
    public void setTitleCache(String titleCache, boolean protectCache){
1426
        super.setTitleCache(titleCache, protectCache);
1427
    }
1428

    
1429
    /**
1430
     * Returns the concatenated and formated authorteams string including
1431
     * basionym and combination authors of <i>this</i> non viral taxon name.
1432
     * If the protectedAuthorshipCache flag is set this method returns the
1433
     * string stored in the the authorshipCache attribute, otherwise it
1434
     * generates the complete authorship string, returns it and stores it in
1435
     * the authorshipCache attribute.
1436
     *
1437
     * @return  the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
1438
     * @see     #generateAuthorship()
1439
     */
1440
    @Override
1441
    @Transient
1442
    public String getAuthorshipCache() {
1443
        if (protectedAuthorshipCache){
1444
            return this.authorshipCache;
1445
        }
1446
        if (this.authorshipCache == null ){
1447
            this.authorshipCache = generateAuthorship();
1448
        }else{
1449
            //TODO get isDirty of authors, make better if possible
1450
            this.setAuthorshipCache(generateAuthorship(), protectedAuthorshipCache); //throw change event to inform higher caches
1451

    
1452
        }
1453
        return authorshipCache;
1454
    }
1455

    
1456

    
1457

    
1458
    /**
1459
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
1460
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
1461
     * @return
1462
     */
1463
    private void updateAuthorshipCache() {
1464
        //updates the authorship cache if necessary and via the listener updates all higher caches
1465
        if (protectedAuthorshipCache == false){
1466
            String oldCache = this.authorshipCache;
1467
            String newCache = this.getAuthorshipCache();
1468
            if ( (oldCache == null && newCache != null)  ||  CdmUtils.nullSafeEqual(oldCache,newCache)){
1469
                this.setAuthorshipCache(this.getAuthorshipCache(), false);
1470
            }
1471
        }
1472
    }
1473

    
1474

    
1475
    /**
1476
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1477
     * flag to <code>true</code>.
1478
     *
1479
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1480
     * @see    #getAuthorshipCache()
1481
     */
1482
    @Override
1483
    public void setAuthorshipCache(String authorshipCache) {
1484
        setAuthorshipCache(authorshipCache, true);
1485
    }
1486

    
1487

    
1488
    /**
1489
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1490
     *
1491
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1492
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1493
     * the flag is set to <code>false</code>.
1494
     * @see    #getAuthorshipCache()
1495
     */
1496
    @Override
1497
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1498
        this.authorshipCache = authorshipCache;
1499
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1500
    }
1501

    
1502
    /**
1503
     * Generates and returns a concatenated and formated authorteams string
1504
     * including basionym and combination authors of <i>this</i> non viral taxon name
1505
     * according to the strategy defined in
1506
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonNameBase) INonViralNameCacheStrategy}.
1507
     *
1508
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1509
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonNameBase)
1510
     */
1511
    @Override
1512
    public String generateAuthorship(){
1513
        if (cacheStrategy == null){
1514
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1515
            return null;
1516
        }else{
1517
            return ((INonViralNameCacheStrategy)cacheStrategy).getAuthorshipCache(this);
1518
        }
1519
    }
1520

    
1521

    
1522

    
1523
    /**
1524
     * Tests if the given name has any authors.
1525
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1526
     */
1527
    @Override
1528
    public boolean hasAuthors() {
1529
        return (this.getCombinationAuthorship() != null ||
1530
                this.getExCombinationAuthorship() != null ||
1531
                this.getBasionymAuthorship() != null ||
1532
                this.getExBasionymAuthorship() != null);
1533
    }
1534

    
1535
    /**
1536
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1537
     * @return
1538
     */
1539
    @Override
1540
    public String computeCombinationAuthorNomenclaturalTitle() {
1541
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1542
    }
1543

    
1544
    /**
1545
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1546
     * @return
1547
     */
1548
    @Override
1549
    public String computeBasionymAuthorNomenclaturalTitle() {
1550
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1551
    }
1552

    
1553

    
1554
    /**
1555
     * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1556
     * @return
1557
     */
1558
    @Override
1559
    public String computeExCombinationAuthorNomenclaturalTitle() {
1560
        return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1561
    }
1562

    
1563
    /**
1564
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1565
     * @return
1566
     */
1567
    @Override
1568
    public String computeExBasionymAuthorNomenclaturalTitle() {
1569
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1570
    }
1571

    
1572
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1573
        if (author == null){
1574
            return null;
1575
        }else{
1576
            return author.getNomenclaturalTitle();
1577
        }
1578
    }
1579

    
1580
    /**
1581
     * Returns the set of all {@link NameRelationship name relationships}
1582
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1583
     * in some name relationships or target in some others.
1584
     *
1585
     * @see    #getRelationsToThisName()
1586
     * @see    #getRelationsFromThisName()
1587
     * @see    #addNameRelationship(NameRelationship)
1588
     * @see    #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1589
     * @see    #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1590
     */
1591
    @Override
1592
    @Transient
1593
    public Set<NameRelationship> getNameRelations() {
1594
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1595
        rels.addAll(getRelationsFromThisName());
1596
        rels.addAll(getRelationsToThisName());
1597
        return rels;
1598
    }
1599

    
1600
    /**
1601
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1602
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1603
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1604
     *
1605
     * @param toName		  the taxon name of the target for this new name relationship
1606
     * @param type			  the type of this new name relationship
1607
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1608
     * @see    				  #getRelationsToThisName()
1609
     * @see    				  #getNameRelations()
1610
     * @see    				  #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1611
     * @see    				  #addNameRelationship(NameRelationship)
1612
     */
1613
    @Override
1614
    public void addRelationshipToName(TaxonNameBase toName, NameRelationshipType type, String ruleConsidered){
1615
        addRelationshipToName(toName, type, null, null, ruleConsidered);
1616
        //		NameRelationship rel = new NameRelationship(toName, this, type, ruleConsidered);
1617
    }
1618

    
1619
    /**
1620
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1621
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1622
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1623
     *
1624
     * @param toName		  the taxon name of the target for this new name relationship
1625
     * @param type			  the type of this new name relationship
1626
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1627
     * @return
1628
     * @see    				  #getRelationsToThisName()
1629
     * @see    				  #getNameRelations()
1630
     * @see    				  #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1631
     * @see    				  #addNameRelationship(NameRelationship)
1632
     */
1633
    @Override
1634
    public NameRelationship addRelationshipToName(TaxonNameBase toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1635
        if (toName == null){
1636
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1637
        }
1638
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered);
1639
        return rel;
1640
    }
1641

    
1642
    /**
1643
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1644
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1645
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1646
     *
1647
     * @param fromName		  the taxon name of the source for this new name relationship
1648
     * @param type			  the type of this new name relationship
1649
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1650
     * @param citation		  the reference in which this relation was described
1651
     * @param microCitation	  the reference detail for this relation (e.g. page)
1652
     * @see    				  #getRelationsFromThisName()
1653
     * @see    				  #getNameRelations()
1654
     * @see    				  #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1655
     * @see    				  #addNameRelationship(NameRelationship)
1656
     */
1657
    @Override
1658
    public NameRelationship addRelationshipFromName(TaxonNameBase fromName, NameRelationshipType type, String ruleConsidered){
1659
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1660
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
1661
    }
1662
    /**
1663
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1664
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1665
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1666
     *
1667
     * @param fromName		  the taxon name of the source 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
     * @param citation		  the reference in which this relation was described
1671
     * @param microCitation	  the reference detail for this relation (e.g. page)
1672
     * @see    				  #getRelationsFromThisName()
1673
     * @see    				  #getNameRelations()
1674
     * @see    				  #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1675
     * @see    				  #addNameRelationship(NameRelationship)
1676
     */
1677
    @Override
1678
    public NameRelationship addRelationshipFromName(TaxonNameBase fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1679
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1680
    }
1681

    
1682
    /**
1683
     * Adds an existing {@link NameRelationship name relationship} either to the set of
1684
     * {@link #getRelationsToThisName() relations to <i>this</i> taxon name} or to the set of
1685
     * {@link #getRelationsFromThisName() relations from <i>this</i> taxon name}. If neither the
1686
     * source nor the target of the name relationship match with <i>this</i> taxon name
1687
     * no addition will be carried out.
1688
     *
1689
     * @param rel  the name relationship to be added to one of <i>this</i> taxon name's name relationships sets
1690
     * @see    	   #getNameRelations()
1691
     * @see    	   #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1692
     * @see    	   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1693
     */
1694
    protected void addNameRelationship(NameRelationship rel) {
1695
        if (rel != null ){
1696
            if (rel.getToName().equals(this)){
1697
                this.relationsToThisName.add(rel);
1698
            }else if(rel.getFromName().equals(this)){
1699
                this.relationsFromThisName.add(rel);
1700
            }
1701
            NameRelationshipType type = rel.getType();
1702
            if (type != null && ( type.isBasionymRelation() || type.isReplacedSynonymRelation() ) ){
1703
                rel.getFromName().mergeHomotypicGroups(rel.getToName());
1704
            }
1705
        }else{
1706
            throw new RuntimeException("NameRelationship is either null or the relationship does not reference this name");
1707
        }
1708
    }
1709
    /**
1710
     * Removes one {@link NameRelationship name relationship} from one of both sets of
1711
     * {@link #getNameRelations() name relationships} in which <i>this</i> taxon name is involved.
1712
     * The name relationship will also be removed from one of both sets belonging
1713
     * to the second taxon name involved. Furthermore the fromName and toName
1714
     * attributes of the name relationship object will be nullified.
1715
     *
1716
     * @param  nameRelation  the name relationship which should be deleted from one of both sets
1717
     * @see    				 #getNameRelations()
1718
     */
1719
    @Override
1720
    public void removeNameRelationship(NameRelationship nameRelation) {
1721

    
1722
        TaxonNameBase fromName = nameRelation.getFromName();
1723
        TaxonNameBase toName = nameRelation.getToName();
1724

    
1725
        if (nameRelation != null) {
1726
            nameRelation.setToName(null);
1727
            nameRelation.setFromName(null);
1728
        }
1729

    
1730
        if (fromName != null) {
1731
            fromName.removeNameRelationship(nameRelation);
1732
        }
1733

    
1734
        if (toName != null) {
1735
            toName.removeNameRelationship(nameRelation);
1736
        }
1737

    
1738
        this.relationsToThisName.remove(nameRelation);
1739
        this.relationsFromThisName.remove(nameRelation);
1740
    }
1741

    
1742
    @Override
1743
    public void removeRelationToTaxonName(TaxonNameBase toTaxonName) {
1744
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1745
//		nameRelationships.addAll(this.getNameRelations());
1746
        nameRelationships.addAll(this.getRelationsFromThisName());
1747
        nameRelationships.addAll(this.getRelationsToThisName());
1748
        for(NameRelationship nameRelationship : nameRelationships) {
1749
            // remove name relationship from this side
1750
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1751
                this.removeNameRelationship(nameRelationship);
1752
            }
1753
        }
1754
    }
1755

    
1756

    
1757
    /**
1758
     * If relation is of type NameRelationship, addNameRelationship is called;
1759
     * if relation is of type HybridRelationship addHybridRelationship is called,
1760
     * otherwise an IllegalArgumentException is thrown.
1761
     *
1762
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1763
     * @see    	   		#addNameRelationship(NameRelationship)
1764
     * @see    	   		#getNameRelations()
1765
     * @see    	   		NameRelationship
1766
     * @see    	   		RelationshipBase
1767
     * @see             #addHybridRelationship(HybridRelationship)
1768

    
1769
     * @deprecated to be used by RelationshipBase only
1770
     */
1771
    @Deprecated
1772
    @Override
1773
    public void addRelationship(RelationshipBase relation) {
1774
        if (relation instanceof NameRelationship){
1775
            addNameRelationship((NameRelationship)relation);
1776

    
1777
        }else if (relation instanceof HybridRelationship){
1778
            addHybridRelationship((HybridRelationship)relation);
1779
        }else{
1780
            logger.warn("Relationship not of type NameRelationship!");
1781
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1782
        }
1783
    }
1784

    
1785
    /**
1786
     * Returns the set of all {@link NameRelationship name relationships}
1787
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1788
     *
1789
     * @see    #getNameRelations()
1790
     * @see    #getRelationsToThisName()
1791
     * @see    #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1792
     */
1793
    @Override
1794
    public Set<NameRelationship> getRelationsFromThisName() {
1795
        if(relationsFromThisName == null) {
1796
            this.relationsFromThisName = new HashSet<>();
1797
        }
1798
        return relationsFromThisName;
1799
    }
1800

    
1801
    /**
1802
     * Returns the set of all {@link NameRelationship name relationships}
1803
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1804
     *
1805
     * @see    #getNameRelations()
1806
     * @see    #getRelationsFromThisName()
1807
     * @see    #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1808
     */
1809
    @Override
1810
    public Set<NameRelationship> getRelationsToThisName() {
1811
        if(relationsToThisName == null) {
1812
            this.relationsToThisName = new HashSet<>();
1813
        }
1814
        return relationsToThisName;
1815
    }
1816

    
1817
    /**
1818
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1819
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1820
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1821
     * and the nomenclatural code rule considered.
1822
     *
1823
     * @see     NomenclaturalStatus
1824
     * @see     NomenclaturalStatusType
1825
     */
1826
    @Override
1827
    public Set<NomenclaturalStatus> getStatus() {
1828
        if(status == null) {
1829
            this.status = new HashSet<>();
1830
        }
1831
        return status;
1832
    }
1833

    
1834
    /**
1835
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1836
     * to <i>this</i> taxon name's set of nomenclatural status.
1837
     *
1838
     * @param  nomStatus  the nomenclatural status to be added
1839
     * @see 			  #getStatus()
1840
     */
1841
    @Override
1842
    public void addStatus(NomenclaturalStatus nomStatus) {
1843
        this.status.add(nomStatus);
1844
    }
1845
    @Override
1846
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1847
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1848
        this.status.add(newStatus);
1849
        return newStatus;
1850
    }
1851

    
1852
    /**
1853
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1854
     * Type and ruleConsidered attributes of the nomenclatural status object
1855
     * will be nullified.
1856
     *
1857
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1858
     * @see     		  #getStatus()
1859
     */
1860
    @Override
1861
    public void removeStatus(NomenclaturalStatus nomStatus) {
1862
        //TODO to be implemented?
1863
        logger.warn("not yet fully implemented?");
1864
        this.status.remove(nomStatus);
1865
    }
1866

    
1867

    
1868
    /**
1869
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1870
     * strings or year according to the strategy defined in
1871
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1872
     * The result might be stored in {@link #getNameCache() nameCache} if the
1873
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1874
     *
1875
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1876
     * @see     #getNameCache()
1877
     */
1878
    protected String generateNameCache(){
1879
        if (cacheStrategy == null){
1880
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1881
            return null;
1882
        }else{
1883
            return cacheStrategy.getNameCache(this);
1884
        }
1885
    }
1886

    
1887
    /**
1888
     * Returns or generates the nameCache (scientific name
1889
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1890
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1891
     * the string will be generated according to a defined strategy,
1892
     * otherwise the value of the actual nameCache string will be returned.
1893
     *
1894
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1895
     * @see     #generateNameCache()
1896
     */
1897
    @Override
1898
    @Transient
1899
    public String getNameCache() {
1900
        if (protectedNameCache){
1901
            return this.nameCache;
1902
        }
1903
        // is title dirty, i.e. equal NULL?
1904
        if (nameCache == null){
1905
            this.nameCache = generateNameCache();
1906
        }
1907
        return nameCache;
1908
    }
1909

    
1910
    /**
1911
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1912
     * Sets the protectedNameCache flag to <code>true</code>.
1913
     *
1914
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1915
     * @see    #getNameCache()
1916
     */
1917
    @Override
1918
    public void setNameCache(String nameCache){
1919
        setNameCache(nameCache, true);
1920
    }
1921

    
1922
    /**
1923
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1924
     * Sets the protectedNameCache flag to <code>true</code>.
1925
     *
1926
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1927
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
1928
     * <code>false</code>
1929
     * @see    #getNameCache()
1930
     */
1931
    @Override
1932
    public void setNameCache(String nameCache, boolean protectedNameCache){
1933
        this.nameCache = nameCache;
1934
        this.setProtectedNameCache(protectedNameCache);
1935
    }
1936

    
1937

    
1938
    /**
1939
     * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
1940
     * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1941
     * of any other taxon name. Returns "true", if a basionym or a replaced
1942
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1943
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1944
     * homotypical group).
1945
     */
1946
    @Override
1947
    @Transient
1948
    public boolean isOriginalCombination(){
1949
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1950
        for (NameRelationship relation : relationsFromThisName) {
1951
            if (relation.getType().isBasionymRelation() ||
1952
                    relation.getType().isReplacedSynonymRelation()) {
1953
                return true;
1954
            }
1955
        }
1956
        return false;
1957
    }
1958

    
1959
    /**
1960
     * Indicates <i>this</i> taxon name is a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1961
     * of any other taxon name. Returns "true", if a replaced
1962
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1963
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1964
     * homotypical group).
1965
     */
1966
    @Override
1967
    @Transient
1968
    public boolean isReplacedSynonym(){
1969
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1970
        for (NameRelationship relation : relationsFromThisName) {
1971
            if (relation.getType().isReplacedSynonymRelation()) {
1972
                return true;
1973
            }
1974
        }
1975
        return false;
1976
    }
1977

    
1978
    /**
1979
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
1980
     * The basionym of a taxon name is its epithet-bringing synonym.
1981
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
1982
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
1983
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
1984
     *
1985
     * If more than one basionym exists one is choosen at radom.
1986
     *
1987
     * If no basionym exists null is returned.
1988
     */
1989
    @Override
1990
    @Transient
1991
    public TaxonNameBase getBasionym(){
1992
        Set<TaxonNameBase> basionyms = getBasionyms();
1993
        if (basionyms.size() == 0){
1994
            return null;
1995
        }else{
1996
            return basionyms.iterator().next();
1997
        }
1998
    }
1999

    
2000
    /**
2001
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2002
     * The basionym of a taxon name is its epithet-bringing synonym.
2003
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2004
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2005
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2006
     */
2007
    @Override
2008
    @Transient
2009
    public Set<TaxonNameBase> getBasionyms(){
2010
        Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
2011
        Set<NameRelationship> rels = this.getRelationsToThisName();
2012
        for (NameRelationship rel : rels){
2013
            if (rel.getType()!= null && rel.getType().isBasionymRelation()){
2014
                TaxonNameBase<?,?> basionym = rel.getFromName();
2015
                result.add(basionym);
2016
            }
2017
        }
2018
        return result;
2019
    }
2020

    
2021
    /**
2022
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2023
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2024
     * and to the basionym. The basionym cannot have itself a basionym.
2025
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2026
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2027
     *
2028
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2029
     * @see  				#getBasionym()
2030
     * @see  				#addBasionym(TaxonNameBase, String)
2031
     */
2032
    @Override
2033
    public void addBasionym(TaxonNameBase basionym){
2034
        addBasionym(basionym, null, null, null);
2035
    }
2036
    /**
2037
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2038
     * and keeps the nomenclatural rule considered for it. The basionym
2039
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2040
     * The basionym cannot have itself a basionym.
2041
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2042
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2043
     *
2044
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2045
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2046
     * @return
2047
     * @see  					#getBasionym()
2048
     * @see  					#addBasionym(TaxonNameBase)
2049
     */
2050
    @Override
2051
    public NameRelationship addBasionym(TaxonNameBase basionym, Reference citation, String microcitation, String ruleConsidered){
2052
        if (basionym != null){
2053
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
2054
        }else{
2055
            return null;
2056
        }
2057
    }
2058

    
2059
    /**
2060
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2061
     *
2062
     */
2063
    @Override
2064
    @Transient
2065
    public Set<TaxonNameBase> getReplacedSynonyms(){
2066
        Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
2067
        Set<NameRelationship> rels = this.getRelationsToThisName();
2068
        for (NameRelationship rel : rels){
2069
            if (rel.getType().isReplacedSynonymRelation()){
2070
                TaxonNameBase replacedSynonym = rel.getFromName();
2071
                result.add(replacedSynonym);
2072
            }
2073
        }
2074
        return result;
2075
    }
2076

    
2077
    /**
2078
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2079
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2080
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2081
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2082
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2083
     *
2084
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2085
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2086
     * @see  					#getBasionym()
2087
     * @see  					#addBasionym(TaxonNameBase)
2088
     */
2089
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2090
    @Override
2091
    public void addReplacedSynonym(TaxonNameBase replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
2092
        if (replacedSynonym != null){
2093
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
2094
        }
2095
    }
2096

    
2097
    /**
2098
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2099
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2100
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2101
     * previously used as basionym.
2102
     *
2103
     * @see   #getBasionym()
2104
     * @see   #addBasionym(TaxonNameBase)
2105
     */
2106
    @Override
2107
    public void removeBasionyms(){
2108
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
2109
        for (NameRelationship nameRelation : this.getRelationsToThisName()){
2110
            if (nameRelation.getType().isBasionymRelation()){
2111
                removeRelations.add(nameRelation);
2112
            }
2113
        }
2114
        // Removing relations from a set through which we are iterating causes a
2115
        // ConcurrentModificationException. Therefore, we delete the targeted
2116
        // relations in a second step.
2117
        for (NameRelationship relation : removeRelations){
2118
            this.removeNameRelationship(relation);
2119
        }
2120
    }
2121

    
2122
    /**
2123
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2124
     *
2125
     * @see 	Rank
2126
     */
2127
    @Override
2128
    public Rank getRank(){
2129
        return this.rank;
2130
    }
2131

    
2132
    /**
2133
     * @see  #getRank()
2134
     */
2135
    @Override
2136
    public void setRank(Rank rank){
2137
        this.rank = rank;
2138
    }
2139

    
2140
    /**
2141
     * Returns the {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} of <i>this</i> taxon name.
2142
     * The nomenclatural reference is here meant to be the one publication
2143
     * <i>this</i> taxon name was originally published in while fulfilling the formal
2144
     * requirements as specified by the corresponding {@link NomenclaturalCode nomenclatural code}.
2145
     *
2146
     * @see 	eu.etaxonomy.cdm.model.reference.INomenclaturalReference
2147
     * @see 	eu.etaxonomy.cdm.model.reference.Reference
2148
     */
2149
    @Override
2150
    public INomenclaturalReference getNomenclaturalReference(){
2151
        return this.nomenclaturalReference;
2152
    }
2153
    /**
2154
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2155
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2156
     * as it is obviously used for nomenclatural purposes.
2157
     *
2158
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2159
     * @see  #getNomenclaturalReference()
2160
     */
2161
    @Override
2162
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2163
        if(nomenclaturalReference != null){
2164
            if(!INomenclaturalReference.class.isAssignableFrom(nomenclaturalReference.getClass())){
2165
                throw new IllegalArgumentException("Parameter nomenclaturalReference is not assignable from INomenclaturalReference");
2166
            }
2167
            this.nomenclaturalReference = (Reference)nomenclaturalReference;
2168
        } else {
2169
            this.nomenclaturalReference = null;
2170
        }
2171
    }
2172

    
2173
    /**
2174
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2175
     * The appended phrase is a non-atomised addition to a name. It is
2176
     * not ruled by a nomenclatural code.
2177
     */
2178
    @Override
2179
    public String getAppendedPhrase(){
2180
        return this.appendedPhrase;
2181
    }
2182

    
2183
    /**
2184
     * @see  #getAppendedPhrase()
2185
     */
2186
    @Override
2187
    public void setAppendedPhrase(String appendedPhrase){
2188
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2189
    }
2190

    
2191
    /**
2192
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2193
     * to <i>this</i> taxon name. The details describe the exact localisation within
2194
     * the publication used as nomenclature reference. These are mostly
2195
     * (implicitly) pages but can also be figures or tables or any other
2196
     * element of a publication. A nomenclatural micro reference (details)
2197
     * requires the existence of a nomenclatural reference.
2198
     */
2199
    //Details of the nomenclatural reference (protologue).
2200
    @Override
2201
    public String getNomenclaturalMicroReference(){
2202
        return this.nomenclaturalMicroReference;
2203
    }
2204
    /**
2205
     * @see  #getNomenclaturalMicroReference()
2206
     */
2207
    @Override
2208
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2209
        this.nomenclaturalMicroReference = StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
2210
    }
2211

    
2212
    @Override
2213
    public int getParsingProblem(){
2214
        return this.parsingProblem;
2215
    }
2216

    
2217
    @Override
2218
    public void setParsingProblem(int parsingProblem){
2219
        this.parsingProblem = parsingProblem;
2220
    }
2221

    
2222
    @Override
2223
    public void addParsingProblem(ParserProblem problem){
2224
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2225
    }
2226

    
2227
    @Override
2228
    public void removeParsingProblem(ParserProblem problem) {
2229
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2230
    }
2231

    
2232
    /**
2233
     * @param warnings
2234
     */
2235
    @Override
2236
    public void addParsingProblems(int problems){
2237
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2238
    }
2239

    
2240
    @Override
2241
    public boolean hasProblem(){
2242
        return parsingProblem != 0;
2243
    }
2244

    
2245
    @Override
2246
    public boolean hasProblem(ParserProblem problem) {
2247
        return getParsingProblems().contains(problem);
2248
    }
2249

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

    
2255
    @Override
2256
    public void setProblemStarts(int start) {
2257
        this.problemStarts = start;
2258
    }
2259

    
2260
    @Override
2261
    public int getProblemEnds(){
2262
        return this.problemEnds;
2263
    }
2264

    
2265
    @Override
2266
    public void setProblemEnds(int end) {
2267
        this.problemEnds = end;
2268
    }
2269

    
2270
//*********************** TYPE DESIGNATION *********************************************//
2271

    
2272
    /**
2273
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2274
     * to <i>this</i> taxon name.
2275
     * @see     NameTypeDesignation
2276
     * @see     SpecimenTypeDesignation
2277
     */
2278
    @Override
2279
    public Set<TypeDesignationBase> getTypeDesignations() {
2280
        if(typeDesignations == null) {
2281
            this.typeDesignations = new HashSet<TypeDesignationBase>();
2282
        }
2283
        return typeDesignations;
2284
    }
2285

    
2286
    /**
2287
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2288
     * <i>this</i> taxon name. The type designation itself will be nullified.
2289
     *
2290
     * @param  typeDesignation  the type designation which should be deleted
2291
     */
2292
    @Override
2293
    @SuppressWarnings("deprecation")
2294
    public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2295
        this.typeDesignations.remove(typeDesignation);
2296
        typeDesignation.removeTypifiedName(this);
2297
    }
2298

    
2299
    /**
2300
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2301
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2302
     * "species" or below. The specimen type designations include all the
2303
     * specimens on which the typification of this name is based (which are
2304
     * exclusively used to typify taxon names belonging to the same
2305
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2306
     * belongs) and eventually the status of these designations.
2307
     *
2308
     * @see     SpecimenTypeDesignation
2309
     * @see     NameTypeDesignation
2310
     * @see     HomotypicalGroup
2311
     */
2312
    @Override
2313
    @Transient
2314
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2315
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2316
    }
2317

    
2318
//*********************** NAME TYPE DESIGNATION *********************************************//
2319

    
2320
    /**
2321
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2322
     * to <i>this</i> taxon name the rank of which must be above "species".
2323
     * The name type designations include all the taxon names used to typify
2324
     * <i>this</i> taxon name and eventually the rejected or conserved status
2325
     * of these designations.
2326
     *
2327
     * @see     NameTypeDesignation
2328
     * @see     SpecimenTypeDesignation
2329
     */
2330
    @Override
2331
    @Transient
2332
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2333
        Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2334
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2335
            if (typeDesignation instanceof NameTypeDesignation){
2336
                result.add((NameTypeDesignation)typeDesignation);
2337
            }
2338
        }
2339
        return result;
2340
    }
2341

    
2342
    /**
2343
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2344
     * to <i>this</i> taxon name's set of type designations.
2345
     *
2346
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2347
     * @param  citation					the reference for this new designation
2348
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2349
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2350
     * @param  isRejectedType			the boolean status for a rejected name type designation
2351
     * @param  isConservedType			the boolean status for a conserved name type designation
2352
     * @param  isLectoType				the boolean status for a lectotype name type designation
2353
     * @param  isNotDesignated			the boolean status for a name type designation without name type
2354
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2355
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2356
     * @return
2357
     * @see 			  				#getNameTypeDesignations()
2358
     * @see 			  				NameTypeDesignation
2359
     * @see 			  				TypeDesignationBase#isNotDesignated()
2360
     */
2361
    @Override
2362
    public NameTypeDesignation addNameTypeDesignation(TaxonNameBase typeSpecies,
2363
                Reference citation,
2364
                String citationMicroReference,
2365
                String originalNameString,
2366
                NameTypeDesignationStatus status,
2367
                boolean isRejectedType,
2368
                boolean isConservedType,
2369
                /*boolean isLectoType, */
2370
                boolean isNotDesignated,
2371
                boolean addToAllHomotypicNames) {
2372
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalNameString, status, isRejectedType, isConservedType, isNotDesignated);
2373
        //nameTypeDesignation.setLectoType(isLectoType);
2374
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2375
        return nameTypeDesignation;
2376
    }
2377

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

    
2406
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2407

    
2408
    /**
2409
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2410
     * that typify <i>this</i> taxon name.
2411
     */
2412
    @Override
2413
    @Transient
2414
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2415
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2416
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2417
            if (typeDesignation instanceof SpecimenTypeDesignation){
2418
                result.add((SpecimenTypeDesignation)typeDesignation);
2419
            }
2420
        }
2421
        return result;
2422
    }
2423

    
2424

    
2425
    /**
2426
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2427
     * to <i>this</i> taxon name's set of type designations.
2428
     *
2429
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2430
     * @param  status					the specimen type designation status
2431
     * @param  citation					the reference for this new specimen type designation
2432
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2433
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2434
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2435
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2436
     * 									added to all taxon names of the homotypical group the typified
2437
     * 									taxon name belongs to
2438
     * @return
2439
     * @see 			  				#getSpecimenTypeDesignations()
2440
     * @see 			  				SpecimenTypeDesignationStatus
2441
     * @see 			  				SpecimenTypeDesignation
2442
     * @see 			  				TypeDesignationBase#isNotDesignated()
2443
     */
2444
    @Override
2445
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2446
                SpecimenTypeDesignationStatus status,
2447
                Reference citation,
2448
                String citationMicroReference,
2449
                String originalNameString,
2450
                boolean isNotDesignated,
2451
                boolean addToAllHomotypicNames) {
2452
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2453
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2454
        return specimenTypeDesignation;
2455
    }
2456

    
2457
    //used by merge strategy
2458
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2459
        return addTypeDesignation(typeDesignation, true);
2460
    }
2461

    
2462
    /**
2463
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2464
     *
2465
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2466
     * @param addToAllNames				the boolean indicating whether the type designation should be
2467
     * 									added to all taxon names of the homotypical group the typified
2468
     * 									taxon name belongs to
2469
     * @return							true if the operation was succesful
2470
     *
2471
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2472
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2473
     *
2474
     */
2475
    @Override
2476
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2477
        //currently typeDesignations are not persisted with the homotypical group
2478
        //so explicit adding to the homotypical group is not necessary.
2479
        if (typeDesignation != null){
2480
            checkHomotypicalGroup(typeDesignation);
2481
            this.typeDesignations.add(typeDesignation);
2482
            typeDesignation.addTypifiedName(this);
2483

    
2484
            if (addToAllNames){
2485
                for (TaxonNameBase taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2486
                    if (taxonName != this){
2487
                        taxonName.addTypeDesignation(typeDesignation, false);
2488
                    }
2489
                }
2490
            }
2491
        }
2492
        return true;
2493
    }
2494

    
2495
    /**
2496
     * Throws an Exception this type designation already has typified names from another homotypical group.
2497
     * @param typeDesignation
2498
     */
2499
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2500
        if(typeDesignation.getTypifiedNames().size() > 0){
2501
            Set<HomotypicalGroup> groups = new HashSet<HomotypicalGroup>();
2502
            Set<TaxonNameBase> names = typeDesignation.getTypifiedNames();
2503
            for (TaxonNameBase taxonName: names){
2504
                groups.add(taxonName.getHomotypicalGroup());
2505
            }
2506
            if (groups.size() > 1){
2507
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2508
            }
2509
        }
2510
    }
2511

    
2512

    
2513

    
2514
//*********************** HOMOTYPICAL GROUP *********************************************//
2515

    
2516

    
2517
    /**
2518
     * Returns the {@link HomotypicalGroup homotypical group} to which
2519
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2520
     * that share the same types.
2521
     *
2522
     * @see 	HomotypicalGroup
2523
     */
2524

    
2525
    @Override
2526
    public HomotypicalGroup getHomotypicalGroup() {
2527
        if (homotypicalGroup == null){
2528
            homotypicalGroup = new HomotypicalGroup();
2529
            homotypicalGroup.typifiedNames.add(this);
2530
        }
2531
    	return homotypicalGroup;
2532
    }
2533

    
2534
    /**
2535
     * @see #getHomotypicalGroup()
2536
     */
2537
    @Override
2538
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2539
        if (homotypicalGroup == null){
2540
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2541
        }
2542
        /*if (this.homotypicalGroup != null){
2543
        	this.homotypicalGroup.removeTypifiedName(this, false);
2544
        }*/
2545
        this.homotypicalGroup = homotypicalGroup;
2546
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2547
        	 this.homotypicalGroup.addTypifiedName(this);
2548
        }
2549
    }
2550

    
2551

    
2552

    
2553
// *************************************************************************//
2554

    
2555
    /**
2556
     * Returns the complete string containing the
2557
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2558
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2559
     *
2560
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2561
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2562
     * @see		#getNomenclaturalReference()
2563
     * @see		#getNomenclaturalMicroReference()
2564
     */
2565
    @Override
2566
    @Transient
2567
    public String getCitationString(){
2568
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2569
    }
2570

    
2571
    /**
2572
     * Returns the parsing problems
2573
     * @return
2574
     */
2575
    @Override
2576
    public List<ParserProblem> getParsingProblems(){
2577
        return ParserProblem.warningList(this.parsingProblem);
2578
    }
2579

    
2580
    /**
2581
     * Returns the string containing the publication date (generally only year)
2582
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2583
     * no nomenclatural reference.
2584
     *
2585
     * @return  the string containing the publication date of <i>this</i> taxon name
2586
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2587
     */
2588
    @Override
2589
    @Transient
2590
    @ValidTaxonomicYear(groups=Level3.class)
2591
    public String getReferenceYear(){
2592
        if (this.getNomenclaturalReference() != null ){
2593
            return this.getNomenclaturalReference().getYear();
2594
        }else{
2595
            return null;
2596
        }
2597
    }
2598

    
2599
    /**
2600
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2601
     * In this context a taxon base means the use of a taxon name by a reference
2602
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2603
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2604
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2605
     * within a taxonomic treatment (identified by one reference).
2606
     *
2607
     * @see	#getTaxa()
2608
     * @see	#getSynonyms()
2609
     */
2610
    @Override
2611
    public Set<TaxonBase> getTaxonBases() {
2612
        if(taxonBases == null) {
2613
            this.taxonBases = new HashSet<TaxonBase>();
2614
        }
2615
        return this.taxonBases;
2616
    }
2617

    
2618
    /**
2619
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2620
     * to the set of taxon bases using <i>this</i> taxon name.
2621
     *
2622
     * @param  taxonBase  the taxon base to be added
2623
     * @see 			  #getTaxonBases()
2624
     * @see 			  #removeTaxonBase(TaxonBase)
2625
     */
2626
    //TODO protected
2627
    @Override
2628
    public void addTaxonBase(TaxonBase taxonBase){
2629
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonNameBase.class});
2630
        ReflectionUtils.makeAccessible(method);
2631
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2632
        taxonBases.add(taxonBase);
2633
    }
2634
    /**
2635
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2636
     *
2637
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2638
     * @see    				#getTaxonBases()
2639
     * @see    				#addTaxonBase(TaxonBase)
2640
     */
2641
    @Override
2642
    public void removeTaxonBase(TaxonBase taxonBase){
2643
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonNameBase.class});
2644
        ReflectionUtils.makeAccessible(method);
2645
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2646

    
2647

    
2648
    }
2649

    
2650
    /**
2651
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2652
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2653
     * the set returned by getTaxonBases().
2654
     *
2655
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2656
     * @see	#getTaxonBases()
2657
     * @see	#getSynonyms()
2658
     */
2659
    @Override
2660
    @Transient
2661
    public Set<Taxon> getTaxa(){
2662
        Set<Taxon> result = new HashSet<Taxon>();
2663
        for (TaxonBase taxonBase : this.taxonBases){
2664
            if (taxonBase instanceof Taxon){
2665
                result.add((Taxon)taxonBase);
2666
            }
2667
        }
2668
        return result;
2669
    }
2670

    
2671
    /**
2672
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2673
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2674
     * the set returned by getTaxonBases().
2675
     *
2676
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2677
     * @see	#getTaxonBases()
2678
     * @see	#getTaxa()
2679
     */
2680
    @Override
2681
    @Transient
2682
    public Set<Synonym> getSynonyms() {
2683
        Set<Synonym> result = new HashSet<Synonym>();
2684
        for (TaxonBase taxonBase : this.taxonBases){
2685
            if (taxonBase instanceof Synonym){
2686
                result.add((Synonym)taxonBase);
2687
            }
2688
        }
2689
        return result;
2690
    }
2691

    
2692
// ************* RELATIONSHIPS *****************************/
2693

    
2694

    
2695
    /**
2696
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2697
     * by title cache of the related names.
2698
     * @see #getHybridParentRelations()
2699
     */
2700
    @Override
2701
    @Transient
2702
    public List<HybridRelationship> getOrderedChildRelationships(){
2703
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2704
        result.addAll(this.hybridChildRelations);
2705
        Collections.sort(result);
2706
        Collections.reverse(result);
2707
        return result;
2708

    
2709
    }
2710

    
2711

    
2712
    /**
2713
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2714
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2715
     * "is first/second parent" or "is male/female parent". By invoking this
2716
     * method <i>this</i> botanical name becomes a hybrid child of the parent
2717
     * botanical name.
2718
     *
2719
     * @param parentName      the botanical name of the parent for this new hybrid name relationship
2720
     * @param type            the type of this new name relationship
2721
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2722
     * @return
2723
     * @see                   #addHybridChild(BotanicalName, HybridRelationshipType,String )
2724
     * @see                   #getRelationsToThisName()
2725
     * @see                   #getNameRelations()
2726
     * @see                   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
2727
     * @see                   #addNameRelationship(NameRelationship)
2728
     */
2729
    @Override
2730
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2731
        return new HybridRelationship(this, parentName, type, ruleConsidered);
2732
    }
2733

    
2734
    /**
2735
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2736
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2737
     * "is first/second parent" or "is male/female parent". By invoking this
2738
     * method <i>this</i> botanical name becomes a parent of the hybrid child
2739
     * botanical name.
2740
     *
2741
     * @param childName       the botanical name of the child for this new hybrid name relationship
2742
     * @param type            the type of this new name relationship
2743
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2744
     * @return
2745
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
2746
     * @see                   #getRelationsToThisName()
2747
     * @see                   #getNameRelations()
2748
     * @see                   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
2749
     * @see                   #addNameRelationship(NameRelationship)
2750
     */
2751
    @Override
2752
    public HybridRelationship addHybridChild(INonViralName childName, HybridRelationshipType type, String ruleConsidered){
2753
        return new HybridRelationship(childName, this, type, ruleConsidered);
2754
    }
2755

    
2756
    @Override
2757
    public void removeHybridChild(INonViralName child) {
2758
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2759
        hybridRelationships.addAll(this.getHybridChildRelations());
2760
        hybridRelationships.addAll(this.getHybridParentRelations());
2761
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2762
            // remove name relationship from this side
2763
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2764
                this.removeHybridRelationship(hybridRelationship);
2765
            }
2766
        }
2767
    }
2768

    
2769
    @Override
2770
    public void removeHybridParent(INonViralName parent) {
2771
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2772
        hybridRelationships.addAll(this.getHybridChildRelations());
2773
        hybridRelationships.addAll(this.getHybridParentRelations());
2774
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2775
            // remove name relationship from this side
2776
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2777
                this.removeHybridRelationship(hybridRelationship);
2778
            }
2779
        }
2780
    }
2781

    
2782

    
2783

    
2784
// *********** DESCRIPTIONS *************************************
2785

    
2786
    /**
2787
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2788
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2789
     * concerning the taxon name like for instance the content of its first
2790
     * publication (protolog) or a picture of this publication.
2791
     *
2792
     * @see	#addDescription(TaxonNameDescription)
2793
     * @see	#removeDescription(TaxonNameDescription)
2794
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2795
     */
2796
    @Override
2797
    public Set<TaxonNameDescription> getDescriptions() {
2798
        return descriptions;
2799
    }
2800

    
2801
    /**
2802
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2803
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2804
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2805
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2806
     *
2807
     * @param  description  the taxon name description to be added
2808
     * @see					#getDescriptions()
2809
     * @see 			  	#removeDescription(TaxonNameDescription)
2810
     */
2811
    @Override
2812
    public void addDescription(TaxonNameDescription description) {
2813
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonNameBase.class);
2814
        ReflectionUtils.makeAccessible(field);
2815
        ReflectionUtils.setField(field, description, this);
2816
        descriptions.add(description);
2817
    }
2818
    /**
2819
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2820
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2821
     * of the description itself will be set to "null".
2822
     *
2823
     * @param  description  the taxon name description which should be removed
2824
     * @see     		  	#getDescriptions()
2825
     * @see     		  	#addDescription(TaxonNameDescription)
2826
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
2827
     */
2828
    @Override
2829
    public void removeDescription(TaxonNameDescription description) {
2830
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonNameBase.class);
2831
        ReflectionUtils.makeAccessible(field);
2832
        ReflectionUtils.setField(field, description, null);
2833
        descriptions.remove(description);
2834
    }
2835

    
2836
// *********** HOMOTYPIC GROUP METHODS **************************************************
2837

    
2838
    @Override
2839
    @Transient
2840
    public void mergeHomotypicGroups(TaxonNameBase name){
2841
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2842
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2843
        name.setHomotypicalGroup(this.homotypicalGroup);
2844
    }
2845

    
2846
    /**
2847
     * Returns the boolean value indicating whether a given taxon name belongs
2848
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
2849
     * or not (false). Returns "true" only if the homotypical groups of both
2850
     * taxon names exist and if they are identical.
2851
     *
2852
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
2853
     * @return  			   the boolean value of the check
2854
     * @see     			   HomotypicalGroup
2855
     */
2856
    @Override
2857
    @Transient
2858
    public boolean isHomotypic(TaxonNameBase homoTypicName) {
2859
        if (homoTypicName == null) {
2860
            return false;
2861
        }
2862
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
2863
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
2864
            return false;
2865
        }
2866
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
2867
            return true;
2868
        }
2869
        return false;
2870
    }
2871

    
2872

    
2873
    /**
2874
     * Checks whether name is a basionym for ALL names
2875
     * in its homotypical group.
2876
     * Returns <code>false</code> if there are no other names in the group
2877
     * @param name
2878
     * @return
2879
     */
2880
    @Override
2881
    @Transient
2882
    public boolean isGroupsBasionym() {
2883
    	if (homotypicalGroup == null){
2884
    		homotypicalGroup = HomotypicalGroup.NewInstance();
2885
    		homotypicalGroup.addTypifiedName(this);
2886
    	}
2887
        Set<TaxonNameBase> typifiedNames = homotypicalGroup.getTypifiedNames();
2888

    
2889
        // Check whether there are any other names in the group
2890
        if (typifiedNames.size() == 1) {
2891
                return false;
2892
        }
2893

    
2894
        boolean isBasionymToAll = true;
2895

    
2896
        for (TaxonNameBase taxonName : typifiedNames) {
2897
                if (!taxonName.equals(this)) {
2898
                        if (! isBasionymFor(taxonName)) {
2899
                                return false;
2900
                        }
2901
                }
2902
        }
2903
        return true;
2904
    }
2905

    
2906
    /**
2907
     * Checks whether a basionym relationship exists between fromName and toName.
2908
     *
2909
     * @param fromName
2910
     * @param toName
2911
     * @return
2912
     */
2913
    @Override
2914
    @Transient
2915
    public boolean isBasionymFor(TaxonNameBase newCombinationName) {
2916
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
2917
            for (NameRelationship relation : relations) {
2918
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
2919
                                    relation.getFromName().equals(this)) {
2920
                            return true;
2921
                    }
2922
            }
2923
            return false;
2924
    }
2925

    
2926
    /**
2927
     * Creates a basionym relationship to all other names in this names homotypical
2928
     * group.
2929
     *
2930
     * @see HomotypicalGroup.setGroupBasionym(TaxonNameBase basionymName)
2931
     */
2932
    @Override
2933
    @Transient
2934
    public void makeGroupsBasionym() {
2935
        this.homotypicalGroup.setGroupBasionym(this);
2936
    }
2937

    
2938

    
2939
//*********  Rank comparison shortcuts   ********************//
2940
    /**
2941
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2942
     * taxon name is higher than the genus rank (true) or not (false).
2943
     * Suprageneric non viral names are monomials.
2944
     * Returns false if rank is null.
2945
     *
2946
     * @see  #isGenus()
2947
     * @see  #isInfraGeneric()
2948
     * @see  #isSpecies()
2949
     * @see  #isInfraSpecific()
2950
     */
2951
    @Override
2952
    @Transient
2953
    public boolean isSupraGeneric() {
2954
        if (rank == null){
2955
            return false;
2956
        }
2957
        return getRank().isSupraGeneric();
2958
    }
2959
    /**
2960
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2961
     * taxon name is the genus rank (true) or not (false). Non viral names with
2962
     * genus rank are monomials. Returns false if rank is null.
2963
     *
2964
     * @see  #isSupraGeneric()
2965
     * @see  #isInfraGeneric()
2966
     * @see  #isSpecies()
2967
     * @see  #isInfraSpecific()
2968
     */
2969
    @Override
2970
    @Transient
2971
    public boolean isGenus() {
2972
        if (rank == null){
2973
            return false;
2974
        }
2975
        return getRank().isGenus();
2976
    }
2977
    /**
2978
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2979
     * taxon name is higher than the species rank and lower than the
2980
     * genus rank (true) or not (false). Infrageneric non viral names are
2981
     * binomials. Returns false if rank is null.
2982
     *
2983
     * @see  #isSupraGeneric()
2984
     * @see  #isGenus()
2985
     * @see  #isSpecies()
2986
     * @see  #isInfraSpecific()
2987
     */
2988
    @Override
2989
    @Transient
2990
    public boolean isInfraGeneric() {
2991
        if (rank == null){
2992
            return false;
2993
        }
2994
        return getRank().isInfraGeneric();
2995
    }
2996

    
2997
    /**
2998
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2999
     * taxon name is higher than the species rank (true) or not (false).
3000
     * Returns false if rank is null.
3001
     *
3002
     * @see  #isGenus()
3003
     * @see  #isInfraGeneric()
3004
     * @see  #isSpecies()
3005
     * @see  #isInfraSpecific()
3006
     */
3007
    @Override
3008
    @Transient
3009
    public boolean isSupraSpecific(){
3010
        if (rank == null) {
3011
            return false;
3012
        }
3013
        return getRank().isHigher(Rank.SPECIES());
3014
    }
3015

    
3016
    /**
3017
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3018
     * taxon name is the species rank (true) or not (false). Non viral names
3019
     * with species rank are binomials.
3020
     * Returns false if rank is null.
3021
     *
3022
     * @see  #isSupraGeneric()
3023
     * @see  #isGenus()
3024
     * @see  #isInfraGeneric()
3025
     * @see  #isInfraSpecific()
3026
     */
3027
    @Override
3028
    @Transient
3029
    public boolean isSpecies() {
3030
        if (rank == null){
3031
            return false;
3032
        }
3033
        return getRank().isSpecies();
3034
    }
3035
    /**
3036
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3037
     * taxon name is lower than the species rank (true) or not (false).
3038
     * Infraspecific non viral names are trinomials.
3039
     * Returns false if rank is null.
3040
     *
3041
     * @see  #isSupraGeneric()
3042
     * @see  #isGenus()
3043
     * @see  #isInfraGeneric()
3044
     * @see  #isSpecies()
3045
     */
3046
    @Override
3047
    @Transient
3048
    public boolean isInfraSpecific() {
3049
        if (rank == null){
3050
            return false;
3051
        }
3052
        return getRank().isInfraSpecific();
3053
    }
3054

    
3055
    /**
3056
     * Returns true if this name's rank indicates a rank that aggregates species like species
3057
     * aggregates or species groups, false otherwise. This methods currently returns false
3058
     * for all user defined ranks.
3059
     *
3060
     *@see Rank#isSpeciesAggregate()
3061
     *
3062
     * @return
3063
     */
3064
    @Override
3065
    @Transient
3066
    public boolean isSpeciesAggregate() {
3067
        if (rank == null){
3068
            return false;
3069
        }
3070
        return getRank().isSpeciesAggregate();
3071
    }
3072

    
3073

    
3074
    /**
3075
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3076
     * the construction of <i>this</i> taxon name since there is no specific
3077
     * nomenclatural code defined. The real implementention takes place in the
3078
     * subclasses {@link IBacterialName BacterialName},
3079
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3080
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3081
     * and only one nomenclatural code.
3082
     *
3083
     * @return  null
3084
     * @see  	#isCodeCompliant()
3085
     * @see  	#getHasProblem()
3086
     */
3087
    @Override
3088
    public NomenclaturalCode getNomenclaturalCode() {
3089
        logger.warn("TaxonNameBase has no specific Code defined. Use subclasses");
3090
        return null;
3091
    }
3092

    
3093

    
3094
    /**
3095
     * Generates and returns the string with the scientific name of <i>this</i>
3096
     * taxon name (only non viral taxon names can be generated from their
3097
     * components). This string may be stored in the inherited
3098
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3099
     * This method overrides the generic and inherited
3100
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3101
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3102
     *
3103
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3104
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3105
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3106
     */
3107
//	@Override
3108
//	public abstract String generateTitle();
3109

    
3110
    /**
3111
     * Creates a basionym relationship between this name and
3112
     * 	each name in its homotypic group.
3113
     *
3114
     * @param basionymName
3115
     */
3116
    @Override
3117
    @Transient
3118
    public void setAsGroupsBasionym() {
3119

    
3120
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3121
        if (homotypicalGroup == null) {
3122
            return;
3123
        }
3124

    
3125
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3126
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3127

    
3128
        for(TaxonNameBase<?, ?> typifiedName : homotypicalGroup.getTypifiedNames()){
3129

    
3130
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3131

    
3132
            for(NameRelationship nameRelation : nameRelations){
3133
                relations.add(nameRelation);
3134
            }
3135
        }
3136

    
3137
        for (NameRelationship relation : relations) {
3138

    
3139
            // If this is a basionym relation, and toName is in the homotypical group,
3140
            //	remove the relationship.
3141
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3142
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3143
                removeRelations.add(relation);
3144
            }
3145
        }
3146

    
3147
        // Removing relations from a set through which we are iterating causes a
3148
        //	ConcurrentModificationException. Therefore, we delete the targeted
3149
        //	relations in a second step.
3150
        for (NameRelationship relation : removeRelations) {
3151
            this.removeNameRelationship(relation);
3152
        }
3153

    
3154
        for (TaxonNameBase<?, ?> name : homotypicalGroup.getTypifiedNames()) {
3155
            if (!name.equals(this)) {
3156

    
3157
                // First check whether the relationship already exists
3158
                if (!this.isBasionymFor(name)) {
3159

    
3160
                    // Then create it
3161
                    name.addRelationshipFromName(this,
3162
                            NameRelationshipType.BASIONYM(), null);
3163
                }
3164
            }
3165
        }
3166
    }
3167

    
3168
    /**
3169
     * Removes basionym relationship between this name and
3170
     * 	each name in its homotypic group.
3171
     *
3172
     * @param basionymName
3173
     */
3174
    @Override
3175
    @Transient
3176
    public void removeAsGroupsBasionym() {
3177

    
3178
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3179

    
3180
        if (homotypicalGroup == null) {
3181
            return;
3182
        }
3183

    
3184
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3185
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3186

    
3187
        for(TaxonNameBase<?, ?> typifiedName : homotypicalGroup.getTypifiedNames()){
3188

    
3189
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3190

    
3191
            for(NameRelationship nameRelation : nameRelations){
3192
                relations.add(nameRelation);
3193
            }
3194
        }
3195

    
3196
        for (NameRelationship relation : relations) {
3197

    
3198
            // If this is a basionym relation, and toName is in the homotypical group,
3199
            //	and fromName is basionymName, remove the relationship.
3200
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3201
                    relation.getFromName().equals(this) &&
3202
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3203
                removeRelations.add(relation);
3204
            }
3205
        }
3206

    
3207
        // Removing relations from a set through which we are iterating causes a
3208
        //	ConcurrentModificationException. Therefore, we delete the targeted
3209
        //	relations in a second step.
3210
        for (NameRelationship relation : removeRelations) {
3211
            this.removeNameRelationship(relation);
3212
        }
3213
    }
3214

    
3215

    
3216
    /**
3217
     * Defines the last part of the name.
3218
     * This is for infraspecific taxa, the infraspecific epithet,
3219
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3220
     * else the genusOrUninomial.
3221
     * However, the result does not depend on the rank (which may be not correctly set
3222
     * in case of dirty data) but returns the first name part which is not blank
3223
     * considering the above order.
3224
     * @return the first not blank name part in reverse order
3225
     */
3226
    @Override
3227
    public String getLastNamePart() {
3228
        String result =
3229
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3230
                    this.getInfraSpecificEpithet() :
3231
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3232
                    this.getSpecificEpithet():
3233
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3234
                    this.getInfraGenericEpithet():
3235
                this.getGenusOrUninomial();
3236
        return result;
3237
    }
3238

    
3239
// ***************** COMPARE ********************************/
3240

    
3241
    @Override
3242
    public int compareToName(TaxonNameBase<?,?> otherName){
3243

    
3244
        int result = 0;
3245

    
3246
        if (otherName == null) {
3247
            throw new NullPointerException("Cannot compare to null.");
3248
        }
3249

    
3250
        //other
3251
        otherName = deproxy(otherName);
3252
        String otherNameCache = otherName.getNameCache();
3253
        String otherTitleCache = otherName.getTitleCache();
3254
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3255
        if (otherName.isAutonym()){
3256
            boolean isProtected = otherName.isProtectedNameCache();
3257
            String oldNameCache = otherName.getNameCache();
3258
            otherName.setProtectedNameCache(false);
3259
            otherName.setNameCache(null, false);
3260
            otherNameCache = otherName.getNameCache();
3261
            otherName.setNameCache(oldNameCache, isProtected);
3262
        }
3263

    
3264
        //this
3265
        String thisNameCache = this.getNameCache();
3266
        String thisTitleCache = this.getTitleCache();
3267

    
3268
        if (this.isAutonym()){
3269
            boolean isProtected = this.isProtectedNameCache();
3270
            String oldNameCache = this.getNameCache();
3271
            this.setProtectedNameCache(false);
3272
            this.setNameCache(null, false);
3273
            thisNameCache = this.getNameCache();
3274
            this.setNameCache(oldNameCache, isProtected);
3275
        }
3276

    
3277

    
3278
        // Compare name cache of taxon names
3279
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3280
            thisNameCache = normalizeName(thisNameCache);
3281
            otherNameCache = normalizeName(otherNameCache);
3282
            result = thisNameCache.compareTo(otherNameCache);
3283
        }
3284

    
3285
        // Compare title cache of taxon names
3286
        if (result == 0){
3287
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3288
                thisTitleCache = normalizeName(thisTitleCache);
3289
                otherTitleCache = normalizeName(otherTitleCache);
3290
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3291
            }
3292
        }
3293

    
3294
        return result;
3295
    }
3296

    
3297
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3298
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3299

    
3300
    /**
3301
     * @param thisNameCache
3302
     * @param HYBRID_SIGN
3303
     * @param QUOT_SIGN
3304
     * @return
3305
     */
3306
    private String normalizeName(String thisNameCache) {
3307
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3308
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3309
        return thisNameCache;
3310
    }
3311

    
3312
// ********************** INTERFACES ********************************************/
3313

    
3314
    /**
3315
     * Method to cast a interfaced name to a concrete name.
3316
     * The method includes a deproxy to guarantee that no
3317
     * class cast exception is thrown.
3318
     *
3319
     * @see #castAndDeproxy(Set)
3320
     * @param interfacedName
3321
     * @return
3322
     */
3323
    public static TaxonNameBase castAndDeproxy(ITaxonNameBase interfacedName){
3324
        return deproxy(interfacedName, TaxonNameBase.class);
3325
    }
3326

    
3327
    /**
3328
     * Method to cast a set of interfaced names to concrete namex.
3329
     * The method includes a deproxy to guarantee that no
3330
     * class cast exception is thrown.
3331
     *
3332
     * @see #castAndDeproxy(ITaxonNameBase)
3333
     * @param naminterfacedNames
3334
     * @return
3335
     */
3336
    public static Set<TaxonNameBase> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3337
        Set<TaxonNameBase> result = new HashSet<>();
3338
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3339
            result.add(castAndDeproxy(naminterfacedName));
3340
        }
3341
        return result;
3342
    }
3343

    
3344

    
3345
//*********************** CLONE ********************************************************/
3346

    
3347
    /**
3348
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3349
     * a new instance that differs only slightly from <i>this</i> taxon name by
3350
     * modifying only some of the attributes.<BR><BR>
3351
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3352
     * <b>The name gets a newly created homotypical group</b><BR>
3353
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3354
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3355
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3356
     *
3357
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3358
     * @see java.lang.Object#clone()
3359
     */
3360
    @Override
3361
    public Object clone() {
3362
        TaxonNameBase<?,?> result;
3363
        try {
3364
            result = (TaxonNameBase)super.clone();
3365

    
3366
            //taxonBases -> empty
3367
            result.taxonBases = new HashSet<>();
3368

    
3369
            //empty caches
3370
            if (! protectedFullTitleCache){
3371
                result.fullTitleCache = null;
3372
            }
3373

    
3374
            //descriptions
3375
            result.descriptions = new HashSet<>();
3376
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3377
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3378
                result.descriptions.add(newDescription);
3379
            }
3380

    
3381
            //status
3382
            result.status = new HashSet<>();
3383
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3384
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3385
                result.status.add(newStatus);
3386
            }
3387

    
3388

    
3389
            //To Relations
3390
            result.relationsToThisName = new HashSet<>();
3391
            for (NameRelationship toRelationship : getRelationsToThisName()){
3392
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3393
                newRelationship.setRelatedTo(result);
3394
                result.relationsToThisName.add(newRelationship);
3395
            }
3396

    
3397
            //From Relations
3398
            result.relationsFromThisName = new HashSet<>();
3399
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3400
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3401
                newRelationship.setRelatedFrom(result);
3402
                result.relationsFromThisName.add(newRelationship);
3403
            }
3404

    
3405
            //type designations
3406
            result.typeDesignations = new HashSet<>();
3407
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3408
                TypeDesignationBase<?> newDesignation = (TypeDesignationBase<?>)typeDesignation.clone();
3409
                result.typeDesignations.add(newDesignation);
3410
                newDesignation.addTypifiedName(result);
3411
            }
3412

    
3413
            //homotypicalGroup
3414
            //TODO still needs to be discussed
3415
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3416
            result.homotypicalGroup.addTypifiedName(this);
3417

    
3418

    
3419
            //HybridChildRelations
3420
            result.hybridChildRelations = new HashSet<HybridRelationship>();
3421
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3422
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3423
                newChildRelationship.setRelatedTo(result);
3424
                result.hybridChildRelations.add(newChildRelationship);
3425
            }
3426

    
3427
            //HybridParentRelations
3428
            result.hybridParentRelations = new HashSet<HybridRelationship>();
3429
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3430
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3431
                newParentRelationship.setRelatedFrom(result);
3432
                result.hybridParentRelations.add(newParentRelationship);
3433
            }
3434

    
3435
            //empty caches
3436
            if (! protectedNameCache){
3437
                result.nameCache = null;
3438
            }
3439

    
3440
            //empty caches
3441
            if (! protectedAuthorshipCache){
3442
                result.authorshipCache = null;
3443
            }
3444

    
3445
            //no changes to: appendedPharse, nomenclaturalReference,
3446
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3447
            //protectedFullTitleCache, rank
3448
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3449
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3450
            //protectedAuthorshipCache, protectedNameCache,
3451
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3452
            //acronym
3453
            //subGenusAuthorship, nameApprobation
3454
            //anamorphic
3455
            //cultivarName
3456
            return result;
3457
        } catch (CloneNotSupportedException e) {
3458
            logger.warn("Object does not implement cloneable");
3459
            e.printStackTrace();
3460
            return null;
3461
        }
3462

    
3463
    }
3464

    
3465
}
(29-29/37)