Project

General

Profile

Download (137 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.IIntextReferenceTarget;
71
import eu.etaxonomy.cdm.model.common.IParsable;
72
import eu.etaxonomy.cdm.model.common.IRelated;
73
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
74
import eu.etaxonomy.cdm.model.common.RelationshipBase;
75
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
76
import eu.etaxonomy.cdm.model.description.IDescribable;
77
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
78
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
79
import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
80
import eu.etaxonomy.cdm.model.reference.Reference;
81
import eu.etaxonomy.cdm.model.taxon.Synonym;
82
import eu.etaxonomy.cdm.model.taxon.Taxon;
83
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
84
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
85
import eu.etaxonomy.cdm.strategy.cache.name.CacheUpdate;
86
import eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy;
87
import eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy;
88
import eu.etaxonomy.cdm.strategy.match.IMatchable;
89
import eu.etaxonomy.cdm.strategy.match.Match;
90
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
91
import eu.etaxonomy.cdm.strategy.match.MatchMode;
92
import eu.etaxonomy.cdm.strategy.merge.Merge;
93
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
94
import eu.etaxonomy.cdm.strategy.parser.ParserProblem;
95
import eu.etaxonomy.cdm.validation.Level2;
96
import eu.etaxonomy.cdm.validation.Level3;
97
import eu.etaxonomy.cdm.validation.annotation.CorrectEpithetsForRank;
98
import eu.etaxonomy.cdm.validation.annotation.NameMustFollowCode;
99
import eu.etaxonomy.cdm.validation.annotation.NameMustHaveAuthority;
100
import eu.etaxonomy.cdm.validation.annotation.NoDuplicateNames;
101
import eu.etaxonomy.cdm.validation.annotation.NullOrNotEmpty;
102
import eu.etaxonomy.cdm.validation.annotation.ValidTaxonomicYear;
103

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

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

    
158
    "acronym",
159

    
160
    "subGenusAuthorship",
161
    "nameApprobation",
162

    
163
    "breed",
164
    "publicationYear",
165
    "originalPublicationYear",
166

    
167
    "anamorphic",
168

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

    
190
    private static final long serialVersionUID = -791164269603409712L;
191
    private static final Logger logger = Logger.getLogger(TaxonName.class);
192

    
193

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

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

    
214
    //if true titleCache will not be automatically generated/updated
215
    @XmlElement(name = "ProtectedFullTitleCache")
216
    @CacheUpdate(value ="fullTitleCache", noUpdate ="titleCache")
217
    private boolean protectedFullTitleCache;
218

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

    
226
    @XmlElement(name = "AppendedPhrase")
227
    @Field
228
    @CacheUpdate(value ="nameCache")
229
    //TODO Val #3379
230
//    @NullOrNotEmpty
231
    @Column(length=255)
232
    private String appendedPhrase;
233

    
234
    @XmlElement(name = "NomenclaturalMicroReference")
235
    @Field
236
    @CacheUpdate(noUpdate ="titleCache")
237
    //TODO Val #3379
238
//    @NullOrNotEmpty
239
    @Column(length=255)
240
    private String nomenclaturalMicroReference;
241

    
242
    @XmlAttribute
243
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
244
    private int parsingProblem = 0;
245

    
246
    @XmlAttribute
247
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
248
    private int problemStarts = -1;
249

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

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

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

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

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

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

    
307
    @XmlElementWrapper(name = "TaxonBases")
308
    @XmlElement(name = "TaxonBase")
309
    @XmlIDREF
310
    @XmlSchemaType(name = "IDREF")
311
    @OneToMany(mappedBy="name", fetch= FetchType.LAZY)
312
    @NotNull
313
    @IndexedEmbedded(depth=1)
314
    private Set<TaxonBase> taxonBases = new HashSet<>();
315

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

    
326
    @XmlElement(name = "NomenclaturalReference")
327
    @XmlIDREF
328
    @XmlSchemaType(name = "IDREF")
329
    @ManyToOne(fetch = FetchType.LAZY)
330
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
331
    @CacheUpdate(noUpdate ="titleCache")
332
    @IndexedEmbedded
333
    private Reference nomenclaturalReference;
334

    
335
    @XmlElementWrapper(name = "Registrations")
336
    @XmlElement(name = "Registration")
337
    @XmlIDREF
338
    @XmlSchemaType(name = "IDREF")
339
    @OneToMany(mappedBy="name", fetch= FetchType.LAZY)
340
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
341
    @NotNull
342
    @IndexedEmbedded(depth=1)
343
    private Set<Registration> registrations = new HashSet<>();
344

    
345
//****** Non-ViralName attributes ***************************************/
346

    
347
    @XmlElement(name = "NameCache")
348
    @Fields({
349
        @Field(name = "nameCache_tokenized"),
350
        @Field(store = Store.YES, index = Index.YES, analyze = Analyze.YES)
351
    })
352
    @Analyzer(impl = org.apache.lucene.analysis.core.KeywordAnalyzer.class)
353
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
354
            cacheReplacedProperties={"genusOrUninomial", "infraGenericEpithet", "specificEpithet", "infraSpecificEpithet"} )
355
    @NotEmpty(groups = Level2.class) // implicitly NotNull
356
    @Column(length=255)
357
    private String nameCache;
358

    
359
    @XmlElement(name = "ProtectedNameCache")
360
    @CacheUpdate(value="nameCache")
361
    protected boolean protectedNameCache;
362

    
363
    @XmlElement(name = "GenusOrUninomial")
364
    @Field(analyze = Analyze.YES, indexNullAs=Field.DEFAULT_NULL_TOKEN)
365
    @Match(MatchMode.EQUAL_REQUIRED)
366
    @CacheUpdate("nameCache")
367
    @Column(length=255)
368
    @Pattern(regexp = "[A-Z][a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForUninomial.message}")
369
    @NullOrNotEmpty
370
    @NotNull(groups = Level2.class)
371
    private String genusOrUninomial;
372

    
373
    @XmlElement(name = "InfraGenericEpithet")
374
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
375
    @CacheUpdate("nameCache")
376
    //TODO Val #3379
377
//    @NullOrNotEmpty
378
    @Column(length=255)
379
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class,message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
380
    private String infraGenericEpithet;
381

    
382
    @XmlElement(name = "SpecificEpithet")
383
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
384
    @CacheUpdate("nameCache")
385
    //TODO Val #3379
386
//    @NullOrNotEmpty
387
    @Column(length=255)
388
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
389
    private String specificEpithet;
390

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

    
400
    @XmlElement(name = "CombinationAuthorship", type = TeamOrPersonBase.class)
401
    @XmlIDREF
402
    @XmlSchemaType(name = "IDREF")
403
    @ManyToOne(fetch = FetchType.LAZY)
404
//    @Target(TeamOrPersonBase.class)
405
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
406
    @JoinColumn(name="combinationAuthorship_id")
407
    @CacheUpdate("authorshipCache")
408
    @IndexedEmbedded
409
    private TeamOrPersonBase<?> combinationAuthorship;
410

    
411
    @XmlElement(name = "ExCombinationAuthorship", type = TeamOrPersonBase.class)
412
    @XmlIDREF
413
    @XmlSchemaType(name = "IDREF")
414
    @ManyToOne(fetch = FetchType.LAZY)
415
//    @Target(TeamOrPersonBase.class)
416
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
417
    @JoinColumn(name="exCombinationAuthorship_id")
418
    @CacheUpdate("authorshipCache")
419
    @IndexedEmbedded
420
    private TeamOrPersonBase<?> exCombinationAuthorship;
421

    
422
    @XmlElement(name = "BasionymAuthorship", type = TeamOrPersonBase.class)
423
    @XmlIDREF
424
    @XmlSchemaType(name = "IDREF")
425
    @ManyToOne(fetch = FetchType.LAZY)
426
//    @Target(TeamOrPersonBase.class)
427
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
428
    @JoinColumn(name="basionymAuthorship_id")
429
    @CacheUpdate("authorshipCache")
430
    @IndexedEmbedded
431
    private TeamOrPersonBase<?> basionymAuthorship;
432

    
433
    @XmlElement(name = "ExBasionymAuthorship", type = TeamOrPersonBase.class)
434
    @XmlIDREF
435
    @XmlSchemaType(name = "IDREF")
436
    @ManyToOne(fetch = FetchType.LAZY)
437
//    @Target(TeamOrPersonBase.class)
438
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
439
    @JoinColumn(name="exBasionymAuthorship_id")
440
    @CacheUpdate("authorshipCache")
441
    @IndexedEmbedded
442
    private TeamOrPersonBase<?> exBasionymAuthorship;
443

    
444
    @XmlElement(name = "AuthorshipCache")
445
    @Fields({
446
        @Field(name = "authorshipCache_tokenized"),
447
        @Field(analyze = Analyze.NO)
448
    })
449
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
450
            cacheReplacedProperties={"combinationAuthorship", "basionymAuthorship", "exCombinationAuthorship", "exBasionymAuthorship"} )
451
    //TODO Val #3379
452
//    @NotNull
453
    @Column(length=255)
454
    @Pattern(regexp = "^[A-Za-z0-9 \\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-\\&\\,\\(\\)\\.]+$", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForAuthority.message}")
455
    private String authorshipCache;
456

    
457
    @XmlElement(name = "ProtectedAuthorshipCache")
458
    @CacheUpdate("authorshipCache")
459
    protected boolean protectedAuthorshipCache;
460

    
461
    @XmlElementWrapper(name = "HybridRelationsFromThisName")
462
    @XmlElement(name = "HybridRelationsFromThisName")
463
    @OneToMany(mappedBy="relatedFrom", fetch = FetchType.LAZY)
464
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
465
    @Merge(MergeMode.RELATION)
466
    @NotNull
467
    private Set<HybridRelationship> hybridParentRelations = new HashSet<>();
468

    
469
    @XmlElementWrapper(name = "HybridRelationsToThisName")
470
    @XmlElement(name = "HybridRelationsToThisName")
471
    @OneToMany(mappedBy="relatedTo", fetch = FetchType.LAZY, orphanRemoval=true) //a hybrid relation can be deleted automatically if the child is deleted.
472
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
473
    @Merge(MergeMode.RELATION)
474
    @NotNull
475
    private Set<HybridRelationship> hybridChildRelations = new HashSet<>();
476

    
477

    
478
    //if set: this name is a hybrid formula (a hybrid that does not have an own name) and no
479
    //other hybrid flags may be set. A
480
    //hybrid name  may not have either an authorteam nor other name components.
481
    @XmlElement(name ="IsHybridFormula")
482
    @CacheUpdate("nameCache")
483
    private boolean hybridFormula = false;
484

    
485
    @XmlElement(name ="IsMonomHybrid")
486
    @CacheUpdate("nameCache")
487
    private boolean monomHybrid = false;
488

    
489
    @XmlElement(name ="IsBinomHybrid")
490
    @CacheUpdate("nameCache")
491
    private boolean binomHybrid = false;
492

    
493
    @XmlElement(name ="IsTrinomHybrid")
494
    @CacheUpdate("nameCache")
495
    private boolean trinomHybrid = false;
496

    
497
// ViralName attributes ************************* /
498

    
499
    @XmlElement(name = "Acronym")
500
    @Field
501
    //TODO Val #3379
502
//  @NullOrNotEmpty
503
    @Column(length=255)
504
    private String acronym;
505

    
506
// BacterialName attributes ***********************/
507

    
508
    //Author team and year of the subgenus name
509
    @XmlElement(name = "SubGenusAuthorship")
510
    @Field
511
    private String subGenusAuthorship;
512

    
513
    //Approbation of name according to approved list, validation list, or validly published, paper in IJSB after 1980
514
    @XmlElement(name = "NameApprobation")
515
    @Field
516
    private String nameApprobation;
517

    
518
    //ZOOLOGICAL NAME
519

    
520
    //Name of the breed of an animal
521
    @XmlElement(name = "Breed")
522
    @Field
523
    @NullOrNotEmpty
524
    @Column(length=255)
525
    private String breed;
526

    
527
    @XmlElement(name = "PublicationYear")
528
    @Field(analyze = Analyze.NO)
529
    @CacheUpdate(value ="authorshipCache")
530
    @Min(0)
531
    private Integer publicationYear;
532

    
533
    @XmlElement(name = "OriginalPublicationYear")
534
    @Field(analyze = Analyze.NO)
535
    @CacheUpdate(value ="authorshipCache")
536
    @Min(0)
537
    private Integer originalPublicationYear;
538

    
539
    //Cultivar attribute(s)
540

    
541
    //the characteristical name of the cultivar
542
    @XmlElement(name = "CultivarName")
543
    //TODO Val #3379
544
    //@NullOrNotEmpty
545
    @Column(length=255)
546
    private String cultivarName;
