Project

General

Profile

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

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

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

    
149
    "acronym",
150

    
151
    "subGenusAuthorship",
152
    "nameApprobation",
153

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

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

    
169
                IParsable, IRelated, IMatchable, Cloneable {
170

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

    
174
    static private INonViralNameParser<?> nameParser = new NonViralNameParserImpl();
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
//****** NonViralName 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

    
499
// *************** FACTORY METHODS ********************************/
500

    
501
    /**
502
     * Creates a new non viral taxon name instance
503
     * only containing its {@link common.Rank rank} and
504
      * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
505
     *
506
     * @param  rank  the rank to be assigned to <i>this</i> non viral taxon name
507
     * @see    #NewInstance(Rank, HomotypicalGroup)
508
     * @see    #NonViralName(Rank, HomotypicalGroup)
509
     * @see    #NonViralName()
510
     * @see    #NonViralName(Rank, String, String, String, String, TeamOrPersonBase, Reference, String, HomotypicalGroup)
511
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
512
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
513
     * @see    eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
514
     */
515
    public static NonViralName NewNonViralInstance(Rank rank){
516
        return new NonViralName(rank, null);
517
    }
518

    
519
    /**
520
     * Creates a new non viral taxon name instance
521
     * only containing its {@link common.Rank rank},
522
     * its {@link HomotypicalGroup homotypical group} and
523
      * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
524
     * The new non viral taxon name instance will be also added to the set of
525
     * non viral taxon names belonging to this homotypical group.
526
     *
527
     * @param  rank  the rank to be assigned to <i>this</i> non viral taxon name
528
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> non viral taxon name belongs
529
     * @see    #NewInstance(Rank)
530
     * @see    #NonViralName(Rank, HomotypicalGroup)
531
     * @see    #NonViralName()
532
     * @see    #NonViralName(Rank, String, String, String, String, TeamOrPersonBase, Reference, String, HomotypicalGroup)
533
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
534
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
535
     * @see    eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
536
     */
537
    public static NonViralName NewNonViralInstance(Rank rank, HomotypicalGroup homotypicalGroup){
538
        return new NonViralName(rank, homotypicalGroup);
539
    }
540

    
541

    
542
    /**
543
     * Creates a new viral taxon name instance only containing its {@link Rank rank}.
544
     *
545
     * @param   rank  the rank to be assigned to <i>this</i> viral taxon name
546
     * @see     #ViralName(Rank)
547
     */
548
    public static ViralName NewViralInstance(Rank rank){
549
        return new ViralName(rank);
550
    }
551

    
552
    /**
553
     * Creates a new bacterial taxon name instance
554
     * only containing its {@link Rank rank} and
555
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
556
     *
557
     * @param  rank  the rank to be assigned to <i>this</i> bacterial taxon name
558
     * @see    #NewInstance(Rank, HomotypicalGroup)
559
     * @see    #BacterialName(Rank, HomotypicalGroup)
560
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
561
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
562
     * @see    eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
563
     */
564
    public static BacterialName NewBacterialInstance(Rank rank){
565
        return new BacterialName(rank, null);
566
    }
567

    
568
    /**
569
     * Creates a new bacterial taxon name instance
570
     * only containing its {@link Rank rank},
571
     * its {@link HomotypicalGroup homotypical group} and
572
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
573
     * The new bacterial taxon name instance will be also added to the set of
574
     * bacterial taxon names belonging to this homotypical group.
575
     *
576
     * @param  rank  the rank to be assigned to <i>this</i> bacterial taxon name
577
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> bacterial taxon name belongs
578
     * @see    #NewInstance(Rank)
579
     * @see    #BacterialName(Rank, HomotypicalGroup)
580
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
581
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
582
     * @see    eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
583
     */
584
    public static BacterialName NewBacterialInstance(Rank rank, HomotypicalGroup homotypicalGroup){
585
        return new BacterialName(rank, homotypicalGroup);
586
    }
587

    
588

    
589
    /**
590
     * Creates a new zoological taxon name instance
591
     * only containing its {@link Rank rank} and
592
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.ZooNameDefaultCacheStrategy default cache strategy}.
593
     *
594
     * @param   rank    the rank to be assigned to <i>this</i> zoological taxon name
595
     * @see             #ZoologicalName(Rank, HomotypicalGroup)
596
     * @see             #NewInstance(Rank, HomotypicalGroup)
597
     * @see             #NewZoologicalInstance(Rank, String, String, String, String, TeamOrPersonBase, INomenclaturalReference, String, HomotypicalGroup)
598
     * @see             eu.etaxonomy.cdm.strategy.cache.name.ZooNameDefaultCacheStrategy
599
     */
600
    public static ZoologicalName NewZoologicalInstance(Rank rank){
601
        return new ZoologicalName(rank, null);
602
    }
603

    
604
    /**
605
     * Creates a new zoological taxon name instance
606
     * only containing its {@link Rank rank},
607
     * its {@link HomotypicalGroup homotypical group} and
608
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.ZooNameDefaultCacheStrategy default cache strategy}.
609
     * The new zoological taxon name instance will be also added to the set of
610
     * zoological taxon names belonging to the given homotypical group.
611
     *
612
     * @param  rank  the rank to be assigned to <i>this</i> zoological taxon name
613
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> zoological taxon name belongs
614
     * @see    #NewInstance(Rank)
615
     * @see    #NewZoologicalInstance(Rank, String, String, String, String, TeamOrPersonBase, INomenclaturalReference, String, HomotypicalGroup)
616
     * @see    #ZoologicalName(Rank, HomotypicalGroup)
617
     * @see    eu.etaxonomy.cdm.strategy.cache.name.ZooNameDefaultCacheStrategy
618
     */
619
    public static ZoologicalName NewZoologicalInstance(Rank rank, HomotypicalGroup homotypicalGroup){
620
        return new ZoologicalName(rank, homotypicalGroup);
621
    }
622
    /**
623
     * Creates a new zoological taxon name instance
624
     * containing its {@link Rank rank},
625
     * its {@link HomotypicalGroup homotypical group},
626
     * its scientific name components, its {@link eu.etaxonomy.cdm.agent.TeamOrPersonBase author(team)},
627
     * its {@link eu.etaxonomy.cdm.reference.INomenclaturalReference nomenclatural reference} and
628
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.ZooNameDefaultCacheStrategy default cache strategy}.
629
     * The new zoological taxon name instance will be also added to the set of
630
     * zoological taxon names belonging to the given homotypical group.
631
     *
632
     * @param   rank  the rank to be assigned to <i>this</i> zoological taxon name
633
     * @param   genusOrUninomial the string for <i>this</i> zoological taxon name
634
     *          if its rank is genus or higher or for the genus part
635
     *          if its rank is lower than genus
636
     * @param   infraGenericEpithet  the string for the first epithet of
637
     *          <i>this</i> zoological taxon name if its rank is lower than genus
638
     *          and higher than species aggregate
639
     * @param   specificEpithet  the string for the first epithet of
640
     *          <i>this</i> zoological taxon name if its rank is species aggregate or lower
641
     * @param   infraSpecificEpithet  the string for the second epithet of
642
     *          <i>this</i> zoological taxon name if its rank is lower than species
643
     * @param   combinationAuthorship  the author or the team who published <i>this</i> zoological taxon name
644
     * @param   nomenclaturalReference  the nomenclatural reference where <i>this</i> zoological taxon name was published
645
     * @param   nomenclMicroRef  the string with the details for precise location within the nomenclatural reference
646
     * @param   homotypicalGroup  the homotypical group to which <i>this</i> zoological taxon name belongs
647
     * @see     #NewInstance(Rank)
648
     * @see     #NewInstance(Rank, HomotypicalGroup)
649
     * @see     #ZoologicalName(Rank, String, String, String, String, TeamOrPersonBase, INomenclaturalReference, String, HomotypicalGroup)
650
     * @see     eu.etaxonomy.cdm.strategy.cache.name.ZooNameDefaultCacheStrategy
651
     */
652
    public static ZoologicalName NewZoologicalInstance(Rank rank, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, TeamOrPersonBase combinationAuthorship, INomenclaturalReference nomenclaturalReference, String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
653
        return new ZoologicalName(rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
654
    }
655

    
656
    /**
657
     * Creates a new botanical taxon name instance
658
     * only containing its {@link Rank rank} and
659
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.BotanicNameDefaultCacheStrategy default cache strategy}.
660
     *
661
     * @param   rank    the rank to be assigned to <i>this</i> botanical taxon name
662
     * @see             #BotanicalName(Rank, HomotypicalGroup)
663
     * @see             #NewInstance(Rank, HomotypicalGroup)
664
     * @see             #NewBotanicalInstance(Rank, String, String, String, String, TeamOrPersonBase, INomenclaturalReference, String, HomotypicalGroup)
665
     * @see             eu.etaxonomy.cdm.strategy.cache.name.BotanicNameDefaultCacheStrategy
666
     */
667
    public static BotanicalName NewBotanicalInstance(Rank rank){
668
        return new BotanicalName(rank, null);
669
    }
670
    /**
671
     * Creates a new botanical taxon name instance
672
     * only containing its {@link Rank rank},
673
     * its {@link HomotypicalGroup homotypical group} and
674
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.BotanicNameDefaultCacheStrategy default cache strategy}.
675
     * The new botanical taxon name instance will be also added to the set of
676
     * botanical taxon names belonging to this homotypical group.
677
     *
678
     * @param  rank  the rank to be assigned to <i>this</i> botanical taxon name
679
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> botanical taxon name belongs
680
     * @see    #NewInstance(Rank)
681
     * @see    #NewBotanicalInstance(Rank, String, String, String, String, TeamOrPersonBase, INomenclaturalReference, String, HomotypicalGroup)
682
     * @see    #BotanicalName(Rank, HomotypicalGroup)
683
     * @see    eu.etaxonomy.cdm.strategy.cache.name.BotanicNameDefaultCacheStrategy
684
     */
685
    public static BotanicalName NewBotanicalInstance(Rank rank, HomotypicalGroup homotypicalGroup){
686
        return new BotanicalName(rank, homotypicalGroup);
687
    }
688
    /**
689
     * Creates a new botanical taxon name instance
690
     * containing its {@link Rank rank},
691
     * its {@link HomotypicalGroup homotypical group},
692
     * its scientific name components, its {@link eu.etaxonomy.cdm.model.agent.TeamOrPersonBase author(team)},
693
     * its {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} and
694
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.BotanicNameDefaultCacheStrategy default cache strategy}.
695
     * The new botanical taxon name instance will be also added to the set of
696
     * botanical taxon names belonging to this homotypical group.
697
     *
698
     * @param   rank  the rank to be assigned to <i>this</i> botanical taxon name
699
     * @param   genusOrUninomial the string for <i>this</i> botanical taxon name
700
     *          if its rank is genus or higher or for the genus part
701
     *          if its rank is lower than genus
702
     * @param   infraGenericEpithet  the string for the first epithet of
703
     *          <i>this</i> botanical taxon name if its rank is lower than genus
704
     *          and higher than species aggregate
705
     * @param   specificEpithet  the string for the first epithet of
706
     *          <i>this</i> botanical taxon name if its rank is species aggregate or lower
707
     * @param   infraSpecificEpithet  the string for the second epithet of
708
     *          <i>this</i> botanical taxon name if its rank is lower than species
709
     * @param   combinationAuthorship  the author or the team who published <i>this</i> botanical taxon name
710
     * @param   nomenclaturalReference  the nomenclatural reference where <i>this</i> botanical taxon name was published
711
     * @param   nomenclMicroRef  the string with the details for precise location within the nomenclatural reference
712
     * @param   homotypicalGroup  the homotypical group to which <i>this</i> botanical taxon name belongs
713
     * @see     #NewInstance(Rank)
714
     * @see     #NewInstance(Rank, HomotypicalGroup)
715
     * @see     ZoologicalName#ZoologicalName(Rank, String, String, String, String, TeamOrPersonBase, INomenclaturalReference, String, HomotypicalGroup)
716
     * @see     eu.etaxonomy.cdm.strategy.cache.name.BotanicNameDefaultCacheStrategy
717
     */
718
    public static  BotanicalName NewBotanicalInstance(Rank rank, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, TeamOrPersonBase combinationAuthorship, INomenclaturalReference nomenclaturalReference, String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
719
        return new BotanicalName(rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
720
    }
721

    
722
// *********************** PARSER STATIC *******************************/
723

    
724

    
725
    /**
726
     * Returns a botanical taxon name based on parsing a string representing
727
     * all elements (according to the ICBN) of a botanical taxon name (where
728
     * the scientific name is an uninomial) including authorship but without
729
     * nomenclatural reference. If the {@link Rank rank} is not "Genus" it should be
730
     * set afterwards with the {@link TaxonNameBase#setRank(Rank) setRank} methode.
731
     *
732
     * @param   fullNameString  the string to be parsed
733
     * @return                  the new botanical taxon name
734
     */
735
    public static BotanicalName PARSED_BOTANICAL(String fullNameString){
736
        return PARSED_BOTANICAL(fullNameString, Rank.GENUS());
737
    }
738

    
739

    
740
    /**
741
     * Returns a botanical taxon name based on parsing a string representing
742
     * all elements (according to the ICBN) of a botanical taxon name including
743
     * authorship but without nomenclatural reference. The parsing result
744
     * depends on the given rank of the botanical taxon name to be created.
745
     *
746
     * @param   fullNameString  the string to be parsed
747
     * @param   rank            the rank of the taxon name
748
     * @return                  the new botanical taxon name
749
     */
750
    public static BotanicalName PARSED_BOTANICAL(String fullNameString, Rank rank){
751
        if (nameParser == null){
752
            nameParser = new NonViralNameParserImpl();
753
        }
754
        return (BotanicalName)nameParser.parseFullName(fullNameString, NomenclaturalCode.ICNAFP,  rank);
755
    }
756

    
757

    
758
    /**
759
     * Returns a botanical taxon name based on parsing a string representing
760
     * all elements (according to the ICBN) of a botanical taxon name (where
761
     * the scientific name is an uninomial) including authorship and
762
     * nomenclatural reference. Eventually a new {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference}
763
     * instance will also be created. If the {@link Rank rank} is not "Genus" it should be
764
     * set afterwards with the {@link TaxonNameBase#setRank(Rank) setRank} methode.
765
     *
766
     * @param   fullNameAndReferenceString  the string to be parsed
767
     * @return                              the new botanical taxon name
768
     */
769
    public static BotanicalName PARSED_BOTANICAL_REFERENCE(String fullNameAndReferenceString){
770
        return PARSED_BOTANICAL_REFERENCE(fullNameAndReferenceString, Rank.GENUS());
771
    }
772

    
773
    /**
774
     * Returns a botanical taxon name based on parsing a string representing
775
     * all elements (according to the ICBN) of a botanical taxon name including
776
     * authorship and nomenclatural reference. The parsing result depends on
777
     * the given rank of the botanical taxon name to be created.
778
     * Eventually a new {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference}
779
     * instance will also be created.
780
     *
781
     * @param   fullNameAndReferenceString  the string to be parsed
782
     * @param   rank                        the rank of the taxon name
783
     * @return                              the new botanical taxon name
784
     */
785
    public static BotanicalName PARSED_BOTANICAL_REFERENCE(String fullNameAndReferenceString, Rank rank){
786
        if (nameParser == null){
787
            nameParser = new NonViralNameParserImpl();
788
        }
789
        return (BotanicalName)nameParser.parseReferencedName(fullNameAndReferenceString, NomenclaturalCode.ICNAFP, rank);
790
    }
791

    
792
// ************* CONSTRUCTORS *************/
793
    /**
794
     * Class constructor: creates a new empty taxon name.
795
     *
796
     * @see #TaxonNameBase(Rank)
797
     * @see #TaxonNameBase(HomotypicalGroup)
798
     * @see #TaxonNameBase(Rank, HomotypicalGroup)
799
     */
800
    protected TaxonNameBase() {
801
        super();
802
        setNameCacheStrategy();
803
    }
804
    /**
805
     * Class constructor: creates a new taxon name
806
     * only containing its {@link Rank rank}.
807
     *
808
     * @param  rank  the rank to be assigned to <i>this</i> taxon name
809
     * @see    		 #TaxonNameBase()
810
     * @see    		 #TaxonNameBase(HomotypicalGroup)
811
     * @see    		 #TaxonNameBase(Rank, HomotypicalGroup)
812
     */
813
    protected TaxonNameBase(Rank rank) {
814
        this(rank, null);
815
    }
816
    /**
817
     * Class constructor: creates a new taxon name instance
818
     * only containing its {@link HomotypicalGroup homotypical group}.
819
     * The new taxon name will be also added to the set of taxon names
820
     * belonging to this homotypical group.
821
     *
822
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
823
     * @see    					 #TaxonNameBase()
824
     * @see    					 #TaxonNameBase(Rank)
825
     * @see    					 #TaxonNameBase(Rank, HomotypicalGroup)
826
     */
827
    protected TaxonNameBase(HomotypicalGroup homotypicalGroup) {
828
        this(null, homotypicalGroup);
829
    }
830

    
831
    /**
832
     * Class constructor: creates a new taxon name instance
833
     * only containing its {@link Rank rank} and
834
     * its {@link HomotypicalGroup homotypical group} and
835
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
836
     * The new taxon name will be also added to the set of taxon names
837
     * belonging to this homotypical group.
838
     *
839
     * @param  rank  			 the rank to be assigned to <i>this</i> taxon name
840
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
841
     * @see    					 #TaxonNameBase()
842
     * @see    					 #TaxonNameBase(Rank)
843
     * @see    					 #TaxonNameBase(HomotypicalGroup)
844
     */
845
    protected TaxonNameBase(Rank rank, HomotypicalGroup homotypicalGroup) {
846
        super();
847
        this.setRank(rank);
848
        if (homotypicalGroup == null){
849
            homotypicalGroup = new HomotypicalGroup();
850
        }
851
        homotypicalGroup.addTypifiedName(this);
852
        this.homotypicalGroup = homotypicalGroup;
853
        setNameCacheStrategy();
854
    }
855

    
856

    
857
    /**
858
     * Class constructor: creates a new non viral taxon name instance
859
     * containing its {@link Rank rank},
860
     * its {@link HomotypicalGroup homotypical group},
861
     * its scientific name components, its {@link eu.etaxonomy.cdm.model.agent.TeamOrPersonBase author(team)},
862
     * its {@link eu.etaxonomy.cdm.model.reference.Reference nomenclatural reference} and
863
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
864
     * The new non viral taxon name instance will be also added to the set of
865
     * non viral taxon names belonging to this homotypical group.
866
     *
867
     * @param   rank  the rank to be assigned to <i>this</i> non viral taxon name
868
     * @param   genusOrUninomial the string for <i>this</i> non viral taxon name
869
     *          if its rank is genus or higher or for the genus part
870
     *          if its rank is lower than genus
871
     * @param   infraGenericEpithet  the string for the first epithet of
872
     *          <i>this</i> non viral taxon name if its rank is lower than genus
873
     *          and higher than species aggregate
874
     * @param   specificEpithet  the string for the first epithet of
875
     *          <i>this</i> non viral taxon name if its rank is species aggregate or lower
876
     * @param   infraSpecificEpithet  the string for the second epithet of
877
     *          <i>this</i> non viral taxon name if its rank is lower than species
878
     * @param   combinationAuthorship  the author or the team who published <i>this</i> non viral taxon name
879
     * @param   nomenclaturalReference  the nomenclatural reference where <i>this</i> non viral taxon name was published
880
     * @param   nomenclMicroRef  the string with the details for precise location within the nomenclatural reference
881
     * @param   homotypicalGroup  the homotypical group to which <i>this</i> non viral taxon name belongs
882
     * @see     #NonViralName()
883
     * @see     #NonViralName(Rank, HomotypicalGroup)
884
     * @see     #NewInstance(Rank, HomotypicalGroup)
885
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
886
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
887
     * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
888
     */
889
    protected TaxonNameBase(Rank rank, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, TeamOrPersonBase combinationAuthorship, INomenclaturalReference nomenclaturalReference, String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
890
        this(rank, homotypicalGroup);
891
        setGenusOrUninomial(genusOrUninomial);
892
        setInfraGenericEpithet (infraGenericEpithet);
893
        setSpecificEpithet(specificEpithet);
894
        setInfraSpecificEpithet(infraSpecificEpithet);
895
        setCombinationAuthorship(combinationAuthorship);
896
        setNomenclaturalReference(nomenclaturalReference);
897
        this.setNomenclaturalMicroReference(nomenclMicroRef);
898
    }
899

    
900

    
901
    private void setNameCacheStrategy(){
902
        if (getClass() == NonViralName.class){
903
            this.cacheStrategy = (S)NonViralNameDefaultCacheStrategy.NewInstance();
904
        }
905
    }
906

    
907

    
908
    @Override
909
    public void initListener(){
910
        PropertyChangeListener listener = new PropertyChangeListener() {
911
            @Override
912
            public void propertyChange(PropertyChangeEvent e) {
913
                boolean protectedByLowerCache = false;
914
                //authorship cache
915
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
916
                    if (protectedAuthorshipCache){
917
                        protectedByLowerCache = true;
918
                    }else{
919
                        authorshipCache = null;
920
                    }
921
                }
922

    
923
                //nameCache
924
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "nameCache")){
925
                    if (protectedNameCache){
926
                        protectedByLowerCache = true;
927
                    }else{
928
                        nameCache = null;
929
                    }
930
                }
931
                //title cache
932
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "titleCache")){
933
                    if (isProtectedTitleCache()|| protectedByLowerCache == true ){
934
                        protectedByLowerCache = true;
935
                    }else{
936
                        titleCache = null;
937
                    }
938
                }
939
                //full title cache
940
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "fullTitleCache")){
941
                    if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
942
                        protectedByLowerCache = true;
943
                    }else{
944
                        fullTitleCache = null;
945
                    }
