Project

General

Profile

Download (139 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.annotations.Type;
53
import org.hibernate.envers.Audited;
54
import org.hibernate.search.annotations.Analyze;
55
import org.hibernate.search.annotations.Analyzer;
56
import org.hibernate.search.annotations.Field;
57
import org.hibernate.search.annotations.Fields;
58
import org.hibernate.search.annotations.Index;
59
import org.hibernate.search.annotations.Indexed;
60
import org.hibernate.search.annotations.IndexedEmbedded;
61
import org.hibernate.search.annotations.Store;
62
import org.hibernate.validator.constraints.NotEmpty;
63
import org.springframework.util.ReflectionUtils;
64

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

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

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

    
164
    "acronym",
165

    
166
    "subGenusAuthorship",
167
    "nameApprobation",
168

    
169
    "breed",
170
    "publicationYear",
171
    "originalPublicationYear",
172

    
173
    "anamorphic",
174

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

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

    
199

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

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

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

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

    
232
    @XmlElement(name = "AppendedPhrase")
233
    @Field
234
    @CacheUpdate(value ="nameCache")
235
    //TODO Val #3379
236
//    @NullOrNotEmpty
237
    @Column(length=255)
238
    private String appendedPhrase;
239
//
240
//    @XmlElement(name = "NomenclaturalMicroReference")
241
//    @Field
242
//    @CacheUpdate(noUpdate ="titleCache")
243
//    //TODO Val #3379
244
////    @NullOrNotEmpty
245
//    @Column(length=255)
246
//    private String nomenclaturalMicroReference;
247

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

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

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

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

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

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

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

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

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

    
322
    @XmlElement(name = "Rank")
323
    @XmlIDREF
324
    @XmlSchemaType(name = "IDREF")
325
    @ManyToOne(fetch = FetchType.EAGER)
326
    @CacheUpdate(value ="nameCache")
327
    //TODO Val #3379, handle maybe as groups = Level2.class ??
328
//    @NotNull
329
    @IndexedEmbedded(depth=1)
330
    private Rank rank;
331
//
332
//    @XmlElement(name = "NomenclaturalReference")
333
//    @XmlIDREF
334
//    @XmlSchemaType(name = "IDREF")
335
//    @ManyToOne(fetch = FetchType.LAZY)
336
//    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
337
//    @CacheUpdate(noUpdate ="titleCache")
338
//    @IndexedEmbedded
339
//    private Reference nomenclaturalReference;
340

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

    
350

    
351

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

    
362
//****** Non-ViralName attributes ***************************************/
363

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

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

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

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

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

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

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

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

    
439
    @XmlElement(name = "BasionymAuthorship", type = TeamOrPersonBase.class)
440
    @XmlIDREF
441
    @XmlSchemaType(name = "IDREF")
442
    @ManyToOne(fetch = FetchType.LAZY)
443
//    @Target(TeamOrPersonBase.class)
444
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
445
    @JoinColumn(name="basionymAuthorship_id")
446
    @CacheUpdate("authorshipCache")
447
    @IndexedEmbedded
448
    private TeamOrPersonBase<?> basionymAuthorship;
449

    
450
    @XmlElement(name = "ExBasionymAuthorship", type = TeamOrPersonBase.class)
451
    @XmlIDREF
452
    @XmlSchemaType(name = "IDREF")
453
    @ManyToOne(fetch = FetchType.LAZY)
454
//    @Target(TeamOrPersonBase.class)
455
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
456
    @JoinColumn(name="exBasionymAuthorship_id")
457
    @CacheUpdate("authorshipCache")
458
    @IndexedEmbedded
459
    private TeamOrPersonBase<?> exBasionymAuthorship;
460

    
461
    @XmlElement(name = "AuthorshipCache")
462
    @Fields({
463
        @Field(name = "authorshipCache_tokenized"),
464
        @Field(analyze = Analyze.NO)
465
    })
466
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
467
            cacheReplacedProperties={"combinationAuthorship", "basionymAuthorship", "exCombinationAuthorship", "exBasionymAuthorship"} )
468
    //TODO Val #3379
469
//    @NotNull
470
    @Column(length=255)
471
    @Pattern(regexp = "^[A-Za-z0-9 \\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-\\&\\,\\(\\)\\.]+$", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForAuthority.message}")
472
    private String authorshipCache;
473

    
474
    @XmlElement(name = "ProtectedAuthorshipCache")
475
    @CacheUpdate("authorshipCache")
476
    protected boolean protectedAuthorshipCache;
477

    
478
    @XmlElementWrapper(name = "HybridRelationsFromThisName")
479
    @XmlElement(name = "HybridRelationsFromThisName")
480
    @OneToMany(mappedBy="relatedFrom", fetch = FetchType.LAZY)
481
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
482
    @Merge(MergeMode.RELATION)
483
    @NotNull
484
    private Set<HybridRelationship> hybridParentRelations = new HashSet<>();
485

    
486
    @XmlElementWrapper(name = "HybridRelationsToThisName")
487
    @XmlElement(name = "HybridRelationsToThisName")
488
    @OneToMany(mappedBy="relatedTo", fetch = FetchType.LAZY, orphanRemoval=true) //a hybrid relation can be deleted automatically if the child is deleted.
489
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
490
    @Merge(MergeMode.RELATION)
491
    @NotNull
492
    private Set<HybridRelationship> hybridChildRelations = new HashSet<>();
493

    
494

    
495
    //if set: this name is a hybrid formula (a hybrid that does not have an own name) and no
496
    //other hybrid flags may be set. A
497
    //hybrid name  may not have either an authorteam nor other name components.
498
    @XmlElement(name ="IsHybridFormula")
499
    @CacheUpdate("nameCache")
500
    private boolean hybridFormula = false;
501

    
502
    @XmlElement(name ="IsMonomHybrid")
503
    @CacheUpdate("nameCache")
504
    private boolean monomHybrid = false;
505

    
506
    @XmlElement(name ="IsBinomHybrid")
507
    @CacheUpdate("nameCache")
508
    private boolean binomHybrid = false;
509

    
510
    @XmlElement(name ="IsTrinomHybrid")
511
    @CacheUpdate("nameCache")
512
    private boolean trinomHybrid = false;
513

    
514
// ViralName attributes ************************* /
515

    
516
    @XmlElement(name = "Acronym")
517
    @Field
518
    //TODO Val #3379
519
//  @NullOrNotEmpty
520
    @Column(length=255)
521
    private String acronym;
522

    
523
// BacterialName attributes ***********************/
524

    
525
    //Author team and year of the subgenus name
526
    @XmlElement(name = "SubGenusAuthorship")
527
    @Field
528
    private String subGenusAuthorship;
529

    
530
    //Approbation of name according to approved list, validation list, or validly published, paper in IJSB after 1980
531
    @XmlElement(name = "NameApprobation")
532
    @Field
533
    private String nameApprobation;
534

    
535
    //ZOOLOGICAL NAME
536

    
537
    //Name of the breed of an animal
538
    @XmlElement(name = "Breed")
539
    @Field
540
    @NullOrNotEmpty
541
    @Column(length=255)
542
    private String breed;
543

    
544
    @XmlElement(name = "PublicationYear")
545
    @Field(analyze = Analyze.NO)
546
    @CacheUpdate(value ="authorshipCache")
547
    @Min(0)
548
    private Integer publicationYear;
549

    
550
    @XmlElement(name = "OriginalPublicationYear")
551
    @Field(analyze = Analyze.NO)
552
    @CacheUpdate(value ="authorshipCache")
553
    @Min(0)
554
    private Integer originalPublicationYear;
555

    
556
    //Cultivar attribute(s)
557

    
558
    //the characteristical name of the cultivar
559
    @XmlElement(name = "CultivarName")
560
    //TODO Val #3379
561
    //@NullOrNotEmpty
562
    @Column(length=255)
563
    private String cultivarName;
564

    
565
    // ************** FUNGUS name attributes
566
    //to indicate that the type of the name is asexual or not
567
    @XmlElement(name ="IsAnamorphic")
568
    private boolean anamorphic = false;
569

    
570
// *************** FACTORY METHODS ********************************/
571

    
572
    //see TaxonNameFactory
573
    /**
574
     * @param code
575
     * @param rank
576
     * @param homotypicalGroup
577
     * @return
578
     */
579
    protected static TaxonName NewInstance(NomenclaturalCode code, Rank rank,
580
            HomotypicalGroup homotypicalGroup) {
581
        TaxonName result = new TaxonName(code, rank, homotypicalGroup);
582
        return result;
583
    }
584

    
585

    
586
    /**
587
     * @param icnafp
588
     * @param rank2
589
     * @param genusOrUninomial2
590
     * @param infraGenericEpithet2
591
     * @param specificEpithet2
592
     * @param infraSpecificEpithet2
593
     * @param combinationAuthorship2
594
     * @param nomenclaturalReference2
595
     * @param nomenclMicroRef
596
     * @param homotypicalGroup2
597
     * @return
598
     */
599
    public static TaxonName NewInstance(NomenclaturalCode code, Rank rank, String genusOrUninomial,
600
            String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet,
601
            TeamOrPersonBase combinationAuthorship, Reference nomenclaturalReference,
602
            String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
603
        TaxonName result = new TaxonName(code, rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
604
        return result;
605
    }
606

    
607

    
608
// ************* CONSTRUCTORS *************/
609
    /**
610
     * Class constructor: creates a new empty taxon name.
611
     * @param code
612
     *
613
     * @see #TaxonName(Rank)
614
     * @see #TaxonName(HomotypicalGroup)
615
     * @see #TaxonName(Rank, HomotypicalGroup)
616
     */
617
    protected TaxonName() {
618
        super();
619
        rectifyNameCacheStrategy();
620
    }
621

    
622

    
623
    /**
624
     * Class constructor: creates a new taxon name instance
625
     * only containing its {@link Rank rank} and
626
     * its {@link HomotypicalGroup homotypical group} and
627
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy default cache strategy}.
628
     * The new taxon name will be also added to the set of taxon names
629
     * belonging to this homotypical group.
630
     *
631
     * @param  rank  			 the rank to be assigned to <i>this</i> taxon name
632
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
633
     * @see    					 #TaxonName()
634
     * @see    					 #TaxonName(Rank)
635
     * @see    					 #TaxonName(HomotypicalGroup)
636
     */
637
    protected TaxonName(NomenclaturalCode type, Rank rank, HomotypicalGroup homotypicalGroup) {
638
        this();
639
        setNameType(type);
640
        this.setRank(rank);
641
        if (homotypicalGroup == null){
642
            homotypicalGroup = HomotypicalGroup.NewInstance();
643
        }
644
        homotypicalGroup.addTypifiedName(this);
645
        this.homotypicalGroup = homotypicalGroup;
646
    }
647

    
648

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

    
694

    
695
    /**
696
     * This method was originally needed to distinguish cache strategies
697
     * depending on the name type. Now we have a unified cache strategy
698
     * which does not require this anymore. Maybe we could even further remove this method.
699
     */
700
    private void rectifyNameCacheStrategy(){
701
        if (this.cacheStrategy == null){
702
            this.cacheStrategy = TaxonNameDefaultCacheStrategy.NewInstance();
703
        }
704
    }
705

    
706

    
707
    @Override
708
    public void initListener(){
709
        PropertyChangeListener listener = new PropertyChangeListener() {
710
            @Override
711
            public void propertyChange(PropertyChangeEvent e) {
712
                boolean protectedByLowerCache = false;
713
                //authorship cache
714
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
715
                    if (protectedAuthorshipCache){
716
                        protectedByLowerCache = true;
717
                    }else{
718
                        authorshipCache = null;
719
                    }
720
                }
721

    
722
                //nameCache
723
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "nameCache")){
724
                    if (protectedNameCache){
725
                        protectedByLowerCache = true;
726
                    }else{
727
                        nameCache = null;
728
                    }
729
                }
730
                //title cache
731
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "titleCache")){
732
                    if (isProtectedTitleCache()|| protectedByLowerCache == true ){
733
                        protectedByLowerCache = true;
734
                    }else{
735
                        titleCache = null;
736
                    }
737
                }