547

    
548
    // ************** FUNGUS name attributes
549
    //to indicate that the type of the name is asexual or not
550
    @XmlElement(name ="IsAnamorphic")
551
    private boolean anamorphic = false;
552

    
553
// *************** FACTORY METHODS ********************************/
554

    
555
    //see TaxonNameFactory
556
    /**
557
     * @param code
558
     * @param rank
559
     * @param homotypicalGroup
560
     * @return
561
     */
562
    protected static TaxonName NewInstance(NomenclaturalCode code, Rank rank,
563
            HomotypicalGroup homotypicalGroup) {
564
        TaxonName result = new TaxonName(code, rank, homotypicalGroup);
565
        return result;
566
    }
567

    
568

    
569
    /**
570
     * @param icnafp
571
     * @param rank2
572
     * @param genusOrUninomial2
573
     * @param infraGenericEpithet2
574
     * @param specificEpithet2
575
     * @param infraSpecificEpithet2
576
     * @param combinationAuthorship2
577
     * @param nomenclaturalReference2
578
     * @param nomenclMicroRef
579
     * @param homotypicalGroup2
580
     * @return
581
     */
582
    public static TaxonName NewInstance(NomenclaturalCode code, Rank rank, String genusOrUninomial,
583
            String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet,
584
            TeamOrPersonBase combinationAuthorship, Reference nomenclaturalReference,
585
            String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
586
        TaxonName result = new TaxonName(code, rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
587
        return result;
588
    }
589

    
590

    
591
// ************* CONSTRUCTORS *************/
592
    /**
593
     * Class constructor: creates a new empty taxon name.
594
     * @param code
595
     *
596
     * @see #TaxonName(Rank)
597
     * @see #TaxonName(HomotypicalGroup)
598
     * @see #TaxonName(Rank, HomotypicalGroup)
599
     */
600
    protected TaxonName() {
601
        super();
602
        rectifyNameCacheStrategy();
603
    }
604

    
605

    
606
    /**
607
     * Class constructor: creates a new taxon name instance
608
     * only containing its {@link Rank rank} and
609
     * its {@link HomotypicalGroup homotypical group} and
610
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy default cache strategy}.
611
     * The new taxon name will be also added to the set of taxon names
612
     * belonging to this homotypical group.
613
     *
614
     * @param  rank  			 the rank to be assigned to <i>this</i> taxon name
615
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
616
     * @see    					 #TaxonName()
617
     * @see    					 #TaxonName(Rank)
618
     * @see    					 #TaxonName(HomotypicalGroup)
619
     */
620
    protected TaxonName(NomenclaturalCode type, Rank rank, HomotypicalGroup homotypicalGroup) {
621
        this();
622
        setNameType(type);
623
        this.setRank(rank);
624
        if (homotypicalGroup == null){
625
            homotypicalGroup = HomotypicalGroup.NewInstance();
626
        }
627
        homotypicalGroup.addTypifiedName(this);
628
        this.homotypicalGroup = homotypicalGroup;
629
    }
630

    
631

    
632
    /**
633
     * Class constructor: creates a new non viral taxon name instance
634
     * containing its {@link Rank rank},
635
     * its {@link HomotypicalGroup homotypical group},
636
     * its scientific name components, its {@link eu.etaxonomy.cdm.model.agent.TeamOrPersonBase author(team)},
637
     * its {@link eu.etaxonomy.cdm.model.reference.Reference nomenclatural reference} and
638
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy default cache strategy}.
639
     * The new non viral taxon name instance will be also added to the set of
640
     * non viral taxon names belonging to this homotypical group.
641
     *
642
     * @param   rank  the rank to be assigned to <i>this</i> non viral taxon name
643
     * @param   genusOrUninomial the string for <i>this</i> non viral taxon name
644
     *          if its rank is genus or higher or for the genus part
645
     *          if its rank is lower than genus
646
     * @param   infraGenericEpithet  the string for the first epithet of
647
     *          <i>this</i> non viral taxon name if its rank is lower than genus
648
     *          and higher than species aggregate
649
     * @param   specificEpithet  the string for the first epithet of
650
     *          <i>this</i> non viral taxon name if its rank is species aggregate or lower
651
     * @param   infraSpecificEpithet  the string for the second epithet of
652
     *          <i>this</i> non viral taxon name if its rank is lower than species
653
     * @param   combinationAuthorship  the author or the team who published <i>this</i> non viral taxon name
654
     * @param   nomenclaturalReference  the nomenclatural reference where <i>this</i> non viral taxon name was published
655
     * @param   nomenclMicroRef  the string with the details for precise location within the nomenclatural reference
656
     * @param   homotypicalGroup  the homotypical group to which <i>this</i> non viral taxon name belongs
657
     * @see     #NewInstance(NomenclaturalCode, Rank, HomotypicalGroup)
658
     * @see     #NewInstance(NomenclaturalCode, Rank, String, String, String, String, TeamOrPersonBase, Reference, String, HomotypicalGroup)
659
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
660
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
661
     * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
662
     */
663
    protected TaxonName(NomenclaturalCode type, Rank rank, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, TeamOrPersonBase combinationAuthorship, INomenclaturalReference nomenclaturalReference, String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
664
        this(type, rank, homotypicalGroup);
665
        setGenusOrUninomial(genusOrUninomial);
666
        setInfraGenericEpithet (infraGenericEpithet);
667
        setSpecificEpithet(specificEpithet);
668
        setInfraSpecificEpithet(infraSpecificEpithet);
669
        setCombinationAuthorship(combinationAuthorship);
670
        setNomenclaturalReference(nomenclaturalReference);
671
        this.setNomenclaturalMicroReference(nomenclMicroRef);
672
    }
673

    
674

    
675
    /**
676
     * This method was originally needed to distinguish cache strategies
677
     * depending on the name type. Now we have a unified cache strategy
678
     * which does not require this anymore. Maybe we could even further remove this method.
679
     */
680
    private void rectifyNameCacheStrategy(){
681
        if (this.cacheStrategy == null){
682
            this.cacheStrategy = TaxonNameDefaultCacheStrategy.NewInstance();
683
        }
684
    }
685

    
686

    
687
    @Override
688
    public void initListener(){
689
        PropertyChangeListener listener = new PropertyChangeListener() {
690
            @Override
691
            public void propertyChange(PropertyChangeEvent e) {
692
                boolean protectedByLowerCache = false;
693
                //authorship cache
694
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
695
                    if (protectedAuthorshipCache){
696
                        protectedByLowerCache = true;
697
                    }else{
698
                        authorshipCache = null;
699
                    }
700
                }
701

    
702
                //nameCache
703
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "nameCache")){
704
                    if (protectedNameCache){
705
                        protectedByLowerCache = true;
706
                    }else{
707
                        nameCache = null;
708
                    }
709
                }
710
                //title cache
711
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "titleCache")){
712
                    if (isProtectedTitleCache()|| protectedByLowerCache == true ){
713
                        protectedByLowerCache = true;
714
                    }else{
715
                        titleCache = null;
716
                    }
717
                }
718
                //full title cache
719
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "fullTitleCache")){
720
                    if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
721
                        protectedByLowerCache = true;
722
                    }else{
723
                        fullTitleCache = null;
724
                    }
725
                }
726
            }
727
        };
728
        addPropertyChangeListener(listener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
729
    }
730

    
731
    private static Map<String, java.lang.reflect.Field> allFields = null;
732
    protected Map<String, java.lang.reflect.Field> getAllFields(){
733
        if (allFields == null){
734
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
735
        }
736
        return allFields;
737
    }
738

    
739
    /**
740
     * @param propertyName
741
     * @param string
742
     * @return
743
     */
744
    private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
745
        java.lang.reflect.Field field;
746
        try {
747
            field = getAllFields().get(propertyName);
748
            if (field != null){
749
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
750
                if (updateAnnotation != null){
751
                    for (String value : updateAnnotation.value()){
752
                        if (cacheName.equals(value)){
753
                            return true;
754
                        }
755
                    }
756
                }
757
            }
758
            return false;
759
        } catch (SecurityException e1) {
760
            throw e1;
761
        }
762
    }
763

    
764
    private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
765
        java.lang.reflect.Field field;
766
        //do not update fields with the same name
767
        if (cacheName.equals(propertyName)){
768
            return true;
769
        }
770
        //evaluate annotation
771
        try {
772
            field = getAllFields().get(propertyName);
773
            if (field != null){
774
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
775
                if (updateAnnotation != null){
776
                    for (String value : updateAnnotation.noUpdate()){
777
                        if (cacheName.equals(value)){
778
                            return true;
779
                        }
780
                    }
781
                }
782
            }
783
            return false;
784
        } catch (SecurityException e1) {
785
            throw e1;
786
        }
787
    }
788

    
789
// ****************** GETTER / SETTER ****************************/
790

    
791
    @Override
792
    public NomenclaturalCode getNameType() {
793
        return nameType;
794
    }
795

    
796
    @Override
797
    public void setNameType(NomenclaturalCode nameType) {
798
        this.nameType = nameType;
799
    }
800

    
801
    /**
802
     * Returns the boolean value of the flag intended to protect (true)
803
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
804
     * string of <i>this</i> non viral taxon name.
805
     *
806
     * @return  the boolean value of the protectedNameCache flag
807
     * @see     #getNameCache()
808
     */
809
    @Override
810
    public boolean isProtectedNameCache() {
811
        return protectedNameCache;
812
    }
813

    
814
    /**
815
     * @see     #isProtectedNameCache()
816
     */
817
    @Override
818
    public void setProtectedNameCache(boolean protectedNameCache) {
819
        this.protectedNameCache = protectedNameCache;
820
    }
821

    
822
    /**
823
     * Returns either the scientific name string (without authorship) for <i>this</i>
824
     * non viral taxon name if its rank is genus or higher (monomial) or the string for
825
     * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
826
     * Genus or uninomial strings begin with an upper case letter.
827
     *
828
     * @return  the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
829
     * @see     #getNameCache()
830
     */
831
    @Override
832
    public String getGenusOrUninomial() {
833
        return genusOrUninomial;
834
    }
835

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

    
844
    /**
845
     * Returns the genus subdivision epithet string (infrageneric part) for
846
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
847
     * higher than species aggregate: binomial). Genus subdivision epithet
848
     * strings begin with an upper case letter.
849
     *
850
     * @return  the string containing the infrageneric part of <i>this</i> non viral taxon name
851
     * @see     #getNameCache()
852
     */
853
    @Override
854
    public String getInfraGenericEpithet(){
855
        return this.infraGenericEpithet;
856
    }
857

    
858
    /**
859
     * @see  #getInfraGenericEpithet()
860
     */
861
    @Override
862
    public void setInfraGenericEpithet(String infraGenericEpithet){
863
        this.infraGenericEpithet = StringUtils.isBlank(infraGenericEpithet)? null : infraGenericEpithet;
864
    }
865

    
866
    /**
867
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
868
     * species aggregate or lower (bi- or trinomial). Species epithet strings
869
     * begin with a lower case letter.
870
     *
871
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
872
     * @see     #getNameCache()
873
     */
874
    @Override
875
    public String getSpecificEpithet(){
876
        return this.specificEpithet;
877
    }
878

    
879
    /**
880
     * @see  #getSpecificEpithet()
881
     */
882
    @Override
883
    public void setSpecificEpithet(String specificEpithet){
884
        this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
885
    }
886

    
887
    /**
888
     * Returns the species subdivision epithet string (infraspecific part) for
889
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
890
     * (lower than species: trinomial). Species subdivision epithet strings
891
     * begin with a lower case letter.
892
     *
893
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
894
     * @see     #getNameCache()
895
     */
896
    @Override
897
    public String getInfraSpecificEpithet(){
898
        return this.infraSpecificEpithet;
899
    }
900

    
901
    /**
902
     * @see  #getInfraSpecificEpithet()
903
     */
904
    @Override
905
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
906
        this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
907
    }
908

    
909
    /**
910
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
911
     * taxon name.
912
     *
913
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
914
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
915
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
916
     */
917
    @Override
918
    public TeamOrPersonBase<?> getCombinationAuthorship(){
919
        return this.combinationAuthorship;
920
    }
921

    
922
    /**
923
     * @see  #getCombinationAuthorship()
924
     */
925
    @Override
926
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
927
        this.combinationAuthorship = combinationAuthorship;
928
    }