946
                }
947
            }
948
        };
949
        addPropertyChangeListener(listener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
950
    }
951

    
952
    private static Map<String, java.lang.reflect.Field> allFields = null;
953
    protected Map<String, java.lang.reflect.Field> getAllFields(){
954
        if (allFields == null){
955
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
956
        }
957
        return allFields;
958
    }
959

    
960
    /**
961
     * @param propertyName
962
     * @param string
963
     * @return
964
     */
965
    private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
966
        java.lang.reflect.Field field;
967
        try {
968
            field = getAllFields().get(propertyName);
969
            if (field != null){
970
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
971
                if (updateAnnotation != null){
972
                    for (String value : updateAnnotation.value()){
973
                        if (cacheName.equals(value)){
974
                            return true;
975
                        }
976
                    }
977
                }
978
            }
979
            return false;
980
        } catch (SecurityException e1) {
981
            throw e1;
982
        }
983
    }
984

    
985
    private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
986
        java.lang.reflect.Field field;
987
        //do not update fields with the same name
988
        if (cacheName.equals(propertyName)){
989
            return true;
990
        }
991
        //evaluate annotation
992
        try {
993
            field = getAllFields().get(propertyName);
994
            if (field != null){
995
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
996
                if (updateAnnotation != null){
997
                    for (String value : updateAnnotation.noUpdate()){
998
                        if (cacheName.equals(value)){
999
                            return true;
1000
                        }
1001
                    }
1002
                }
1003
            }
1004
            return false;
1005
        } catch (SecurityException e1) {
1006
            throw e1;
1007
        }
1008
    }
1009

    
1010
// ****************** GETTER / SETTER ****************************/
1011

    
1012
    /**
1013
     * Returns the boolean value of the flag intended to protect (true)
1014
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
1015
     * string of <i>this</i> non viral taxon name.
1016
     *
1017
     * @return  the boolean value of the protectedNameCache flag
1018
     * @see     #getNameCache()
1019
     */
1020
    @Override
1021
    public boolean isProtectedNameCache() {
1022
        return protectedNameCache;
1023
    }
1024

    
1025
    /**
1026
     * @see     #isProtectedNameCache()
1027
     */
1028
    @Override
1029
    public void setProtectedNameCache(boolean protectedNameCache) {
1030
        this.protectedNameCache = protectedNameCache;
1031
    }
1032

    
1033
    /**
1034
     * Returns either the scientific name string (without authorship) for <i>this</i>
1035
     * non viral taxon name if its rank is genus or higher (monomial) or the string for
1036
     * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
1037
     * Genus or uninomial strings begin with an upper case letter.
1038
     *
1039
     * @return  the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
1040
     * @see     #getNameCache()
1041
     */
1042
    @Override
1043
    public String getGenusOrUninomial() {
1044
        return genusOrUninomial;
1045
    }
1046

    
1047
    /**
1048
     * @see  #getGenusOrUninomial()
1049
     */
1050
    @Override
1051
    public void setGenusOrUninomial(String genusOrUninomial) {
1052
        this.genusOrUninomial = StringUtils.isBlank(genusOrUninomial) ? null : genusOrUninomial;
1053
    }
1054

    
1055
    /**
1056
     * Returns the genus subdivision epithet string (infrageneric part) for
1057
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
1058
     * higher than species aggregate: binomial). Genus subdivision epithet
1059
     * strings begin with an upper case letter.
1060
     *
1061
     * @return  the string containing the infrageneric part of <i>this</i> non viral taxon name
1062
     * @see     #getNameCache()
1063
     */
1064
    @Override
1065
    public String getInfraGenericEpithet(){
1066
        return this.infraGenericEpithet;
1067
    }
1068

    
1069
    /**
1070
     * @see  #getInfraGenericEpithet()
1071
     */
1072
    @Override
1073
    public void setInfraGenericEpithet(String infraGenericEpithet){
1074
        this.infraGenericEpithet = StringUtils.isBlank(infraGenericEpithet)? null : infraGenericEpithet;
1075
    }
1076

    
1077
    /**
1078
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
1079
     * species aggregate or lower (bi- or trinomial). Species epithet strings
1080
     * begin with a lower case letter.
1081
     *
1082
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
1083
     * @see     #getNameCache()
1084
     */
1085
    @Override
1086
    public String getSpecificEpithet(){
1087
        return this.specificEpithet;
1088
    }
1089

    
1090
    /**
1091
     * @see  #getSpecificEpithet()
1092
     */
1093
    @Override
1094
    public void setSpecificEpithet(String specificEpithet){
1095
        this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
1096
    }
1097

    
1098
    /**
1099
     * Returns the species subdivision epithet string (infraspecific part) for
1100
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
1101
     * (lower than species: trinomial). Species subdivision epithet strings
1102
     * begin with a lower case letter.
1103
     *
1104
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
1105
     * @see     #getNameCache()
1106
     */
1107
    @Override
1108
    public String getInfraSpecificEpithet(){
1109
        return this.infraSpecificEpithet;
1110
    }
1111

    
1112
    /**
1113
     * @see  #getInfraSpecificEpithet()
1114
     */
1115
    @Override
1116
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
1117
        this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
1118
    }
1119

    
1120
    /**
1121
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
1122
     * taxon name.
1123
     *
1124
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
1125
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
1126
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
1127
     */