738
                //full title cache
739
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "fullTitleCache")){
740
                    if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
741
                        protectedByLowerCache = true;
742
                    }else{
743
                        fullTitleCache = null;
744
                    }
745
                }
746
            }
747
        };
748
        addPropertyChangeListener(listener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
749
    }
750

    
751
    private static Map<String, java.lang.reflect.Field> allFields = null;
752
    protected Map<String, java.lang.reflect.Field> getAllFields(){
753
        if (allFields == null){
754
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
755
        }
756
        return allFields;
757
    }
758

    
759
    /**
760
     * @param propertyName
761
     * @param string
762
     * @return
763
     */
764
    private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
765
        java.lang.reflect.Field field;
766
        try {
767
            field = getAllFields().get(propertyName);
768
            if (field != null){
769
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
770
                if (updateAnnotation != null){
771
                    for (String value : updateAnnotation.value()){
772
                        if (cacheName.equals(value)){
773
                            return true;
774
                        }
775
                    }
776
                }
777
            }
778
            return false;
779
        } catch (SecurityException e1) {
780
            throw e1;
781
        }
782
    }
783

    
784
    private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
785
        java.lang.reflect.Field field;
786
        //do not update fields with the same name
787
        if (cacheName.equals(propertyName)){
788
            return true;
789
        }
790
        //evaluate annotation
791
        try {
792
            field = getAllFields().get(propertyName);
793
            if (field != null){
794
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
795
                if (updateAnnotation != null){
796
                    for (String value : updateAnnotation.noUpdate()){
797
                        if (cacheName.equals(value)){
798
                            return true;
799
                        }
800
                    }
801
                }
802
            }
803
            return false;
804
        } catch (SecurityException e1) {
805
            throw e1;
806
        }
807
    }
808

    
809
// ****************** GETTER / SETTER ****************************/
810

    
811
    @Override
812
    public NomenclaturalCode getNameType() {
813
        return nameType;
814
    }
815

    
816
    @Override
817
    public void setNameType(NomenclaturalCode nameType) {
818
        this.nameType = nameType;
819
    }
820

    
821
    /**
822
     * Returns the boolean value of the flag intended to protect (true)
823
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
824
     * string of <i>this</i> non viral taxon name.
825
     *
826
     * @return  the boolean value of the protectedNameCache flag
827
     * @see     #getNameCache()
828
     */
829
    @Override
830
    public boolean isProtectedNameCache() {
831
        return protectedNameCache;
832
    }
833

    
834
    /**
835
     * @see     #isProtectedNameCache()
836
     */
837
    @Override
838
    public void setProtectedNameCache(boolean protectedNameCache) {
839
        this.protectedNameCache = protectedNameCache;
840
    }
841

    
842
    /**
843
     * Returns either the scientific name string (without authorship) for <i>this</i>
844
     * non viral taxon name if its rank is genus or higher (monomial) or the string for
845
     * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
846
     * Genus or uninomial strings begin with an upper case letter.
847
     *
848
     * @return  the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
849
     * @see     #getNameCache()
850
     */
851
    @Override
852
    public String getGenusOrUninomial() {
853
        return genusOrUninomial;
854
    }
855

    
856
    /**
857
     * @see  #getGenusOrUninomial()
858
     */
859
    @Override
860
    public void setGenusOrUninomial(String genusOrUninomial) {
861
        this.genusOrUninomial = StringUtils.isBlank(genusOrUninomial) ? null : genusOrUninomial;
862
    }
863

    
864
    /**
865
     * Returns the genus subdivision epithet string (infrageneric part) for
866
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
867
     * higher than species aggregate: binomial). Genus subdivision epithet
868
     * strings begin with an upper case letter.
869
     *
870
     * @return  the string containing the infrageneric part of <i>this</i> non viral taxon name
871
     * @see     #getNameCache()
872
     */
873
    @Override
874
    public String getInfraGenericEpithet(){
875
        return this.infraGenericEpithet;
876
    }
877

    
878
    /**
879
     * @see  #getInfraGenericEpithet()
880
     */
881
    @Override
882
    public void setInfraGenericEpithet(String infraGenericEpithet){
883
        this.infraGenericEpithet = StringUtils.isBlank(infraGenericEpithet)? null : infraGenericEpithet;
884
    }
885

    
886
    /**
887
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
888
     * species aggregate or lower (bi- or trinomial). Species epithet strings
889
     * begin with a lower case letter.
890
     *
891
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
892
     * @see     #getNameCache()
893
     */
894
    @Override
895
    public String getSpecificEpithet(){
896
        return this.specificEpithet;
897
    }
898

    
899
    /**
900
     * @see  #getSpecificEpithet()
901
     */
902
    @Override
903
    public void setSpecificEpithet(String specificEpithet){
904
        this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
905
    }
906

    
907
    /**
908
     * Returns the species subdivision epithet string (infraspecific part) for
909
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
910
     * (lower than species: trinomial). Species subdivision epithet strings
911
     * begin with a lower case letter.
912
     *
913
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
914
     * @see     #getNameCache()
915
     */
916
    @Override
917
    public String getInfraSpecificEpithet(){
918
        return this.infraSpecificEpithet;
919
    }
920

    
921
    /**
922
     * @see  #getInfraSpecificEpithet()
923
     */
924
    @Override
925
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
926
        this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
927
    }
928

    
929
    /**
930
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
931
     * taxon name.
932
     *
933
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
934
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
935
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
936
     */
937
    @Override
938
    public TeamOrPersonBase<?> getCombinationAuthorship(){
939
        return this.combinationAuthorship;
940
    }
941

    
942
    /**
943
     * @see  #getCombinationAuthorship()
944
     */
945
    @Override
946
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
947
        this.combinationAuthorship = combinationAuthorship;
948
    }
949

    
950
    /**
951
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
952
     * the publication of <i>this</i> non viral taxon name as generally stated by
953
     * the {@link #getCombinationAuthorship() combination author (team)} itself.<BR>
954
     * An ex-author(-team) is an author(-team) to whom a taxon name was ascribed
955
     * although it is not the author(-team) of a valid publication (for instance
956
     * without the validating description or diagnosis in case of a name for a
957
     * new taxon). The name of this ascribed authorship, followed by "ex", may
958
     * be inserted before the name(s) of the publishing author(s) of the validly
959
     * published name:<BR>
960
     * <i>Lilium tianschanicum</i> was described by Grubov (1977) as a new species and
961
     * its name was ascribed to Ivanova; since there is no indication that
962
     * Ivanova provided the validating description, the name may be cited as
963
     * <i>Lilium tianschanicum</i> N. A. Ivanova ex Grubov or <i>Lilium tianschanicum</i> Grubov.
964
     * <P>
965
     * The presence of an author (team) of <i>this</i> non viral taxon name is a
966
     * condition for the existence of an ex author (team) for <i>this</i> same name.
967
     *
968
     * @return  the nomenclatural ex author (team) of <i>this</i> non viral taxon name
969
     * @see     #getCombinationAuthorship()
970
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
971
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
972
     */
973
    @Override
974
    public TeamOrPersonBase<?> getExCombinationAuthorship(){
975
        return this.exCombinationAuthorship;
976
    }
977

    
978
    /**
979
     * @see  #getExCombinationAuthorship()
980
     */
981
    @Override
982
    public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
983
        this.exCombinationAuthorship = exCombinationAuthorship;
984
    }
985

    
986
    /**
987
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
988
     * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
989
     * author (team) can only exist if <i>this</i> non viral taxon name is a new
990
     * combination due to a taxonomical revision.
991
     *
992
     * @return  the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
993
     * @see     #getCombinationAuthorship()
994
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
995
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
996
     */
997
    @Override
998
    public TeamOrPersonBase<?> getBasionymAuthorship(){
999
        return basionymAuthorship;
1000
    }
1001

    
1002
    /**
1003
     * @see  #getBasionymAuthorship()
1004
     */
1005
    @Override
1006
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
1007
        this.basionymAuthorship = basionymAuthorship;
1008
    }
1009

    
1010
    /**
1011
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
1012
     * the publication of the original combination <i>this</i> non viral taxon name is
1013
     * based on. This should have been generally stated by
1014
     * the {@link #getBasionymAuthorship() basionym author (team)} itself.
1015
     * The presence of a basionym author (team) of <i>this</i> non viral taxon name is a
1016
     * condition for the existence of an ex basionym author (team)
1017
     * for <i>this</i> same name.
1018
     *
1019
     * @return  the nomenclatural ex basionym author (team) of <i>this</i> non viral taxon name
1020
     * @see     #getBasionymAuthorship()
1021
     * @see     #getExCombinationAuthorship()
1022
     * @see     #getCombinationAuthorship()
1023
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
1024
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
1025
     */
1026
    @Override
1027
    public TeamOrPersonBase<?> getExBasionymAuthorship(){
1028
        return exBasionymAuthorship;
1029
    }
1030

    
1031
    /**
1032
     * @see  #getExBasionymAuthorship()
1033
     */
1034
    @Override
1035
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
1036
        this.exBasionymAuthorship = exBasionymAuthorship;
1037
    }
1038

    
1039
    /**
1040
     * Returns the boolean value of the flag intended to protect (true)
1041
     * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
1042
     * of <i>this</i> non viral taxon name.
1043
     *
1044
     * @return  the boolean value of the protectedAuthorshipCache flag
1045
     * @see     #getAuthorshipCache()
1046
     */
1047
    @Override
1048
    public boolean isProtectedAuthorshipCache() {
1049
        return protectedAuthorshipCache;
1050
    }
1051

    
1052
    /**
1053
     * @see     #isProtectedAuthorshipCache()
1054
     * @see     #getAuthorshipCache()
1055
     */
1056
    @Override
1057
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
1058
        this.protectedAuthorshipCache = protectedAuthorshipCache;
1059
    }
1060

    
1061
    /**
1062
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1063
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
1064
     *
1065
     * @see    #getHybridRelationships()
1066
     * @see    #getChildRelationships()
1067
     * @see    HybridRelationshipType
1068
     */
1069
    @Override
1070
    public Set<HybridRelationship> getHybridParentRelations() {
1071
        if(hybridParentRelations == null) {
1072
            this.hybridParentRelations = new HashSet<>();
1073
        }
1074
        return hybridParentRelations;
1075
    }
1076

    
1077
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1078
        this.hybridParentRelations = hybridParentRelations;
1079
    }
1080

    
1081

    
1082
    /**
1083
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1084
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
1085
     *
1086
     * @see    #getHybridRelationships()
1087
     * @see    #getParentRelationships()
1088
     * @see    HybridRelationshipType
1089
     */
1090
    @Override
1091
    public Set<HybridRelationship> getHybridChildRelations() {
1092
        if(hybridChildRelations == null) {
1093
            this.hybridChildRelations = new HashSet<>();
1094
        }
1095
        return hybridChildRelations;
1096
    }
1097

    
1098
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1099
        this.hybridChildRelations = hybridChildRelations;
1100
    }
1101

    
1102
    @Override
1103
    public boolean isProtectedFullTitleCache() {
1104
        return protectedFullTitleCache;
1105
    }
1106

    
1107
    @Override
1108
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1109
        this.protectedFullTitleCache = protectedFullTitleCache;
1110
    }
1111

    
1112
    /**
1113
     * Returns the boolean value of the flag indicating whether the name of <i>this</i>
1114
     * botanical taxon name is a hybrid formula (true) or not (false). A hybrid
1115
     * named by a hybrid formula (composed with its parent names by placing the
1116
     * multiplication sign between them) does not have an own published name
1117
     * and therefore has neither an {@link NonViralName#getAuthorshipCache() autorship}
1118
     * nor other name components. If this flag is set no other hybrid flags may
1119
     * be set.
1120
     *
1121
     * @return  the boolean value of the isHybridFormula flag
1122
     * @see     #isMonomHybrid()
1123
     * @see     #isBinomHybrid()
1124
     * @see     #isTrinomHybrid()
1125
     */