929

    
930
    /**
931
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
932
     * the publication of <i>this</i> non viral taxon name as generally stated by
933
     * the {@link #getCombinationAuthorship() combination author (team)} itself.<BR>
934
     * An ex-author(-team) is an author(-team) to whom a taxon name was ascribed
935
     * although it is not the author(-team) of a valid publication (for instance
936
     * without the validating description or diagnosis in case of a name for a
937
     * new taxon). The name of this ascribed authorship, followed by "ex", may
938
     * be inserted before the name(s) of the publishing author(s) of the validly
939
     * published name:<BR>
940
     * <i>Lilium tianschanicum</i> was described by Grubov (1977) as a new species and
941
     * its name was ascribed to Ivanova; since there is no indication that
942
     * Ivanova provided the validating description, the name may be cited as
943
     * <i>Lilium tianschanicum</i> N. A. Ivanova ex Grubov or <i>Lilium tianschanicum</i> Grubov.
944
     * <P>
945
     * The presence of an author (team) of <i>this</i> non viral taxon name is a
946
     * condition for the existence of an ex author (team) for <i>this</i> same name.
947
     *
948
     * @return  the nomenclatural ex author (team) of <i>this</i> non viral taxon name
949
     * @see     #getCombinationAuthorship()
950
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
951
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
952
     */
953
    @Override
954
    public TeamOrPersonBase<?> getExCombinationAuthorship(){
955
        return this.exCombinationAuthorship;
956
    }
957

    
958
    /**
959
     * @see  #getExCombinationAuthorship()
960
     */
961
    @Override
962
    public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
963
        this.exCombinationAuthorship = exCombinationAuthorship;
964
    }
965

    
966
    /**
967
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
968
     * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
969
     * author (team) can only exist if <i>this</i> non viral taxon name is a new
970
     * combination due to a taxonomical revision.
971
     *
972
     * @return  the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
973
     * @see     #getCombinationAuthorship()
974
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
975
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
976
     */
977
    @Override
978
    public TeamOrPersonBase<?> getBasionymAuthorship(){
979
        return basionymAuthorship;
980
    }
981

    
982
    /**
983
     * @see  #getBasionymAuthorship()
984
     */
985
    @Override
986
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
987
        this.basionymAuthorship = basionymAuthorship;
988
    }
989

    
990
    /**
991
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
992
     * the publication of the original combination <i>this</i> non viral taxon name is
993
     * based on. This should have been generally stated by
994
     * the {@link #getBasionymAuthorship() basionym author (team)} itself.
995
     * The presence of a basionym author (team) of <i>this</i> non viral taxon name is a
996
     * condition for the existence of an ex basionym author (team)
997
     * for <i>this</i> same name.
998
     *
999
     * @return  the nomenclatural ex basionym author (team) of <i>this</i> non viral taxon name
1000
     * @see     #getBasionymAuthorship()
1001
     * @see     #getExCombinationAuthorship()
1002
     * @see     #getCombinationAuthorship()
1003
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
1004
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
1005
     */
1006
    @Override
1007
    public TeamOrPersonBase<?> getExBasionymAuthorship(){
1008
        return exBasionymAuthorship;
1009
    }
1010

    
1011
    /**
1012
     * @see  #getExBasionymAuthorship()
1013
     */
1014
    @Override
1015
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
1016
        this.exBasionymAuthorship = exBasionymAuthorship;
1017
    }
1018

    
1019
    /**
1020
     * Returns the boolean value of the flag intended to protect (true)
1021
     * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
1022
     * of <i>this</i> non viral taxon name.
1023
     *
1024
     * @return  the boolean value of the protectedAuthorshipCache flag
1025
     * @see     #getAuthorshipCache()
1026
     */
1027
    @Override
1028
    public boolean isProtectedAuthorshipCache() {
1029
        return protectedAuthorshipCache;
1030
    }
1031

    
1032
    /**
1033
     * @see     #isProtectedAuthorshipCache()
1034
     * @see     #getAuthorshipCache()
1035
     */
1036
    @Override
1037
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
1038
        this.protectedAuthorshipCache = protectedAuthorshipCache;
1039
    }
1040

    
1041
    /**
1042
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1043
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
1044
     *
1045
     * @see    #getHybridRelationships()
1046
     * @see    #getChildRelationships()
1047
     * @see    HybridRelationshipType
1048
     */
1049
    @Override
1050
    public Set<HybridRelationship> getHybridParentRelations() {
1051
        if(hybridParentRelations == null) {
1052
            this.hybridParentRelations = new HashSet<>();
1053
        }
1054
        return hybridParentRelations;
1055
    }
1056

    
1057
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1058
        this.hybridParentRelations = hybridParentRelations;
1059
    }
1060

    
1061

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

    
1078
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1079
        this.hybridChildRelations = hybridChildRelations;
1080
    }
1081

    
1082
    @Override
1083
    public boolean isProtectedFullTitleCache() {
1084
        return protectedFullTitleCache;
1085
    }
1086

    
1087
    @Override
1088
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1089
        this.protectedFullTitleCache = protectedFullTitleCache;
1090
    }
1091

    
1092
    /**
1093
     * Returns the boolean value of the flag indicating whether the name of <i>this</i>
1094
     * botanical taxon name is a hybrid formula (true) or not (false). A hybrid
1095
     * named by a hybrid formula (composed with its parent names by placing the
1096
     * multiplication sign between them) does not have an own published name
1097
     * and therefore has neither an {@link NonViralName#getAuthorshipCache() autorship}
1098
     * nor other name components. If this flag is set no other hybrid flags may
1099
     * be set.
1100
     *
1101
     * @return  the boolean value of the isHybridFormula flag
1102
     * @see     #isMonomHybrid()
1103
     * @see     #isBinomHybrid()
1104
     * @see     #isTrinomHybrid()
1105
     */
1106
    @Override
1107
    @Transient
1108
    @java.beans.Transient
1109
    public boolean isHybridFormula(){
1110
        return this.hybridFormula;
1111
    }
1112

    
1113
    /**
1114
     * @see  #isHybridFormula()
1115
     */
1116
    @Override
1117
    public void setHybridFormula(boolean hybridFormula){
1118
        this.hybridFormula = hybridFormula;
1119
    }
1120

    
1121
    /**
1122
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1123
     * taxon name is the name of an intergeneric hybrid (true) or not (false).
1124
     * In this case the multiplication sign is placed before the scientific
1125
     * name. If this flag is set no other hybrid flags may be set.
1126
     *
1127
     * @return  the boolean value of the isMonomHybrid flag
1128
     * @see     #isHybridFormula()
1129
     * @see     #isBinomHybrid()
1130
     * @see     #isTrinomHybrid()
1131
     */
1132
    @Override
1133
    public boolean isMonomHybrid(){
1134
        return this.monomHybrid;
1135
    }
1136

    
1137
    /**
1138
     * @see  #isMonomHybrid()
1139
     * @see  #isBinomHybrid()
1140
     * @see  #isTrinomHybrid()
1141
     */
1142
    @Override
1143
    public void setMonomHybrid(boolean monomHybrid){
1144
        this.monomHybrid = monomHybrid;
1145
    }
1146

    
1147
    /**
1148
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1149
     * taxon name is the name of an interspecific hybrid (true) or not (false).
1150
     * In this case the multiplication sign is placed before the species
1151
     * epithet. If this flag is set no other hybrid flags may be set.
1152
     *
1153
     * @return  the boolean value of the isBinomHybrid flag
1154
     * @see     #isHybridFormula()
1155
     * @see     #isMonomHybrid()
1156
     * @see     #isTrinomHybrid()
1157
     */
1158
    @Override
1159
    public boolean isBinomHybrid(){
1160
        return this.binomHybrid;
1161
    }
1162

    
1163
    /**
1164
     * @see  #isBinomHybrid()
1165
     * @see  #isMonomHybrid()
1166
     * @see  #isTrinomHybrid()
1167
     */
1168
    @Override
1169
    public void setBinomHybrid(boolean binomHybrid){
1170
        this.binomHybrid = binomHybrid;
1171
    }
1172

    
1173
    @Override
1174
    public boolean isTrinomHybrid(){
1175
        return this.trinomHybrid;
1176
    }
1177

    
1178
    /**
1179
     * @see  #isTrinomHybrid()
1180
     * @see  #isBinomHybrid()
1181
     * @see  #isMonomHybrid()
1182
     */
1183
    @Override
1184
    public void setTrinomHybrid(boolean trinomHybrid){
1185
        this.trinomHybrid = trinomHybrid;
1186
    }
1187

    
1188
    // ****************** VIRAL NAME ******************/
1189

    
1190
    @Override
1191
    public String getAcronym(){
1192
        return this.acronym;
1193
    }
1194

    
1195
    /**
1196
     * @see  #getAcronym()
1197
     */
1198
    @Override
1199
    public void setAcronym(String acronym){
1200
        this.acronym = StringUtils.isBlank(acronym)? null : acronym;
1201
    }
1202

    
1203
    // ****************** BACTERIAL NAME ******************/
1204

    
1205
    @Override
1206
    public String getSubGenusAuthorship(){
1207
        return this.subGenusAuthorship;
1208
    }
1209

    
1210
    @Override
1211
    public void setSubGenusAuthorship(String subGenusAuthorship){
1212
        this.subGenusAuthorship = subGenusAuthorship;
1213
    }
1214

    
1215

    
1216
    @Override
1217
    public String getNameApprobation(){
1218
        return this.nameApprobation;
1219
    }
1220

    
1221
    /**
1222
     * @see  #getNameApprobation()
1223
     */
1224
    @Override
1225
    public void setNameApprobation(String nameApprobation){
1226
        this.nameApprobation = nameApprobation;
1227
    }
1228

    
1229
    //************ Zoological Name
1230

    
1231

    
1232
    @Override
1233
    public String getBreed(){
1234
        return this.breed;
1235
    }
1236
    /**
1237
     * @see  #getBreed()
1238
     */
1239
    @Override
1240
    public void setBreed(String breed){
1241
        this.breed = StringUtils.isBlank(breed) ? null : breed;
1242
    }
1243

    
1244

    
1245
    @Override
1246
    public Integer getPublicationYear() {
1247
        return publicationYear;
1248
    }
1249
    /**
1250
     * @see  #getPublicationYear()
1251
     */
1252
    @Override
1253
    public void setPublicationYear(Integer publicationYear) {
1254
        this.publicationYear = publicationYear;
1255
    }
1256

    
1257

    
1258
    @Override
1259
    public Integer getOriginalPublicationYear() {
1260
        return originalPublicationYear;
1261
    }
1262
    /**
1263
     * @see  #getOriginalPublicationYear()
1264
     */
1265
    @Override
1266
    public void setOriginalPublicationYear(Integer originalPublicationYear) {
1267
        this.originalPublicationYear = originalPublicationYear;
1268
    }
1269

    
1270
    // **** Cultivar Name ************
1271

    
1272

    
1273
    @Override
1274
    public String getCultivarName(){
1275
        return this.cultivarName;
1276
    }
1277

    
1278
    /**
1279
     * @see  #getCultivarName()
1280
     */
1281
    @Override
1282
    public void setCultivarName(String cultivarName){
1283
        this.cultivarName = StringUtils.isBlank(cultivarName) ? null : cultivarName;
1284
    }
1285

    
1286
    // **************** Fungus Name
1287
    @Override
1288
    public boolean isAnamorphic(){
1289
        return this.anamorphic;
1290
    }
1291

    
1292
    /**
1293
     * @see  #isAnamorphic()
1294
     */
1295
    @Override
1296
    public void setAnamorphic(boolean anamorphic){
1297
        this.anamorphic = anamorphic;
1298
    }
1299

    
1300

    
1301
// **************** ADDER / REMOVE *************************/
1302

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

    
1332

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

    
1348
        TaxonName parent = hybridRelation.getParentName();
1349
        TaxonName child = hybridRelation.getHybridName();
1350
        if (this.equals(parent)){
1351
            this.hybridParentRelations.remove(hybridRelation);
1352
            child.hybridChildRelations.remove(hybridRelation);
1353
            hybridRelation.setHybridName(null);
1354
            hybridRelation.setParentName(null);
1355
        }
1356
        if (this.equals(child)){
1357
            parent.hybridParentRelations.remove(hybridRelation);
1358
            this.hybridChildRelations.remove(hybridRelation);
1359
            hybridRelation.setHybridName(null);
1360
            hybridRelation.setParentName(null);
1361
        }
1362
    }
1363

    
1364
//********* METHODS **************************************/
1365

    
1366
    @Override
1367
    public INameCacheStrategy getCacheStrategy() {
1368
        rectifyNameCacheStrategy();
1369
        return this.cacheStrategy;
1370
    }
1371

    
1372
    @Override
1373
    public String generateFullTitle(){
1374
        if (getCacheStrategy() == null){
1375
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1376
            return null;
1377
        }else{
1378
            return cacheStrategy.getFullTitleCache(this);
1379
        }
1380
    }
1381

    
1382

    
1383
    @Override
1384
    public void setFullTitleCache(String fullTitleCache){
1385
        setFullTitleCache(fullTitleCache, PROTECTED);
1386
    }