1128
    @Override
1129
    public TeamOrPersonBase<?> getCombinationAuthorship(){
1130
        return this.combinationAuthorship;
1131
    }
1132

    
1133
    /**
1134
     * @see  #getCombinationAuthorship()
1135
     */
1136
    @Override
1137
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
1138
        this.combinationAuthorship = combinationAuthorship;
1139
    }
1140

    
1141
    /**
1142
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
1143
     * the publication of <i>this</i> non viral taxon name as generally stated by
1144
     * the {@link #getCombinationAuthorship() combination author (team)} itself.<BR>
1145
     * An ex-author(-team) is an author(-team) to whom a taxon name was ascribed
1146
     * although it is not the author(-team) of a valid publication (for instance
1147
     * without the validating description or diagnosis in case of a name for a
1148
     * new taxon). The name of this ascribed authorship, followed by "ex", may
1149
     * be inserted before the name(s) of the publishing author(s) of the validly
1150
     * published name:<BR>
1151
     * <i>Lilium tianschanicum</i> was described by Grubov (1977) as a new species and
1152
     * its name was ascribed to Ivanova; since there is no indication that
1153
     * Ivanova provided the validating description, the name may be cited as
1154
     * <i>Lilium tianschanicum</i> N. A. Ivanova ex Grubov or <i>Lilium tianschanicum</i> Grubov.
1155
     * <P>
1156
     * The presence of an author (team) of <i>this</i> non viral taxon name is a
1157
     * condition for the existence of an ex author (team) for <i>this</i> same name.
1158
     *
1159
     * @return  the nomenclatural ex author (team) of <i>this</i> non viral taxon name
1160
     * @see     #getCombinationAuthorship()
1161
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
1162
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
1163
     */
1164
    @Override
1165
    public TeamOrPersonBase<?> getExCombinationAuthorship(){
1166
        return this.exCombinationAuthorship;
1167
    }
1168

    
1169
    /**
1170
     * @see  #getExCombinationAuthorship()
1171
     */
1172
    @Override
1173
    public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
1174
        this.exCombinationAuthorship = exCombinationAuthorship;
1175
    }
1176

    
1177
    /**
1178
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
1179
     * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
1180
     * author (team) can only exist if <i>this</i> non viral taxon name is a new
1181
     * combination due to a taxonomical revision.
1182
     *
1183
     * @return  the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
1184
     * @see     #getCombinationAuthorship()
1185
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
1186
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
1187
     */
1188
    @Override
1189
    public TeamOrPersonBase<?> getBasionymAuthorship(){
1190
        return basionymAuthorship;
1191
    }
1192

    
1193
    /**
1194
     * @see  #getBasionymAuthorship()
1195
     */
1196
    @Override
1197
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
1198
        this.basionymAuthorship = basionymAuthorship;
1199
    }
1200

    
1201
    /**
1202
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
1203
     * the publication of the original combination <i>this</i> non viral taxon name is
1204
     * based on. This should have been generally stated by
1205
     * the {@link #getBasionymAuthorship() basionym author (team)} itself.
1206
     * The presence of a basionym author (team) of <i>this</i> non viral taxon name is a
1207
     * condition for the existence of an ex basionym author (team)
1208
     * for <i>this</i> same name.
1209
     *
1210
     * @return  the nomenclatural ex basionym author (team) of <i>this</i> non viral taxon name
1211
     * @see     #getBasionymAuthorship()
1212
     * @see     #getExCombinationAuthorship()
1213
     * @see     #getCombinationAuthorship()
1214
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
1215
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
1216
     */
1217
    @Override
1218
    public TeamOrPersonBase<?> getExBasionymAuthorship(){
1219
        return exBasionymAuthorship;
1220
    }
1221

    
1222
    /**
1223
     * @see  #getExBasionymAuthorship()
1224
     */
1225
    @Override
1226
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
1227
        this.exBasionymAuthorship = exBasionymAuthorship;
1228
    }
1229

    
1230
    /**
1231
     * Returns the boolean value of the flag intended to protect (true)
1232
     * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
1233
     * of <i>this</i> non viral taxon name.
1234
     *
1235
     * @return  the boolean value of the protectedAuthorshipCache flag
1236
     * @see     #getAuthorshipCache()
1237
     */
1238
    @Override
1239
    public boolean isProtectedAuthorshipCache() {
1240
        return protectedAuthorshipCache;
1241
    }
1242

    
1243
    /**
1244
     * @see     #isProtectedAuthorshipCache()
1245
     * @see     #getAuthorshipCache()
1246
     */
1247
    @Override
1248
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
1249
        this.protectedAuthorshipCache = protectedAuthorshipCache;
1250
    }
1251

    
1252
    /**
1253
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1254
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
1255
     *
1256
     * @see    #getHybridRelationships()
1257
     * @see    #getChildRelationships()
1258
     * @see    HybridRelationshipType
1259
     */
1260
    @Override
1261
    public Set<HybridRelationship> getHybridParentRelations() {
1262
        if(hybridParentRelations == null) {
1263
            this.hybridParentRelations = new HashSet<>();
1264
        }
1265
        return hybridParentRelations;
1266
    }
1267

    
1268
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1269
        this.hybridParentRelations = hybridParentRelations;
1270
    }
1271

    
1272

    
1273
    /**
1274
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1275
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
1276
     *
1277
     * @see    #getHybridRelationships()
1278
     * @see    #getParentRelationships()
1279
     * @see    HybridRelationshipType
1280
     */
1281
    @Override
1282
    public Set<HybridRelationship> getHybridChildRelations() {
1283
        if(hybridChildRelations == null) {
1284
            this.hybridChildRelations = new HashSet<>();
1285
        }
1286
        return hybridChildRelations;
1287
    }
1288

    
1289
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1290
        this.hybridChildRelations = hybridChildRelations;
1291
    }
1292

    
1293
    @Override
1294
    public boolean isProtectedFullTitleCache() {
1295
        return protectedFullTitleCache;
1296
    }
1297

    
1298
    @Override
1299
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1300
        this.protectedFullTitleCache = protectedFullTitleCache;
1301
    }
1302

    
1303
    /**
1304
     * Returns the boolean value of the flag indicating whether the name of <i>this</i>
1305
     * botanical taxon name is a hybrid formula (true) or not (false). A hybrid
1306
     * named by a hybrid formula (composed with its parent names by placing the
1307
     * multiplication sign between them) does not have an own published name
1308
     * and therefore has neither an {@link NonViralName#getAuthorshipCache() autorship}
1309
     * nor other name components. If this flag is set no other hybrid flags may
1310
     * be set.
1311
     *
1312
     * @return  the boolean value of the isHybridFormula flag
1313
     * @see     #isMonomHybrid()
1314
     * @see     #isBinomHybrid()
1315
     * @see     #isTrinomHybrid()
1316
     */
1317
    @Override
1318
    public boolean isHybridFormula(){
1319
        return this.hybridFormula;
1320
    }
1321

    
1322
    /**
1323
     * @see  #isHybridFormula()
1324
     */
1325
    @Override
1326
    public void setHybridFormula(boolean hybridFormula){
1327
        this.hybridFormula = hybridFormula;
1328
    }
1329

    
1330
    /**
1331
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1332
     * taxon name is the name of an intergeneric hybrid (true) or not (false).
1333
     * In this case the multiplication sign is placed before the scientific
1334
     * name. If this flag is set no other hybrid flags may be set.
1335
     *
1336
     * @return  the boolean value of the isMonomHybrid flag
1337
     * @see     #isHybridFormula()
1338
     * @see     #isBinomHybrid()
1339
     * @see     #isTrinomHybrid()
1340
     */
1341
    @Override
1342
    public boolean isMonomHybrid(){
1343
        return this.monomHybrid;
1344
    }
1345

    
1346
    /**
1347
     * @see  #isMonomHybrid()
1348
     * @see  #isBinomHybrid()
1349
     * @see  #isTrinomHybrid()
1350
     */
1351
    @Override
1352
    public void setMonomHybrid(boolean monomHybrid){
1353
        this.monomHybrid = monomHybrid;
1354
    }
1355

    
1356
    /**
1357
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1358
     * taxon name is the name of an interspecific hybrid (true) or not (false).
1359
     * In this case the multiplication sign is placed before the species
1360
     * epithet. If this flag is set no other hybrid flags may be set.
1361
     *
1362
     * @return  the boolean value of the isBinomHybrid flag
1363
     * @see     #isHybridFormula()
1364
     * @see     #isMonomHybrid()
1365
     * @see     #isTrinomHybrid()
1366
     */
1367
    @Override
1368
    public boolean isBinomHybrid(){
1369
        return this.binomHybrid;
1370
    }
1371

    
1372
    /**
1373
     * @see  #isBinomHybrid()
1374
     * @see  #isMonomHybrid()
1375
     * @see  #isTrinomHybrid()
1376
     */
1377
    @Override
1378
    public void setBinomHybrid(boolean binomHybrid){
1379
        this.binomHybrid = binomHybrid;
1380
    }
1381

    
1382
    /**
1383
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1384
     * taxon name is the name of an infraspecific hybrid (true) or not (false).
1385
     * In this case the term "notho-" (optionally abbreviated "n-") is used as
1386
     * a prefix to the term denoting the infraspecific rank of <i>this</i> botanical
1387
     * taxon name. If this flag is set no other hybrid flags may be set.
1388
     *
1389
     * @return  the boolean value of the isTrinomHybrid flag
1390
     * @see     #isHybridFormula()
1391
     * @see     #isMonomHybrid()
1392
     * @see     #isBinomHybrid()
1393
     */
1394
    @Override
1395
    public boolean isTrinomHybrid(){
1396
        return this.trinomHybrid;
1397
    }
1398

    
1399
    /**
1400
     * @see  #isTrinomHybrid()
1401
     * @see  #isBinomHybrid()
1402
     * @see  #isMonomHybrid()
1403
     */
1404
    @Override
1405
    public void setTrinomHybrid(boolean trinomHybrid){
1406
        this.trinomHybrid = trinomHybrid;
1407
    }
1408

    
1409
    // ****************** VIRAL NAME ******************/
1410

    
1411
    /**
1412
     * Returns the accepted acronym (an assigned abbreviation) string for <i>this</i>
1413
     * viral taxon name. For instance PCV stays for Peanut Clump Virus.
1414
     *
1415
     * @return  the string containing the accepted acronym of <i>this</i> viral taxon name
1416
     */
1417
    @Override
1418
    public String getAcronym(){
1419
        return this.acronym;
1420
    }
1421

    
1422
    /**
1423
     * @see  #getAcronym()
1424
     */
1425
    @Override
1426
    public void setAcronym(String acronym){
1427
        this.acronym = StringUtils.isBlank(acronym)? null : acronym;
1428
    }
1429

    
1430
    // ****************** BACTERIAL NAME ******************/
1431

    
1432
    /**
1433
     * Returns the string containing the authorship with the year and details
1434
     * of the reference in which the subgenus included in the scientific name
1435
     * of <i>this</i> bacterial taxon name was published.
1436
     * For instance if the bacterial taxon name is
1437
     * 'Bacillus (subgen. Aerobacillus Donker 1926, 128) polymyxa' the subgenus
1438
     * authorship string is 'Donker 1926, 128'.
1439
     *
1440
     * @return  the string containing the complete subgenus' authorship
1441
     *          included in <i>this</i> bacterial taxon name
1442
     */
1443
    @Override
1444
    public String getSubGenusAuthorship(){
1445
        return this.subGenusAuthorship;
1446
    }
1447

    
1448
    /**
1449
     * @see  #getSubGenusAuthorship()
1450
     */
1451
    @Override
1452
    public void setSubGenusAuthorship(String subGenusAuthorship){
1453
        this.subGenusAuthorship = subGenusAuthorship;
1454
    }
1455

    
1456
    /**
1457
     * Returns the string representing the reason for the approbation of <i>this</i>
1458
     * bacterial taxon name. Bacterial taxon names are valid or approved
1459
     * according to:
1460
     * <ul>
1461
     * <li>the approved list, c.f.r. IJSB 1980 (AL)
1462
     * <li>the validation list, in IJSB after 1980 (VL)
1463
     * </ul>
1464
     * or
1465
     * <ul>
1466
     * <li>are validly published as paper in IJSB after 1980 (VP).
1467
     * </ul>
1468
     * IJSB is the acronym for International Journal of Systematic Bacteriology.
1469
     *
1470
     * @return  the string with the source of the approbation for <i>this</i> bacterial taxon name
1471
     */
1472
    @Override
1473
    public String getNameApprobation(){
1474
        return this.nameApprobation;
1475
    }
1476

    
1477
    /**
1478
     * @see  #getNameApprobation()
1479
     */
1480
    @Override
1481
    public void setNameApprobation(String nameApprobation){
1482
        this.nameApprobation = nameApprobation;
1483
    }
1484

    
1485
    //************ Zoological Name
1486

    
1487
    /**
1488
     * Returns the breed name string for <i>this</i> animal (zoological taxon name).
1489
     *
1490
     * @return  the string containing the breed name for <i>this</i> zoological taxon name
1491
     */
1492
    @Override
1493
    public String getBreed(){
1494
        return this.breed;
1495
    }
1496
    /**
1497
     * @see  #getBreed()
1498
     */
1499
    @Override
1500
    public void setBreed(String breed){
1501
        this.breed = StringUtils.isBlank(breed) ? null : breed;
1502
    }