1126
    @Override
1127
    @Transient
1128
    @java.beans.Transient
1129
    public boolean isHybridFormula(){
1130
        return this.hybridFormula;
1131
    }
1132

    
1133
    /**
1134
     * @see  #isHybridFormula()
1135
     */
1136
    @Override
1137
    public void setHybridFormula(boolean hybridFormula){
1138
        this.hybridFormula = hybridFormula;
1139
    }
1140

    
1141
    /**
1142
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1143
     * taxon name is the name of an intergeneric hybrid (true) or not (false).
1144
     * In this case the multiplication sign is placed before the scientific
1145
     * name. If this flag is set no other hybrid flags may be set.
1146
     *
1147
     * @return  the boolean value of the isMonomHybrid flag
1148
     * @see     #isHybridFormula()
1149
     * @see     #isBinomHybrid()
1150
     * @see     #isTrinomHybrid()
1151
     */
1152
    @Override
1153
    public boolean isMonomHybrid(){
1154
        return this.monomHybrid;
1155
    }
1156

    
1157
    /**
1158
     * @see  #isMonomHybrid()
1159
     * @see  #isBinomHybrid()
1160
     * @see  #isTrinomHybrid()
1161
     */
1162
    @Override
1163
    public void setMonomHybrid(boolean monomHybrid){
1164
        this.monomHybrid = monomHybrid;
1165
    }
1166

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

    
1183
    /**
1184
     * @see  #isBinomHybrid()
1185
     * @see  #isMonomHybrid()
1186
     * @see  #isTrinomHybrid()
1187
     */
1188
    @Override
1189
    public void setBinomHybrid(boolean binomHybrid){
1190
        this.binomHybrid = binomHybrid;
1191
    }
1192

    
1193
    @Override
1194
    public boolean isTrinomHybrid(){
1195
        return this.trinomHybrid;
1196
    }
1197

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

    
1208
    // ****************** VIRAL NAME ******************/
1209

    
1210
    @Override
1211
    public String getAcronym(){
1212
        return this.acronym;
1213
    }
1214

    
1215
    /**
1216
     * @see  #getAcronym()
1217
     */
1218
    @Override
1219
    public void setAcronym(String acronym){
1220
        this.acronym = StringUtils.isBlank(acronym)? null : acronym;
1221
    }
1222

    
1223
    // ****************** BACTERIAL NAME ******************/
1224

    
1225
    @Override
1226
    public String getSubGenusAuthorship(){
1227
        return this.subGenusAuthorship;
1228
    }
1229

    
1230
    @Override
1231
    public void setSubGenusAuthorship(String subGenusAuthorship){
1232
        this.subGenusAuthorship = subGenusAuthorship;
1233
    }
1234

    
1235

    
1236
    @Override
1237
    public String getNameApprobation(){
1238
        return this.nameApprobation;
1239
    }
1240

    
1241
    /**
1242
     * @see  #getNameApprobation()
1243
     */
1244
    @Override
1245
    public void setNameApprobation(String nameApprobation){
1246
        this.nameApprobation = nameApprobation;
1247
    }
1248

    
1249
    //************ Zoological Name
1250

    
1251

    
1252
    @Override
1253
    public String getBreed(){
1254
        return this.breed;
1255
    }
1256
    /**
1257
     * @see  #getBreed()
1258
     */
1259
    @Override
1260
    public void setBreed(String breed){
1261
        this.breed = StringUtils.isBlank(breed) ? null : breed;
1262
    }
1263

    
1264

    
1265
    @Override
1266
    public Integer getPublicationYear() {
1267
        return publicationYear;
1268
    }
1269
    /**
1270
     * @see  #getPublicationYear()
1271
     */
1272
    @Override
1273
    public void setPublicationYear(Integer publicationYear) {
1274
        this.publicationYear = publicationYear;
1275
    }
1276

    
1277

    
1278
    @Override
1279
    public Integer getOriginalPublicationYear() {
1280
        return originalPublicationYear;
1281
    }
1282
    /**
1283
     * @see  #getOriginalPublicationYear()
1284
     */
1285
    @Override
1286
    public void setOriginalPublicationYear(Integer originalPublicationYear) {
1287
        this.originalPublicationYear = originalPublicationYear;
1288
    }
1289

    
1290
    // **** Cultivar Name ************
1291

    
1292

    
1293
    @Override
1294
    public String getCultivarName(){
1295
        return this.cultivarName;
1296
    }
1297

    
1298
    /**
1299
     * @see  #getCultivarName()
1300
     */
1301
    @Override
1302
    public void setCultivarName(String cultivarName){
1303
        this.cultivarName = StringUtils.isBlank(cultivarName) ? null : cultivarName;
1304
    }
1305

    
1306
    // **************** Fungus Name
1307
    @Override
1308
    public boolean isAnamorphic(){
1309
        return this.anamorphic;
1310
    }
1311

    
1312
    /**
1313
     * @see  #isAnamorphic()
1314
     */
1315
    @Override
1316
    public void setAnamorphic(boolean anamorphic){
1317
        this.anamorphic = anamorphic;
1318
    }
1319

    
1320

    
1321
// **************** ADDER / REMOVE *************************/
1322

    
1323
    /**
1324
     * Adds the given {@link HybridRelationship hybrid relationship} to the set
1325
     * of {@link #getHybridRelationships() hybrid relationships} of both non-viral names
1326
     * involved in this hybrid relationship. One of both non-viral names
1327
     * must be <i>this</i> non-viral name otherwise no addition will be carried
1328
     * out. The {@link eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedTo() child
1329
     * non viral taxon name} must be a hybrid, which means that one of its four hybrid flags must be set.
1330
     *
1331
     * @param relationship  the hybrid relationship to be added
1332
     * @see                 #isHybridFormula()
1333
     * @see                 #isMonomHybrid()
1334
     * @see                 #isBinomHybrid()
1335
     * @see                 #isTrinomHybrid()
1336
     * @see                 #getHybridRelationships()
1337
     * @see                 #getParentRelationships()
1338
     * @see                 #getChildRelationships()
1339
     * @see                 #addRelationship(RelationshipBase)
1340
     * @throws              IllegalArgumentException
1341
     */
1342
    protected void addHybridRelationship(HybridRelationship rel) {
1343
        if (rel!=null && rel.getHybridName().equals(this)){
1344
            this.hybridChildRelations.add(rel);
1345
        }else if(rel!=null && rel.getParentName().equals(this)){
1346
            this.hybridParentRelations.add(rel);
1347
        }else{
1348
            throw new IllegalArgumentException("Hybrid relationship is either null or the relationship does not reference this name");
1349
        }
1350
    }
1351

    
1352

    
1353
    /**
1354
     * Removes one {@link HybridRelationship hybrid relationship} from the set of
1355
     * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1356
     * is involved. The hybrid relationship will also be removed from the set
1357
     * belonging to the second botanical taxon name involved.
1358
     *
1359
     * @param  relationship  the hybrid relationship which should be deleted from the corresponding sets
1360
     * @see                  #getHybridRelationships()
1361
     */
1362
    @Override
1363
    public void removeHybridRelationship(HybridRelationship hybridRelation) {
1364
        if (hybridRelation == null) {
1365
            return;
1366
        }
1367

    
1368
        TaxonName parent = hybridRelation.getParentName();
1369
        TaxonName child = hybridRelation.getHybridName();
1370
        if (this.equals(parent)){
1371
            this.hybridParentRelations.remove(hybridRelation);
1372
            child.hybridChildRelations.remove(hybridRelation);
1373
            hybridRelation.setHybridName(null);
1374
            hybridRelation.setParentName(null);
1375
        }
1376
        if (this.equals(child)){
1377
            parent.hybridParentRelations.remove(hybridRelation);
1378
            this.hybridChildRelations.remove(hybridRelation);
1379
            hybridRelation.setHybridName(null);
1380
            hybridRelation.setParentName(null);
1381
        }
1382
    }
1383

    
1384
//********* METHODS **************************************/
1385

    
1386
    @Override
1387
    public INameCacheStrategy getCacheStrategy() {
1388
        rectifyNameCacheStrategy();
1389
        return this.cacheStrategy;
1390
    }
1391

    
1392
    @Override
1393
    public String generateFullTitle(){
1394
        if (getCacheStrategy() == null){
1395
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1396
            return null;
1397
        }else{
1398
            return cacheStrategy.getFullTitleCache(this);
1399
        }
1400
    }
1401

    
1402

    
1403
    @Override
1404
    public void setFullTitleCache(String fullTitleCache){
1405
        setFullTitleCache(fullTitleCache, PROTECTED);
1406
    }
1407

    
1408
    @Override
1409
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1410
        fullTitleCache = getTruncatedCache(fullTitleCache);
1411
        this.fullTitleCache = fullTitleCache;
1412
        this.setProtectedFullTitleCache(protectCache);
1413
    }
1414

    
1415
   /** Checks if this name is an autonym.<BR>
1416
    * An autonym is a taxon name that has equal specific and infra specific epithets.<BR>
1417
    * {@link http://ibot.sav.sk/icbn/frameset/0010Ch2Sec1a006.htm#6.8. Vienna Code §6.8}
1418
    * or a taxon name that has equal generic and infrageneric epithets (A22.2).<BR>
1419
    * Only relevant for botanical names.
1420
    * @return true, if name has Rank, Rank is below species and species epithet equals infraSpeciesEpithtet, else false
1421
    */
1422
    @Override
1423
    @Transient
1424
    public boolean isAutonym(){
1425
        if (isBotanical()){
1426
            if (this.getRank() != null && this.getSpecificEpithet() != null && this.getInfraSpecificEpithet() != null &&
1427
                this.isInfraSpecific() && this.getSpecificEpithet().trim().equals(this.getInfraSpecificEpithet().trim())){
1428
                return true;
1429
            }else if (this.getRank() != null && this.getGenusOrUninomial() != null && this.getInfraGenericEpithet() != null &&
1430
                    this.isInfraGeneric() && this.getGenusOrUninomial().trim().equals(this.getInfraGenericEpithet().trim())){
1431
                return true;
1432
            }else{
1433
                return false;
1434
            }
1435
        }else{
1436
            return false;
1437
        }
1438
    }
1439

    
1440

    
1441
    @Override
1442
    @Transient
1443
    public List<TaggedText> getTaggedName(){
1444
        INameCacheStrategy strat = getCacheStrategy();
1445
        return strat.getTaggedTitle(this);
1446
    }
1447

    
1448
    @Override
1449
    @Transient
1450
    public String getFullTitleCache(){
1451
        if (protectedFullTitleCache){
1452
            return this.fullTitleCache;
1453
        }
1454
        updateAuthorshipCache();
1455
        if (fullTitleCache == null ){
1456
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1457
        }
1458
        return fullTitleCache;
1459
    }
1460

    
1461

    
1462
    @Override
1463
    public String getTitleCache(){
1464
        if(!protectedTitleCache) {
1465
            updateAuthorshipCache();
1466
        }
1467
        return super.getTitleCache();
1468
    }
1469

    
1470
    @Override
1471
    public void setTitleCache(String titleCache, boolean protectCache){
1472
        super.setTitleCache(titleCache, protectCache);
1473
    }
1474

    
1475
    /**
1476
     * Returns the concatenated and formated authorteams string including
1477
     * basionym and combination authors of <i>this</i> non viral taxon name.
1478
     * If the protectedAuthorshipCache flag is set this method returns the
1479
     * string stored in the the authorshipCache attribute, otherwise it
1480
     * generates the complete authorship string, returns it and stores it in
1481
     * the authorshipCache attribute.
1482
     *
1483
     * @return  the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
1484
     * @see     #generateAuthorship()
1485
     */