1387

    
1388
    @Override
1389
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1390
        fullTitleCache = getTruncatedCache(fullTitleCache);
1391
        this.fullTitleCache = fullTitleCache;
1392
        this.setProtectedFullTitleCache(protectCache);
1393
    }
1394

    
1395
   /** Checks if this name is an autonym.<BR>
1396
    * An autonym is a taxon name that has equal specific and infra specific epithets.<BR>
1397
    * {@link http://ibot.sav.sk/icbn/frameset/0010Ch2Sec1a006.htm#6.8. Vienna Code §6.8}
1398
    * or a taxon name that has equal generic and infrageneric epithets (A22.2).<BR>
1399
    * Only relevant for botanical names.
1400
    * @return true, if name has Rank, Rank is below species and species epithet equals infraSpeciesEpithtet, else false
1401
    */
1402
    @Override
1403
    @Transient
1404
    public boolean isAutonym(){
1405
        if (isBotanical()){
1406
            if (this.getRank() != null && this.getSpecificEpithet() != null && this.getInfraSpecificEpithet() != null &&
1407
                this.isInfraSpecific() && this.getSpecificEpithet().trim().equals(this.getInfraSpecificEpithet().trim())){
1408
                return true;
1409
            }else if (this.getRank() != null && this.getGenusOrUninomial() != null && this.getInfraGenericEpithet() != null &&
1410
                    this.isInfraGeneric() && this.getGenusOrUninomial().trim().equals(this.getInfraGenericEpithet().trim())){
1411
                return true;
1412
            }else{
1413
                return false;
1414
            }
1415
        }else{
1416
            return false;
1417
        }
1418
    }
1419

    
1420

    
1421
    @Override
1422
    @Transient
1423
    public List<TaggedText> getTaggedName(){
1424
        INameCacheStrategy strat = getCacheStrategy();
1425
        return strat.getTaggedTitle(this);
1426
    }
1427

    
1428
    @Override
1429
    @Transient
1430
    public String getFullTitleCache(){
1431
        if (protectedFullTitleCache){
1432
            return this.fullTitleCache;
1433
        }
1434
        updateAuthorshipCache();
1435
        if (fullTitleCache == null ){
1436
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1437
        }
1438
        return fullTitleCache;
1439
    }
1440

    
1441

    
1442
    @Override
1443
    public String getTitleCache(){
1444
        if(!protectedTitleCache) {
1445
            updateAuthorshipCache();
1446
        }
1447
        return super.getTitleCache();
1448
    }
1449

    
1450
    @Override
1451
    public void setTitleCache(String titleCache, boolean protectCache){
1452
        super.setTitleCache(titleCache, protectCache);
1453
    }
1454

    
1455
    /**
1456
     * Returns the concatenated and formated authorteams string including
1457
     * basionym and combination authors of <i>this</i> non viral taxon name.
1458
     * If the protectedAuthorshipCache flag is set this method returns the
1459
     * string stored in the the authorshipCache attribute, otherwise it
1460
     * generates the complete authorship string, returns it and stores it in
1461
     * the authorshipCache attribute.
1462
     *
1463
     * @return  the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
1464
     * @see     #generateAuthorship()
1465
     */
1466
    @Override
1467
    @Transient
1468
    public String getAuthorshipCache() {
1469
        if (protectedAuthorshipCache){
1470
            return this.authorshipCache;
1471
        }
1472
        if (this.authorshipCache == null ){
1473
            this.authorshipCache = generateAuthorship();
1474
        }else{
1475
            //TODO get isDirty of authors, make better if possible
1476
            this.setAuthorshipCache(generateAuthorship(), protectedAuthorshipCache); //throw change event to inform higher caches
1477

    
1478
        }
1479
        return authorshipCache;
1480
    }
1481

    
1482

    
1483

    
1484
    /**
1485
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
1486
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
1487
     * @return
1488
     */
1489
    private void updateAuthorshipCache() {
1490
        //updates the authorship cache if necessary and via the listener updates all higher caches
1491
        if (protectedAuthorshipCache == false){
1492
            String oldCache = this.authorshipCache;
1493
            String newCache = this.getAuthorshipCache();
1494
            if ( (oldCache == null && newCache != null)  ||  CdmUtils.nullSafeEqual(oldCache,newCache)){
1495
                this.setAuthorshipCache(this.getAuthorshipCache(), false);
1496
            }
1497
        }
1498
    }
1499

    
1500

    
1501
    /**
1502
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1503
     * flag to <code>true</code>.
1504
     *
1505
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1506
     * @see    #getAuthorshipCache()
1507
     */
1508
    @Override
1509
    public void setAuthorshipCache(String authorshipCache) {
1510
        setAuthorshipCache(authorshipCache, true);
1511
    }
1512

    
1513

    
1514
    /**
1515
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1516
     *
1517
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1518
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1519
     * the flag is set to <code>false</code>.
1520
     * @see    #getAuthorshipCache()
1521
     */
1522
    @Override
1523
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1524
        this.authorshipCache = authorshipCache;
1525
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1526
    }
1527

    
1528
    /**
1529
     * Generates and returns a concatenated and formated authorteams string
1530
     * including basionym and combination authors of <i>this</i> non viral taxon name
1531
     * according to the strategy defined in
1532
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName) INonViralNameCacheStrategy}.
1533
     *
1534
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1535
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName)
1536
     */
1537
    @Override
1538
    public String generateAuthorship(){
1539
        if (getCacheStrategy() == null){
1540
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1541
            return null;
1542
        }else{
1543
            return cacheStrategy.getAuthorshipCache(this);
1544
        }
1545
    }
1546

    
1547

    
1548

    
1549
    /**
1550
     * Tests if the given name has any authors.
1551
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1552
     */
1553
    @Override
1554
    public boolean hasAuthors() {
1555
        return (this.getCombinationAuthorship() != null ||
1556
                this.getExCombinationAuthorship() != null ||
1557
                this.getBasionymAuthorship() != null ||
1558
                this.getExBasionymAuthorship() != null);
1559
    }
1560

    
1561
    /**
1562
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1563
     * @return
1564
     */
1565
    @Override
1566
    public String computeCombinationAuthorNomenclaturalTitle() {
1567
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1568
    }
1569

    
1570
    /**
1571
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1572
     * @return
1573
     */
1574
    @Override
1575
    public String computeBasionymAuthorNomenclaturalTitle() {
1576
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1577
    }
1578

    
1579

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

    
1589
    /**
1590
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1591
     * @return
1592
     */
1593
    @Override
1594
    public String computeExBasionymAuthorNomenclaturalTitle() {
1595
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1596
    }
1597

    
1598
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1599
        if (author == null){
1600
            return null;
1601
        }else{
1602
            return author.getNomenclaturalTitle();
1603
        }
1604
    }
1605

    
1606
    /**
1607
     * Returns the set of all {@link NameRelationship name relationships}
1608
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1609
     * in some name relationships or target in some others.
1610
     *
1611
     * @see    #getRelationsToThisName()
1612
     * @see    #getRelationsFromThisName()
1613
     * @see    #addNameRelationship(NameRelationship)
1614
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1615
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1616
     */
1617
    @Override
1618
    @Transient
1619
    public Set<NameRelationship> getNameRelations() {
1620
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1621
        rels.addAll(getRelationsFromThisName());
1622
        rels.addAll(getRelationsToThisName());
1623
        return rels;
1624
    }
1625

    
1626
    /**
1627
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1628
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1629
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1630
     *
1631
     * @param toName		  the taxon name of the target for this new name relationship
1632
     * @param type			  the type of this new name relationship
1633
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1634
     * @return
1635
     * @see    				  #getRelationsToThisName()
1636
     * @see    				  #getNameRelations()
1637
     * @see    				  #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1638
     * @see    				  #addNameRelationship(NameRelationship)
1639
     */
1640
    @Override
1641
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, String ruleConsidered){
1642
        return addRelationshipToName(toName, type, null, null, ruleConsidered);
1643
    }
1644

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

    
1668
    /**
1669
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1670
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1671
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1672
     *
1673
     * @param fromName		  the taxon name of the source for this new name relationship
1674
     * @param type			  the type of this new name relationship
1675
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1676
     * @param citation		  the reference in which this relation was described
1677
     * @param microCitation	  the reference detail for this relation (e.g. page)
1678
     * @see    				  #getRelationsFromThisName()
1679
     * @see    				  #getNameRelations()
1680
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1681
     * @see    				  #addNameRelationship(NameRelationship)
1682
     */
1683
    @Override
1684
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered){
1685
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1686
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
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, Reference citation, String microCitation, String ruleConsidered){
1705
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1706
    }
1707

    
1708
    /**
1709
     * Adds an existing {@link NameRelationship name relationship} either to the set of
1710
     * {@link #getRelationsToThisName() relations to <i>this</i> taxon name} or to the set of
1711
     * {@link #getRelationsFromThisName() relations from <i>this</i> taxon name}. If neither the
1712
     * source nor the target of the name relationship match with <i>this</i> taxon name
1713
     * no addition will be carried out.
1714
     *
1715
     * @param rel  the name relationship to be added to one of <i>this</i> taxon name's name relationships sets
1716
     * @see    	   #getNameRelations()
1717
     * @see    	   #addRelationshipToName(TaxonName, NameRelationshipType, String)
1718
     * @see    	   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1719
     */
1720
    protected void addNameRelationship(NameRelationship rel) {
1721
        if (rel != null ){
1722
            if (rel.getToName().equals(this)){
1723
                this.relationsToThisName.add(rel);
1724
            }else if(rel.getFromName().equals(this)){
1725
                this.relationsFromThisName.add(rel);
1726
            }
1727
            NameRelationshipType type = rel.getType();
1728
            if (type != null && ( type.isBasionymRelation() || type.isReplacedSynonymRelation() ) ){
1729
                rel.getFromName().mergeHomotypicGroups(rel.getToName());
1730
            }
1731
        }else{
1732
            throw new RuntimeException("NameRelationship is either null or the relationship does not reference this name");
1733
        }
1734
    }
1735
    /**
1736
     * Removes one {@link NameRelationship name relationship} from one of both sets of
1737
     * {@link #getNameRelations() name relationships} in which <i>this</i> taxon name is involved.
1738
     * The name relationship will also be removed from one of both sets belonging
1739
     * to the second taxon name involved. Furthermore the fromName and toName
1740
     * attributes of the name relationship object will be nullified.
1741
     *
1742
     * @param  nameRelation  the name relationship which should be deleted from one of both sets
1743
     * @see    				 #getNameRelations()
1744
     */
1745
    @Override
1746
    public void removeNameRelationship(NameRelationship nameRelation) {
1747

    
1748
        TaxonName fromName = nameRelation.getFromName();
1749
        TaxonName toName = nameRelation.getToName();
1750

    
1751
        if (nameRelation != null) {
1752
            nameRelation.setToName(null);
1753
            nameRelation.setFromName(null);
1754
        }
1755

    
1756
        if (fromName != null) {
1757
            fromName.removeNameRelationship(nameRelation);
1758
        }
1759

    
1760
        if (toName != null) {
1761
            toName.removeNameRelationship(nameRelation);
1762
        }
1763

    
1764
        this.relationsToThisName.remove(nameRelation);
1765
        this.relationsFromThisName.remove(nameRelation);
1766
    }
1767

    
1768
    @Override
1769
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1770
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1771
//		nameRelationships.addAll(this.getNameRelations());
1772
        nameRelationships.addAll(this.getRelationsFromThisName());
1773
        nameRelationships.addAll(this.getRelationsToThisName());
1774
        for(NameRelationship nameRelationship : nameRelationships) {
1775
            // remove name relationship from this side
1776
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1777
                this.removeNameRelationship(nameRelationship);
1778
            }
1779
        }
1780
    }
1781

    
1782
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1783

    
1784
        for(NameRelationship nameRelationship : relationsWithThisName(direction)) {
1785
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1786
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1787
                this.removeNameRelationship(nameRelationship);
1788
            }
1789
        }
1790
    }
1791

    
1792

    
1793
    /**
1794
     * If relation is of type NameRelationship, addNameRelationship is called;
1795
     * if relation is of type HybridRelationship addHybridRelationship is called,
1796
     * otherwise an IllegalArgumentException is thrown.
1797
     *
1798
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1799
     * @see    	   		#addNameRelationship(NameRelationship)
1800
     * @see    	   		#getNameRelations()
1801
     * @see    	   		NameRelationship
1802
     * @see    	   		RelationshipBase
1803
     * @see             #addHybridRelationship(HybridRelationship)
1804

    
1805
     * @deprecated to be used by RelationshipBase only
1806
     */
1807
    @Deprecated
1808
    @Override