1503

    
1504
    /**
1505
     * Returns the publication year (as an integer) for <i>this</i> zoological taxon
1506
     * name. If the publicationYear attribute is null and a nomenclatural
1507
     * reference exists the year could be computed from the
1508
     * {@link eu.etaxonomy.cdm.reference.INomenclaturalReference nomenclatural reference}.
1509
     *
1510
     * @return  the integer representing the publication year for <i>this</i> zoological taxon name
1511
     * @see     #getOriginalPublicationYear()
1512
     */
1513
    @Override
1514
    public Integer getPublicationYear() {
1515
        return publicationYear;
1516
    }
1517
    /**
1518
     * @see  #getPublicationYear()
1519
     */
1520
    @Override
1521
    public void setPublicationYear(Integer publicationYear) {
1522
        this.publicationYear = publicationYear;
1523
    }
1524

    
1525
    /**
1526
     * Returns the publication year (as an integer) of the original validly
1527
     * published species epithet for <i>this</i> zoological taxon name. This only
1528
     * applies for zoological taxon names that are no {@link TaxonNameBase#isOriginalCombination() original combinations}.
1529
     * If the originalPublicationYear attribute is null the year could be taken
1530
     * from the publication year of the corresponding original name (basionym)
1531
     * or from the {@link eu.etaxonomy.cdm.reference.INomenclaturalReference nomenclatural reference} of the basionym
1532
     * if it exists.
1533
     *
1534
     * @return  the integer representing the publication year of the original
1535
     *          species epithet corresponding to <i>this</i> zoological taxon name
1536
     * @see     #getPublicationYear()
1537
     */
1538
    @Override
1539
    public Integer getOriginalPublicationYear() {
1540
        return originalPublicationYear;
1541
    }
1542
    /**
1543
     * @see  #getOriginalPublicationYear()
1544
     */
1545
    @Override
1546
    public void setOriginalPublicationYear(Integer originalPublicationYear) {
1547
        this.originalPublicationYear = originalPublicationYear;
1548
    }
1549

    
1550

    
1551
// **************** ADDER / REMOVE *************************/
1552

    
1553
    /**
1554
     * Adds the given {@link HybridRelationship hybrid relationship} to the set
1555
     * of {@link #getHybridRelationships() hybrid relationships} of both non-viral names
1556
     * involved in this hybrid relationship. One of both non-viral names
1557
     * must be <i>this</i> non-viral name otherwise no addition will be carried
1558
     * out. The {@link eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedTo() child
1559
     * non viral taxon name} must be a hybrid, which means that one of its four hybrid flags must be set.
1560
     *
1561
     * @param relationship  the hybrid relationship to be added
1562
     * @see                 #isHybridFormula()
1563
     * @see                 #isMonomHybrid()
1564
     * @see                 #isBinomHybrid()
1565
     * @see                 #isTrinomHybrid()
1566
     * @see                 #getHybridRelationships()
1567
     * @see                 #getParentRelationships()
1568
     * @see                 #getChildRelationships()
1569
     * @see                 #addRelationship(RelationshipBase)
1570
     * @throws              IllegalArgumentException
1571
     */
1572
    protected void addHybridRelationship(HybridRelationship rel) {
1573
        if (rel!=null && rel.getHybridName().equals(this)){
1574
            this.hybridChildRelations.add(rel);
1575
        }else if(rel!=null && rel.getParentName().equals(this)){
1576
            this.hybridParentRelations.add(rel);
1577
        }else{
1578
            throw new IllegalArgumentException("Hybrid relationship is either null or the relationship does not reference this name");
1579
        }
1580
    }
1581

    
1582

    
1583
    /**
1584
     * Removes one {@link HybridRelationship hybrid relationship} from the set of
1585
     * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1586
     * is involved. The hybrid relationship will also be removed from the set
1587
     * belonging to the second botanical taxon name involved.
1588
     *
1589
     * @param  relationship  the hybrid relationship which should be deleted from the corresponding sets
1590
     * @see                  #getHybridRelationships()
1591
     */
1592
    @Override
1593
    public void removeHybridRelationship(HybridRelationship hybridRelation) {
1594
        if (hybridRelation == null) {
1595
            return;
1596
        }
1597

    
1598
        TaxonNameBase<?,?> parent = hybridRelation.getParentName();
1599
        TaxonNameBase<?,?> child = hybridRelation.getHybridName();
1600
        if (this.equals(parent)){
1601
            this.hybridParentRelations.remove(hybridRelation);
1602
            child.hybridChildRelations.remove(hybridRelation);
1603
            hybridRelation.setHybridName(null);
1604
            hybridRelation.setParentName(null);
1605
        }
1606
        if (this.equals(child)){
1607
            parent.hybridParentRelations.remove(hybridRelation);
1608
            this.hybridChildRelations.remove(hybridRelation);
1609
            hybridRelation.setHybridName(null);
1610
            hybridRelation.setParentName(null);
1611
        }
1612
    }
1613

    
1614
//********* METHODS **************************************/
1615

    
1616
    @Override
1617
    public String generateFullTitle(){
1618
        if (cacheStrategy == null){
1619
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1620
            return null;
1621
        }else{
1622
            return cacheStrategy.getFullTitleCache(this);
1623
        }
1624
    }
1625

    
1626

    
1627
    @Override
1628
    public void setFullTitleCache(String fullTitleCache){
1629
        setFullTitleCache(fullTitleCache, PROTECTED);
1630
    }
1631

    
1632
    @Override
1633
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1634
        fullTitleCache = getTruncatedCache(fullTitleCache);
1635
        this.fullTitleCache = fullTitleCache;
1636
        this.setProtectedFullTitleCache(protectCache);
1637
    }
1638

    
1639
    /**
1640
      * Needs to be implemented by those classes that handle autonyms (e.g. botanical names).
1641
      **/
1642
    @Override
1643
    @Transient
1644
    public boolean isAutonym(){
1645
        return false;
1646
    }
1647

    
1648

    
1649
    /**
1650
     * Returns the boolean value "false" since the components of <i>this</i> taxon name
1651
     * cannot follow the rules of a corresponding {@link NomenclaturalCode nomenclatural code}
1652
     * which is not defined for this class. The nomenclature code depends on
1653
     * the concrete name subclass ({@link BacterialName BacterialName},
1654
     * {@link BotanicalName BotanicalName}, {@link CultivarPlantName CultivarPlantName} or
1655
     * {@link ZoologicalName ZoologicalName} to which <i>this</i> non taxon name.
1656
     *
1657
     * @return  false
1658
     */
1659
    @Override
1660
    @Transient
1661
    public boolean isCodeCompliant() {
1662
        //FIXME
1663
        logger.warn("is CodeCompliant not implemented for TaxonNameBase");
1664
        return false;
1665
    }
1666

    
1667
    @Override
1668
    @Transient
1669
    public List<TaggedText> getTaggedName(){
1670
        return getCacheStrategy().getTaggedTitle(this);
1671
    }
1672

    
1673
    @Override
1674
    @Transient
1675
    public String getFullTitleCache(){
1676
        if (protectedFullTitleCache){
1677
            return this.fullTitleCache;
1678
        }
1679
        updateAuthorshipCache();
1680
        if (fullTitleCache == null ){
1681
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1682
        }
1683
        return fullTitleCache;
1684
    }
1685

    
1686

    
1687
    @Override
1688
    public String getTitleCache(){
1689
        if(!protectedTitleCache) {
1690
            updateAuthorshipCache();
1691
        }
1692
        return super.getTitleCache();
1693
    }
1694

    
1695
    @Override
1696
    public void setTitleCache(String titleCache, boolean protectCache){
1697
        super.setTitleCache(titleCache, protectCache);
1698
    }
1699

    
1700
    /**
1701
     * Returns the concatenated and formated authorteams string including
1702
     * basionym and combination authors of <i>this</i> non viral taxon name.
1703
     * If the protectedAuthorshipCache flag is set this method returns the
1704
     * string stored in the the authorshipCache attribute, otherwise it
1705
     * generates the complete authorship string, returns it and stores it in
1706
     * the authorshipCache attribute.
1707
     *
1708
     * @return  the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
1709
     * @see     #generateAuthorship()
1710
     */
1711
    @Override
1712
    @Transient
1713
    public String getAuthorshipCache() {
1714
        if (protectedAuthorshipCache){
1715
            return this.authorshipCache;
1716
        }
1717
        if (this.authorshipCache == null ){
1718
            this.authorshipCache = generateAuthorship();
1719
        }else{
1720
            //TODO get isDirty of authors, make better if possible
1721
            this.setAuthorshipCache(generateAuthorship(), protectedAuthorshipCache); //throw change event to inform higher caches
1722

    
1723
        }
1724
        return authorshipCache;
1725
    }
1726

    
1727

    
1728

    
1729
    /**
1730
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
1731
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
1732
     * @return
1733
     */
1734
    private void updateAuthorshipCache() {
1735
        //updates the authorship cache if necessary and via the listener updates all higher caches
1736
        if (protectedAuthorshipCache == false){
1737
            String oldCache = this.authorshipCache;
1738
            String newCache = this.getAuthorshipCache();
1739
            if ( (oldCache == null && newCache != null)  ||  CdmUtils.nullSafeEqual(oldCache,newCache)){
1740
                this.setAuthorshipCache(this.getAuthorshipCache(), false);
1741
            }
1742
        }
1743
    }
1744

    
1745

    
1746
    /**
1747
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1748
     * flag to <code>true</code>.
1749
     *
1750
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1751
     * @see    #getAuthorshipCache()
1752
     */
1753
    @Override
1754
    public void setAuthorshipCache(String authorshipCache) {
1755
        setAuthorshipCache(authorshipCache, true);
1756
    }
1757

    
1758

    
1759
    /**
1760
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1761
     *
1762
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1763
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1764
     * the flag is set to <code>false</code>.
1765
     * @see    #getAuthorshipCache()
1766
     */
1767
    @Override
1768
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1769
        this.authorshipCache = authorshipCache;
1770
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1771
    }
1772

    
1773
    /**
1774
     * Generates and returns a concatenated and formated authorteams string
1775
     * including basionym and combination authors of <i>this</i> non viral taxon name
1776
     * according to the strategy defined in
1777
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonNameBase) INonViralNameCacheStrategy}.
1778
     *
1779
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1780
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonNameBase)
1781
     */
1782
    @Override
1783
    public String generateAuthorship(){
1784
        if (cacheStrategy == null){
1785
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1786
            return null;
1787
        }else{
1788
            return ((INonViralNameCacheStrategy)cacheStrategy).getAuthorshipCache(this);
1789
        }
1790
    }
1791

    
1792

    
1793

    
1794
    /**
1795
     * Tests if the given name has any authors.
1796
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1797
     */
1798
    @Override
1799
    public boolean hasAuthors() {
1800
        return (this.getCombinationAuthorship() != null ||
1801
                this.getExCombinationAuthorship() != null ||
1802
                this.getBasionymAuthorship() != null ||
1803
                this.getExBasionymAuthorship() != null);
1804
    }
1805

    
1806
    /**
1807
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1808
     * @return
1809
     */
1810
    @Override
1811
    public String computeCombinationAuthorNomenclaturalTitle() {
1812
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1813
    }
1814

    
1815
    /**
1816
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1817
     * @return
1818
     */
1819
    @Override
1820
    public String computeBasionymAuthorNomenclaturalTitle() {
1821
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1822
    }
1823

    
1824

    
1825
    /**
1826
     * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1827
     * @return
1828
     */
1829
    @Override
1830
    public String computeExCombinationAuthorNomenclaturalTitle() {
1831
        return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1832
    }
1833

    
1834
    /**
1835
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1836
     * @return
1837
     */
1838
    @Override
1839
    public String computeExBasionymAuthorNomenclaturalTitle() {
1840
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1841
    }
1842

    
1843
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1844
        if (author == null){
1845
            return null;
1846
        }else{
1847
            return author.getNomenclaturalTitle();
1848
        }
1849
    }
1850

    
1851
    /**
1852
     * Returns the set of all {@link NameRelationship name relationships}
1853
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1854
     * in some name relationships or target in some others.
1855
     *
1856
     * @see    #getRelationsToThisName()
1857
     * @see    #getRelationsFromThisName()
1858
     * @see    #addNameRelationship(NameRelationship)
1859
     * @see    #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1860
     * @see    #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1861
     */
1862
    @Override
1863
    @Transient
1864
    public Set<NameRelationship> getNameRelations() {
1865
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1866
        rels.addAll(getRelationsFromThisName());
1867
        rels.addAll(getRelationsToThisName());
1868
        return rels;
1869
    }
1870

    
1871
    /**
1872
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1873
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1874
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1875
     *
1876
     * @param toName		  the taxon name of the target for this new name relationship
1877
     * @param type			  the type of this new name relationship
1878
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1879
     * @see    				  #getRelationsToThisName()
1880
     * @see    				  #getNameRelations()
1881
     * @see    				  #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1882
     * @see    				  #addNameRelationship(NameRelationship)
1883
     */
1884
    @Override
1885
    public void addRelationshipToName(TaxonNameBase toName, NameRelationshipType type, String ruleConsidered){
1886
        addRelationshipToName(toName, type, null, null, ruleConsidered);
1887
        //		NameRelationship rel = new NameRelationship(toName, this, type, ruleConsidered);
1888
    }
1889

    
1890
    /**
1891
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1892
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1893
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1894
     *
1895
     * @param toName		  the taxon name of the target for this new name relationship
1896
     * @param type			  the type of this new name relationship
1897
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1898
     * @return
1899
     * @see    				  #getRelationsToThisName()
1900
     * @see    				  #getNameRelations()
1901
     * @see    				  #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1902
     * @see    				  #addNameRelationship(NameRelationship)
1903
     */