1486
    @Override
1487
    @Transient
1488
    public String getAuthorshipCache() {
1489
        if (protectedAuthorshipCache){
1490
            return this.authorshipCache;
1491
        }
1492
        if (this.authorshipCache == null ){
1493
            this.authorshipCache = generateAuthorship();
1494
        }else{
1495
            //TODO get isDirty of authors, make better if possible
1496
            this.setAuthorshipCache(generateAuthorship(), protectedAuthorshipCache); //throw change event to inform higher caches
1497

    
1498
        }
1499
        return authorshipCache;
1500
    }
1501

    
1502

    
1503

    
1504
    /**
1505
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
1506
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
1507
     * @return
1508
     */
1509
    private void updateAuthorshipCache() {
1510
        //updates the authorship cache if necessary and via the listener updates all higher caches
1511
        if (protectedAuthorshipCache == false){
1512
            String oldCache = this.authorshipCache;
1513
            String newCache = this.getAuthorshipCache();
1514
            if ( (oldCache == null && newCache != null)  ||  CdmUtils.nullSafeEqual(oldCache,newCache)){
1515
                this.setAuthorshipCache(this.getAuthorshipCache(), false);
1516
            }
1517
        }
1518
    }
1519

    
1520

    
1521
    /**
1522
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1523
     * flag to <code>true</code>.
1524
     *
1525
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1526
     * @see    #getAuthorshipCache()
1527
     */
1528
    @Override
1529
    public void setAuthorshipCache(String authorshipCache) {
1530
        setAuthorshipCache(authorshipCache, true);
1531
    }
1532

    
1533

    
1534
    /**
1535
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1536
     *
1537
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1538
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1539
     * the flag is set to <code>false</code>.
1540
     * @see    #getAuthorshipCache()
1541
     */
1542
    @Override
1543
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1544
        this.authorshipCache = authorshipCache;
1545
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1546
    }
1547

    
1548
    /**
1549
     * Generates and returns a concatenated and formated authorteams string
1550
     * including basionym and combination authors of <i>this</i> non viral taxon name
1551
     * according to the strategy defined in
1552
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName) INonViralNameCacheStrategy}.
1553
     *
1554
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1555
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName)
1556
     */
1557
    @Override
1558
    public String generateAuthorship(){
1559
        if (getCacheStrategy() == null){
1560
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1561
            return null;
1562
        }else{
1563
            return cacheStrategy.getAuthorshipCache(this);
1564
        }
1565
    }
1566

    
1567

    
1568

    
1569
    /**
1570
     * Tests if the given name has any authors.
1571
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1572
     */
1573
    @Override
1574
    public boolean hasAuthors() {
1575
        return (this.getCombinationAuthorship() != null ||
1576
                this.getExCombinationAuthorship() != null ||
1577
                this.getBasionymAuthorship() != null ||
1578
                this.getExBasionymAuthorship() != null);
1579
    }
1580

    
1581
    /**
1582
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1583
     * @return
1584
     */
1585
    @Override
1586
    public String computeCombinationAuthorNomenclaturalTitle() {
1587
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1588
    }
1589

    
1590
    /**
1591
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1592
     * @return
1593
     */
1594
    @Override
1595
    public String computeBasionymAuthorNomenclaturalTitle() {
1596
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1597
    }
1598

    
1599

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

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

    
1618
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1619
        if (author == null){
1620
            return null;
1621
        }else{
1622
            return author.getNomenclaturalTitle();
1623
        }
1624
    }
1625

    
1626
    /**
1627
     * Returns the set of all {@link NameRelationship name relationships}
1628
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1629
     * in some name relationships or target in some others.
1630
     *
1631
     * @see    #getRelationsToThisName()
1632
     * @see    #getRelationsFromThisName()
1633
     * @see    #addNameRelationship(NameRelationship)
1634
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1635
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1636
     */
1637
    @Override
1638
    @Transient
1639
    public Set<NameRelationship> getNameRelations() {
1640
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1641
        rels.addAll(getRelationsFromThisName());
1642
        rels.addAll(getRelationsToThisName());
1643
        return rels;
1644
    }
1645

    
1646
    /**
1647
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1648
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1649
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1650
     *
1651
     * @param toName		  the taxon name of the target for this new name relationship
1652
     * @param type			  the type of this new name relationship
1653
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1654
     * @return
1655
     * @see    				  #getRelationsToThisName()
1656
     * @see    				  #getNameRelations()
1657
     * @see    				  #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1658
     * @see    				  #addNameRelationship(NameRelationship)
1659
     */
1660
    @Override
1661
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, String ruleConsidered){
1662
        return addRelationshipToName(toName, type, null, null, ruleConsidered);
1663
    }
1664

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

    
1688
    /**
1689
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1690
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1691
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1692
     *
1693
     * @param fromName		  the taxon name of the source for this new name relationship
1694
     * @param type			  the type of this new name relationship
1695
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1696
     * @param citation		  the reference in which this relation was described
1697
     * @param microCitation	  the reference detail for this relation (e.g. page)
1698
     * @see    				  #getRelationsFromThisName()
1699
     * @see    				  #getNameRelations()
1700
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1701
     * @see    				  #addNameRelationship(NameRelationship)
1702
     */
1703
    @Override
1704
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered){
1705
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1706
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
1707
    }
1708
    /**
1709
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1710
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1711
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1712
     *
1713
     * @param fromName		  the taxon name of the source for this new name relationship
1714
     * @param type			  the type of this new name relationship
1715
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1716
     * @param citation		  the reference in which this relation was described
1717
     * @param microCitation	  the reference detail for this relation (e.g. page)
1718
     * @see    				  #getRelationsFromThisName()
1719
     * @see    				  #getNameRelations()
1720
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1721
     * @see    				  #addNameRelationship(NameRelationship)
1722
     */
1723
    @Override
1724
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1725
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1726
    }
1727

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

    
1768
        TaxonName fromName = nameRelation.getFromName();
1769
        TaxonName toName = nameRelation.getToName();
1770

    
1771
        if (nameRelation != null) {
1772
            nameRelation.setToName(null);
1773
            nameRelation.setFromName(null);
1774
        }
1775

    
1776
        if (fromName != null) {
1777
            fromName.removeNameRelationship(nameRelation);
1778
        }
1779

    
1780
        if (toName != null) {
1781
            toName.removeNameRelationship(nameRelation);
1782
        }
1783

    
1784
        this.relationsToThisName.remove(nameRelation);
1785
        this.relationsFromThisName.remove(nameRelation);
1786
    }
1787

    
1788
    @Override
1789
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1790
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1791
//		nameRelationships.addAll(this.getNameRelations());
1792
        nameRelationships.addAll(this.getRelationsFromThisName());
1793
        nameRelationships.addAll(this.getRelationsToThisName());
1794
        for(NameRelationship nameRelationship : nameRelationships) {
1795
            // remove name relationship from this side
1796
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1797
                this.removeNameRelationship(nameRelationship);
1798
            }
1799
        }
1800
    }
1801

    
1802
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1803

    
1804
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1805
        for(NameRelationship nameRelationship : tmpRels) {
1806
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1807
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1808
                if (type == null || type.equals(nameRelationship.getType())){
1809
                    this.removeNameRelationship(nameRelationship);
1810
                }
1811
            }
1812
        }
1813
    }
1814

    
1815

    
1816
    /**
1817
     * If relation is of type NameRelationship, addNameRelationship is called;
1818
     * if relation is of type HybridRelationship addHybridRelationship is called,
1819
     * otherwise an IllegalArgumentException is thrown.
1820
     *
1821
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1822
     * @see    	   		#addNameRelationship(NameRelationship)
1823
     * @see    	   		#getNameRelations()
1824
     * @see    	   		NameRelationship
1825
     * @see    	   		RelationshipBase
1826
     * @see             #addHybridRelationship(HybridRelationship)
1827

    
1828
     * @deprecated to be used by RelationshipBase only
1829
     */
1830
    @Deprecated
1831
    @Override
1832
    public void addRelationship(RelationshipBase relation) {
1833
        if (relation instanceof NameRelationship){
1834
            addNameRelationship((NameRelationship)relation);
1835

    
1836
        }else if (relation instanceof HybridRelationship){
1837
            addHybridRelationship((HybridRelationship)relation);
1838
        }else{
1839
            logger.warn("Relationship not of type NameRelationship!");
1840
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1841
        }
1842
    }
1843

    
1844
    /**
1845
     * Returns the set of all {@link NameRelationship name relationships}
1846
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1847
     *
1848
     * @see    #getNameRelations()
1849
     * @see    #getRelationsToThisName()
1850
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1851
     */
1852
    @Override
1853
    public Set<NameRelationship> getRelationsFromThisName() {
1854
        if(relationsFromThisName == null) {
1855
            this.relationsFromThisName = new HashSet<>();
1856
        }
1857
        return relationsFromThisName;
1858
    }
1859

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

    
1876
    /**
1877
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1878
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1879
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1880
     * and the nomenclatural code rule considered.
1881
     *
1882
     * @see     NomenclaturalStatus
1883
     * @see     NomenclaturalStatusType
1884
     */
1885
    @Override
1886
    public Set<NomenclaturalStatus> getStatus() {
1887
        if(status == null) {
1888
            this.status = new HashSet<>();
1889
        }
1890
        return status;
1891
    }
1892

    
1893
    /**
1894
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1895
     * to <i>this</i> taxon name's set of nomenclatural status.
1896
     *
1897
     * @param  nomStatus  the nomenclatural status to be added
1898
     * @see 			  #getStatus()
1899
     */
1900
    @Override
1901
    public void addStatus(NomenclaturalStatus nomStatus) {
1902
        this.status.add(nomStatus);
1903
    }
1904
    @Override
1905
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1906
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1907
        this.status.add(newStatus);
1908
        return newStatus;
1909
    }
1910

    
1911
    /**
1912
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1913
     * Type and ruleConsidered attributes of the nomenclatural status object
1914
     * will be nullified.
1915
     *
1916
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1917
     * @see     		  #getStatus()
1918
     */
1919
    @Override
1920
    public void removeStatus(NomenclaturalStatus nomStatus) {
1921
        //TODO to be implemented?
1922
        logger.warn("not yet fully implemented?");
1923
        this.status.remove(nomStatus);
1924
    }
1925

    
1926

    
1927
    /**
1928
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1929
     * strings or year according to the strategy defined in
1930
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1931
     * The result might be stored in {@link #getNameCache() nameCache} if the
1932
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1933
     *
1934
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1935
     * @see     #getNameCache()
1936
     */
1937
    protected String generateNameCache(){
1938
        if (getCacheStrategy() == null){
1939
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1940
            return null;
1941
        }else{
1942
            return cacheStrategy.getNameCache(this);
1943
        }
1944
    }
1945

    
1946
    /**
1947
     * Returns or generates the nameCache (scientific name
1948
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1949
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1950
     * the string will be generated according to a defined strategy,
1951
     * otherwise the value of the actual nameCache string will be returned.
1952
     *
1953
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1954
     * @see     #generateNameCache()
1955
     */
1956
    @Override
1957
    @Transient
1958
    public String getNameCache() {
1959
        if (protectedNameCache){
1960
            return this.nameCache;
1961
        }
1962
        // is title dirty, i.e. equal NULL?
1963
        if (nameCache == null){
1964
            this.nameCache = generateNameCache();
1965
        }
1966
        return nameCache;
1967
    }
1968

    
1969
    /**
1970
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1971
     * Sets the protectedNameCache flag to <code>true</code>.
1972
     *
1973
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1974
     * @see    #getNameCache()
1975
     */
1976
    @Override
1977
    public void setNameCache(String nameCache){
1978
        setNameCache(nameCache, true);
1979
    }