1809
    public void addRelationship(RelationshipBase relation) {
1810
        if (relation instanceof NameRelationship){
1811
            addNameRelationship((NameRelationship)relation);
1812

    
1813
        }else if (relation instanceof HybridRelationship){
1814
            addHybridRelationship((HybridRelationship)relation);
1815
        }else{
1816
            logger.warn("Relationship not of type NameRelationship!");
1817
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1818
        }
1819
    }
1820

    
1821
    /**
1822
     * Returns the set of all {@link NameRelationship name relationships}
1823
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1824
     *
1825
     * @see    #getNameRelations()
1826
     * @see    #getRelationsToThisName()
1827
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1828
     */
1829
    @Override
1830
    public Set<NameRelationship> getRelationsFromThisName() {
1831
        if(relationsFromThisName == null) {
1832
            this.relationsFromThisName = new HashSet<>();
1833
        }
1834
        return relationsFromThisName;
1835
    }
1836

    
1837
    /**
1838
     * Returns the set of all {@link NameRelationship name relationships}
1839
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1840
     *
1841
     * @see    #getNameRelations()
1842
     * @see    #getRelationsFromThisName()
1843
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1844
     */
1845
    @Override
1846
    public Set<NameRelationship> getRelationsToThisName() {
1847
        if(relationsToThisName == null) {
1848
            this.relationsToThisName = new HashSet<>();
1849
        }
1850
        return relationsToThisName;
1851
    }
1852

    
1853
    /**
1854
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1855
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1856
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1857
     * and the nomenclatural code rule considered.
1858
     *
1859
     * @see     NomenclaturalStatus
1860
     * @see     NomenclaturalStatusType
1861
     */
1862
    @Override
1863
    public Set<NomenclaturalStatus> getStatus() {
1864
        if(status == null) {
1865
            this.status = new HashSet<>();
1866
        }
1867
        return status;
1868
    }
1869

    
1870
    /**
1871
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1872
     * to <i>this</i> taxon name's set of nomenclatural status.
1873
     *
1874
     * @param  nomStatus  the nomenclatural status to be added
1875
     * @see 			  #getStatus()
1876
     */
1877
    @Override
1878
    public void addStatus(NomenclaturalStatus nomStatus) {
1879
        this.status.add(nomStatus);
1880
    }
1881
    @Override
1882
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1883
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1884
        this.status.add(newStatus);
1885
        return newStatus;
1886
    }
1887

    
1888
    /**
1889
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1890
     * Type and ruleConsidered attributes of the nomenclatural status object
1891
     * will be nullified.
1892
     *
1893
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1894
     * @see     		  #getStatus()
1895
     */
1896
    @Override
1897
    public void removeStatus(NomenclaturalStatus nomStatus) {
1898
        //TODO to be implemented?
1899
        logger.warn("not yet fully implemented?");
1900
        this.status.remove(nomStatus);
1901
    }
1902

    
1903

    
1904
    /**
1905
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1906
     * strings or year according to the strategy defined in
1907
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1908
     * The result might be stored in {@link #getNameCache() nameCache} if the
1909
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1910
     *
1911
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1912
     * @see     #getNameCache()
1913
     */
1914
    protected String generateNameCache(){
1915
        if (getCacheStrategy() == null){
1916
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1917
            return null;
1918
        }else{
1919
            return cacheStrategy.getNameCache(this);
1920
        }
1921
    }
1922

    
1923
    /**
1924
     * Returns or generates the nameCache (scientific name
1925
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1926
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1927
     * the string will be generated according to a defined strategy,
1928
     * otherwise the value of the actual nameCache string will be returned.
1929
     *
1930
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1931
     * @see     #generateNameCache()
1932
     */
1933
    @Override
1934
    @Transient
1935
    public String getNameCache() {
1936
        if (protectedNameCache){
1937
            return this.nameCache;
1938
        }
1939
        // is title dirty, i.e. equal NULL?
1940
        if (nameCache == null){
1941
            this.nameCache = generateNameCache();
1942
        }
1943
        return nameCache;
1944
    }
1945

    
1946
    /**
1947
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1948
     * Sets the protectedNameCache flag to <code>true</code>.
1949
     *
1950
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1951
     * @see    #getNameCache()
1952
     */
1953
    @Override
1954
    public void setNameCache(String nameCache){
1955
        setNameCache(nameCache, true);
1956
    }
1957

    
1958
    /**
1959
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1960
     * Sets the protectedNameCache flag to <code>true</code>.
1961
     *
1962
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1963
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
1964
     * <code>false</code>
1965
     * @see    #getNameCache()
1966
     */
1967
    @Override
1968
    public void setNameCache(String nameCache, boolean protectedNameCache){
1969
        this.nameCache = nameCache;
1970
        this.setProtectedNameCache(protectedNameCache);
1971
    }
1972

    
1973

    
1974
    /**
1975
     * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
1976
     * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1977
     * of any other taxon name. Returns "true", if a basionym or a replaced
1978
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1979
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1980
     * homotypical group).
1981
     */
1982
    @Override
1983
    @Transient
1984
    public boolean isOriginalCombination(){
1985
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1986
        for (NameRelationship relation : relationsFromThisName) {
1987
            if (relation.getType().isBasionymRelation() ||
1988
                    relation.getType().isReplacedSynonymRelation()) {
1989
                return true;
1990
            }
1991
        }
1992
        return false;
1993
    }
1994

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

    
2014
    /**
2015
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2016
     * The basionym of a taxon name is its epithet-bringing synonym.
2017
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2018
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2019
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2020
     *
2021
     * If more than one basionym exists one is choosen at radom.
2022
     *
2023
     * If no basionym exists null is returned.
2024
     */
2025
    @Override
2026
    @Transient
2027
    public TaxonName getBasionym(){
2028
        Set<TaxonName> basionyms = getBasionyms();
2029
        if (basionyms.size() == 0){
2030
            return null;
2031
        }else{
2032
            return basionyms.iterator().next();
2033
        }
2034
    }
2035

    
2036
    /**
2037
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2038
     * The basionym of a taxon name is its epithet-bringing synonym.
2039
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2040
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2041
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2042
     */
2043
    @Override
2044
    @Transient
2045
    public Set<TaxonName> getBasionyms(){
2046

    
2047
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
2048
    }
2049

    
2050
    /**
2051
     *
2052
     * @param direction
2053
     * @param type
2054
     * @return
2055
     */
2056
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
2057

    
2058
        return getRelatedNames(relationsWithThisName(direction), NameRelationshipType.BASIONYM());
2059
    }
2060

    
2061
    /**
2062
     * @param rels
2063
     * @param type
2064
     * @return
2065
     */
2066
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2067
        Set<TaxonName> result = new HashSet<>();
2068
        for (NameRelationship rel : rels){
2069
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2070
                TaxonName basionym = rel.getFromName();
2071
                result.add(basionym);
2072
            }
2073
        }
2074
        return result;
2075
    }
2076

    
2077
    /**
2078
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2079
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2080
     * and to the basionym. The basionym cannot have itself as a basionym.
2081
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2082
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2083
     *
2084
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2085
     * @see  				#getBasionym()
2086
     * @see  				#addBasionym(TaxonName, String)
2087
     */
2088
    @Override
2089
    public void addBasionym(TaxonName basionym){
2090
        addBasionym(basionym, null, null, null);
2091
    }
2092
    /**
2093
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2094
     * and keeps the nomenclatural rule considered for it. The basionym
2095
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2096
     * The basionym cannot have itself as a basionym.
2097
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2098
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2099
     *
2100
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2101
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2102
     * @return
2103
     * @see  					#getBasionym()
2104
     * @see  					#addBasionym(TaxonName)
2105
     */
2106
    @Override
2107
    public NameRelationship addBasionym(TaxonName basionym, Reference citation, String microcitation, String ruleConsidered){
2108
        if (basionym != null){
2109
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
2110
        }else{
2111
            return null;
2112
        }
2113
    }
2114

    
2115
    /**
2116
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2117
     *
2118
     */
2119
    @Override
2120
    @Transient
2121
    public Set<TaxonName> getReplacedSynonyms(){
2122

    
2123
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2124
    }
2125

    
2126
    /**
2127
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2128
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2129
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2130
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2131
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2132
     *
2133
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2134
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2135
     * @see  					#getBasionym()
2136
     * @see  					#addBasionym(TaxonName)
2137
     */
2138
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2139
    @Override
2140
    public void addReplacedSynonym(TaxonName replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
2141
        if (replacedSynonym != null){
2142
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
2143
        }
2144
    }
2145

    
2146
    /**
2147
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2148
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2149
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2150
     * previously used as basionym.
2151
     *
2152
     * @see   #getBasionym()
2153
     * @see   #addBasionym(TaxonName)
2154
     */
2155
    @Override
2156
    public void removeBasionyms(){
2157
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2158
    }
2159

    
2160

    
2161
    /**
2162
     * Removes all {@link NameRelationship relationships} of the given <code>type</code> from the set of
2163
     * relations in the specified <code>direction</code> direction wich are related from or to this
2164
     * <i>this</i> taxon name. The same relationship will be removed from the set of
2165
     * reverse relations of the other taxon name.
2166
     *
2167
     * @param direction
2168
     * @param type
2169
     */
2170
    public void removeNameRelations(Direction direction, NameRelationshipType type) {
2171
        Set<NameRelationship> relationsWithThisName = relationsWithThisName(direction);
2172
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
2173
        for (NameRelationship nameRelation : relationsWithThisName){
2174
            if (nameRelation.getType().isRelationshipType(type)){
2175
                removeRelations.add(nameRelation);
2176
            }
2177
        }
2178
        // Removing relations from a set through which we are iterating causes a
2179
        // ConcurrentModificationException. Therefore, we delete the targeted
2180
        // relations in a second step.
2181
        for (NameRelationship relation : removeRelations){
2182
            this.removeNameRelationship(relation);
2183
        }
2184
    }
2185

    
2186

    
2187
    /**
2188
     * @param direction
2189
     * @return
2190
     */
2191
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2192

    
2193
        switch(direction) {
2194
            case relatedTo:
2195
                return this.getRelationsToThisName();
2196
            case relatedFrom:
2197
                return this.getRelationsFromThisName();
2198
            default: throw new RuntimeException("invalid Direction:" + direction);
2199
        }
2200
    }
2201

    
2202
    /**
2203
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2204
     *
2205
     * @see 	Rank
2206
     */
2207
    @Override
2208
    public Rank getRank(){
2209
        return this.rank;
2210
    }
2211

    
2212
    /**
2213
     * @see  #getRank()
2214
     */
2215
    @Override
2216
    public void setRank(Rank rank){
2217
        this.rank = rank;
2218
    }
2219

    
2220
    /**
2221
     * Returns the {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} of <i>this</i> taxon name.
2222
     * The nomenclatural reference is here meant to be the one publication
2223
     * <i>this</i> taxon name was originally published in while fulfilling the formal
2224
     * requirements as specified by the corresponding {@link NomenclaturalCode nomenclatural code}.
2225
     *
2226
     * @see 	eu.etaxonomy.cdm.model.reference.INomenclaturalReference
2227
     * @see 	eu.etaxonomy.cdm.model.reference.Reference
2228
     */
2229
    @Override
2230
    public INomenclaturalReference getNomenclaturalReference(){
2231
        return this.nomenclaturalReference;
2232
    }
2233
    /**
2234
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2235
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2236
     * as it is obviously used for nomenclatural purposes.
2237
     *
2238
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2239
     * @see  #getNomenclaturalReference()
2240
     */
2241
    @Override
2242
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2243
        if(nomenclaturalReference != null){
2244
            if(!INomenclaturalReference.class.isAssignableFrom(nomenclaturalReference.getClass())){
2245
                throw new IllegalArgumentException("Parameter nomenclaturalReference is not assignable from INomenclaturalReference");
2246
            }
2247
            this.nomenclaturalReference = (Reference)nomenclaturalReference;
2248
        } else {
2249
            this.nomenclaturalReference = null;
2250
        }
2251
    }
2252

    
2253
    /**
2254
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2255
     * The appended phrase is a non-atomised addition to a name. It is
2256
     * not ruled by a nomenclatural code.
2257
     */
2258
    @Override
2259
    public String getAppendedPhrase(){
2260
        return this.appendedPhrase;
2261
    }
2262

    
2263
    /**
2264
     * @see  #getAppendedPhrase()
2265
     */
2266
    @Override
2267
    public void setAppendedPhrase(String appendedPhrase){
2268
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2269
    }
2270

    
2271
    /**
2272
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2273
     * to <i>this</i> taxon name. The details describe the exact localisation within
2274
     * the publication used as nomenclature reference. These are mostly
2275
     * (implicitly) pages but can also be figures or tables or any other
2276
     * element of a publication. A nomenclatural micro reference (details)
2277
     * requires the existence of a nomenclatural reference.
2278
     */