1904
    @Override
1905
    public NameRelationship addRelationshipToName(TaxonNameBase toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1906
        if (toName == null){
1907
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1908
        }
1909
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered);
1910
        return rel;
1911
    }
1912

    
1913
    /**
1914
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1915
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1916
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1917
     *
1918
     * @param fromName		  the taxon name of the source for this new name relationship
1919
     * @param type			  the type of this new name relationship
1920
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1921
     * @param citation		  the reference in which this relation was described
1922
     * @param microCitation	  the reference detail for this relation (e.g. page)
1923
     * @see    				  #getRelationsFromThisName()
1924
     * @see    				  #getNameRelations()
1925
     * @see    				  #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1926
     * @see    				  #addNameRelationship(NameRelationship)
1927
     */
1928
    @Override
1929
    public NameRelationship addRelationshipFromName(TaxonNameBase fromName, NameRelationshipType type, String ruleConsidered){
1930
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1931
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
1932
    }
1933
    /**
1934
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1935
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1936
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1937
     *
1938
     * @param fromName		  the taxon name of the source for this new name relationship
1939
     * @param type			  the type of this new name relationship
1940
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1941
     * @param citation		  the reference in which this relation was described
1942
     * @param microCitation	  the reference detail for this relation (e.g. page)
1943
     * @see    				  #getRelationsFromThisName()
1944
     * @see    				  #getNameRelations()
1945
     * @see    				  #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1946
     * @see    				  #addNameRelationship(NameRelationship)
1947
     */
1948
    @Override
1949
    public NameRelationship addRelationshipFromName(TaxonNameBase fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1950
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1951
    }
1952

    
1953
    /**
1954
     * Adds an existing {@link NameRelationship name relationship} either to the set of
1955
     * {@link #getRelationsToThisName() relations to <i>this</i> taxon name} or to the set of
1956
     * {@link #getRelationsFromThisName() relations from <i>this</i> taxon name}. If neither the
1957
     * source nor the target of the name relationship match with <i>this</i> taxon name
1958
     * no addition will be carried out.
1959
     *
1960
     * @param rel  the name relationship to be added to one of <i>this</i> taxon name's name relationships sets
1961
     * @see    	   #getNameRelations()
1962
     * @see    	   #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1963
     * @see    	   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1964
     */
1965
    protected void addNameRelationship(NameRelationship rel) {
1966
        if (rel != null ){
1967
            if (rel.getToName().equals(this)){
1968
                this.relationsToThisName.add(rel);
1969
            }else if(rel.getFromName().equals(this)){
1970
                this.relationsFromThisName.add(rel);
1971
            }
1972
            NameRelationshipType type = rel.getType();
1973
            if (type != null && ( type.isBasionymRelation() || type.isReplacedSynonymRelation() ) ){
1974
                rel.getFromName().mergeHomotypicGroups(rel.getToName());
1975
            }
1976
        }else{
1977
            throw new RuntimeException("NameRelationship is either null or the relationship does not reference this name");
1978
        }
1979
    }
1980
    /**
1981
     * Removes one {@link NameRelationship name relationship} from one of both sets of
1982
     * {@link #getNameRelations() name relationships} in which <i>this</i> taxon name is involved.
1983
     * The name relationship will also be removed from one of both sets belonging
1984
     * to the second taxon name involved. Furthermore the fromName and toName
1985
     * attributes of the name relationship object will be nullified.
1986
     *
1987
     * @param  nameRelation  the name relationship which should be deleted from one of both sets
1988
     * @see    				 #getNameRelations()
1989
     */
1990
    @Override
1991
    public void removeNameRelationship(NameRelationship nameRelation) {
1992

    
1993
        TaxonNameBase fromName = nameRelation.getFromName();
1994
        TaxonNameBase toName = nameRelation.getToName();
1995

    
1996
        if (nameRelation != null) {
1997
            nameRelation.setToName(null);
1998
            nameRelation.setFromName(null);
1999
        }
2000

    
2001
        if (fromName != null) {
2002
            fromName.removeNameRelationship(nameRelation);
2003
        }
2004

    
2005
        if (toName != null) {
2006
            toName.removeNameRelationship(nameRelation);
2007
        }
2008

    
2009
        this.relationsToThisName.remove(nameRelation);
2010
        this.relationsFromThisName.remove(nameRelation);
2011
    }
2012

    
2013
    @Override
2014
    public void removeRelationToTaxonName(TaxonNameBase toTaxonName) {
2015
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
2016
//		nameRelationships.addAll(this.getNameRelations());
2017
        nameRelationships.addAll(this.getRelationsFromThisName());
2018
        nameRelationships.addAll(this.getRelationsToThisName());
2019
        for(NameRelationship nameRelationship : nameRelationships) {
2020
            // remove name relationship from this side
2021
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
2022
                this.removeNameRelationship(nameRelationship);
2023
            }
2024
        }
2025
    }
2026

    
2027

    
2028
    /**
2029
     * If relation is of type NameRelationship, addNameRelationship is called;
2030
     * if relation is of type HybridRelationship addHybridRelationship is called,
2031
     * otherwise an IllegalArgumentException is thrown.
2032
     *
2033
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
2034
     * @see    	   		#addNameRelationship(NameRelationship)
2035
     * @see    	   		#getNameRelations()
2036
     * @see    	   		NameRelationship
2037
     * @see    	   		RelationshipBase
2038
     * @see             #addHybridRelationship(HybridRelationship)
2039

    
2040
     * @deprecated to be used by RelationshipBase only
2041
     */
2042
    @Deprecated
2043
    @Override
2044
    public void addRelationship(RelationshipBase relation) {
2045
        if (relation instanceof NameRelationship){
2046
            addNameRelationship((NameRelationship)relation);
2047

    
2048
        }else if (relation instanceof HybridRelationship){
2049
            addHybridRelationship((HybridRelationship)relation);
2050
        }else{
2051
            logger.warn("Relationship not of type NameRelationship!");
2052
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
2053
        }
2054
    }
2055

    
2056
    /**
2057
     * Returns the set of all {@link NameRelationship name relationships}
2058
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
2059
     *
2060
     * @see    #getNameRelations()
2061
     * @see    #getRelationsToThisName()
2062
     * @see    #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
2063
     */
2064
    @Override
2065
    public Set<NameRelationship> getRelationsFromThisName() {
2066
        if(relationsFromThisName == null) {
2067
            this.relationsFromThisName = new HashSet<>();
2068
        }
2069
        return relationsFromThisName;
2070
    }
2071

    
2072
    /**
2073
     * Returns the set of all {@link NameRelationship name relationships}
2074
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
2075
     *
2076
     * @see    #getNameRelations()
2077
     * @see    #getRelationsFromThisName()
2078
     * @see    #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
2079
     */
2080
    @Override
2081
    public Set<NameRelationship> getRelationsToThisName() {
2082
        if(relationsToThisName == null) {
2083
            this.relationsToThisName = new HashSet<>();
2084
        }
2085
        return relationsToThisName;
2086
    }
2087

    
2088
    /**
2089
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
2090
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
2091
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
2092
     * and the nomenclatural code rule considered.
2093
     *
2094
     * @see     NomenclaturalStatus
2095
     * @see     NomenclaturalStatusType
2096
     */
2097
    @Override
2098
    public Set<NomenclaturalStatus> getStatus() {
2099
        if(status == null) {
2100
            this.status = new HashSet<>();
2101
        }
2102
        return status;
2103
    }
2104

    
2105
    /**
2106
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
2107
     * to <i>this</i> taxon name's set of nomenclatural status.
2108
     *
2109
     * @param  nomStatus  the nomenclatural status to be added
2110
     * @see 			  #getStatus()
2111
     */
2112
    @Override
2113
    public void addStatus(NomenclaturalStatus nomStatus) {
2114
        this.status.add(nomStatus);
2115
    }
2116
    @Override
2117
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
2118
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
2119
        this.status.add(newStatus);
2120
        return newStatus;
2121
    }
2122

    
2123
    /**
2124
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
2125
     * Type and ruleConsidered attributes of the nomenclatural status object
2126
     * will be nullified.
2127
     *
2128
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
2129
     * @see     		  #getStatus()
2130
     */
2131
    @Override
2132
    public void removeStatus(NomenclaturalStatus nomStatus) {
2133
        //TODO to be implemented?
2134
        logger.warn("not yet fully implemented?");
2135
        this.status.remove(nomStatus);
2136
    }
2137

    
2138

    
2139
    /**
2140
     * Generates the composed name string of <i>this</i> non viral taxon name without author
2141
     * strings or year according to the strategy defined in
2142
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
2143
     * The result might be stored in {@link #getNameCache() nameCache} if the
2144
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
2145
     *
2146
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
2147
     * @see     #getNameCache()
2148
     */
2149
    protected String generateNameCache(){
2150
        if (cacheStrategy == null){
2151
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
2152
            return null;
2153
        }else{
2154
            return cacheStrategy.getNameCache(this);
2155
        }
2156
    }
2157

    
2158
    /**
2159
     * Returns or generates the nameCache (scientific name
2160
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
2161
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
2162
     * the string will be generated according to a defined strategy,
2163
     * otherwise the value of the actual nameCache string will be returned.
2164
     *
2165
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
2166
     * @see     #generateNameCache()
2167
     */
2168
    @Override
2169
    @Transient
2170
    public String getNameCache() {
2171
        if (protectedNameCache){
2172
            return this.nameCache;
2173
        }
2174
        // is title dirty, i.e. equal NULL?
2175
        if (nameCache == null){
2176
            this.nameCache = generateNameCache();
2177
        }
2178
        return nameCache;
2179
    }
2180

    
2181
    /**
2182
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
2183
     * Sets the protectedNameCache flag to <code>true</code>.
2184
     *
2185
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
2186
     * @see    #getNameCache()
2187
     */
2188
    @Override
2189
    public void setNameCache(String nameCache){
2190
        setNameCache(nameCache, true);
2191
    }
2192

    
2193
    /**
2194
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
2195
     * Sets the protectedNameCache flag to <code>true</code>.
2196
     *
2197
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
2198
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
2199
     * <code>false</code>
2200
     * @see    #getNameCache()
2201
     */
2202
    @Override
2203
    public void setNameCache(String nameCache, boolean protectedNameCache){
2204
        this.nameCache = nameCache;
2205
        this.setProtectedNameCache(protectedNameCache);
2206
    }
2207

    
2208

    
2209
    /**
2210
     * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
2211
     * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
2212
     * of any other taxon name. Returns "true", if a basionym or a replaced
2213
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
2214
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
2215
     * homotypical group).
2216
     */
2217
    @Override
2218
    @Transient
2219
    public boolean isOriginalCombination(){
2220
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
2221
        for (NameRelationship relation : relationsFromThisName) {
2222
            if (relation.getType().isBasionymRelation() ||
2223
                    relation.getType().isReplacedSynonymRelation()) {
2224
                return true;
2225
            }
2226
        }
2227
        return false;
2228
    }
2229

    
2230
    /**
2231
     * Indicates <i>this</i> taxon name is a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
2232
     * of any other taxon name. Returns "true", if a replaced
2233
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
2234
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
2235
     * homotypical group).
2236
     */
2237
    @Override
2238
    @Transient
2239
    public boolean isReplacedSynonym(){
2240
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
2241
        for (NameRelationship relation : relationsFromThisName) {
2242
            if (relation.getType().isReplacedSynonymRelation()) {
2243
                return true;
2244
            }
2245
        }
2246
        return false;
2247
    }
2248

    
2249
    /**
2250
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2251
     * The basionym of a taxon name is its epithet-bringing synonym.
2252
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2253
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2254
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2255
     *
2256
     * If more than one basionym exists one is choosen at radom.
2257
     *
2258
     * If no basionym exists null is returned.
2259
     */
2260
    @Override
2261
    @Transient
2262
    public TaxonNameBase getBasionym(){
2263
        Set<TaxonNameBase> basionyms = getBasionyms();
2264
        if (basionyms.size() == 0){
2265
            return null;
2266
        }else{
2267
            return basionyms.iterator().next();
2268
        }
2269
    }
2270

    
2271
    /**
2272
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2273
     * The basionym of a taxon name is its epithet-bringing synonym.
2274
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2275
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2276
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2277
     */
2278
    @Override
2279
    @Transient
2280
    public Set<TaxonNameBase> getBasionyms(){
2281
        Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
2282
        Set<NameRelationship> rels = this.getRelationsToThisName();
2283
        for (NameRelationship rel : rels){
2284
            if (rel.getType()!= null && rel.getType().isBasionymRelation()){
2285
                TaxonNameBase<?,?> basionym = rel.getFromName();
2286
                result.add(basionym);
2287
            }
2288
        }
2289
        return result;
2290
    }
2291

    
2292
    /**
2293
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2294
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2295
     * and to the basionym. The basionym cannot have itself a basionym.
2296
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2297
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2298
     *
2299
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2300
     * @see  				#getBasionym()
2301
     * @see  				#addBasionym(TaxonNameBase, String)
2302
     */
2303
    @Override
2304
    public void addBasionym(TaxonNameBase basionym){
2305
        addBasionym(basionym, null, null, null);
2306
    }
2307
    /**
2308
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2309
     * and keeps the nomenclatural rule considered for it. The basionym
2310
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2311
     * The basionym cannot have itself a basionym.
2312
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2313
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2314
     *
2315
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2316
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2317
     * @return
2318
     * @see  					#getBasionym()
2319
     * @see  					#addBasionym(TaxonNameBase)
2320
     */
2321
    @Override