1980

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

    
1996

    
1997
    /**
1998
     * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
1999
     * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
2000
     * of any other taxon name. Returns "true", if a basionym or a replaced
2001
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
2002
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
2003
     * homotypical group).
2004
     */
2005
    @Override
2006
    @Transient
2007
    public boolean isOriginalCombination(){
2008
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
2009
        for (NameRelationship relation : relationsFromThisName) {
2010
            if (relation.getType().isBasionymRelation() ||
2011
                    relation.getType().isReplacedSynonymRelation()) {
2012
                return true;
2013
            }
2014
        }
2015
        return false;
2016
    }
2017

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

    
2037
    /**
2038
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2039
     * The basionym of a taxon name is its epithet-bringing synonym.
2040
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2041
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2042
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2043
     *
2044
     * If more than one basionym exists one is choosen at radom.
2045
     *
2046
     * If no basionym exists null is returned.
2047
     */
2048
    @Override
2049
    @Transient
2050
    public TaxonName getBasionym(){
2051
        Set<TaxonName> basionyms = getBasionyms();
2052
        if (basionyms.size() == 0){
2053
            return null;
2054
        }else{
2055
            return basionyms.iterator().next();
2056
        }
2057
    }
2058

    
2059
    /**
2060
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2061
     * The basionym of a taxon name is its epithet-bringing synonym.
2062
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2063
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2064
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2065
     */
2066
    @Override
2067
    @Transient
2068
    public Set<TaxonName> getBasionyms(){
2069

    
2070
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
2071
    }
2072

    
2073
    /**
2074
     *
2075
     * @param direction
2076
     * @param type
2077
     * @return
2078
     */
2079
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
2080

    
2081
        return getRelatedNames(relationsWithThisName(direction), type);
2082
    }
2083

    
2084
    /**
2085
     * @param rels
2086
     * @param type
2087
     * @return
2088
     */
2089
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2090
        Set<TaxonName> result = new HashSet<>();
2091
        for (NameRelationship rel : rels){
2092
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2093
                TaxonName basionym = rel.getFromName();
2094
                result.add(basionym);
2095
            }
2096
        }
2097
        return result;
2098
    }
2099

    
2100
    /**
2101
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2102
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2103
     * and to the basionym. The basionym cannot have itself as a basionym.
2104
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2105
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2106
     *
2107
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2108
     * @see  				#getBasionym()
2109
     * @see  				#addBasionym(TaxonName, String)
2110
     */
2111
    @Override
2112
    public void addBasionym(TaxonName basionym){
2113
        addBasionym(basionym, null, null, null);
2114
    }
2115
    /**
2116
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2117
     * and keeps the nomenclatural rule considered for it. The basionym
2118
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2119
     * The basionym cannot have itself as a basionym.
2120
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2121
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2122
     *
2123
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2124
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2125
     * @return
2126
     * @see  					#getBasionym()
2127
     * @see  					#addBasionym(TaxonName)
2128
     */
2129
    @Override
2130
    public NameRelationship addBasionym(TaxonName basionym, Reference citation, String microcitation, String ruleConsidered){
2131
        if (basionym != null){
2132
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
2133
        }else{
2134
            return null;
2135
        }
2136
    }
2137

    
2138
    /**
2139
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2140
     *
2141
     */
2142
    @Override
2143
    @Transient
2144
    public Set<TaxonName> getReplacedSynonyms(){
2145

    
2146
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2147
    }
2148

    
2149
    /**
2150
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2151
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2152
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2153
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2154
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2155
     *
2156
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2157
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2158
     * @see  					#getBasionym()
2159
     * @see  					#addBasionym(TaxonName)
2160
     */
2161
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2162
    @Override
2163
    public void addReplacedSynonym(TaxonName replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
2164
        if (replacedSynonym != null){
2165
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
2166
        }
2167
    }
2168

    
2169
    /**
2170
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2171
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2172
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2173
     * previously used as basionym.
2174
     *
2175
     * @see   #getBasionym()
2176
     * @see   #addBasionym(TaxonName)
2177
     */
2178
    @Override
2179
    public void removeBasionyms(){
2180
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2181
    }
2182

    
2183

    
2184
    /**
2185
     * Removes all {@link NameRelationship relationships} of the given <code>type</code> from the set of
2186
     * relations in the specified <code>direction</code> direction wich are related from or to this
2187
     * <i>this</i> taxon name. The same relationship will be removed from the set of
2188
     * reverse relations of the other taxon name.
2189
     *
2190
     * @param direction
2191
     * @param type
2192
     */
2193
    public void removeNameRelations(Direction direction, NameRelationshipType type) {
2194
        Set<NameRelationship> relationsWithThisName = relationsWithThisName(direction);
2195
        Set<NameRelationship> removeRelations = new HashSet<>();
2196
        for (NameRelationship nameRelation : relationsWithThisName){
2197
            if (nameRelation.getType().isRelationshipType(type)){
2198
                removeRelations.add(nameRelation);
2199
            }
2200
        }
2201
        // Removing relations from a set through which we are iterating causes a
2202
        // ConcurrentModificationException. Therefore, we delete the targeted
2203
        // relations in a second step.
2204
        for (NameRelationship relation : removeRelations){
2205
            this.removeNameRelationship(relation);
2206
        }
2207
    }
2208

    
2209

    
2210
    /**
2211
     * @param direction
2212
     * @return
2213
     */
2214
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2215

    
2216
        switch(direction) {
2217
            case relatedTo:
2218
                return this.getRelationsToThisName();
2219
            case relatedFrom:
2220
                return this.getRelationsFromThisName();
2221
            default: throw new RuntimeException("invalid Direction:" + direction);
2222
        }
2223
    }
2224

    
2225
    /**
2226
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2227
     *
2228
     * @see 	Rank
2229
     */
2230
    @Override
2231
    public Rank getRank(){
2232
        return this.rank;
2233
    }
2234

    
2235
    /**
2236
     * @see  #getRank()
2237
     */
2238
    @Override
2239
    public void setRank(Rank rank){
2240
        this.rank = rank;
2241
    }
2242

    
2243

    
2244
    @Override
2245
    public Reference getNomenclaturalReference(){
2246
        if (this.nomenclaturalSource == null){
2247
            return null;
2248
        }
2249
        return this.nomenclaturalSource.getCitation();
2250
    }
2251

    
2252
    @Override
2253
    public DescriptionElementSource getNomenclaturalSource(){
2254
        return this.nomenclaturalSource;
2255
    }
2256

    
2257
    protected DescriptionElementSource getNomenclaturalSource(boolean createIfNotExist){
2258
        if (this.nomenclaturalSource == null){
2259
            if (!createIfNotExist){
2260
                return null;
2261
            }
2262
            this.nomenclaturalSource = DescriptionElementSource.NewInstance(OriginalSourceType.NomenclaturalReference);
2263
        }
2264
        return this.nomenclaturalSource;
2265
    }
2266

    
2267
    /**
2268
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2269
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2270
     * as it is obviously used for nomenclatural purposes.
2271
     *
2272
     * Shortcut to set the nomenclatural reference.
2273
     *
2274
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2275
     * @see  #getNomenclaturalReference()
2276
     */
2277

    
2278
    @Override
2279
    public void setNomenclaturalReference(Reference nomenclaturalReference){
2280
        getNomenclaturalSource(true).setCitation(nomenclaturalReference);
2281
        checkNullSource();
2282
    }
2283
    @Override
2284
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2285
        setNomenclaturalReference(CdmBase.deproxy(nomenclaturalReference, Reference.class));
2286
    }
2287

    
2288

    
2289

    
2290
    /**
2291
     *
2292
     */
2293
    private void checkNullSource() {
2294
        if (this.nomenclaturalSource == null){
2295
            return;
2296
        }else if (this.nomenclaturalSource.getCitation() != null
2297
           || this.nomenclaturalSource.getCitationMicroReference() != null
2298
           || this.nomenclaturalSource.getNameUsedInSource() != null
2299
           || isBlank(this.nomenclaturalSource.getOriginalNameString())){
2300
            //TODO what about supplemental data?
2301
                return;
2302
        }else{
2303
            this.nomenclaturalSource = null;
2304
        }
2305
    }
2306

    
2307

    
2308
    @Override
2309
    public void setNomenclaturalSource(DescriptionElementSource nomenclaturalSource) throws IllegalArgumentException {
2310
        if (!OriginalSourceType.NomenclaturalReference.equals(nomenclaturalSource.getType()) ){
2311
            throw new IllegalArgumentException("Nomenclatural source must be of type " + OriginalSourceType.NomenclaturalReference.getMessage());
2312
        }
2313
        this.nomenclaturalSource = nomenclaturalSource;
2314
    }
2315

    
2316
    /**
2317
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2318
     * The appended phrase is a non-atomised addition to a name. It is
2319
     * not ruled by a nomenclatural code.
2320
     */
2321
    @Override
2322
    public String getAppendedPhrase(){
2323
        return this.appendedPhrase;
2324
    }
2325

    
2326
    /**
2327
     * @see  #getAppendedPhrase()
2328
     */
2329
    @Override
2330
    public void setAppendedPhrase(String appendedPhrase){
2331
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2332
    }
2333

    
2334
    /**
2335
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2336
     * to <i>this</i> taxon name. The details describe the exact localisation within
2337
     * the publication used as nomenclature reference. These are mostly
2338
     * (implicitly) pages but can also be figures or tables or any other
2339
     * element of a publication. A nomenclatural micro reference (details)
2340
     * requires the existence of a nomenclatural reference.
2341
     */
2342
    //Details of the nomenclatural reference (protologue).
2343
    @Override
2344
    public String getNomenclaturalMicroReference(){
2345
        if (this.nomenclaturalSource == null){
2346
            return null;
2347
        }
2348
        return this.nomenclaturalSource.getCitationMicroReference();
2349
    }
2350
    /**
2351
     * @see  #getNomenclaturalMicroReference()
2352
     */
2353
    @Override
2354
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2355
        this.getNomenclaturalSource(true).setCitationMicroReference(StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference);
2356
        checkNullSource();
2357
    }
2358

    
2359
    @Override
2360
    public int getParsingProblem(){
2361
        return this.parsingProblem;
2362
    }
2363

    
2364
    @Override
2365
    public void setParsingProblem(int parsingProblem){
2366
        this.parsingProblem = parsingProblem;
2367
    }
2368

    
2369
    @Override
2370
    public void addParsingProblem(ParserProblem problem){
2371
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2372
    }
2373

    
2374
    @Override
2375
    public void removeParsingProblem(ParserProblem problem) {
2376
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2377
    }
2378

    
2379
    /**
2380
     * @param warnings
2381
     */
2382
    @Override
2383
    public void addParsingProblems(int problems){
2384
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2385
    }
2386

    
2387
    @Override
2388
    public boolean hasProblem(){
2389
        return parsingProblem != 0;
2390
    }
2391

    
2392
    @Override
2393
    public boolean hasProblem(ParserProblem problem) {
2394
        return getParsingProblems().contains(problem);
2395
    }
2396

    
2397
    @Override
2398
    public int getProblemStarts(){
2399
        return this.problemStarts;
2400
    }
2401

    
2402
    @Override
2403
    public void setProblemStarts(int start) {
2404
        this.problemStarts = start;
2405
    }
2406

    
2407
    @Override
2408
    public int getProblemEnds(){
2409
        return this.problemEnds;
2410
    }
2411

    
2412
    @Override
2413
    public void setProblemEnds(int end) {
2414
        this.problemEnds = end;
2415
    }
2416

    
2417
//*********************** TYPE DESIGNATION *********************************************//
2418

    
2419
    /**
2420
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2421
     * to <i>this</i> taxon name.
2422
     * @see     NameTypeDesignation
2423
     * @see     SpecimenTypeDesignation
2424
     */
2425
    @Override
2426
    public Set<TypeDesignationBase> getTypeDesignations() {
2427
        if(typeDesignations == null) {
2428
            this.typeDesignations = new HashSet<TypeDesignationBase>();
2429
        }
2430
        return typeDesignations;
2431
    }