2279
    //Details of the nomenclatural reference (protologue).
2280
    @Override
2281
    public String getNomenclaturalMicroReference(){
2282
        return this.nomenclaturalMicroReference;
2283
    }
2284
    /**
2285
     * @see  #getNomenclaturalMicroReference()
2286
     */
2287
    @Override
2288
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2289
        this.nomenclaturalMicroReference = StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
2290
    }
2291

    
2292
    @Override
2293
    public int getParsingProblem(){
2294
        return this.parsingProblem;
2295
    }
2296

    
2297
    @Override
2298
    public void setParsingProblem(int parsingProblem){
2299
        this.parsingProblem = parsingProblem;
2300
    }
2301

    
2302
    @Override
2303
    public void addParsingProblem(ParserProblem problem){
2304
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2305
    }
2306

    
2307
    @Override
2308
    public void removeParsingProblem(ParserProblem problem) {
2309
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2310
    }
2311

    
2312
    /**
2313
     * @param warnings
2314
     */
2315
    @Override
2316
    public void addParsingProblems(int problems){
2317
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2318
    }
2319

    
2320
    @Override
2321
    public boolean hasProblem(){
2322
        return parsingProblem != 0;
2323
    }
2324

    
2325
    @Override
2326
    public boolean hasProblem(ParserProblem problem) {
2327
        return getParsingProblems().contains(problem);
2328
    }
2329

    
2330
    @Override
2331
    public int getProblemStarts(){
2332
        return this.problemStarts;
2333
    }
2334

    
2335
    @Override
2336
    public void setProblemStarts(int start) {
2337
        this.problemStarts = start;
2338
    }
2339

    
2340
    @Override
2341
    public int getProblemEnds(){
2342
        return this.problemEnds;
2343
    }
2344

    
2345
    @Override
2346
    public void setProblemEnds(int end) {
2347
        this.problemEnds = end;
2348
    }
2349

    
2350
//*********************** TYPE DESIGNATION *********************************************//
2351

    
2352
    /**
2353
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2354
     * to <i>this</i> taxon name.
2355
     * @see     NameTypeDesignation
2356
     * @see     SpecimenTypeDesignation
2357
     */
2358
    @Override
2359
    public Set<TypeDesignationBase> getTypeDesignations() {
2360
        if(typeDesignations == null) {
2361
            this.typeDesignations = new HashSet<TypeDesignationBase>();
2362
        }
2363
        return typeDesignations;
2364
    }
2365

    
2366
    /**
2367
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2368
     * <i>this</i> taxon name. The type designation itself will be nullified.
2369
     *
2370
     * @param  typeDesignation  the type designation which should be deleted
2371
     */
2372
    @Override
2373
    @SuppressWarnings("deprecation")
2374
    public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2375
        this.typeDesignations.remove(typeDesignation);
2376
        typeDesignation.removeTypifiedName(this);
2377
    }
2378

    
2379
    /**
2380
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2381
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2382
     * "species" or below. The specimen type designations include all the
2383
     * specimens on which the typification of this name is based (which are
2384
     * exclusively used to typify taxon names belonging to the same
2385
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2386
     * belongs) and eventually the status of these designations.
2387
     *
2388
     * @see     SpecimenTypeDesignation
2389
     * @see     NameTypeDesignation
2390
     * @see     HomotypicalGroup
2391
     */
2392
    @Override
2393
    @Transient
2394
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2395
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2396
    }
2397

    
2398
//*********************** NAME TYPE DESIGNATION *********************************************//
2399

    
2400
    /**
2401
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2402
     * to <i>this</i> taxon name the rank of which must be above "species".
2403
     * The name type designations include all the taxon names used to typify
2404
     * <i>this</i> taxon name and eventually the rejected or conserved status
2405
     * of these designations.
2406
     *
2407
     * @see     NameTypeDesignation
2408
     * @see     SpecimenTypeDesignation
2409
     */
2410
    @Override
2411
    @Transient
2412
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2413
        Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2414
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2415
            if (typeDesignation instanceof NameTypeDesignation){
2416
                result.add((NameTypeDesignation)typeDesignation);
2417
            }
2418
        }
2419
        return result;
2420
    }
2421

    
2422
    /**
2423
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2424
     * to <i>this</i> taxon name's set of type designations.
2425
     *
2426
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2427
     * @param  citation					the reference for this new designation
2428
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2429
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2430
     * @param  isRejectedType			the boolean status for a rejected name type designation
2431
     * @param  isConservedType			the boolean status for a conserved name type designation
2432
     * @param  isLectoType				the boolean status for a lectotype name type designation
2433
     * @param  isNotDesignated			the boolean status for a name type designation without name type
2434
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2435
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2436
     * @return
2437
     * @see 			  				#getNameTypeDesignations()
2438
     * @see 			  				NameTypeDesignation
2439
     * @see 			  				TypeDesignationBase#isNotDesignated()
2440
     */
2441
    @Override
2442
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2443
                Reference citation,
2444
                String citationMicroReference,
2445
                String originalNameString,
2446
                NameTypeDesignationStatus status,
2447
                boolean isRejectedType,
2448
                boolean isConservedType,
2449
                /*boolean isLectoType, */
2450
                boolean isNotDesignated,
2451
                boolean addToAllHomotypicNames) {
2452
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalNameString, status, isRejectedType, isConservedType, isNotDesignated);
2453
        //nameTypeDesignation.setLectoType(isLectoType);
2454
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2455
        return nameTypeDesignation;
2456
    }
2457

    
2458
    /**
2459
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2460
     * to <i>this</i> taxon name's set of type designations.
2461
     *
2462
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2463
     * @param  citation					the reference for this new designation
2464
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2465
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2466
     * @param  status                   the name type designation status
2467
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2468
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2469
     * @return
2470
     * @see 			  				#getNameTypeDesignations()
2471
     * @see 			  				NameTypeDesignation
2472
     * @see 			  				TypeDesignationBase#isNotDesignated()
2473
     */
2474
    @Override
2475
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2476
                Reference citation,
2477
                String citationMicroReference,
2478
                String originalNameString,
2479
                NameTypeDesignationStatus status,
2480
                boolean addToAllHomotypicNames) {
2481
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2482
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2483
        return nameTypeDesignation;
2484
    }
2485

    
2486
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2487

    
2488
    /**
2489
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2490
     * that typify <i>this</i> taxon name.
2491
     */
2492
    @Override
2493
    @Transient
2494
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2495
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2496
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2497
            if (typeDesignation instanceof SpecimenTypeDesignation){
2498
                result.add((SpecimenTypeDesignation)typeDesignation);
2499
            }
2500
        }
2501
        return result;
2502
    }
2503

    
2504

    
2505
    /**
2506
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2507
     * to <i>this</i> taxon name's set of type designations.
2508
     *
2509
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2510
     * @param  status					the specimen type designation status
2511
     * @param  citation					the reference for this new specimen type designation
2512
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2513
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2514
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2515
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2516
     * 									added to all taxon names of the homotypical group the typified
2517
     * 									taxon name belongs to
2518
     * @return
2519
     * @see 			  				#getSpecimenTypeDesignations()
2520
     * @see 			  				SpecimenTypeDesignationStatus
2521
     * @see 			  				SpecimenTypeDesignation
2522
     * @see 			  				TypeDesignationBase#isNotDesignated()
2523
     */
2524
    @Override
2525
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2526
                SpecimenTypeDesignationStatus status,
2527
                Reference citation,
2528
                String citationMicroReference,
2529
                String originalNameString,
2530
                boolean isNotDesignated,
2531
                boolean addToAllHomotypicNames) {
2532
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2533
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2534
        return specimenTypeDesignation;
2535
    }
2536

    
2537
    //used by merge strategy
2538
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2539
        return addTypeDesignation(typeDesignation, true);
2540
    }
2541

    
2542
    /**
2543
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2544
     *
2545
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2546
     * @param addToAllNames				the boolean indicating whether the type designation should be
2547
     * 									added to all taxon names of the homotypical group the typified
2548
     * 									taxon name belongs to
2549
     * @return							true if the operation was successful
2550
     *
2551
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2552
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2553
     *
2554
     */
2555
    @Override
2556
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2557
        //currently typeDesignations are not persisted with the homotypical group
2558
        //so explicit adding to the homotypical group is not necessary.
2559
        if (typeDesignation != null){
2560
            checkHomotypicalGroup(typeDesignation);
2561
            this.typeDesignations.add(typeDesignation);
2562
            typeDesignation.addTypifiedName(this);
2563

    
2564
            if (addToAllNames){
2565
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2566
                    if (taxonName != this){
2567
                        taxonName.addTypeDesignation(typeDesignation, false);
2568
                    }
2569
                }
2570
            }
2571
        }
2572
        return true;
2573
    }
2574

    
2575
    /**
2576
     * Throws an Exception this type designation already has typified names from another homotypical group.
2577
     * @param typeDesignation
2578
     */
2579
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2580
        if(typeDesignation.getTypifiedNames().size() > 0){
2581
            Set<HomotypicalGroup> groups = new HashSet<>();
2582
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2583
            for (TaxonName taxonName: names){
2584
                groups.add(taxonName.getHomotypicalGroup());
2585
            }
2586
            if (groups.size() > 1){
2587
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2588
            }
2589
        }
2590
    }
2591

    
2592

    
2593

    
2594
//*********************** HOMOTYPICAL GROUP *********************************************//
2595

    
2596

    
2597
    /**
2598
     * Returns the {@link HomotypicalGroup homotypical group} to which
2599
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2600
     * that share the same types.
2601
     *
2602
     * @see 	HomotypicalGroup
2603
     */
2604

    
2605
    @Override
2606
    public HomotypicalGroup getHomotypicalGroup() {
2607
        if (homotypicalGroup == null){
2608
            homotypicalGroup = new HomotypicalGroup();
2609
            homotypicalGroup.typifiedNames.add(this);
2610
        }
2611
    	return homotypicalGroup;
2612
    }
2613

    
2614
    /**
2615
     * @see #getHomotypicalGroup()
2616
     */
2617
    @Override
2618
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2619
        if (homotypicalGroup == null){
2620
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2621
        }
2622
        /*if (this.homotypicalGroup != null){
2623
        	this.homotypicalGroup.removeTypifiedName(this, false);
2624
        }*/
2625
        this.homotypicalGroup = homotypicalGroup;
2626
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2627
        	 this.homotypicalGroup.addTypifiedName(this);
2628
        }
2629
    }
2630

    
2631

    
2632

    
2633
// *************************************************************************//
2634

    
2635
    /**
2636
     * Returns the complete string containing the
2637
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2638
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2639
     *
2640
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2641
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2642
     * @see		#getNomenclaturalReference()
2643
     * @see		#getNomenclaturalMicroReference()
2644
     */
2645
    @Override
2646
    @Transient
2647
    public String getCitationString(){
2648
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2649
    }
2650

    
2651
    /**
2652
     * Returns the parsing problems
2653
     * @return
2654
     */
2655
    @Override
2656
    public List<ParserProblem> getParsingProblems(){
2657
        return ParserProblem.warningList(this.parsingProblem);
2658
    }
2659

    
2660
    /**
2661
     * Returns the string containing the publication date (generally only year)
2662
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2663
     * no nomenclatural reference.
2664
     *
2665
     * @return  the string containing the publication date of <i>this</i> taxon name
2666
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2667
     */
2668
    @Override
2669
    @Transient
2670
    @ValidTaxonomicYear(groups=Level3.class)
2671
    public String getReferenceYear(){
2672
        if (this.getNomenclaturalReference() != null ){
2673
            return this.getNomenclaturalReference().getYear();
2674
        }else{
2675
            return null;
2676
        }
2677
    }
2678

    
2679
    /**
2680
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2681
     * In this context a taxon base means the use of a taxon name by a reference
2682
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2683
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2684
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2685
     * within a taxonomic treatment (identified by one reference).
2686
     *
2687
     * @see	#getTaxa()
2688
     * @see	#getSynonyms()
2689
     */
2690
    @Override
2691
    public Set<TaxonBase> getTaxonBases() {
2692
        if(taxonBases == null) {
2693
            this.taxonBases = new HashSet<TaxonBase>();
2694
        }
2695
        return this.taxonBases;
2696
    }
2697

    
2698
    /**
2699
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2700
     * to the set of taxon bases using <i>this</i> taxon name.
2701
     *
2702
     * @param  taxonBase  the taxon base to be added
2703
     * @see 			  #getTaxonBases()
2704
     * @see 			  #removeTaxonBase(TaxonBase)
2705
     */
2706
    //TODO protected
2707
    @Override