2322
    public NameRelationship addBasionym(TaxonNameBase basionym, Reference citation, String microcitation, String ruleConsidered){
2323
        if (basionym != null){
2324
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
2325
        }else{
2326
            return null;
2327
        }
2328
    }
2329

    
2330
    /**
2331
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2332
     *
2333
     */
2334
    @Override
2335
    @Transient
2336
    public Set<TaxonNameBase> getReplacedSynonyms(){
2337
        Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
2338
        Set<NameRelationship> rels = this.getRelationsToThisName();
2339
        for (NameRelationship rel : rels){
2340
            if (rel.getType().isReplacedSynonymRelation()){
2341
                TaxonNameBase replacedSynonym = rel.getFromName();
2342
                result.add(replacedSynonym);
2343
            }
2344
        }
2345
        return result;
2346
    }
2347

    
2348
    /**
2349
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2350
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2351
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2352
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2353
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2354
     *
2355
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2356
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2357
     * @see  					#getBasionym()
2358
     * @see  					#addBasionym(TaxonNameBase)
2359
     */
2360
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2361
    @Override
2362
    public void addReplacedSynonym(TaxonNameBase replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
2363
        if (replacedSynonym != null){
2364
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
2365
        }
2366
    }
2367

    
2368
    /**
2369
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2370
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2371
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2372
     * previously used as basionym.
2373
     *
2374
     * @see   #getBasionym()
2375
     * @see   #addBasionym(TaxonNameBase)
2376
     */
2377
    @Override
2378
    public void removeBasionyms(){
2379
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
2380
        for (NameRelationship nameRelation : this.getRelationsToThisName()){
2381
            if (nameRelation.getType().isBasionymRelation()){
2382
                removeRelations.add(nameRelation);
2383
            }
2384
        }
2385
        // Removing relations from a set through which we are iterating causes a
2386
        // ConcurrentModificationException. Therefore, we delete the targeted
2387
        // relations in a second step.
2388
        for (NameRelationship relation : removeRelations){
2389
            this.removeNameRelationship(relation);
2390
        }
2391
    }
2392

    
2393
    /**
2394
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2395
     *
2396
     * @see 	Rank
2397
     */
2398
    @Override
2399
    public Rank getRank(){
2400
        return this.rank;
2401
    }
2402

    
2403
    /**
2404
     * @see  #getRank()
2405
     */
2406
    @Override
2407
    public void setRank(Rank rank){
2408
        this.rank = rank;
2409
    }
2410

    
2411
    /**
2412
     * Returns the {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} of <i>this</i> taxon name.
2413
     * The nomenclatural reference is here meant to be the one publication
2414
     * <i>this</i> taxon name was originally published in while fulfilling the formal
2415
     * requirements as specified by the corresponding {@link NomenclaturalCode nomenclatural code}.
2416
     *
2417
     * @see 	eu.etaxonomy.cdm.model.reference.INomenclaturalReference
2418
     * @see 	eu.etaxonomy.cdm.model.reference.Reference
2419
     */
2420
    @Override
2421
    public INomenclaturalReference getNomenclaturalReference(){
2422
        return this.nomenclaturalReference;
2423
    }
2424
    /**
2425
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2426
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2427
     * as it is obviously used for nomenclatural purposes.
2428
     *
2429
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2430
     * @see  #getNomenclaturalReference()
2431
     */
2432
    @Override
2433
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2434
        if(nomenclaturalReference != null){
2435
            if(!INomenclaturalReference.class.isAssignableFrom(nomenclaturalReference.getClass())){
2436
                throw new IllegalArgumentException("Parameter nomenclaturalReference is not assignable from INomenclaturalReference");
2437
            }
2438
            this.nomenclaturalReference = (Reference)nomenclaturalReference;
2439
        } else {
2440
            this.nomenclaturalReference = null;
2441
        }
2442
    }
2443

    
2444
    /**
2445
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2446
     * The appended phrase is a non-atomised addition to a name. It is
2447
     * not ruled by a nomenclatural code.
2448
     */
2449
    @Override
2450
    public String getAppendedPhrase(){
2451
        return this.appendedPhrase;
2452
    }
2453

    
2454
    /**
2455
     * @see  #getAppendedPhrase()
2456
     */
2457
    @Override
2458
    public void setAppendedPhrase(String appendedPhrase){
2459
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2460
    }
2461

    
2462
    /**
2463
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2464
     * to <i>this</i> taxon name. The details describe the exact localisation within
2465
     * the publication used as nomenclature reference. These are mostly
2466
     * (implicitly) pages but can also be figures or tables or any other
2467
     * element of a publication. A nomenclatural micro reference (details)
2468
     * requires the existence of a nomenclatural reference.
2469
     */
2470
    //Details of the nomenclatural reference (protologue).
2471
    @Override
2472
    public String getNomenclaturalMicroReference(){
2473
        return this.nomenclaturalMicroReference;
2474
    }
2475
    /**
2476
     * @see  #getNomenclaturalMicroReference()
2477
     */
2478
    @Override
2479
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2480
        this.nomenclaturalMicroReference = StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
2481
    }
2482

    
2483
    @Override
2484
    public int getParsingProblem(){
2485
        return this.parsingProblem;
2486
    }
2487

    
2488
    @Override
2489
    public void setParsingProblem(int parsingProblem){
2490
        this.parsingProblem = parsingProblem;
2491
    }
2492

    
2493
    @Override
2494
    public void addParsingProblem(ParserProblem problem){
2495
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2496
    }
2497

    
2498
    @Override
2499
    public void removeParsingProblem(ParserProblem problem) {
2500
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2501
    }
2502

    
2503
    /**
2504
     * @param warnings
2505
     */
2506
    @Override
2507
    public void addParsingProblems(int problems){
2508
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2509
    }
2510

    
2511
    @Override
2512
    public boolean hasProblem(){
2513
        return parsingProblem != 0;
2514
    }
2515

    
2516
    @Override
2517
    public boolean hasProblem(ParserProblem problem) {
2518
        return getParsingProblems().contains(problem);
2519
    }
2520

    
2521
    @Override
2522
    public int getProblemStarts(){
2523
        return this.problemStarts;
2524
    }
2525

    
2526
    @Override
2527
    public void setProblemStarts(int start) {
2528
        this.problemStarts = start;
2529
    }
2530

    
2531
    @Override
2532
    public int getProblemEnds(){
2533
        return this.problemEnds;
2534
    }
2535

    
2536
    @Override
2537
    public void setProblemEnds(int end) {
2538
        this.problemEnds = end;
2539
    }
2540

    
2541
//*********************** TYPE DESIGNATION *********************************************//
2542

    
2543
    /**
2544
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2545
     * to <i>this</i> taxon name.
2546
     * @see     NameTypeDesignation
2547
     * @see     SpecimenTypeDesignation
2548
     */
2549
    @Override
2550
    public Set<TypeDesignationBase> getTypeDesignations() {
2551
        if(typeDesignations == null) {
2552
            this.typeDesignations = new HashSet<TypeDesignationBase>();
2553
        }
2554
        return typeDesignations;
2555
    }
2556

    
2557
    /**
2558
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2559
     * <i>this</i> taxon name. The type designation itself will be nullified.
2560
     *
2561
     * @param  typeDesignation  the type designation which should be deleted
2562
     */
2563
    @Override
2564
    @SuppressWarnings("deprecation")
2565
    public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2566
        this.typeDesignations.remove(typeDesignation);
2567
        typeDesignation.removeTypifiedName(this);
2568
    }
2569

    
2570
    /**
2571
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2572
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2573
     * "species" or below. The specimen type designations include all the
2574
     * specimens on which the typification of this name is based (which are
2575
     * exclusively used to typify taxon names belonging to the same
2576
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2577
     * belongs) and eventually the status of these designations.
2578
     *
2579
     * @see     SpecimenTypeDesignation
2580
     * @see     NameTypeDesignation
2581
     * @see     HomotypicalGroup
2582
     */
2583
    @Override
2584
    @Transient
2585
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2586
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2587
    }
2588

    
2589
//*********************** NAME TYPE DESIGNATION *********************************************//
2590

    
2591
    /**
2592
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2593
     * to <i>this</i> taxon name the rank of which must be above "species".
2594
     * The name type designations include all the taxon names used to typify
2595
     * <i>this</i> taxon name and eventually the rejected or conserved status
2596
     * of these designations.
2597
     *
2598
     * @see     NameTypeDesignation
2599
     * @see     SpecimenTypeDesignation
2600
     */
2601
    @Override
2602
    @Transient
2603
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2604
        Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2605
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2606
            if (typeDesignation instanceof NameTypeDesignation){
2607
                result.add((NameTypeDesignation)typeDesignation);
2608
            }
2609
        }
2610
        return result;
2611
    }
2612

    
2613
    /**
2614
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2615
     * to <i>this</i> taxon name's set of type designations.
2616
     *
2617
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2618
     * @param  citation					the reference for this new designation
2619
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2620
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2621
     * @param  isRejectedType			the boolean status for a rejected name type designation
2622
     * @param  isConservedType			the boolean status for a conserved name type designation
2623
     * @param  isLectoType				the boolean status for a lectotype name type designation
2624
     * @param  isNotDesignated			the boolean status for a name type designation without name type
2625
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2626
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2627
     * @return
2628
     * @see 			  				#getNameTypeDesignations()
2629
     * @see 			  				NameTypeDesignation
2630
     * @see 			  				TypeDesignationBase#isNotDesignated()
2631
     */
2632
    @Override
2633
    public NameTypeDesignation addNameTypeDesignation(TaxonNameBase typeSpecies,
2634
                Reference citation,
2635
                String citationMicroReference,
2636
                String originalNameString,
2637
                NameTypeDesignationStatus status,
2638
                boolean isRejectedType,
2639
                boolean isConservedType,
2640
                /*boolean isLectoType, */
2641
                boolean isNotDesignated,
2642
                boolean addToAllHomotypicNames) {
2643
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalNameString, status, isRejectedType, isConservedType, isNotDesignated);
2644
        //nameTypeDesignation.setLectoType(isLectoType);
2645
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2646
        return nameTypeDesignation;
2647
    }
2648

    
2649
    /**
2650
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2651
     * to <i>this</i> taxon name's set of type designations.
2652
     *
2653
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2654
     * @param  citation					the reference for this new designation
2655
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2656
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2657
     * @param  status                   the name type designation status
2658
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2659
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2660
     * @return
2661
     * @see 			  				#getNameTypeDesignations()
2662
     * @see 			  				NameTypeDesignation
2663
     * @see 			  				TypeDesignationBase#isNotDesignated()
2664
     */
2665
    @Override
2666
    public NameTypeDesignation addNameTypeDesignation(TaxonNameBase typeSpecies,
2667
                Reference citation,
2668
                String citationMicroReference,
2669
                String originalNameString,
2670
                NameTypeDesignationStatus status,
2671
                boolean addToAllHomotypicNames) {
2672
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2673
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2674
        return nameTypeDesignation;
2675
    }
2676

    
2677
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2678

    
2679
    /**
2680
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2681
     * that typify <i>this</i> taxon name.
2682
     */
2683
    @Override
2684
    @Transient
2685
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2686
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2687
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2688
            if (typeDesignation instanceof SpecimenTypeDesignation){
2689
                result.add((SpecimenTypeDesignation)typeDesignation);
2690
            }
2691
        }
2692
        return result;
2693
    }
2694

    
2695

    
2696
    /**
2697
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2698
     * to <i>this</i> taxon name's set of type designations.
2699
     *
2700
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2701
     * @param  status					the specimen type designation status
2702
     * @param  citation					the reference for this new specimen type designation
2703
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2704
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2705
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2706
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2707
     * 									added to all taxon names of the homotypical group the typified
2708
     * 									taxon name belongs to
2709
     * @return
2710
     * @see 			  				#getSpecimenTypeDesignations()
2711
     * @see 			  				SpecimenTypeDesignationStatus
2712
     * @see 			  				SpecimenTypeDesignation
2713
     * @see 			  				TypeDesignationBase#isNotDesignated()
2714
     */
2715
    @Override
2716
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2717
                SpecimenTypeDesignationStatus status,
2718
                Reference citation,
2719
                String citationMicroReference,
2720
                String originalNameString,
2721
                boolean isNotDesignated,
2722
                boolean addToAllHomotypicNames) {
2723
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2724
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2725
        return specimenTypeDesignation;
2726
    }
2727

    
2728
    //used by merge strategy
2729
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2730
        return addTypeDesignation(typeDesignation, true);
2731
    }
2732

    
2733
    /**
2734
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2735
     *
2736
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2737
     * @param addToAllNames				the boolean indicating whether the type designation should be
2738
     * 									added to all taxon names of the homotypical group the typified
2739
     * 									taxon name belongs to
2740
     * @return							true if the operation was succesful
2741
     *
2742
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2743
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2744
     *
2745
     */
2746
    @Override
2747
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2748
        //currently typeDesignations are not persisted with the homotypical group
2749
        //so explicit adding to the homotypical group is not necessary.
2750
        if (typeDesignation != null){
2751
            checkHomotypicalGroup(typeDesignation);
2752
            this.typeDesignations.add(typeDesignation);
2753
            typeDesignation.addTypifiedName(this);
2754

    
2755
            if (addToAllNames){
2756
                for (TaxonNameBase taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2757
                    if (taxonName != this){
2758
                        taxonName.addTypeDesignation(typeDesignation, false);
2759
                    }
2760
                }
2761
            }
2762
        }
2763
        return true;
2764
    }
2765

    
2766
    /**
2767
     * Throws an Exception this type designation already has typified names from another homotypical group.
2768
     * @param typeDesignation
2769
     */