2432

    
2433
    /**
2434
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2435
     * <i>this</i> taxon name. The type designation itself will be nullified.
2436
     *
2437
     * @param  typeDesignation  the type designation which should be deleted
2438
     */
2439
    @Override
2440
    @SuppressWarnings("deprecation")
2441
    public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2442
        this.typeDesignations.remove(typeDesignation);
2443
        typeDesignation.removeTypifiedName(this);
2444
    }
2445

    
2446
    /**
2447
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2448
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2449
     * "species" or below. The specimen type designations include all the
2450
     * specimens on which the typification of this name is based (which are
2451
     * exclusively used to typify taxon names belonging to the same
2452
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2453
     * belongs) and eventually the status of these designations.
2454
     *
2455
     * @see     SpecimenTypeDesignation
2456
     * @see     NameTypeDesignation
2457
     * @see     HomotypicalGroup
2458
     */
2459
    @Override
2460
    @Transient
2461
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2462
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2463
    }
2464

    
2465
//*********************** NAME TYPE DESIGNATION *********************************************//
2466

    
2467
    /**
2468
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2469
     * to <i>this</i> taxon name the rank of which must be above "species".
2470
     * The name type designations include all the taxon names used to typify
2471
     * <i>this</i> taxon name and eventually the rejected or conserved status
2472
     * of these designations.
2473
     *
2474
     * @see     NameTypeDesignation
2475
     * @see     SpecimenTypeDesignation
2476
     */
2477
    @Override
2478
    @Transient
2479
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2480
        Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2481
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2482
            if (typeDesignation instanceof NameTypeDesignation){
2483
                result.add((NameTypeDesignation)typeDesignation);
2484
            }
2485
        }
2486
        return result;
2487
    }
2488

    
2489
    /**
2490
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2491
     * to <i>this</i> taxon name's set of type designations.
2492
     *
2493
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2494
     * @param  citation					the reference for this new designation
2495
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2496
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2497
     * @param  isRejectedType			the boolean status for a rejected name type designation
2498
     * @param  isConservedType			the boolean status for a conserved name type designation
2499
     * @param  isLectoType				the boolean status for a lectotype name type designation
2500
     * @param  isNotDesignated			the boolean status for a name type designation without name type
2501
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2502
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2503
     * @return
2504
     * @see 			  				#getNameTypeDesignations()
2505
     * @see 			  				NameTypeDesignation
2506
     * @see 			  				TypeDesignationBase#isNotDesignated()
2507
     */
2508
    @Override
2509
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2510
                Reference citation,
2511
                String citationMicroReference,
2512
                String originalNameString,
2513
                NameTypeDesignationStatus status,
2514
                boolean isRejectedType,
2515
                boolean isConservedType,
2516
                /*boolean isLectoType, */
2517
                boolean isNotDesignated,
2518
                boolean addToAllHomotypicNames) {
2519
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalNameString, status, isRejectedType, isConservedType, isNotDesignated);
2520
        //nameTypeDesignation.setLectoType(isLectoType);
2521
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2522
        return nameTypeDesignation;
2523
    }
2524

    
2525
    /**
2526
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2527
     * to <i>this</i> taxon name's set of type designations.
2528
     *
2529
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2530
     * @param  citation					the reference for this new designation
2531
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2532
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2533
     * @param  status                   the name type designation status
2534
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2535
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2536
     * @return
2537
     * @see 			  				#getNameTypeDesignations()
2538
     * @see 			  				NameTypeDesignation
2539
     * @see 			  				TypeDesignationBase#isNotDesignated()
2540
     */
2541
    @Override
2542
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2543
                Reference citation,
2544
                String citationMicroReference,
2545
                String originalNameString,
2546
                NameTypeDesignationStatus status,
2547
                boolean addToAllHomotypicNames) {
2548
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2549
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2550
        return nameTypeDesignation;
2551
    }
2552

    
2553
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2554

    
2555
    /**
2556
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2557
     * that typify <i>this</i> taxon name.
2558
     */
2559
    @Override
2560
    @Transient
2561
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2562
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2563
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2564
            if (typeDesignation instanceof SpecimenTypeDesignation){
2565
                result.add((SpecimenTypeDesignation)typeDesignation);
2566
            }
2567
        }
2568
        return result;
2569
    }
2570

    
2571

    
2572
    /**
2573
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2574
     * to <i>this</i> taxon name's set of type designations.
2575
     *
2576
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2577
     * @param  status					the specimen type designation status
2578
     * @param  citation					the reference for this new specimen type designation
2579
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2580
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2581
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2582
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2583
     * 									added to all taxon names of the homotypical group the typified
2584
     * 									taxon name belongs to
2585
     * @return
2586
     * @see 			  				#getSpecimenTypeDesignations()
2587
     * @see 			  				SpecimenTypeDesignationStatus
2588
     * @see 			  				SpecimenTypeDesignation
2589
     * @see 			  				TypeDesignationBase#isNotDesignated()
2590
     */
2591
    @Override
2592
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2593
                SpecimenTypeDesignationStatus status,
2594
                Reference citation,
2595
                String citationMicroReference,
2596
                String originalNameString,
2597
                boolean isNotDesignated,
2598
                boolean addToAllHomotypicNames) {
2599
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2600
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2601
        return specimenTypeDesignation;
2602
    }
2603

    
2604
    //used by merge strategy
2605
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2606
        return addTypeDesignation(typeDesignation, true);
2607
    }
2608

    
2609
    /**
2610
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2611
     *
2612
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2613
     * @param addToAllNames				the boolean indicating whether the type designation should be
2614
     * 									added to all taxon names of the homotypical group the typified
2615
     * 									taxon name belongs to
2616
     * @return							true if the operation was successful
2617
     *
2618
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2619
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2620
     *
2621
     */
2622
    @Override
2623
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2624
        //currently typeDesignations are not persisted with the homotypical group
2625
        //so explicit adding to the homotypical group is not necessary.
2626
        if (typeDesignation != null){
2627
            checkHomotypicalGroup(typeDesignation);
2628
            this.typeDesignations.add(typeDesignation);
2629
            typeDesignation.addTypifiedName(this);
2630

    
2631
            if (addToAllNames){
2632
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2633
                    if (taxonName != this){
2634
                        taxonName.addTypeDesignation(typeDesignation, false);
2635
                    }
2636
                }
2637
            }
2638
        }
2639
        return true;
2640
    }
2641

    
2642
    /**
2643
     * Throws an Exception this type designation already has typified names from another homotypical group.
2644
     * @param typeDesignation
2645
     */
2646
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2647
        if(typeDesignation.getTypifiedNames().size() > 0){
2648
            Set<HomotypicalGroup> groups = new HashSet<>();
2649
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2650
            for (TaxonName taxonName: names){
2651
                groups.add(taxonName.getHomotypicalGroup());
2652
            }
2653
            if (groups.size() > 1){
2654
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2655
            }
2656
        }
2657
    }
2658

    
2659

    
2660

    
2661
//*********************** HOMOTYPICAL GROUP *********************************************//
2662

    
2663

    
2664
    /**
2665
     * Returns the {@link HomotypicalGroup homotypical group} to which
2666
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2667
     * that share the same types.
2668
     *
2669
     * @see 	HomotypicalGroup
2670
     */
2671

    
2672
    @Override
2673
    public HomotypicalGroup getHomotypicalGroup() {
2674
        if (homotypicalGroup == null){
2675
            homotypicalGroup = new HomotypicalGroup();
2676
            homotypicalGroup.typifiedNames.add(this);
2677
        }
2678
    	return homotypicalGroup;
2679
    }
2680

    
2681
    /**
2682
     * @see #getHomotypicalGroup()
2683
     */
2684
    @Override
2685
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2686
        if (homotypicalGroup == null){
2687
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2688
        }
2689
        /*if (this.homotypicalGroup != null){
2690
        	this.homotypicalGroup.removeTypifiedName(this, false);
2691
        }*/
2692
        this.homotypicalGroup = homotypicalGroup;
2693
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2694
        	 this.homotypicalGroup.addTypifiedName(this);
2695
        }
2696
    }
2697

    
2698

    
2699

    
2700
// *************************************************************************//
2701

    
2702
    /**
2703
     * Returns the complete string containing the
2704
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2705
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2706
     *
2707
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2708
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2709
     * @see		#getNomenclaturalReference()
2710
     * @see		#getNomenclaturalMicroReference()
2711
     */
2712
    @Override
2713
    @Transient
2714
    public String getCitationString(){
2715
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2716
    }
2717

    
2718
    /**
2719
     * Returns the parsing problems
2720
     * @return
2721
     */
2722
    @Override
2723
    public List<ParserProblem> getParsingProblems(){
2724
        return ParserProblem.warningList(this.parsingProblem);
2725
    }
2726

    
2727
    /**
2728
     * Returns the string containing the publication date (generally only year)
2729
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2730
     * no nomenclatural reference.
2731
     *
2732
     * @return  the string containing the publication date of <i>this</i> taxon name
2733
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2734
     */
2735
    @Override
2736
    @Transient
2737
    @ValidTaxonomicYear(groups=Level3.class)
2738
    public String getReferenceYear(){
2739
        if (this.getNomenclaturalReference() != null ){
2740
            return this.getNomenclaturalReference().getYear();
2741
        }else{
2742
            return null;
2743
        }
2744
    }
2745

    
2746
    /**
2747
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2748
     * In this context a taxon base means the use of a taxon name by a reference
2749
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2750
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2751
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2752
     * within a taxonomic treatment (identified by one reference).
2753
     *
2754
     * @see	#getTaxa()
2755
     * @see	#getSynonyms()
2756
     */
2757
    @Override
2758
    public Set<TaxonBase> getTaxonBases() {
2759
        if(taxonBases == null) {
2760
            this.taxonBases = new HashSet<TaxonBase>();
2761
        }
2762
        return this.taxonBases;
2763
    }
2764

    
2765
    /**
2766
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2767
     * to the set of taxon bases using <i>this</i> taxon name.
2768
     *
2769
     * @param  taxonBase  the taxon base to be added
2770
     * @see 			  #getTaxonBases()
2771
     * @see 			  #removeTaxonBase(TaxonBase)
2772
     */
2773
    //TODO protected
2774
    @Override
2775
    public void addTaxonBase(TaxonBase taxonBase){
2776
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2777
        ReflectionUtils.makeAccessible(method);
2778
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2779
        taxonBases.add(taxonBase);
2780
    }
2781
    /**
2782
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2783
     *
2784
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2785
     * @see    				#getTaxonBases()
2786
     * @see    				#addTaxonBase(TaxonBase)
2787
     */
2788
    @Override
2789
    public void removeTaxonBase(TaxonBase taxonBase){
2790
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2791
        ReflectionUtils.makeAccessible(method);
2792
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2793

    
2794

    
2795
    }
2796

    
2797
    /**
2798
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2799
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2800
     * the set returned by getTaxonBases().
2801
     *
2802
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2803
     * @see	#getTaxonBases()
2804
     * @see	#getSynonyms()
2805
     */
2806
    @Override
2807
    @Transient
2808
    public Set<Taxon> getTaxa(){
2809
        Set<Taxon> result = new HashSet<>();
2810
        for (TaxonBase taxonBase : this.taxonBases){
2811
            if (taxonBase instanceof Taxon){
2812
                result.add((Taxon)taxonBase);
2813
            }
2814
        }
2815
        return result;
2816
    }
2817

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

    
2839
    //******* REGISTRATION *****************/
2840

    
2841
    @Override
2842
    public Set<Registration> getRegistrations() {
2843
        return this.registrations;
2844
    }
2845

    
2846

    
2847
// ************* RELATIONSHIPS *****************************/
2848

    
2849

    
2850
    /**
2851
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2852
     * by title cache of the related names.
2853
     * @see #getHybridParentRelations()
2854
     */