2708
    public void addTaxonBase(TaxonBase taxonBase){
2709
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2710
        ReflectionUtils.makeAccessible(method);
2711
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2712
        taxonBases.add(taxonBase);
2713
    }
2714
    /**
2715
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2716
     *
2717
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2718
     * @see    				#getTaxonBases()
2719
     * @see    				#addTaxonBase(TaxonBase)
2720
     */
2721
    @Override
2722
    public void removeTaxonBase(TaxonBase taxonBase){
2723
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2724
        ReflectionUtils.makeAccessible(method);
2725
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2726

    
2727

    
2728
    }
2729

    
2730
    /**
2731
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2732
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2733
     * the set returned by getTaxonBases().
2734
     *
2735
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2736
     * @see	#getTaxonBases()
2737
     * @see	#getSynonyms()
2738
     */
2739
    @Override
2740
    @Transient
2741
    public Set<Taxon> getTaxa(){
2742
        Set<Taxon> result = new HashSet<>();
2743
        for (TaxonBase taxonBase : this.taxonBases){
2744
            if (taxonBase instanceof Taxon){
2745
                result.add((Taxon)taxonBase);
2746
            }
2747
        }
2748
        return result;
2749
    }
2750

    
2751
    /**
2752
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2753
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2754
     * the set returned by getTaxonBases().
2755
     *
2756
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2757
     * @see	#getTaxonBases()
2758
     * @see	#getTaxa()
2759
     */
2760
    @Override
2761
    @Transient
2762
    public Set<Synonym> getSynonyms() {
2763
        Set<Synonym> result = new HashSet<>();
2764
        for (TaxonBase taxonBase : this.taxonBases){
2765
            if (taxonBase instanceof Synonym){
2766
                result.add((Synonym)taxonBase);
2767
            }
2768
        }
2769
        return result;
2770
    }
2771

    
2772
    //******* REGISTRATION *****************/
2773

    
2774
    @Override
2775
    public Set<Registration> getRegistrations() {
2776
        return this.registrations;
2777
    }
2778

    
2779

    
2780
// ************* RELATIONSHIPS *****************************/
2781

    
2782

    
2783
    /**
2784
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2785
     * by title cache of the related names.
2786
     * @see #getHybridParentRelations()
2787
     */
2788
    @Override
2789
    @Transient
2790
    public List<HybridRelationship> getOrderedChildRelationships(){
2791
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2792
        result.addAll(this.hybridChildRelations);
2793
        Collections.sort(result);
2794
        Collections.reverse(result);
2795
        return result;
2796

    
2797
    }
2798

    
2799

    
2800
    /**
2801
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2802
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2803
     * "is first/second parent" or "is male/female parent". By invoking this
2804
     * method <i>this</i> botanical name becomes a hybrid child of the parent
2805
     * botanical name.
2806
     *
2807
     * @param parentName      the botanical name of the parent for this new hybrid name relationship
2808
     * @param type            the type of this new name relationship
2809
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2810
     * @return
2811
     * @see                   #addHybridChild(BotanicalName, HybridRelationshipType,String )
2812
     * @see                   #getRelationsToThisName()
2813
     * @see                   #getNameRelations()
2814
     * @see                   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
2815
     * @see                   #addNameRelationship(NameRelationship)
2816
     */
2817
    @Override
2818
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2819
        return new HybridRelationship(this, parentName, type, ruleConsidered);
2820
    }
2821

    
2822
    /**
2823
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2824
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2825
     * "is first/second parent" or "is male/female parent". By invoking this
2826
     * method <i>this</i> botanical name becomes a parent of the hybrid child
2827
     * botanical name.
2828
     *
2829
     * @param childName       the botanical name of the child for this new hybrid name relationship
2830
     * @param type            the type of this new name relationship
2831
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2832
     * @return
2833
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
2834
     * @see                   #getRelationsToThisName()
2835
     * @see                   #getNameRelations()
2836
     * @see                   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
2837
     * @see                   #addNameRelationship(NameRelationship)
2838
     */
2839
    @Override
2840
    public HybridRelationship addHybridChild(INonViralName childName, HybridRelationshipType type, String ruleConsidered){
2841
        return new HybridRelationship(childName, this, type, ruleConsidered);
2842
    }
2843

    
2844
    @Override
2845
    public void removeHybridChild(INonViralName child) {
2846
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2847
        hybridRelationships.addAll(this.getHybridChildRelations());
2848
        hybridRelationships.addAll(this.getHybridParentRelations());
2849
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2850
            // remove name relationship from this side
2851
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2852
                this.removeHybridRelationship(hybridRelationship);
2853
            }
2854
        }
2855
    }
2856

    
2857
    @Override
2858
    public void removeHybridParent(INonViralName parent) {
2859
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2860
        hybridRelationships.addAll(this.getHybridChildRelations());
2861
        hybridRelationships.addAll(this.getHybridParentRelations());
2862
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2863
            // remove name relationship from this side
2864
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2865
                this.removeHybridRelationship(hybridRelationship);
2866
            }
2867
        }
2868
    }
2869

    
2870

    
2871

    
2872
// *********** DESCRIPTIONS *************************************
2873

    
2874
    /**
2875
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2876
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2877
     * concerning the taxon name like for instance the content of its first
2878
     * publication (protolog) or a picture of this publication.
2879
     *
2880
     * @see	#addDescription(TaxonNameDescription)
2881
     * @see	#removeDescription(TaxonNameDescription)
2882
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2883
     */
2884
    @Override
2885
    public Set<TaxonNameDescription> getDescriptions() {
2886
        return descriptions;
2887
    }
2888

    
2889
    /**
2890
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2891
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2892
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2893
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2894
     *
2895
     * @param  description  the taxon name description to be added
2896
     * @see					#getDescriptions()
2897
     * @see 			  	#removeDescription(TaxonNameDescription)
2898
     */
2899
    @Override
2900
    public void addDescription(TaxonNameDescription description) {
2901
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2902
        ReflectionUtils.makeAccessible(field);
2903
        ReflectionUtils.setField(field, description, this);
2904
        descriptions.add(description);
2905
    }
2906
    /**
2907
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2908
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2909
     * of the description itself will be set to "null".
2910
     *
2911
     * @param  description  the taxon name description which should be removed
2912
     * @see     		  	#getDescriptions()
2913
     * @see     		  	#addDescription(TaxonNameDescription)
2914
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
2915
     */
2916
    @Override
2917
    public void removeDescription(TaxonNameDescription description) {
2918
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
2919
        ReflectionUtils.makeAccessible(field);
2920
        ReflectionUtils.setField(field, description, null);
2921
        descriptions.remove(description);
2922
    }
2923

    
2924
// *********** HOMOTYPIC GROUP METHODS **************************************************
2925

    
2926
    @Override
2927
    @Transient
2928
    public void mergeHomotypicGroups(TaxonName name){
2929
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2930
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2931
        name.setHomotypicalGroup(this.homotypicalGroup);
2932
    }
2933

    
2934
    /**
2935
     * Returns the boolean value indicating whether a given taxon name belongs
2936
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
2937
     * or not (false). Returns "true" only if the homotypical groups of both
2938
     * taxon names exist and if they are identical.
2939
     *
2940
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
2941
     * @return  			   the boolean value of the check
2942
     * @see     			   HomotypicalGroup
2943
     */
2944
    @Override
2945
    @Transient
2946
    public boolean isHomotypic(TaxonName homoTypicName) {
2947
        if (homoTypicName == null) {
2948
            return false;
2949
        }
2950
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
2951
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
2952
            return false;
2953
        }
2954
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
2955
            return true;
2956
        }
2957
        return false;
2958
    }
2959

    
2960

    
2961
    /**
2962
     * Checks whether name is a basionym for ALL names
2963
     * in its homotypical group.
2964
     * Returns <code>false</code> if there are no other names in the group
2965
     * @param name
2966
     * @return
2967
     */
2968
    @Override
2969
    @Transient
2970
    public boolean isGroupsBasionym() {
2971
    	if (homotypicalGroup == null){
2972
    		homotypicalGroup = HomotypicalGroup.NewInstance();
2973
    		homotypicalGroup.addTypifiedName(this);
2974
    	}
2975
        Set<TaxonName> typifiedNames = homotypicalGroup.getTypifiedNames();
2976

    
2977
        // Check whether there are any other names in the group
2978
        if (typifiedNames.size() == 1) {
2979
                return false;
2980
        }
2981

    
2982
        for (TaxonName taxonName : typifiedNames) {
2983
                if (!taxonName.equals(this)) {
2984
                        if (! isBasionymFor(taxonName)) {
2985
                                return false;
2986
                        }
2987
                }
2988
        }
2989
        return true;
2990
    }
2991

    
2992
    /**
2993
     * Checks whether a basionym relationship exists between fromName and toName.
2994
     *
2995
     * @param fromName
2996
     * @param toName
2997
     * @return
2998
     */
2999
    @Override
3000
    @Transient
3001
    public boolean isBasionymFor(TaxonName newCombinationName) {
3002
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3003
            for (NameRelationship relation : relations) {
3004
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3005
                                    relation.getFromName().equals(this)) {
3006
                            return true;
3007
                    }
3008
            }
3009
            return false;
3010
    }
3011

    
3012
    /**
3013
     * Creates a basionym relationship to all other names in this names homotypical
3014
     * group.
3015
     *
3016
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3017
     */
3018
    @Override
3019
    @Transient
3020
    public void makeGroupsBasionym() {
3021
        this.homotypicalGroup.setGroupBasionym(this);
3022
    }
3023

    
3024

    
3025
//*********  Rank comparison shortcuts   ********************//
3026
    /**
3027
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3028
     * taxon name is higher than the genus rank (true) or not (false).
3029
     * Suprageneric non viral names are monomials.
3030
     * Returns false if rank is null.
3031
     *
3032
     * @see  #isGenus()
3033
     * @see  #isInfraGeneric()
3034
     * @see  #isSpecies()
3035
     * @see  #isInfraSpecific()
3036
     */
3037
    @Override
3038
    @Transient
3039
    public boolean isSupraGeneric() {
3040
        if (rank == null){
3041
            return false;
3042
        }
3043
        return getRank().isSupraGeneric();
3044
    }
3045
    /**
3046
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3047
     * taxon name is the genus rank (true) or not (false). Non viral names with
3048
     * genus rank are monomials. Returns false if rank is null.
3049
     *
3050
     * @see  #isSupraGeneric()
3051
     * @see  #isInfraGeneric()
3052
     * @see  #isSpecies()
3053
     * @see  #isInfraSpecific()
3054
     */
3055
    @Override
3056
    @Transient
3057
    public boolean isGenus() {
3058
        if (rank == null){
3059
            return false;
3060
        }
3061
        return getRank().isGenus();
3062
    }
3063

    
3064
    @Override
3065
    @Transient
3066
    public boolean isGenusOrSupraGeneric() {
3067
        return isGenus()|| isSupraGeneric();
3068
    }
3069
    /**
3070
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3071
     * taxon name is higher than the species rank and lower than the
3072
     * genus rank (true) or not (false). Infrageneric non viral names are
3073
     * binomials. Returns false if rank is null.
3074
     *
3075
     * @see  #isSupraGeneric()
3076
     * @see  #isGenus()
3077
     * @see  #isSpecies()
3078
     * @see  #isInfraSpecific()
3079
     */
3080
    @Override
3081
    @Transient
3082
    public boolean isInfraGeneric() {
3083
        if (rank == null){
3084
            return false;
3085
        }
3086
        return getRank().isInfraGeneric();
3087
    }
3088

    
3089
    /**
3090
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3091
     * taxon name is higher than the species rank (true) or not (false).
3092
     * Returns false if rank is null.
3093
     *
3094
     * @see  #isGenus()
3095
     * @see  #isInfraGeneric()
3096
     * @see  #isSpecies()
3097
     * @see  #isInfraSpecific()
3098
     */
3099
    @Override
3100
    @Transient
3101
    public boolean isSupraSpecific(){
3102
        if (rank == null) {
3103
            return false;
3104
        }
3105
        return getRank().isHigher(Rank.SPECIES());
3106
    }
3107

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

    
3147
    /**
3148
     * Returns true if this name's rank indicates a rank that aggregates species like species
3149
     * aggregates or species groups, false otherwise. This methods currently returns false
3150
     * for all user defined ranks.
3151
     *
3152
     *@see Rank#isSpeciesAggregate()
3153
     *
3154
     * @return
3155
     */
3156
    @Override
3157
    @Transient
3158
    public boolean isSpeciesAggregate() {
3159
        if (rank == null){
3160
            return false;
3161
        }
3162
        return getRank().isSpeciesAggregate();
3163
    }