2770
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2771
        if(typeDesignation.getTypifiedNames().size() > 0){
2772
            Set<HomotypicalGroup> groups = new HashSet<HomotypicalGroup>();
2773
            Set<TaxonNameBase> names = typeDesignation.getTypifiedNames();
2774
            for (TaxonNameBase taxonName: names){
2775
                groups.add(taxonName.getHomotypicalGroup());
2776
            }
2777
            if (groups.size() > 1){
2778
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2779
            }
2780
        }
2781
    }
2782

    
2783

    
2784

    
2785
//*********************** HOMOTYPICAL GROUP *********************************************//
2786

    
2787

    
2788
    /**
2789
     * Returns the {@link HomotypicalGroup homotypical group} to which
2790
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2791
     * that share the same types.
2792
     *
2793
     * @see 	HomotypicalGroup
2794
     */
2795

    
2796
    @Override
2797
    public HomotypicalGroup getHomotypicalGroup() {
2798
        if (homotypicalGroup == null){
2799
            homotypicalGroup = new HomotypicalGroup();
2800
            homotypicalGroup.typifiedNames.add(this);
2801
        }
2802
    	return homotypicalGroup;
2803
    }
2804

    
2805
    /**
2806
     * @see #getHomotypicalGroup()
2807
     */
2808
    @Override
2809
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2810
        if (homotypicalGroup == null){
2811
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2812
        }
2813
        /*if (this.homotypicalGroup != null){
2814
        	this.homotypicalGroup.removeTypifiedName(this, false);
2815
        }*/
2816
        this.homotypicalGroup = homotypicalGroup;
2817
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2818
        	 this.homotypicalGroup.addTypifiedName(this);
2819
        }
2820
    }
2821

    
2822

    
2823

    
2824
// *************************************************************************//
2825

    
2826
    /**
2827
     * Returns the complete string containing the
2828
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2829
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2830
     *
2831
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2832
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2833
     * @see		#getNomenclaturalReference()
2834
     * @see		#getNomenclaturalMicroReference()
2835
     */
2836
    @Override
2837
    @Transient
2838
    public String getCitationString(){
2839
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2840
    }
2841

    
2842
    /**
2843
     * Returns the parsing problems
2844
     * @return
2845
     */
2846
    @Override
2847
    public List<ParserProblem> getParsingProblems(){
2848
        return ParserProblem.warningList(this.parsingProblem);
2849
    }
2850

    
2851
    /**
2852
     * Returns the string containing the publication date (generally only year)
2853
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2854
     * no nomenclatural reference.
2855
     *
2856
     * @return  the string containing the publication date of <i>this</i> taxon name
2857
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2858
     */
2859
    @Override
2860
    @Transient
2861
    @ValidTaxonomicYear(groups=Level3.class)
2862
    public String getReferenceYear(){
2863
        if (this.getNomenclaturalReference() != null ){
2864
            return this.getNomenclaturalReference().getYear();
2865
        }else{
2866
            return null;
2867
        }
2868
    }
2869

    
2870
    /**
2871
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2872
     * In this context a taxon base means the use of a taxon name by a reference
2873
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2874
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2875
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2876
     * within a taxonomic treatment (identified by one reference).
2877
     *
2878
     * @see	#getTaxa()
2879
     * @see	#getSynonyms()
2880
     */
2881
    @Override
2882
    public Set<TaxonBase> getTaxonBases() {
2883
        if(taxonBases == null) {
2884
            this.taxonBases = new HashSet<TaxonBase>();
2885
        }
2886
        return this.taxonBases;
2887
    }
2888

    
2889
    /**
2890
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2891
     * to the set of taxon bases using <i>this</i> taxon name.
2892
     *
2893
     * @param  taxonBase  the taxon base to be added
2894
     * @see 			  #getTaxonBases()
2895
     * @see 			  #removeTaxonBase(TaxonBase)
2896
     */
2897
    //TODO protected
2898
    @Override
2899
    public void addTaxonBase(TaxonBase taxonBase){
2900
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonNameBase.class});
2901
        ReflectionUtils.makeAccessible(method);
2902
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2903
        taxonBases.add(taxonBase);
2904
    }
2905
    /**
2906
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2907
     *
2908
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2909
     * @see    				#getTaxonBases()
2910
     * @see    				#addTaxonBase(TaxonBase)
2911
     */
2912
    @Override
2913
    public void removeTaxonBase(TaxonBase taxonBase){
2914
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonNameBase.class});
2915
        ReflectionUtils.makeAccessible(method);
2916
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2917

    
2918

    
2919
    }
2920

    
2921
    /**
2922
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2923
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2924
     * the set returned by getTaxonBases().
2925
     *
2926
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2927
     * @see	#getTaxonBases()
2928
     * @see	#getSynonyms()
2929
     */
2930
    @Override
2931
    @Transient
2932
    public Set<Taxon> getTaxa(){
2933
        Set<Taxon> result = new HashSet<Taxon>();
2934
        for (TaxonBase taxonBase : this.taxonBases){
2935
            if (taxonBase instanceof Taxon){
2936
                result.add((Taxon)taxonBase);
2937
            }
2938
        }
2939
        return result;
2940
    }
2941

    
2942
    /**
2943
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2944
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2945
     * the set returned by getTaxonBases().
2946
     *
2947
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2948
     * @see	#getTaxonBases()
2949
     * @see	#getTaxa()
2950
     */
2951
    @Override
2952
    @Transient
2953
    public Set<Synonym> getSynonyms() {
2954
        Set<Synonym> result = new HashSet<Synonym>();
2955
        for (TaxonBase taxonBase : this.taxonBases){
2956
            if (taxonBase instanceof Synonym){
2957
                result.add((Synonym)taxonBase);
2958
            }
2959
        }
2960
        return result;
2961
    }
2962

    
2963
// ************* RELATIONSHIPS *****************************/
2964

    
2965

    
2966
    /**
2967
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2968
     * by title cache of the related names.
2969
     * @see #getHybridParentRelations()
2970
     */
2971
    @Override
2972
    @Transient
2973
    public List<HybridRelationship> getOrderedChildRelationships(){
2974
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2975
        result.addAll(this.hybridChildRelations);
2976
        Collections.sort(result);
2977
        Collections.reverse(result);
2978
        return result;
2979

    
2980
    }
2981

    
2982

    
2983
    /**
2984
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2985
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2986
     * "is first/second parent" or "is male/female parent". By invoking this
2987
     * method <i>this</i> botanical name becomes a hybrid child of the parent
2988
     * botanical name.
2989
     *
2990
     * @param parentName      the botanical name of the parent for this new hybrid name relationship
2991
     * @param type            the type of this new name relationship
2992
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2993
     * @return
2994
     * @see                   #addHybridChild(BotanicalName, HybridRelationshipType,String )
2995
     * @see                   #getRelationsToThisName()
2996
     * @see                   #getNameRelations()
2997
     * @see                   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
2998
     * @see                   #addNameRelationship(NameRelationship)
2999
     */
3000
    @Override
3001
    public HybridRelationship addHybridParent(NonViralName parentName, HybridRelationshipType type, String ruleConsidered){
3002
        return new HybridRelationship(this, parentName, type, ruleConsidered);
3003
    }
3004

    
3005
    /**
3006
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
3007
     * to <i>this</i> botanical name. A HybridRelationship may be of type
3008
     * "is first/second parent" or "is male/female parent". By invoking this
3009
     * method <i>this</i> botanical name becomes a parent of the hybrid child
3010
     * botanical name.
3011
     *
3012
     * @param childName       the botanical name of the child for this new hybrid name relationship
3013
     * @param type            the type of this new name relationship
3014
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
3015
     * @return
3016
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
3017
     * @see                   #getRelationsToThisName()
3018
     * @see                   #getNameRelations()
3019
     * @see                   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
3020
     * @see                   #addNameRelationship(NameRelationship)
3021
     */
3022
    @Override
3023
    public HybridRelationship addHybridChild(NonViralName childName, HybridRelationshipType type, String ruleConsidered){
3024
        return new HybridRelationship(childName, this, type, ruleConsidered);
3025
    }
3026

    
3027
    @Override
3028
    public void removeHybridChild(NonViralName child) {
3029
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
3030
        hybridRelationships.addAll(this.getHybridChildRelations());
3031
        hybridRelationships.addAll(this.getHybridParentRelations());
3032
        for(HybridRelationship hybridRelationship : hybridRelationships) {
3033
            // remove name relationship from this side
3034
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
3035
                this.removeHybridRelationship(hybridRelationship);
3036
            }
3037
        }
3038
    }
3039

    
3040
    @Override
3041
    public void removeHybridParent(NonViralName parent) {
3042
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
3043
        hybridRelationships.addAll(this.getHybridChildRelations());
3044
        hybridRelationships.addAll(this.getHybridParentRelations());
3045
        for(HybridRelationship hybridRelationship : hybridRelationships) {
3046
            // remove name relationship from this side
3047
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
3048
                this.removeHybridRelationship(hybridRelationship);
3049
            }
3050
        }
3051
    }
3052

    
3053

    
3054

    
3055
// *********** DESCRIPTIONS *************************************
3056

    
3057
    /**
3058
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
3059
     * to <i>this</i> taxon name. A taxon name description is a piece of information
3060
     * concerning the taxon name like for instance the content of its first
3061
     * publication (protolog) or a picture of this publication.
3062
     *
3063
     * @see	#addDescription(TaxonNameDescription)
3064
     * @see	#removeDescription(TaxonNameDescription)
3065
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
3066
     */
3067
    @Override
3068
    public Set<TaxonNameDescription> getDescriptions() {
3069
        return descriptions;
3070
    }
3071

    
3072
    /**
3073
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
3074
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
3075
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
3076
     * taxon name description itself will be replaced with <i>this</i> taxon name.
3077
     *
3078
     * @param  description  the taxon name description to be added
3079
     * @see					#getDescriptions()
3080
     * @see 			  	#removeDescription(TaxonNameDescription)
3081
     */
3082
    @Override
3083
    public void addDescription(TaxonNameDescription description) {
3084
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonNameBase.class);
3085
        ReflectionUtils.makeAccessible(field);
3086
        ReflectionUtils.setField(field, description, this);
3087
        descriptions.add(description);
3088
    }
3089
    /**
3090
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
3091
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
3092
     * of the description itself will be set to "null".
3093
     *
3094
     * @param  description  the taxon name description which should be removed
3095
     * @see     		  	#getDescriptions()
3096
     * @see     		  	#addDescription(TaxonNameDescription)
3097
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
3098
     */
3099
    @Override
3100
    public void removeDescription(TaxonNameDescription description) {
3101
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonNameBase.class);
3102
        ReflectionUtils.makeAccessible(field);
3103
        ReflectionUtils.setField(field, description, null);
3104
        descriptions.remove(description);
3105
    }
3106

    
3107
// *********** HOMOTYPIC GROUP METHODS **************************************************
3108

    
3109
    @Override
3110
    @Transient
3111
    public void mergeHomotypicGroups(TaxonNameBase name){
3112
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
3113
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
3114
        name.setHomotypicalGroup(this.homotypicalGroup);
3115
    }
3116

    
3117
    /**
3118
     * Returns the boolean value indicating whether a given taxon name belongs
3119
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
3120
     * or not (false). Returns "true" only if the homotypical groups of both
3121
     * taxon names exist and if they are identical.
3122
     *
3123
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
3124
     * @return  			   the boolean value of the check
3125
     * @see     			   HomotypicalGroup
3126
     */
3127
    @Override
3128
    @Transient
3129
    public boolean isHomotypic(TaxonNameBase homoTypicName) {
3130
        if (homoTypicName == null) {
3131
            return false;
3132
        }
3133
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
3134
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
3135
            return false;
3136
        }
3137
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
3138
            return true;
3139
        }
3140
        return false;
3141
    }
3142

    
3143

    
3144
    /**
3145
     * Checks whether name is a basionym for ALL names
3146
     * in its homotypical group.
3147
     * Returns <code>false</code> if there are no other names in the group
3148
     * @param name
3149
     * @return
3150
     */
3151
    @Override
3152
    @Transient
3153
    public boolean isGroupsBasionym() {
3154
    	if (homotypicalGroup == null){
3155
    		homotypicalGroup = HomotypicalGroup.NewInstance();
3156
    		homotypicalGroup.addTypifiedName(this);
3157
    	}
3158
        Set<TaxonNameBase> typifiedNames = homotypicalGroup.getTypifiedNames();
3159

    
3160
        // Check whether there are any other names in the group
3161
        if (typifiedNames.size() == 1) {
3162
                return false;
3163
        }
3164

    
3165
        boolean isBasionymToAll = true;
3166

    
3167
        for (TaxonNameBase taxonName : typifiedNames) {
3168
                if (!taxonName.equals(this)) {
3169
                        if (! isBasionymFor(taxonName)) {
3170
                                return false;
3171
                        }
3172
                }
3173
        }
3174
        return true;
3175
    }
3176

    
3177
    /**
3178
     * Checks whether a basionym relationship exists between fromName and toName.
3179
     *
3180
     * @param fromName
3181
     * @param toName
3182
     * @return
3183
     */
3184
    @Override
3185
    @Transient
3186
    public boolean isBasionymFor(TaxonNameBase newCombinationName) {
3187
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3188
            for (NameRelationship relation : relations) {
3189
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3190
                                    relation.getFromName().equals(this)) {
3191
                            return true;
3192
                    }
3193
            }
3194
            return false;
3195
    }
3196

    
3197
    /**
3198
     * Creates a basionym relationship to all other names in this names homotypical
3199
     * group.
3200
     *
3201
     * @see HomotypicalGroup.setGroupBasionym(TaxonNameBase basionymName)
3202
     */