2855
    @Override
2856
    @Transient
2857
    public List<HybridRelationship> getOrderedChildRelationships(){
2858
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2859
        result.addAll(this.hybridChildRelations);
2860
        Collections.sort(result);
2861
        Collections.reverse(result);
2862
        return result;
2863

    
2864
    }
2865

    
2866

    
2867
    /**
2868
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2869
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2870
     * "is first/second parent" or "is male/female parent". By invoking this
2871
     * method <i>this</i> botanical name becomes a hybrid child of the parent
2872
     * botanical name.
2873
     *
2874
     * @param parentName      the botanical name of the parent for this new hybrid name relationship
2875
     * @param type            the type of this new name relationship
2876
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2877
     * @return
2878
     * @see                   #addHybridChild(BotanicalName, HybridRelationshipType,String )
2879
     * @see                   #getRelationsToThisName()
2880
     * @see                   #getNameRelations()
2881
     * @see                   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
2882
     * @see                   #addNameRelationship(NameRelationship)
2883
     */
2884
    @Override
2885
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2886
        return new HybridRelationship(this, parentName, type, ruleConsidered);
2887
    }
2888

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

    
2911
    @Override
2912
    public void removeHybridChild(INonViralName child) {
2913
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2914
        hybridRelationships.addAll(this.getHybridChildRelations());
2915
        hybridRelationships.addAll(this.getHybridParentRelations());
2916
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2917
            // remove name relationship from this side
2918
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2919
                this.removeHybridRelationship(hybridRelationship);
2920
            }
2921
        }
2922
    }
2923

    
2924
    @Override
2925
    public void removeHybridParent(INonViralName parent) {
2926
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2927
        hybridRelationships.addAll(this.getHybridChildRelations());
2928
        hybridRelationships.addAll(this.getHybridParentRelations());
2929
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2930
            // remove name relationship from this side
2931
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2932
                this.removeHybridRelationship(hybridRelationship);
2933
            }
2934
        }
2935
    }
2936

    
2937

    
2938

    
2939
// *********** DESCRIPTIONS *************************************
2940

    
2941
    /**
2942
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2943
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2944
     * concerning the taxon name like for instance the content of its first
2945
     * publication (protolog) or a picture of this publication.
2946
     *
2947
     * @see	#addDescription(TaxonNameDescription)
2948
     * @see	#removeDescription(TaxonNameDescription)
2949
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2950
     */
2951
    @Override
2952
    public Set<TaxonNameDescription> getDescriptions() {
2953
        return descriptions;
2954
    }
2955

    
2956
    /**
2957
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2958
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2959
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2960
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2961
     *
2962
     * @param  description  the taxon name description to be added
2963
     * @see					#getDescriptions()
2964
     * @see 			  	#removeDescription(TaxonNameDescription)
2965
     */
2966
    @Override
2967
    public void addDescription(TaxonNameDescription description) {
2968
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2969
        ReflectionUtils.makeAccessible(field);
2970
        ReflectionUtils.setField(field, description, this);
2971
        descriptions.add(description);
2972
    }
2973
    /**
2974
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2975
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2976
     * of the description itself will be set to "null".
2977
     *
2978
     * @param  description  the taxon name description which should be removed
2979
     * @see     		  	#getDescriptions()
2980
     * @see     		  	#addDescription(TaxonNameDescription)
2981
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
2982
     */
2983
    @Override
2984
    public void removeDescription(TaxonNameDescription description) {
2985
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2986
        ReflectionUtils.makeAccessible(field);
2987
        ReflectionUtils.setField(field, description, null);
2988
        descriptions.remove(description);
2989
    }
2990

    
2991
// *********** HOMOTYPIC GROUP METHODS **************************************************
2992

    
2993
    @Override
2994
    @Transient
2995
    public void mergeHomotypicGroups(TaxonName name){
2996
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2997
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2998
        name.setHomotypicalGroup(this.homotypicalGroup);
2999
    }
3000

    
3001
    /**
3002
     * Returns the boolean value indicating whether a given taxon name belongs
3003
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
3004
     * or not (false). Returns "true" only if the homotypical groups of both
3005
     * taxon names exist and if they are identical.
3006
     *
3007
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
3008
     * @return  			   the boolean value of the check
3009
     * @see     			   HomotypicalGroup
3010
     */
3011
    @Override
3012
    @Transient
3013
    public boolean isHomotypic(TaxonName homoTypicName) {
3014
        if (homoTypicName == null) {
3015
            return false;
3016
        }
3017
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
3018
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
3019
            return false;
3020
        }
3021
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
3022
            return true;
3023
        }
3024
        return false;
3025
    }
3026

    
3027

    
3028
    /**
3029
     * Checks whether name is a basionym for ALL names
3030
     * in its homotypical group.
3031
     * Returns <code>false</code> if there are no other names in the group
3032
     * @param name
3033
     * @return
3034
     */
3035
    @Override
3036
    @Transient
3037
    public boolean isGroupsBasionym() {
3038
    	if (homotypicalGroup == null){
3039
    		homotypicalGroup = HomotypicalGroup.NewInstance();
3040
    		homotypicalGroup.addTypifiedName(this);
3041
    	}
3042
        Set<TaxonName> typifiedNames = homotypicalGroup.getTypifiedNames();
3043

    
3044
        // Check whether there are any other names in the group
3045
        if (typifiedNames.size() == 1) {
3046
                return false;
3047
        }
3048

    
3049
        for (TaxonName taxonName : typifiedNames) {
3050
                if (!taxonName.equals(this)) {
3051
                        if (! isBasionymFor(taxonName)) {
3052
                                return false;
3053
                        }
3054
                }
3055
        }
3056
        return true;
3057
    }
3058

    
3059
    /**
3060
     * Checks whether a basionym relationship exists between fromName and toName.
3061
     *
3062
     * @param fromName
3063
     * @param toName
3064
     * @return
3065
     */
3066
    @Override
3067
    @Transient
3068
    public boolean isBasionymFor(TaxonName newCombinationName) {
3069
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3070
            for (NameRelationship relation : relations) {
3071
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3072
                                    relation.getFromName().equals(this)) {
3073
                            return true;
3074
                    }
3075
            }
3076
            return false;
3077
    }
3078

    
3079
    /**
3080
     * Creates a basionym relationship to all other names in this names homotypical
3081
     * group.
3082
     *
3083
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3084
     */
3085
    @Override
3086
    @Transient
3087
    public void makeGroupsBasionym() {
3088
        this.homotypicalGroup.setGroupBasionym(this);
3089
    }
3090

    
3091

    
3092
//*********  Rank comparison shortcuts   ********************//
3093
    /**
3094
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3095
     * taxon name is higher than the genus rank (true) or not (false).
3096
     * Suprageneric non viral names are monomials.
3097
     * Returns false if rank is null.
3098
     *
3099
     * @see  #isGenus()
3100
     * @see  #isInfraGeneric()
3101
     * @see  #isSpecies()
3102
     * @see  #isInfraSpecific()
3103
     */
3104
    @Override
3105
    @Transient
3106
    public boolean isSupraGeneric() {
3107
        if (rank == null){
3108
            return false;
3109
        }
3110
        return getRank().isSupraGeneric();
3111
    }
3112
    /**
3113
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3114
     * taxon name is the genus rank (true) or not (false). Non viral names with
3115
     * genus rank are monomials. Returns false if rank is null.
3116
     *
3117
     * @see  #isSupraGeneric()
3118
     * @see  #isInfraGeneric()
3119
     * @see  #isSpecies()
3120
     * @see  #isInfraSpecific()
3121
     */
3122
    @Override
3123
    @Transient
3124
    public boolean isGenus() {
3125
        if (rank == null){
3126
            return false;
3127
        }
3128
        return getRank().isGenus();
3129
    }
3130

    
3131
    @Override
3132
    @Transient
3133
    public boolean isGenusOrSupraGeneric() {
3134
        return isGenus()|| isSupraGeneric();
3135
    }
3136
    /**
3137
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3138
     * taxon name is higher than the species rank and lower than the
3139
     * genus rank (true) or not (false). Infrageneric non viral names are
3140
     * binomials. Returns false if rank is null.
3141
     *
3142
     * @see  #isSupraGeneric()
3143
     * @see  #isGenus()
3144
     * @see  #isSpecies()
3145
     * @see  #isInfraSpecific()
3146
     */
3147
    @Override
3148
    @Transient
3149
    public boolean isInfraGeneric() {
3150
        if (rank == null){
3151
            return false;
3152
        }
3153
        return getRank().isInfraGeneric();
3154
    }
3155

    
3156
    /**
3157
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3158
     * taxon name is higher than the species rank (true) or not (false).
3159
     * Returns false if rank is null.
3160
     *
3161
     * @see  #isGenus()
3162
     * @see  #isInfraGeneric()
3163
     * @see  #isSpecies()
3164
     * @see  #isInfraSpecific()
3165
     */
3166
    @Override
3167
    @Transient
3168
    public boolean isSupraSpecific(){
3169
        if (rank == null) {
3170
            return false;
3171
        }
3172
        return getRank().isHigher(Rank.SPECIES());
3173
    }
3174

    
3175
    /**
3176
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3177
     * taxon name is the species rank (true) or not (false). Non viral names
3178
     * with species rank are binomials.
3179
     * Returns false if rank is null.
3180
     *
3181
     * @see  #isSupraGeneric()
3182
     * @see  #isGenus()
3183
     * @see  #isInfraGeneric()
3184
     * @see  #isInfraSpecific()
3185
     */
3186
    @Override
3187
    @Transient
3188
    public boolean isSpecies() {
3189
        if (rank == null){
3190
            return false;
3191
        }
3192
        return getRank().isSpecies();
3193
    }
3194
    /**
3195
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3196
     * taxon name is lower than the species rank (true) or not (false).
3197
     * Infraspecific non viral names are trinomials.
3198
     * Returns false if rank is null.
3199
     *
3200
     * @see  #isSupraGeneric()
3201
     * @see  #isGenus()
3202
     * @see  #isInfraGeneric()
3203
     * @see  #isSpecies()
3204
     */
3205
    @Override
3206
    @Transient
3207
    public boolean isInfraSpecific() {
3208
        if (rank == null){
3209
            return false;
3210
        }
3211
        return getRank().isInfraSpecific();
3212
    }
3213

    
3214
    /**
3215
     * Returns true if this name's rank indicates a rank that aggregates species like species
3216
     * aggregates or species groups, false otherwise. This methods currently returns false
3217
     * for all user defined ranks.
3218
     *
3219
     *@see Rank#isSpeciesAggregate()
3220
     *
3221
     * @return
3222
     */
3223
    @Override
3224
    @Transient
3225
    public boolean isSpeciesAggregate() {
3226
        if (rank == null){
3227
            return false;
3228
        }
3229
        return getRank().isSpeciesAggregate();
3230
    }
3231

    
3232

    
3233
    /**
3234
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3235
     * the construction of <i>this</i> taxon name since there is no specific
3236
     * nomenclatural code defined. The real implementention takes place in the
3237
     * subclasses {@link IBacterialName BacterialName},
3238
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3239
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3240
     * and only one nomenclatural code.
3241
     *
3242
     * @return  null
3243
     * @see  	#isCodeCompliant()
3244
     * @see  	#getHasProblem()
3245
     * @deprecated use {@link #getNameType()} instead
3246
     */
3247
    @Override
3248
    @Deprecated
3249
    @Transient
3250
    @java.beans.Transient
3251
    public NomenclaturalCode getNomenclaturalCode() {
3252
        return nameType;
3253
    }
3254

    
3255

    
3256
    /**
3257
     * Generates and returns the string with the scientific name of <i>this</i>
3258
     * taxon name (only non viral taxon names can be generated from their
3259
     * components). This string may be stored in the inherited
3260
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3261
     * This method overrides the generic and inherited
3262
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3263
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3264
     *
3265
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3266
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3267
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3268
     */