3164

    
3165

    
3166
    /**
3167
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3168
     * the construction of <i>this</i> taxon name since there is no specific
3169
     * nomenclatural code defined. The real implementention takes place in the
3170
     * subclasses {@link IBacterialName BacterialName},
3171
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3172
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3173
     * and only one nomenclatural code.
3174
     *
3175
     * @return  null
3176
     * @see  	#isCodeCompliant()
3177
     * @see  	#getHasProblem()
3178
     * @deprecated use {@link #getNameType()} instead
3179
     */
3180
    @Override
3181
    @Deprecated
3182
    @Transient
3183
    @java.beans.Transient
3184
    public NomenclaturalCode getNomenclaturalCode() {
3185
        return nameType;
3186
    }
3187

    
3188

    
3189
    /**
3190
     * Generates and returns the string with the scientific name of <i>this</i>
3191
     * taxon name (only non viral taxon names can be generated from their
3192
     * components). This string may be stored in the inherited
3193
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3194
     * This method overrides the generic and inherited
3195
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3196
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3197
     *
3198
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3199
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3200
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3201
     */
3202
//	@Override
3203
//	public abstract String generateTitle();
3204

    
3205
    /**
3206
     * Creates a basionym relationship between this name and
3207
     * 	each name in its homotypic group.
3208
     *
3209
     * @param basionymName
3210
     */
3211
    @Override
3212
    @Transient
3213
    public void setAsGroupsBasionym() {
3214

    
3215
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3216
        if (homotypicalGroup == null) {
3217
            return;
3218
        }
3219

    
3220
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3221
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3222

    
3223
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3224

    
3225
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3226

    
3227
            for(NameRelationship nameRelation : nameRelations){
3228
                relations.add(nameRelation);
3229
            }
3230
        }
3231

    
3232
        for (NameRelationship relation : relations) {
3233

    
3234
            // If this is a basionym relation, and toName is in the homotypical group,
3235
            //	remove the relationship.
3236
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3237
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3238
                removeRelations.add(relation);
3239
            }
3240
        }
3241

    
3242
        // Removing relations from a set through which we are iterating causes a
3243
        //	ConcurrentModificationException. Therefore, we delete the targeted
3244
        //	relations in a second step.
3245
        for (NameRelationship relation : removeRelations) {
3246
            this.removeNameRelationship(relation);
3247
        }
3248

    
3249
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3250
            if (!name.equals(this)) {
3251

    
3252
                // First check whether the relationship already exists
3253
                if (!this.isBasionymFor(name)) {
3254

    
3255
                    // Then create it
3256
                    name.addRelationshipFromName(this,
3257
                            NameRelationshipType.BASIONYM(), null);
3258
                }
3259
            }
3260
        }
3261
    }
3262

    
3263
    /**
3264
     * Removes basionym relationship between this name and
3265
     * 	each name in its homotypic group.
3266
     *
3267
     * @param basionymName
3268
     */
3269
    @Override
3270
    @Transient
3271
    public void removeAsGroupsBasionym() {
3272

    
3273
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3274

    
3275
        if (homotypicalGroup == null) {
3276
            return;
3277
        }
3278

    
3279
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3280
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3281

    
3282
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3283

    
3284
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3285

    
3286
            for(NameRelationship nameRelation : nameRelations){
3287
                relations.add(nameRelation);
3288
            }
3289
        }
3290

    
3291
        for (NameRelationship relation : relations) {
3292

    
3293
            // If this is a basionym relation, and toName is in the homotypical group,
3294
            //	and fromName is basionymName, remove the relationship.
3295
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3296
                    relation.getFromName().equals(this) &&
3297
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3298
                removeRelations.add(relation);
3299
            }
3300
        }
3301

    
3302
        // Removing relations from a set through which we are iterating causes a
3303
        //	ConcurrentModificationException. Therefore, we delete the targeted
3304
        //	relations in a second step.
3305
        for (NameRelationship relation : removeRelations) {
3306
            this.removeNameRelationship(relation);
3307
        }
3308
    }
3309

    
3310

    
3311
    /**
3312
     * Defines the last part of the name.
3313
     * This is for infraspecific taxa, the infraspecific epithet,
3314
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3315
     * else the genusOrUninomial.
3316
     * However, the result does not depend on the rank (which may be not correctly set
3317
     * in case of dirty data) but returns the first name part which is not blank
3318
     * considering the above order.
3319
     * @return the first not blank name part in reverse order
3320
     */
3321
    @Override
3322
    public String getLastNamePart() {
3323
        String result =
3324
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3325
                    this.getInfraSpecificEpithet() :
3326
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3327
                    this.getSpecificEpithet():
3328
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3329
                    this.getInfraGenericEpithet():
3330
                this.getGenusOrUninomial();
3331
        return result;
3332
    }
3333

    
3334
    /**
3335
     * {@inheritDoc}
3336
     */
3337
    @Override
3338
    public boolean isHybridName() {
3339
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3340
    }
3341

    
3342
    /**
3343
     * {@inheritDoc}
3344
     */
3345
    @Override
3346
    public boolean isHybrid() {
3347
        return this.isHybridName() || this.isHybridFormula();
3348
    }
3349

    
3350
// ***************** COMPARE ********************************/
3351

    
3352
    @Override
3353
    public int compareToName(TaxonName otherName){
3354

    
3355
        int result = 0;
3356

    
3357
        if (otherName == null) {
3358
            throw new NullPointerException("Cannot compare to null.");
3359
        }
3360

    
3361
        //other
3362
        otherName = deproxy(otherName);
3363
        String otherNameCache = otherName.getNameCache();
3364
        String otherTitleCache = otherName.getTitleCache();
3365
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3366
        if (otherName.isAutonym()){
3367
            boolean isProtected = otherName.isProtectedNameCache();
3368
            String oldNameCache = otherName.getNameCache();
3369
            otherName.setProtectedNameCache(false);
3370
            otherName.setNameCache(null, false);
3371
            otherNameCache = otherName.getNameCache();
3372
            otherName.setNameCache(oldNameCache, isProtected);
3373
        }
3374

    
3375
        //this
3376
        String thisNameCache = this.getNameCache();
3377
        String thisTitleCache = this.getTitleCache();
3378

    
3379
        if (this.isAutonym()){
3380
            boolean isProtected = this.isProtectedNameCache();
3381
            String oldNameCache = this.getNameCache();
3382
            this.setProtectedNameCache(false);
3383
            this.setNameCache(null, false);
3384
            thisNameCache = this.getNameCache();
3385
            this.setNameCache(oldNameCache, isProtected);
3386
        }
3387

    
3388

    
3389
        // Compare name cache of taxon names
3390
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3391
            thisNameCache = normalizeName(thisNameCache);
3392
            otherNameCache = normalizeName(otherNameCache);
3393
            result = thisNameCache.compareTo(otherNameCache);
3394
        }
3395

    
3396
        // Compare title cache of taxon names
3397
        if (result == 0){
3398
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3399
                thisTitleCache = normalizeName(thisTitleCache);
3400
                otherTitleCache = normalizeName(otherTitleCache);
3401
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3402
            }
3403
        }
3404

    
3405
        return result;
3406
    }
3407

    
3408
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3409
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3410

    
3411
    /**
3412
     * @param thisNameCache
3413
     * @param HYBRID_SIGN
3414
     * @param QUOT_SIGN
3415
     * @return
3416
     */
3417
    private String normalizeName(String thisNameCache) {
3418
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3419
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3420
        return thisNameCache;
3421
    }
3422

    
3423
// ********************** INTERFACES ********************************************/
3424

    
3425
    /**
3426
     * Method to cast a interfaced name to a concrete name.
3427
     * The method includes a deproxy to guarantee that no
3428
     * class cast exception is thrown.
3429
     *
3430
     * @see #castAndDeproxy(Set)
3431
     * @param interfacedName
3432
     * @return
3433
     */
3434
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3435
        return deproxy(interfacedName, TaxonName.class);
3436
    }
3437

    
3438
    /**
3439
     * Method to cast a set of interfaced names to concrete namex.
3440
     * The method includes a deproxy to guarantee that no
3441
     * class cast exception is thrown.
3442
     *
3443
     * @see #castAndDeproxy(ITaxonNameBase)
3444
     * @param naminterfacedNames
3445
     * @return
3446
     */
3447
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3448
        Set<TaxonName> result = new HashSet<>();
3449
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3450
            result.add(castAndDeproxy(naminterfacedName));
3451
        }
3452
        return result;
3453
    }
3454

    
3455
//************************ isType ***********************************************/
3456

    
3457
    /**
3458
     * @return
3459
     */
3460
    @Override
3461
    public boolean isNonViral() {
3462
        return nameType.isNonViral();
3463
    }
3464

    
3465
    @Override
3466
    public boolean isZoological(){
3467
        return nameType.isZoological();
3468
    }
3469
    @Override
3470
    public boolean isBotanical() {
3471
        return nameType.isBotanical();
3472
    }
3473
    @Override
3474
    public boolean isCultivar() {
3475
        return nameType.isCultivar();
3476
    }
3477
    @Override
3478
    public boolean isBacterial() {
3479
        return nameType.isBacterial();
3480
    }
3481
    @Override
3482
    public boolean isViral() {
3483
        return nameType.isViral();
3484
    }
3485

    
3486

    
3487
//*********************** CLONE ********************************************************/
3488

    
3489
    /**
3490
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3491
     * a new instance that differs only slightly from <i>this</i> taxon name by
3492
     * modifying only some of the attributes.<BR><BR>
3493
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3494
     * <b>The name gets a newly created homotypical group</b><BR>
3495
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3496
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3497
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3498
     *
3499
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3500
     * @see java.lang.Object#clone()
3501
     */
3502
    @Override
3503
    public Object clone() {
3504
        TaxonName result;
3505
        try {
3506
            result = (TaxonName)super.clone();
3507

    
3508
            //taxonBases -> empty
3509
            result.taxonBases = new HashSet<>();
3510

    
3511
            //empty caches
3512
            if (! protectedFullTitleCache){
3513
                result.fullTitleCache = null;
3514
            }
3515

    
3516
            //descriptions
3517
            result.descriptions = new HashSet<>();
3518
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3519
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3520
                result.descriptions.add(newDescription);
3521
            }
3522

    
3523
            //status
3524
            result.status = new HashSet<>();
3525
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3526
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3527
                result.status.add(newStatus);
3528
            }
3529

    
3530

    
3531
            //to relations
3532
            result.relationsToThisName = new HashSet<>();
3533
            for (NameRelationship toRelationship : getRelationsToThisName()){
3534
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3535
                newRelationship.setRelatedTo(result);
3536
                result.relationsToThisName.add(newRelationship);
3537
            }
3538

    
3539
            //from relations
3540
            result.relationsFromThisName = new HashSet<>();
3541
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3542
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3543
                newRelationship.setRelatedFrom(result);
3544
                result.relationsFromThisName.add(newRelationship);
3545
            }
3546

    
3547
            //type designations
3548
            result.typeDesignations = new HashSet<>();
3549
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3550
                TypeDesignationBase<?> newDesignation = (TypeDesignationBase<?>)typeDesignation.clone();
3551
                this.removeTypeDesignation(newDesignation);
3552
                result.addTypeDesignation(newDesignation, false);
3553
            }
3554

    
3555
            //homotypicalGroup
3556
            //TODO still needs to be discussed
3557
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3558
            result.homotypicalGroup.addTypifiedName(this);
3559

    
3560

    
3561
            //HybridChildRelations
3562
            result.hybridChildRelations = new HashSet<>();
3563
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3564
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3565
                newChildRelationship.setRelatedTo(result);
3566
                result.hybridChildRelations.add(newChildRelationship);
3567
            }
3568

    
3569
            //HybridParentRelations
3570
            result.hybridParentRelations = new HashSet<>();
3571
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3572
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3573
                newParentRelationship.setRelatedFrom(result);
3574
                result.hybridParentRelations.add(newParentRelationship);
3575
            }
3576

    
3577
            //empty caches
3578
            if (! protectedNameCache){
3579
                result.nameCache = null;
3580
            }
3581

    
3582
            //empty caches
3583
            if (! protectedAuthorshipCache){
3584
                result.authorshipCache = null;
3585
            }
3586

    
3587
            //no changes to: appendedPharse, nomenclaturalReference,
3588
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3589
            //protectedFullTitleCache, rank
3590
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3591
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3592
            //protectedAuthorshipCache, protectedNameCache,
3593
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3594
            //acronym
3595
            //subGenusAuthorship, nameApprobation
3596
            //anamorphic
3597
            //cultivarName
3598
            return result;
3599
        } catch (CloneNotSupportedException e) {
3600
            logger.warn("Object does not implement cloneable");
3601
            e.printStackTrace();
3602
            return null;
3603
        }
3604

    
3605
    }
3606

    
3607

    
3608
}
3609

    
(29-29/36)