3203
    @Override
3204
    @Transient
3205
    public void makeGroupsBasionym() {
3206
        this.homotypicalGroup.setGroupBasionym(this);
3207
    }
3208

    
3209

    
3210
//*********  Rank comparison shortcuts   ********************//
3211
    /**
3212
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3213
     * taxon name is higher than the genus rank (true) or not (false).
3214
     * Suprageneric non viral names are monomials.
3215
     * Returns false if rank is null.
3216
     *
3217
     * @see  #isGenus()
3218
     * @see  #isInfraGeneric()
3219
     * @see  #isSpecies()
3220
     * @see  #isInfraSpecific()
3221
     */
3222
    @Override
3223
    @Transient
3224
    public boolean isSupraGeneric() {
3225
        if (rank == null){
3226
            return false;
3227
        }
3228
        return getRank().isSupraGeneric();
3229
    }
3230
    /**
3231
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3232
     * taxon name is the genus rank (true) or not (false). Non viral names with
3233
     * genus rank are monomials. Returns false if rank is null.
3234
     *
3235
     * @see  #isSupraGeneric()
3236
     * @see  #isInfraGeneric()
3237
     * @see  #isSpecies()
3238
     * @see  #isInfraSpecific()
3239
     */
3240
    @Override
3241
    @Transient
3242
    public boolean isGenus() {
3243
        if (rank == null){
3244
            return false;
3245
        }
3246
        return getRank().isGenus();
3247
    }
3248
    /**
3249
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3250
     * taxon name is higher than the species rank and lower than the
3251
     * genus rank (true) or not (false). Infrageneric non viral names are
3252
     * binomials. Returns false if rank is null.
3253
     *
3254
     * @see  #isSupraGeneric()
3255
     * @see  #isGenus()
3256
     * @see  #isSpecies()
3257
     * @see  #isInfraSpecific()
3258
     */
3259
    @Override
3260
    @Transient
3261
    public boolean isInfraGeneric() {
3262
        if (rank == null){
3263
            return false;
3264
        }
3265
        return getRank().isInfraGeneric();
3266
    }
3267

    
3268
    /**
3269
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3270
     * taxon name is higher than the species rank (true) or not (false).
3271
     * Returns false if rank is null.
3272
     *
3273
     * @see  #isGenus()
3274
     * @see  #isInfraGeneric()
3275
     * @see  #isSpecies()
3276
     * @see  #isInfraSpecific()
3277
     */
3278
    @Override
3279
    @Transient
3280
    public boolean isSupraSpecific(){
3281
        if (rank == null) {
3282
            return false;
3283
        }
3284
        return getRank().isHigher(Rank.SPECIES());
3285
    }
3286

    
3287
    /**
3288
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3289
     * taxon name is the species rank (true) or not (false). Non viral names
3290
     * with species rank are binomials.
3291
     * Returns false if rank is null.
3292
     *
3293
     * @see  #isSupraGeneric()
3294
     * @see  #isGenus()
3295
     * @see  #isInfraGeneric()
3296
     * @see  #isInfraSpecific()
3297
     */
3298
    @Override
3299
    @Transient
3300
    public boolean isSpecies() {
3301
        if (rank == null){
3302
            return false;
3303
        }
3304
        return getRank().isSpecies();
3305
    }
3306
    /**
3307
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3308
     * taxon name is lower than the species rank (true) or not (false).
3309
     * Infraspecific non viral names are trinomials.
3310
     * Returns false if rank is null.
3311
     *
3312
     * @see  #isSupraGeneric()
3313
     * @see  #isGenus()
3314
     * @see  #isInfraGeneric()
3315
     * @see  #isSpecies()
3316
     */
3317
    @Override
3318
    @Transient
3319
    public boolean isInfraSpecific() {
3320
        if (rank == null){
3321
            return false;
3322
        }
3323
        return getRank().isInfraSpecific();
3324
    }
3325

    
3326
    /**
3327
     * Returns true if this name's rank indicates a rank that aggregates species like species
3328
     * aggregates or species groups, false otherwise. This methods currently returns false
3329
     * for all user defined ranks.
3330
     *
3331
     *@see Rank#isSpeciesAggregate()
3332
     *
3333
     * @return
3334
     */
3335
    @Override
3336
    @Transient
3337
    public boolean isSpeciesAggregate() {
3338
        if (rank == null){
3339
            return false;
3340
        }
3341
        return getRank().isSpeciesAggregate();
3342
    }
3343

    
3344

    
3345
    /**
3346
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3347
     * the construction of <i>this</i> taxon name since there is no specific
3348
     * nomenclatural code defined. The real implementention takes place in the
3349
     * subclasses {@link BacterialName BacterialName},
3350
     * {@link BotanicalName BotanicalName}, {@link CultivarPlantName CultivarPlantName} and
3351
     * {@link ZoologicalName ZoologicalName}. Each taxon name is governed by one
3352
     * and only one nomenclatural code.
3353
     *
3354
     * @return  null
3355
     * @see  	#isCodeCompliant()
3356
     * @see  	#getHasProblem()
3357
     */
3358
    @Override
3359
    public NomenclaturalCode getNomenclaturalCode() {
3360
        logger.warn("TaxonNameBase has no specific Code defined. Use subclasses");
3361
        return null;
3362
    }
3363

    
3364

    
3365
    /**
3366
     * Generates and returns the string with the scientific name of <i>this</i>
3367
     * taxon name (only non viral taxon names can be generated from their
3368
     * components). This string may be stored in the inherited
3369
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3370
     * This method overrides the generic and inherited
3371
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3372
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3373
     *
3374
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3375
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3376
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3377
     */
3378
//	@Override
3379
//	public abstract String generateTitle();
3380

    
3381
    /**
3382
     * Creates a basionym relationship between this name and
3383
     * 	each name in its homotypic group.
3384
     *
3385
     * @param basionymName
3386
     */
3387
    @Override
3388
    @Transient
3389
    public void setAsGroupsBasionym() {
3390

    
3391
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3392
        if (homotypicalGroup == null) {
3393
            return;
3394
        }
3395

    
3396
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3397
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3398

    
3399
        for(TaxonNameBase<?, ?> typifiedName : homotypicalGroup.getTypifiedNames()){
3400

    
3401
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3402

    
3403
            for(NameRelationship nameRelation : nameRelations){
3404
                relations.add(nameRelation);
3405
            }
3406
        }
3407

    
3408
        for (NameRelationship relation : relations) {
3409

    
3410
            // If this is a basionym relation, and toName is in the homotypical group,
3411
            //	remove the relationship.
3412
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3413
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3414
                removeRelations.add(relation);
3415
            }
3416
        }
3417

    
3418
        // Removing relations from a set through which we are iterating causes a
3419
        //	ConcurrentModificationException. Therefore, we delete the targeted
3420
        //	relations in a second step.
3421
        for (NameRelationship relation : removeRelations) {
3422
            this.removeNameRelationship(relation);
3423
        }
3424

    
3425
        for (TaxonNameBase<?, ?> name : homotypicalGroup.getTypifiedNames()) {
3426
            if (!name.equals(this)) {
3427

    
3428
                // First check whether the relationship already exists
3429
                if (!this.isBasionymFor(name)) {
3430

    
3431
                    // Then create it
3432
                    name.addRelationshipFromName(this,
3433
                            NameRelationshipType.BASIONYM(), null);
3434
                }
3435
            }
3436
        }
3437
    }
3438

    
3439
    /**
3440
     * Removes basionym relationship between this name and
3441
     * 	each name in its homotypic group.
3442
     *
3443
     * @param basionymName
3444
     */
3445
    @Override
3446
    @Transient
3447
    public void removeAsGroupsBasionym() {
3448

    
3449
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3450

    
3451
        if (homotypicalGroup == null) {
3452
            return;
3453
        }
3454

    
3455
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3456
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3457

    
3458
        for(TaxonNameBase<?, ?> typifiedName : homotypicalGroup.getTypifiedNames()){
3459

    
3460
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3461

    
3462
            for(NameRelationship nameRelation : nameRelations){
3463
                relations.add(nameRelation);
3464
            }
3465
        }
3466

    
3467
        for (NameRelationship relation : relations) {
3468

    
3469
            // If this is a basionym relation, and toName is in the homotypical group,
3470
            //	and fromName is basionymName, remove the relationship.
3471
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3472
                    relation.getFromName().equals(this) &&
3473
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3474
                removeRelations.add(relation);
3475
            }
3476
        }
3477

    
3478
        // Removing relations from a set through which we are iterating causes a
3479
        //	ConcurrentModificationException. Therefore, we delete the targeted
3480
        //	relations in a second step.
3481
        for (NameRelationship relation : removeRelations) {
3482
            this.removeNameRelationship(relation);
3483
        }
3484
    }
3485

    
3486

    
3487
    /**
3488
     * Defines the last part of the name.
3489
     * This is for infraspecific taxa, the infraspecific epithet,
3490
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3491
     * else the genusOrUninomial.
3492
     * However, the result does not depend on the rank (which may be not correctly set
3493
     * in case of dirty data) but returns the first name part which is not blank
3494
     * considering the above order.
3495
     * @return the first not blank name part in reverse order
3496
     */
3497
    @Override
3498
    public String getLastNamePart() {
3499
        String result =
3500
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3501
                    this.getInfraSpecificEpithet() :
3502
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3503
                    this.getSpecificEpithet():
3504
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3505
                    this.getInfraGenericEpithet():
3506
                this.getGenusOrUninomial();
3507
        return result;
3508
    }
3509

    
3510
// ********************** INTERFACES ********************************************/
3511

    
3512
    public static TaxonNameBase castAndDeproxy(ITaxonNameBase tnb){
3513
        return deproxy(tnb, TaxonNameBase.class);
3514
    }
3515

    
3516

    
3517
//*********************** CLONE ********************************************************/
3518

    
3519
    /**
3520
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3521
     * a new instance that differs only slightly from <i>this</i> taxon name by
3522
     * modifying only some of the attributes.<BR><BR>
3523
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3524
     * <b>The name gets a newly created homotypical group</b><BR>
3525
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3526
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3527
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3528
     *
3529
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3530
     * @see java.lang.Object#clone()
3531
     */
3532
    @Override
3533
    public Object clone() {
3534
        TaxonNameBase<?,?> result;
3535
        try {
3536
            result = (TaxonNameBase)super.clone();
3537

    
3538
            //taxonBases -> empty
3539
            result.taxonBases = new HashSet<TaxonBase>();
3540

    
3541
            //empty caches
3542
            if (! protectedFullTitleCache){
3543
                result.fullTitleCache = null;
3544
            }
3545

    
3546
            //descriptions
3547
            result.descriptions = new HashSet<TaxonNameDescription>();
3548
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3549
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3550
                result.descriptions.add(newDescription);
3551
            }
3552

    
3553
            //status
3554
            result.status = new HashSet<NomenclaturalStatus>();
3555
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3556
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3557
                result.status.add(newStatus);
3558
            }
3559

    
3560

    
3561
            //To Relations
3562
            result.relationsToThisName = new HashSet<NameRelationship>();
3563
            for (NameRelationship toRelationship : getRelationsToThisName()){
3564
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3565
                newRelationship.setRelatedTo(result);
3566
                result.relationsToThisName.add(newRelationship);
3567
            }
3568

    
3569
            //From Relations
3570
            result.relationsFromThisName = new HashSet<NameRelationship>();
3571
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3572
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3573
                newRelationship.setRelatedFrom(result);
3574
                result.relationsFromThisName.add(newRelationship);
3575
            }
3576

    
3577
            //type designations
3578
            result.typeDesignations = new HashSet<TypeDesignationBase>();
3579
            for (TypeDesignationBase typeDesignation : getTypeDesignations()){
3580
                TypeDesignationBase newDesignation = (TypeDesignationBase)typeDesignation.clone();
3581
                result.typeDesignations.add(newDesignation);
3582
                newDesignation.addTypifiedName(result);
3583
            }
3584

    
3585
            //homotypicalGroup
3586
            //TODO still needs to be discussed
3587
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3588
            result.homotypicalGroup.addTypifiedName(this);
3589

    
3590

    
3591
            //HybridChildRelations
3592
            result.hybridChildRelations = new HashSet<HybridRelationship>();
3593
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3594
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3595
                newChildRelationship.setRelatedTo(result);
3596
                result.hybridChildRelations.add(newChildRelationship);
3597
            }
3598

    
3599
            //HybridParentRelations
3600
            result.hybridParentRelations = new HashSet<HybridRelationship>();
3601
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3602
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3603
                newParentRelationship.setRelatedFrom(result);
3604
                result.hybridParentRelations.add(newParentRelationship);
3605
            }
3606

    
3607
            //empty caches
3608
            if (! protectedNameCache){
3609
                result.nameCache = null;
3610
            }
3611

    
3612
            //empty caches
3613
            if (! protectedAuthorshipCache){
3614
                result.authorshipCache = null;
3615
            }
3616

    
3617
            //no changes to: appendedPharse, nomenclaturalReference,
3618
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3619
            //protectedFullTitleCache, rank
3620
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3621
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3622
            //protectedAuthorshipCache, protectedNameCache,
3623
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3624
            //acronym
3625
            //subGenusAuthorship, nameApprobation
3626
            //anamorphic
3627
            return result;
3628
        } catch (CloneNotSupportedException e) {
3629
            logger.warn("Object does not implement cloneable");
3630
            e.printStackTrace();
3631
            return null;
3632
        }
3633

    
3634
    }
3635
}
(29-29/36)