3269
//	@Override
3270
//	public abstract String generateTitle();
3271

    
3272
    /**
3273
     * Creates a basionym relationship between this name and
3274
     * 	each name in its homotypic group.
3275
     *
3276
     * @param basionymName
3277
     */
3278
    @Override
3279
    @Transient
3280
    public void setAsGroupsBasionym() {
3281

    
3282
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3283
        if (homotypicalGroup == null) {
3284
            return;
3285
        }
3286

    
3287
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3288
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3289

    
3290
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3291

    
3292
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3293

    
3294
            for(NameRelationship nameRelation : nameRelations){
3295
                relations.add(nameRelation);
3296
            }
3297
        }
3298

    
3299
        for (NameRelationship relation : relations) {
3300

    
3301
            // If this is a basionym relation, and toName is in the homotypical group,
3302
            //	remove the relationship.
3303
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3304
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3305
                removeRelations.add(relation);
3306
            }
3307
        }
3308

    
3309
        // Removing relations from a set through which we are iterating causes a
3310
        //	ConcurrentModificationException. Therefore, we delete the targeted
3311
        //	relations in a second step.
3312
        for (NameRelationship relation : removeRelations) {
3313
            this.removeNameRelationship(relation);
3314
        }
3315

    
3316
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3317
            if (!name.equals(this)) {
3318

    
3319
                // First check whether the relationship already exists
3320
                if (!this.isBasionymFor(name)) {
3321

    
3322
                    // Then create it
3323
                    name.addRelationshipFromName(this,
3324
                            NameRelationshipType.BASIONYM(), null);
3325
                }
3326
            }
3327
        }
3328
    }
3329

    
3330
    /**
3331
     * Removes basionym relationship between this name and
3332
     * 	each name in its homotypic group.
3333
     *
3334
     * @param basionymName
3335
     */
3336
    @Override
3337
    @Transient
3338
    public void removeAsGroupsBasionym() {
3339

    
3340
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3341

    
3342
        if (homotypicalGroup == null) {
3343
            return;
3344
        }
3345

    
3346
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3347
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3348

    
3349
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3350

    
3351
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3352

    
3353
            for(NameRelationship nameRelation : nameRelations){
3354
                relations.add(nameRelation);
3355
            }
3356
        }
3357

    
3358
        for (NameRelationship relation : relations) {
3359

    
3360
            // If this is a basionym relation, and toName is in the homotypical group,
3361
            //	and fromName is basionymName, remove the relationship.
3362
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3363
                    relation.getFromName().equals(this) &&
3364
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3365
                removeRelations.add(relation);
3366
            }
3367
        }
3368

    
3369
        // Removing relations from a set through which we are iterating causes a
3370
        //	ConcurrentModificationException. Therefore, we delete the targeted
3371
        //	relations in a second step.
3372
        for (NameRelationship relation : removeRelations) {
3373
            this.removeNameRelationship(relation);
3374
        }
3375
    }
3376

    
3377

    
3378
    /**
3379
     * Defines the last part of the name.
3380
     * This is for infraspecific taxa, the infraspecific epithet,
3381
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3382
     * else the genusOrUninomial.
3383
     * However, the result does not depend on the rank (which may be not correctly set
3384
     * in case of dirty data) but returns the first name part which is not blank
3385
     * considering the above order.
3386
     * @return the first not blank name part in reverse order
3387
     */
3388
    @Override
3389
    public String getLastNamePart() {
3390
        String result =
3391
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3392
                    this.getInfraSpecificEpithet() :
3393
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3394
                    this.getSpecificEpithet():
3395
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3396
                    this.getInfraGenericEpithet():
3397
                this.getGenusOrUninomial();
3398
        return result;
3399
    }
3400

    
3401
    /**
3402
     * {@inheritDoc}
3403
     */
3404
    @Override
3405
    public boolean isHybridName() {
3406
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3407
    }
3408

    
3409
    /**
3410
     * {@inheritDoc}
3411
     */
3412
    @Override
3413
    public boolean isHybrid() {
3414
        return this.isHybridName() || this.isHybridFormula();
3415
    }
3416

    
3417
// ***************** COMPARE ********************************/
3418

    
3419
    @Override
3420
    public int compareToName(TaxonName otherName){
3421

    
3422
        int result = 0;
3423

    
3424
        if (otherName == null) {
3425
            throw new NullPointerException("Cannot compare to null.");
3426
        }
3427

    
3428
        //other
3429
        otherName = deproxy(otherName);
3430
        String otherNameCache = otherName.getNameCache();
3431
        String otherTitleCache = otherName.getTitleCache();
3432
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3433
        if (otherName.isAutonym()){
3434
            boolean isProtected = otherName.isProtectedNameCache();
3435
            String oldNameCache = otherName.getNameCache();
3436
            otherName.setProtectedNameCache(false);
3437
            otherName.setNameCache(null, false);
3438
            otherNameCache = otherName.getNameCache();
3439
            otherName.setNameCache(oldNameCache, isProtected);
3440
        }
3441

    
3442
        //this
3443
        String thisNameCache = this.getNameCache();
3444
        String thisTitleCache = this.getTitleCache();
3445

    
3446
        if (this.isAutonym()){
3447
            boolean isProtected = this.isProtectedNameCache();
3448
            String oldNameCache = this.getNameCache();
3449
            this.setProtectedNameCache(false);
3450
            this.setNameCache(null, false);
3451
            thisNameCache = this.getNameCache();
3452
            this.setNameCache(oldNameCache, isProtected);
3453
        }
3454

    
3455

    
3456
        // Compare name cache of taxon names
3457
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3458
            thisNameCache = normalizeName(thisNameCache);
3459
            otherNameCache = normalizeName(otherNameCache);
3460
            result = thisNameCache.compareTo(otherNameCache);
3461
        }
3462

    
3463
        // Compare title cache of taxon names
3464
        if (result == 0){
3465
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3466
                thisTitleCache = normalizeName(thisTitleCache);
3467
                otherTitleCache = normalizeName(otherTitleCache);
3468
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3469
            }
3470
        }
3471

    
3472
        return result;
3473
    }
3474

    
3475
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3476
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3477

    
3478
    /**
3479
     * @param thisNameCache
3480
     * @param HYBRID_SIGN
3481
     * @param QUOT_SIGN
3482
     * @return
3483
     */
3484
    private String normalizeName(String thisNameCache) {
3485
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3486
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3487
        return thisNameCache;
3488
    }
3489

    
3490
// ********************** INTERFACES ********************************************/
3491

    
3492
    /**
3493
     * Method to cast a interfaced name to a concrete name.
3494
     * The method includes a deproxy to guarantee that no
3495
     * class cast exception is thrown.
3496
     *
3497
     * @see #castAndDeproxy(Set)
3498
     * @param interfacedName
3499
     * @return
3500
     */
3501
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3502
        return deproxy(interfacedName, TaxonName.class);
3503
    }
3504

    
3505
    /**
3506
     * Method to cast a set of interfaced names to concrete namex.
3507
     * The method includes a deproxy to guarantee that no
3508
     * class cast exception is thrown.
3509
     *
3510
     * @see #castAndDeproxy(ITaxonNameBase)
3511
     * @param naminterfacedNames
3512
     * @return
3513
     */
3514
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3515
        Set<TaxonName> result = new HashSet<>();
3516
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3517
            result.add(castAndDeproxy(naminterfacedName));
3518
        }
3519
        return result;
3520
    }
3521

    
3522
//************************ isType ***********************************************/
3523

    
3524
    /**
3525
     * @return
3526
     */
3527
    @Override
3528
    public boolean isNonViral() {
3529
        return nameType.isNonViral();
3530
    }
3531

    
3532
    @Override
3533
    public boolean isZoological(){
3534
        return nameType.isZoological();
3535
    }
3536
    @Override
3537
    public boolean isBotanical() {
3538
        return nameType.isBotanical();
3539
    }
3540
    @Override
3541
    public boolean isCultivar() {
3542
        return nameType.isCultivar();
3543
    }
3544
    @Override
3545
    public boolean isBacterial() {
3546
        return nameType.isBacterial();
3547
    }
3548
    @Override
3549
    public boolean isViral() {
3550
        return nameType.isViral();
3551
    }
3552

    
3553

    
3554
//*********************** CLONE ********************************************************/
3555

    
3556
    /**
3557
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3558
     * a new instance that differs only slightly from <i>this</i> taxon name by
3559
     * modifying only some of the attributes.<BR><BR>
3560
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3561
     * <b>The name gets a newly created homotypical group</b><BR>
3562
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3563
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3564
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3565
     *
3566
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3567
     * @see java.lang.Object#clone()
3568
     */
3569
    @Override
3570
    public Object clone() {
3571
        TaxonName result;
3572
        try {
3573
            result = (TaxonName)super.clone();
3574

    
3575
            //taxonBases -> empty
3576
            result.taxonBases = new HashSet<>();
3577

    
3578
            //empty caches
3579
            if (! protectedFullTitleCache){
3580
                result.fullTitleCache = null;
3581
            }
3582

    
3583
            //descriptions
3584
            result.descriptions = new HashSet<>();
3585
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3586
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3587
                result.descriptions.add(newDescription);
3588
            }
3589

    
3590
            //status
3591
            result.status = new HashSet<>();
3592
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3593
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3594
                result.status.add(newStatus);
3595
            }
3596

    
3597

    
3598
            //to relations
3599
            result.relationsToThisName = new HashSet<>();
3600
            for (NameRelationship toRelationship : getRelationsToThisName()){
3601
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3602
                newRelationship.setRelatedTo(result);
3603
                result.relationsToThisName.add(newRelationship);
3604
            }
3605

    
3606
            //from relations
3607
            result.relationsFromThisName = new HashSet<>();
3608
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3609
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3610
                newRelationship.setRelatedFrom(result);
3611
                result.relationsFromThisName.add(newRelationship);
3612
            }
3613

    
3614
            //type designations
3615
            result.typeDesignations = new HashSet<>();
3616
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3617
                TypeDesignationBase<?> newDesignation = (TypeDesignationBase<?>)typeDesignation.clone();
3618
                this.removeTypeDesignation(newDesignation);
3619
                result.addTypeDesignation(newDesignation, false);
3620
            }
3621

    
3622
            //homotypicalGroup
3623
            //TODO still needs to be discussed
3624
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3625
            result.homotypicalGroup.addTypifiedName(this);
3626

    
3627

    
3628
            //HybridChildRelations
3629
            result.hybridChildRelations = new HashSet<>();
3630
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3631
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3632
                newChildRelationship.setRelatedTo(result);
3633
                result.hybridChildRelations.add(newChildRelationship);
3634
            }
3635

    
3636
            //HybridParentRelations
3637
            result.hybridParentRelations = new HashSet<>();
3638
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3639
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3640
                newParentRelationship.setRelatedFrom(result);
3641
                result.hybridParentRelations.add(newParentRelationship);
3642
            }
3643

    
3644
            //empty caches
3645
            if (! protectedNameCache){
3646
                result.nameCache = null;
3647
            }
3648

    
3649
            //empty caches
3650
            if (! protectedAuthorshipCache){
3651
                result.authorshipCache = null;
3652
            }
3653

    
3654
            //no changes to: appendedPharse, nomenclaturalReference,
3655
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3656
            //protectedFullTitleCache, rank
3657
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3658
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3659
            //protectedAuthorshipCache, protectedNameCache,
3660
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3661
            //acronym
3662
            //subGenusAuthorship, nameApprobation
3663
            //anamorphic
3664
            //cultivarName
3665
            return result;
3666
        } catch (CloneNotSupportedException e) {
3667
            logger.warn("Object does not implement cloneable");
3668
            e.printStackTrace();
3669
            return null;
3670
        }
3671

    
3672
    }
3673

    
3674

    
3675
}
3676

    
(29-29/36)