Project

General

Profile

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

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

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

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

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

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

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

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

    
163
    "acronym",
164

    
165
    "subGenusAuthorship",
166
    "nameApprobation",
167

    
168
    "breed",
169
    "publicationYear",
170
    "originalPublicationYear",
171
    "inCombinationAuthorship",
172
    "inBasionymAuthorship",
173

    
174
    "anamorphic",
175

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

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

    
200

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
354

    
355

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

    
366
//****** Non-ViralName attributes ***************************************/
367

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

    
380
    @XmlElement(name = "ProtectedNameCache")
381
    @CacheUpdate(value="nameCache")
382
    protected boolean protectedNameCache;
383

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

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

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

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

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

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

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

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

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

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

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

    
490
    @XmlElement(name = "ProtectedAuthorshipCache")
491
    @CacheUpdate("authorshipCache")
492
    protected boolean protectedAuthorshipCache;
493

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

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

    
510

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

    
518
    @XmlElement(name ="IsMonomHybrid")
519
    @CacheUpdate("nameCache")
520
    private boolean monomHybrid = false;
521

    
522
    @XmlElement(name ="IsBinomHybrid")
523
    @CacheUpdate("nameCache")
524
    private boolean binomHybrid = false;
525

    
526
    @XmlElement(name ="IsTrinomHybrid")
527
    @CacheUpdate("nameCache")
528
    private boolean trinomHybrid = false;
529

    
530
// ViralName attributes ************************* /
531

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

    
539
// BacterialName attributes ***********************/
540

    
541
    //Author team and year of the subgenus name
542
    @XmlElement(name = "SubGenusAuthorship")
543
    @Field
544
    private String subGenusAuthorship;
545

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

    
551
    //ZOOLOGICAL NAME
552

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

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

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

    
572
    //Cultivar attribute(s)
573

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

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

    
586
// *************** FACTORY METHODS ********************************/
587

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

    
601

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

    
623

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

    
638

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

    
664

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

    
710

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

    
722

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

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

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

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

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

    
825
// ****************** GETTER / SETTER ****************************/
826

    
827
    @Override
828
    public NomenclaturalCode getNameType() {
829
        return nameType;
830
    }
831

    
832
    @Override
833
    public void setNameType(NomenclaturalCode nameType) {
834
        this.nameType = nameType;
835
    }
836

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1110
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1111
        this.hybridParentRelations = hybridParentRelations;
1112
    }
1113

    
1114

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

    
1131
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1132
        this.hybridChildRelations = hybridChildRelations;
1133
    }
1134

    
1135
    @Override
1136
    public boolean isProtectedFullTitleCache() {
1137
        return protectedFullTitleCache;
1138
    }
1139

    
1140
    @Override
1141
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1142
        this.protectedFullTitleCache = protectedFullTitleCache;
1143
    }
1144

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

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

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

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

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

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

    
1226
    @Override
1227
    public boolean isTrinomHybrid(){
1228
        return this.trinomHybrid;
1229
    }
1230

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

    
1241
    // ****************** VIRAL NAME ******************/
1242

    
1243
    @Override
1244
    public String getAcronym(){
1245
        return this.acronym;
1246
    }
1247

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

    
1256
    // ****************** BACTERIAL NAME ******************/
1257

    
1258
    @Override
1259
    public String getSubGenusAuthorship(){
1260
        return this.subGenusAuthorship;
1261
    }
1262

    
1263
    @Override
1264
    public void setSubGenusAuthorship(String subGenusAuthorship){
1265
        this.subGenusAuthorship = subGenusAuthorship;
1266
    }
1267

    
1268

    
1269
    @Override
1270
    public String getNameApprobation(){
1271
        return this.nameApprobation;
1272
    }
1273

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

    
1282
    //************ Zoological Name
1283

    
1284

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

    
1297

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

    
1310

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

    
1323
    // **** Cultivar Name ************
1324

    
1325

    
1326
    @Override
1327
    public String getCultivarName(){
1328
        return this.cultivarName;
1329
    }
1330

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

    
1339
    // **************** Fungus Name
1340
    @Override
1341
    public boolean isAnamorphic(){
1342
        return this.anamorphic;
1343
    }
1344

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

    
1353

    
1354
// **************** ADDER / REMOVE *************************/
1355

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

    
1385

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

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

    
1417
//********* METHODS **************************************/
1418

    
1419
    @Override
1420
    public INameCacheStrategy getCacheStrategy() {
1421
        rectifyNameCacheStrategy();
1422
        return this.cacheStrategy;
1423
    }
1424

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

    
1435

    
1436
    @Override
1437
    public void setFullTitleCache(String fullTitleCache){
1438
        setFullTitleCache(fullTitleCache, PROTECTED);
1439
    }
1440

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

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

    
1473

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

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

    
1494

    
1495
    @Override
1496
    public String getTitleCache(){
1497
        if(!protectedTitleCache) {
1498
            updateAuthorshipCache();
1499
        }
1500
        return super.getTitleCache();
1501
    }
1502

    
1503
    @Override
1504
    public void setTitleCache(String titleCache, boolean protectCache){
1505
        super.setTitleCache(titleCache, protectCache);
1506
    }
1507

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

    
1531
        }
1532
        return authorshipCache;
1533
    }
1534

    
1535

    
1536

    
1537
    /**
1538
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
1539
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
1540
     * @return
1541
     */
1542
    private void updateAuthorshipCache() {
1543
        //updates the authorship cache if necessary and via the listener updates all higher caches
1544
        if (protectedAuthorshipCache == false){
1545
            String oldCache = this.authorshipCache;
1546
            String newCache = this.getAuthorshipCache();
1547
            if ( (oldCache == null && newCache != null)  ||  CdmUtils.nullSafeEqual(oldCache,newCache)){
1548
                this.setAuthorshipCache(this.getAuthorshipCache(), false);
1549
            }
1550
        }
1551
    }
1552

    
1553

    
1554
    /**
1555
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1556
     * flag to <code>true</code>.
1557
     *
1558
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1559
     * @see    #getAuthorshipCache()
1560
     */
1561
    @Override
1562
    public void setAuthorshipCache(String authorshipCache) {
1563
        setAuthorshipCache(authorshipCache, true);
1564
    }
1565

    
1566

    
1567
    /**
1568
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1569
     *
1570
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1571
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1572
     * the flag is set to <code>false</code>.
1573
     * @see    #getAuthorshipCache()
1574
     */
1575
    @Override
1576
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1577
        this.authorshipCache = authorshipCache;
1578
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1579
    }
1580

    
1581
    /**
1582
     * Generates and returns a concatenated and formated authorteams string
1583
     * including basionym and combination authors of <i>this</i> non viral taxon name
1584
     * according to the strategy defined in
1585
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName) INonViralNameCacheStrategy}.
1586
     *
1587
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1588
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName)
1589
     */
1590
    @Override
1591
    public String generateAuthorship(){
1592
        if (getCacheStrategy() == null){
1593
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1594
            return null;
1595
        }else{
1596
            return cacheStrategy.getAuthorshipCache(this);
1597
        }
1598
    }
1599

    
1600

    
1601

    
1602
    /**
1603
     * Tests if the given name has any authors.
1604
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1605
     */
1606
    @Override
1607
    public boolean hasAuthors() {
1608
        return (this.getCombinationAuthorship() != null ||
1609
                this.getExCombinationAuthorship() != null ||
1610
                this.getBasionymAuthorship() != null ||
1611
                this.getExBasionymAuthorship() != null);
1612
    }
1613

    
1614
    /**
1615
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1616
     * @return
1617
     */
1618
    @Override
1619
    public String computeCombinationAuthorNomenclaturalTitle() {
1620
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1621
    }
1622

    
1623
    /**
1624
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1625
     * @return
1626
     */
1627
    @Override
1628
    public String computeBasionymAuthorNomenclaturalTitle() {
1629
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1630
    }
1631

    
1632

    
1633
    /**
1634
     * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1635
     * @return
1636
     */
1637
    @Override
1638
    public String computeExCombinationAuthorNomenclaturalTitle() {
1639
        return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1640
    }
1641

    
1642
    /**
1643
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1644
     * @return
1645
     */
1646
    @Override
1647
    public String computeExBasionymAuthorNomenclaturalTitle() {
1648
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1649
    }
1650

    
1651
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1652
        if (author == null){
1653
            return null;
1654
        }else{
1655
            return author.getNomenclaturalTitle();
1656
        }
1657
    }
1658

    
1659
    /**
1660
     * Returns the set of all {@link NameRelationship name relationships}
1661
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1662
     * in some name relationships or target in some others.
1663
     *
1664
     * @see    #getRelationsToThisName()
1665
     * @see    #getRelationsFromThisName()
1666
     * @see    #addNameRelationship(NameRelationship)
1667
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1668
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1669
     */
1670
    @Override
1671
    @Transient
1672
    public Set<NameRelationship> getNameRelations() {
1673
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1674
        rels.addAll(getRelationsFromThisName());
1675
        rels.addAll(getRelationsToThisName());
1676
        return rels;
1677
    }
1678

    
1679
    /**
1680
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1681
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1682
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1683
     *
1684
     * @param toName		  the taxon name of the target for this new name relationship
1685
     * @param type			  the type of this new name relationship
1686
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1687
     * @return
1688
     * @see    				  #getRelationsToThisName()
1689
     * @see    				  #getNameRelations()
1690
     * @see    				  #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1691
     * @see    				  #addNameRelationship(NameRelationship)
1692
     */
1693
    @Override
1694
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, String ruleConsidered){
1695
        return addRelationshipToName(toName, type, null, null, ruleConsidered);
1696
    }
1697

    
1698
    /**
1699
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1700
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1701
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1702
     *
1703
     * @param toName		  the taxon name of the target for this new name relationship
1704
     * @param type			  the type of this new name relationship
1705
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1706
     * @return
1707
     * @see    				  #getRelationsToThisName()
1708
     * @see    				  #getNameRelations()
1709
     * @see    				  #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1710
     * @see    				  #addNameRelationship(NameRelationship)
1711
     */
1712
    @Override
1713
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1714
        if (toName == null){
1715
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1716
        }
1717
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered);
1718
        return rel;
1719
    }
1720

    
1721
    /**
1722
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1723
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1724
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1725
     *
1726
     * @param fromName		  the taxon name of the source for this new name relationship
1727
     * @param type			  the type of this new name relationship
1728
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1729
     * @param citation		  the reference in which this relation was described
1730
     * @param microCitation	  the reference detail for this relation (e.g. page)
1731
     * @see    				  #getRelationsFromThisName()
1732
     * @see    				  #getNameRelations()
1733
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1734
     * @see    				  #addNameRelationship(NameRelationship)
1735
     */
1736
    @Override
1737
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered){
1738
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1739
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
1740
    }
1741
    /**
1742
     * Creates a new {@link NameRelationship#NameRelationship(TaxonName, TaxonName, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1743
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1744
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1745
     *
1746
     * @param fromName		  the taxon name of the source for this new name relationship
1747
     * @param type			  the type of this new name relationship
1748
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1749
     * @param citation		  the reference in which this relation was described
1750
     * @param microCitation	  the reference detail for this relation (e.g. page)
1751
     * @see    				  #getRelationsFromThisName()
1752
     * @see    				  #getNameRelations()
1753
     * @see    				  #addRelationshipToName(TaxonName, NameRelationshipType, String)
1754
     * @see    				  #addNameRelationship(NameRelationship)
1755
     */
1756
    @Override
1757
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1758
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1759
    }
1760

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

    
1801
        TaxonName fromName = nameRelation.getFromName();
1802
        TaxonName toName = nameRelation.getToName();
1803

    
1804
        if (nameRelation != null) {
1805
            nameRelation.setToName(null);
1806
            nameRelation.setFromName(null);
1807
        }
1808

    
1809
        if (fromName != null) {
1810
            fromName.removeNameRelationship(nameRelation);
1811
        }
1812

    
1813
        if (toName != null) {
1814
            toName.removeNameRelationship(nameRelation);
1815
        }
1816

    
1817
        this.relationsToThisName.remove(nameRelation);
1818
        this.relationsFromThisName.remove(nameRelation);
1819
    }
1820

    
1821
    @Override
1822
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1823
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1824
//		nameRelationships.addAll(this.getNameRelations());
1825
        nameRelationships.addAll(this.getRelationsFromThisName());
1826
        nameRelationships.addAll(this.getRelationsToThisName());
1827
        for(NameRelationship nameRelationship : nameRelationships) {
1828
            // remove name relationship from this side
1829
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1830
                this.removeNameRelationship(nameRelationship);
1831
            }
1832
        }
1833
    }
1834

    
1835
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1836

    
1837
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1838
        for(NameRelationship nameRelationship : tmpRels) {
1839
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1840
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1841
                if (type == null || type.equals(nameRelationship.getType())){
1842
                    this.removeNameRelationship(nameRelationship);
1843
                }
1844
            }
1845
        }
1846
    }
1847

    
1848

    
1849
    /**
1850
     * If relation is of type NameRelationship, addNameRelationship is called;
1851
     * if relation is of type HybridRelationship addHybridRelationship is called,
1852
     * otherwise an IllegalArgumentException is thrown.
1853
     *
1854
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1855
     * @see    	   		#addNameRelationship(NameRelationship)
1856
     * @see    	   		#getNameRelations()
1857
     * @see    	   		NameRelationship
1858
     * @see    	   		RelationshipBase
1859
     * @see             #addHybridRelationship(HybridRelationship)
1860

    
1861
     * @deprecated to be used by RelationshipBase only
1862
     */
1863
    @Deprecated
1864
    @Override
1865
    public void addRelationship(RelationshipBase relation) {
1866
        if (relation instanceof NameRelationship){
1867
            addNameRelationship((NameRelationship)relation);
1868

    
1869
        }else if (relation instanceof HybridRelationship){
1870
            addHybridRelationship((HybridRelationship)relation);
1871
        }else{
1872
            logger.warn("Relationship not of type NameRelationship!");
1873
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1874
        }
1875
    }
1876

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

    
1893
    /**
1894
     * Returns the set of all {@link NameRelationship name relationships}
1895
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1896
     *
1897
     * @see    #getNameRelations()
1898
     * @see    #getRelationsFromThisName()
1899
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1900
     */
1901
    @Override
1902
    public Set<NameRelationship> getRelationsToThisName() {
1903
        if(relationsToThisName == null) {
1904
            this.relationsToThisName = new HashSet<>();
1905
        }
1906
        return relationsToThisName;
1907
    }
1908

    
1909
    /**
1910
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1911
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1912
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1913
     * and the nomenclatural code rule considered.
1914
     *
1915
     * @see     NomenclaturalStatus
1916
     * @see     NomenclaturalStatusType
1917
     */
1918
    @Override
1919
    public Set<NomenclaturalStatus> getStatus() {
1920
        if(status == null) {
1921
            this.status = new HashSet<>();
1922
        }
1923
        return status;
1924
    }
1925

    
1926
    /**
1927
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1928
     * to <i>this</i> taxon name's set of nomenclatural status.
1929
     *
1930
     * @param  nomStatus  the nomenclatural status to be added
1931
     * @see 			  #getStatus()
1932
     */
1933
    @Override
1934
    public void addStatus(NomenclaturalStatus nomStatus) {
1935
        this.status.add(nomStatus);
1936
    }
1937
    @Override
1938
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1939
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1940
        this.status.add(newStatus);
1941
        return newStatus;
1942
    }
1943

    
1944
    /**
1945
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1946
     * Type and ruleConsidered attributes of the nomenclatural status object
1947
     * will be nullified.
1948
     *
1949
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1950
     * @see     		  #getStatus()
1951
     */
1952
    @Override
1953
    public void removeStatus(NomenclaturalStatus nomStatus) {
1954
        //TODO to be implemented?
1955
        logger.warn("not yet fully implemented?");
1956
        this.status.remove(nomStatus);
1957
    }
1958

    
1959

    
1960
    /**
1961
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1962
     * strings or year according to the strategy defined in
1963
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1964
     * The result might be stored in {@link #getNameCache() nameCache} if the
1965
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1966
     *
1967
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1968
     * @see     #getNameCache()
1969
     */
1970
    protected String generateNameCache(){
1971
        if (getCacheStrategy() == null){
1972
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1973
            return null;
1974
        }else{
1975
            return cacheStrategy.getNameCache(this);
1976
        }
1977
    }
1978

    
1979
    /**
1980
     * Returns or generates the nameCache (scientific name
1981
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1982
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1983
     * the string will be generated according to a defined strategy,
1984
     * otherwise the value of the actual nameCache string will be returned.
1985
     *
1986
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1987
     * @see     #generateNameCache()
1988
     */
1989
    @Override
1990
    @Transient
1991
    public String getNameCache() {
1992
        if (protectedNameCache){
1993
            return this.nameCache;
1994
        }
1995
        // is title dirty, i.e. equal NULL?
1996
        if (nameCache == null){
1997
            this.nameCache = generateNameCache();
1998
        }
1999
        return nameCache;
2000
    }
2001

    
2002
    /**
2003
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
2004
     * Sets the protectedNameCache flag to <code>true</code>.
2005
     *
2006
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
2007
     * @see    #getNameCache()
2008
     */
2009
    @Override
2010
    public void setNameCache(String nameCache){
2011
        setNameCache(nameCache, true);
2012
    }
2013

    
2014
    /**
2015
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
2016
     * Sets the protectedNameCache flag to <code>true</code>.
2017
     *
2018
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
2019
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
2020
     * <code>false</code>
2021
     * @see    #getNameCache()
2022
     */
2023
    @Override
2024
    public void setNameCache(String nameCache, boolean protectedNameCache){
2025
        this.nameCache = nameCache;
2026
        this.setProtectedNameCache(protectedNameCache);
2027
    }
2028

    
2029

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

    
2051
    /**
2052
     * Indicates <i>this</i> taxon name is a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
2053
     * of any other taxon name. Returns "true", if a replaced
2054
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
2055
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
2056
     * homotypical group).
2057
     */
2058
    @Override
2059
    @Transient
2060
    public boolean isReplacedSynonym(){
2061
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
2062
        for (NameRelationship relation : relationsFromThisName) {
2063
            if (relation.getType().isReplacedSynonymRelation()) {
2064
                return true;
2065
            }
2066
        }
2067
        return false;
2068
    }
2069

    
2070
    /**
2071
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2072
     * The basionym of a taxon name is its epithet-bringing synonym.
2073
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2074
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2075
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2076
     *
2077
     * If more than one basionym exists one is choosen at radom.
2078
     *
2079
     * If no basionym exists null is returned.
2080
     */
2081
    @Override
2082
    @Transient
2083
    public TaxonName getBasionym(){
2084
        Set<TaxonName> basionyms = getBasionyms();
2085
        if (basionyms.size() == 0){
2086
            return null;
2087
        }else{
2088
            return basionyms.iterator().next();
2089
        }
2090
    }
2091

    
2092
    /**
2093
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2094
     * The basionym of a taxon name is its epithet-bringing synonym.
2095
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2096
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2097
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2098
     */
2099
    @Override
2100
    @Transient
2101
    public Set<TaxonName> getBasionyms(){
2102

    
2103
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
2104
    }
2105

    
2106
    /**
2107
     *
2108
     * @param direction
2109
     * @param type
2110
     * @return
2111
     */
2112
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
2113

    
2114
        return getRelatedNames(relationsWithThisName(direction), type);
2115
    }
2116

    
2117
    /**
2118
     * @param rels
2119
     * @param type
2120
     * @return
2121
     */
2122
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2123
        Set<TaxonName> result = new HashSet<>();
2124
        for (NameRelationship rel : rels){
2125
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2126
                TaxonName basionym = rel.getFromName();
2127
                result.add(basionym);
2128
            }
2129
        }
2130
        return result;
2131
    }
2132

    
2133
    /**
2134
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2135
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2136
     * and to the basionym. The basionym cannot have itself as a basionym.
2137
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2138
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2139
     *
2140
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2141
     * @see  				#getBasionym()
2142
     * @see  				#addBasionym(TaxonName, String)
2143
     */
2144
    @Override
2145
    public void addBasionym(TaxonName basionym){
2146
        addBasionym(basionym, null, null, null);
2147
    }
2148
    /**
2149
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2150
     * and keeps the nomenclatural rule considered for it. The basionym
2151
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2152
     * The basionym cannot have itself as a basionym.
2153
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2154
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2155
     *
2156
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2157
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2158
     * @return
2159
     * @see  					#getBasionym()
2160
     * @see  					#addBasionym(TaxonName)
2161
     */
2162
    @Override
2163
    public NameRelationship addBasionym(TaxonName basionym, Reference citation, String microcitation, String ruleConsidered){
2164
        if (basionym != null){
2165
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
2166
        }else{
2167
            return null;
2168
        }
2169
    }
2170

    
2171
    /**
2172
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2173
     *
2174
     */
2175
    @Override
2176
    @Transient
2177
    public Set<TaxonName> getReplacedSynonyms(){
2178

    
2179
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2180
    }
2181

    
2182
    /**
2183
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2184
     * and keeps the nomenclatural rule considered for it. The replaced synonym
2185
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2186
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2187
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2188
     *
2189
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2190
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2191
     * @see  					#getBasionym()
2192
     * @see  					#addBasionym(TaxonName)
2193
     */
2194
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2195
    @Override
2196
    public void addReplacedSynonym(TaxonName replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
2197
        if (replacedSynonym != null){
2198
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
2199
        }
2200
    }
2201

    
2202
    /**
2203
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2204
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2205
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2206
     * previously used as basionym.
2207
     *
2208
     * @see   #getBasionym()
2209
     * @see   #addBasionym(TaxonName)
2210
     */
2211
    @Override
2212
    public void removeBasionyms(){
2213
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2214
    }
2215

    
2216

    
2217
    /**
2218
     * Removes all {@link NameRelationship relationships} of the given <code>type</code> from the set of
2219
     * relations in the specified <code>direction</code> direction wich are related from or to this
2220
     * <i>this</i> taxon name. The same relationship will be removed from the set of
2221
     * reverse relations of the other taxon name.
2222
     *
2223
     * @param direction
2224
     * @param type
2225
     */
2226
    public void removeNameRelations(Direction direction, NameRelationshipType type) {
2227
        Set<NameRelationship> relationsWithThisName = relationsWithThisName(direction);
2228
        Set<NameRelationship> removeRelations = new HashSet<>();
2229
        for (NameRelationship nameRelation : relationsWithThisName){
2230
            if (nameRelation.getType().isRelationshipType(type)){
2231
                removeRelations.add(nameRelation);
2232
            }
2233
        }
2234
        // Removing relations from a set through which we are iterating causes a
2235
        // ConcurrentModificationException. Therefore, we delete the targeted
2236
        // relations in a second step.
2237
        for (NameRelationship relation : removeRelations){
2238
            this.removeNameRelationship(relation);
2239
        }
2240
    }
2241

    
2242

    
2243
    /**
2244
     * @param direction
2245
     * @return
2246
     */
2247
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2248

    
2249
        switch(direction) {
2250
            case relatedTo:
2251
                return this.getRelationsToThisName();
2252
            case relatedFrom:
2253
                return this.getRelationsFromThisName();
2254
            default: throw new RuntimeException("invalid Direction:" + direction);
2255
        }
2256
    }
2257

    
2258
    /**
2259
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2260
     *
2261
     * @see 	Rank
2262
     */
2263
    @Override
2264
    public Rank getRank(){
2265
        return this.rank;
2266
    }
2267

    
2268
    /**
2269
     * @see  #getRank()
2270
     */
2271
    @Override
2272
    public void setRank(Rank rank){
2273
        this.rank = rank;
2274
    }
2275

    
2276

    
2277
    @Override
2278
    public Reference getNomenclaturalReference(){
2279
        //#6581
2280
        return this.nomenclaturalReference;
2281
//        if (this.nomenclaturalSource == null){
2282
//            return null;
2283
//        }
2284
//        return this.nomenclaturalSource.getCitation();
2285
    }
2286

    
2287
    @Override
2288
    public DescriptionElementSource getNomenclaturalSource(){
2289
        return this.nomenclaturalSource;
2290
    }
2291

    
2292
    protected DescriptionElementSource getNomenclaturalSource(boolean createIfNotExist){
2293
        if (this.nomenclaturalSource == null){
2294
            if (!createIfNotExist){
2295
                return null;
2296
            }
2297
            this.nomenclaturalSource = DescriptionElementSource.NewInstance(OriginalSourceType.NomenclaturalReference);
2298
        }
2299
        return this.nomenclaturalSource;
2300
    }
2301

    
2302
    /**
2303
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2304
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2305
     * as it is obviously used for nomenclatural purposes.
2306
     *
2307
     * Shortcut to set the nomenclatural reference.
2308
     *
2309
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2310
     * @see  #getNomenclaturalReference()
2311
     */
2312

    
2313
    @Override
2314
    public void setNomenclaturalReference(Reference nomenclaturalReference){
2315
        //#6581
2316
        this.nomenclaturalReference = nomenclaturalReference;
2317
//        getNomenclaturalSource(true).setCitation(nomenclaturalReference);
2318
//        checkNullSource();
2319
    }
2320
    @Override
2321
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2322
        setNomenclaturalReference(CdmBase.deproxy(nomenclaturalReference, Reference.class));
2323
    }
2324

    
2325
    //#6581
2326
    private void checkNullSource() {
2327
        if (this.nomenclaturalSource == null){
2328
            return;
2329
        }else if (this.nomenclaturalSource.getCitation() != null
2330
           || this.nomenclaturalSource.getCitationMicroReference() != null
2331
           || this.nomenclaturalSource.getNameUsedInSource() != null
2332
           || isBlank(this.nomenclaturalSource.getOriginalNameString())){
2333
            //TODO what about supplemental data?
2334
                return;
2335
        }else{
2336
            this.nomenclaturalSource = null;
2337
        }
2338
    }
2339

    
2340

    
2341
    @Override
2342
    public void setNomenclaturalSource(DescriptionElementSource nomenclaturalSource) throws IllegalArgumentException {
2343
        if (!OriginalSourceType.NomenclaturalReference.equals(nomenclaturalSource.getType()) ){
2344
            throw new IllegalArgumentException("Nomenclatural source must be of type " + OriginalSourceType.NomenclaturalReference.getMessage());
2345
        }
2346
        this.nomenclaturalSource = nomenclaturalSource;
2347
    }
2348

    
2349
    /**
2350
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2351
     * The appended phrase is a non-atomised addition to a name. It is
2352
     * not ruled by a nomenclatural code.
2353
     */
2354
    @Override
2355
    public String getAppendedPhrase(){
2356
        return this.appendedPhrase;
2357
    }
2358

    
2359
    /**
2360
     * @see  #getAppendedPhrase()
2361
     */
2362
    @Override
2363
    public void setAppendedPhrase(String appendedPhrase){
2364
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2365
    }
2366

    
2367
    /**
2368
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2369
     * to <i>this</i> taxon name. The details describe the exact localisation within
2370
     * the publication used as nomenclature reference. These are mostly
2371
     * (implicitly) pages but can also be figures or tables or any other
2372
     * element of a publication. A nomenclatural micro reference (details)
2373
     * requires the existence of a nomenclatural reference.
2374
     */
2375
    //Details of the nomenclatural reference (protologue).
2376
    @Override
2377
    public String getNomenclaturalMicroReference(){
2378
        //#6581
2379
        return this.nomenclaturalMicroReference;
2380
//        if (this.nomenclaturalSource == null){
2381
//            return null;
2382
//        }
2383
//        return this.nomenclaturalSource.getCitationMicroReference();
2384
    }
2385
    /**
2386
     * @see  #getNomenclaturalMicroReference()
2387
     */
2388
    @Override
2389
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2390
        //#6581
2391
        this.nomenclaturalMicroReference = nomenclaturalMicroReference;
2392
//        this.getNomenclaturalSource(true).setCitationMicroReference(StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference);
2393
//        checkNullSource();
2394
    }
2395

    
2396
    @Override
2397
    public int getParsingProblem(){
2398
        return this.parsingProblem;
2399
    }
2400

    
2401
    @Override
2402
    public void setParsingProblem(int parsingProblem){
2403
        this.parsingProblem = parsingProblem;
2404
    }
2405

    
2406
    @Override
2407
    public void addParsingProblem(ParserProblem problem){
2408
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2409
    }
2410

    
2411
    @Override
2412
    public void removeParsingProblem(ParserProblem problem) {
2413
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2414
    }
2415

    
2416
    /**
2417
     * @param warnings
2418
     */
2419
    @Override
2420
    public void addParsingProblems(int problems){
2421
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2422
    }
2423

    
2424
    @Override
2425
    public boolean hasProblem(){
2426
        return parsingProblem != 0;
2427
    }
2428

    
2429
    @Override
2430
    public boolean hasProblem(ParserProblem problem) {
2431
        return getParsingProblems().contains(problem);
2432
    }
2433

    
2434
    @Override
2435
    public int getProblemStarts(){
2436
        return this.problemStarts;
2437
    }
2438

    
2439
    @Override
2440
    public void setProblemStarts(int start) {
2441
        this.problemStarts = start;
2442
    }
2443

    
2444
    @Override
2445
    public int getProblemEnds(){
2446
        return this.problemEnds;
2447
    }
2448

    
2449
    @Override
2450
    public void setProblemEnds(int end) {
2451
        this.problemEnds = end;
2452
    }
2453

    
2454
//*********************** TYPE DESIGNATION *********************************************//
2455

    
2456
    /**
2457
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2458
     * to <i>this</i> taxon name.
2459
     * @see     NameTypeDesignation
2460
     * @see     SpecimenTypeDesignation
2461
     */
2462
    @Override
2463
    public Set<TypeDesignationBase> getTypeDesignations() {
2464
        if(typeDesignations == null) {
2465
            this.typeDesignations = new HashSet<TypeDesignationBase>();
2466
        }
2467
        return typeDesignations;
2468
    }
2469

    
2470
    /**
2471
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2472
     * <i>this</i> taxon name. The type designation itself will be nullified.
2473
     *
2474
     * @param  typeDesignation  the type designation which should be deleted
2475
     */
2476
    @Override
2477
    @SuppressWarnings("deprecation")
2478
    public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2479
        this.typeDesignations.remove(typeDesignation);
2480
        typeDesignation.removeTypifiedName(this);
2481
    }
2482

    
2483
    /**
2484
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2485
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2486
     * "species" or below. The specimen type designations include all the
2487
     * specimens on which the typification of this name is based (which are
2488
     * exclusively used to typify taxon names belonging to the same
2489
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2490
     * belongs) and eventually the status of these designations.
2491
     *
2492
     * @see     SpecimenTypeDesignation
2493
     * @see     NameTypeDesignation
2494
     * @see     HomotypicalGroup
2495
     */
2496
    @Override
2497
    @Transient
2498
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2499
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2500
    }
2501

    
2502
//*********************** NAME TYPE DESIGNATION *********************************************//
2503

    
2504
    /**
2505
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2506
     * to <i>this</i> taxon name the rank of which must be above "species".
2507
     * The name type designations include all the taxon names used to typify
2508
     * <i>this</i> taxon name and eventually the rejected or conserved status
2509
     * of these designations.
2510
     *
2511
     * @see     NameTypeDesignation
2512
     * @see     SpecimenTypeDesignation
2513
     */
2514
    @Override
2515
    @Transient
2516
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2517
        Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2518
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2519
            if (typeDesignation instanceof NameTypeDesignation){
2520
                result.add((NameTypeDesignation)typeDesignation);
2521
            }
2522
        }
2523
        return result;
2524
    }
2525

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

    
2562
    /**
2563
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2564
     * to <i>this</i> taxon name's set of type designations.
2565
     *
2566
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2567
     * @param  citation					the reference for this new designation
2568
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2569
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2570
     * @param  status                   the name type designation status
2571
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2572
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2573
     * @return
2574
     * @see 			  				#getNameTypeDesignations()
2575
     * @see 			  				NameTypeDesignation
2576
     * @see 			  				TypeDesignationBase#isNotDesignated()
2577
     */
2578
    @Override
2579
    public NameTypeDesignation addNameTypeDesignation(TaxonName typeSpecies,
2580
                Reference citation,
2581
                String citationMicroReference,
2582
                String originalNameString,
2583
                NameTypeDesignationStatus status,
2584
                boolean addToAllHomotypicNames) {
2585
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2586
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2587
        return nameTypeDesignation;
2588
    }
2589

    
2590
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2591

    
2592
    /**
2593
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2594
     * that typify <i>this</i> taxon name.
2595
     */
2596
    @Override
2597
    @Transient
2598
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2599
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2600
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2601
            if (typeDesignation instanceof SpecimenTypeDesignation){
2602
                result.add((SpecimenTypeDesignation)typeDesignation);
2603
            }
2604
        }
2605
        return result;
2606
    }
2607

    
2608

    
2609
    /**
2610
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2611
     * to <i>this</i> taxon name's set of type designations.
2612
     *
2613
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2614
     * @param  status					the specimen type designation status
2615
     * @param  citation					the reference for this new specimen type designation
2616
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2617
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2618
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2619
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2620
     * 									added to all taxon names of the homotypical group the typified
2621
     * 									taxon name belongs to
2622
     * @return
2623
     * @see 			  				#getSpecimenTypeDesignations()
2624
     * @see 			  				SpecimenTypeDesignationStatus
2625
     * @see 			  				SpecimenTypeDesignation
2626
     * @see 			  				TypeDesignationBase#isNotDesignated()
2627
     */
2628
    @Override
2629
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2630
                SpecimenTypeDesignationStatus status,
2631
                Reference citation,
2632
                String citationMicroReference,
2633
                String originalNameString,
2634
                boolean isNotDesignated,
2635
                boolean addToAllHomotypicNames) {
2636
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2637
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2638
        return specimenTypeDesignation;
2639
    }
2640

    
2641
    //used by merge strategy
2642
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2643
        return addTypeDesignation(typeDesignation, true);
2644
    }
2645

    
2646
    /**
2647
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2648
     *
2649
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2650
     * @param addToAllNames				the boolean indicating whether the type designation should be
2651
     * 									added to all taxon names of the homotypical group the typified
2652
     * 									taxon name belongs to
2653
     * @return							true if the operation was successful
2654
     *
2655
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2656
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2657
     *
2658
     */
2659
    @Override
2660
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2661
        //currently typeDesignations are not persisted with the homotypical group
2662
        //so explicit adding to the homotypical group is not necessary.
2663
        if (typeDesignation != null){
2664
            checkHomotypicalGroup(typeDesignation);
2665
            this.typeDesignations.add(typeDesignation);
2666
            typeDesignation.addTypifiedName(this);
2667

    
2668
            if (addToAllNames){
2669
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2670
                    if (taxonName != this){
2671
                        taxonName.addTypeDesignation(typeDesignation, false);
2672
                    }
2673
                }
2674
            }
2675
        }
2676
        return true;
2677
    }
2678

    
2679
    /**
2680
     * Throws an Exception this type designation already has typified names from another homotypical group.
2681
     * @param typeDesignation
2682
     */
2683
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2684
        if(typeDesignation.getTypifiedNames().size() > 0){
2685
            Set<HomotypicalGroup> groups = new HashSet<>();
2686
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2687
            for (TaxonName taxonName: names){
2688
                groups.add(taxonName.getHomotypicalGroup());
2689
            }
2690
            if (groups.size() > 1){
2691
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2692
            }
2693
        }
2694
    }
2695

    
2696

    
2697

    
2698
//*********************** HOMOTYPICAL GROUP *********************************************//
2699

    
2700

    
2701
    /**
2702
     * Returns the {@link HomotypicalGroup homotypical group} to which
2703
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2704
     * that share the same types.
2705
     *
2706
     * @see 	HomotypicalGroup
2707
     */
2708

    
2709
    @Override
2710
    public HomotypicalGroup getHomotypicalGroup() {
2711
        if (homotypicalGroup == null){
2712
            homotypicalGroup = new HomotypicalGroup();
2713
            homotypicalGroup.typifiedNames.add(this);
2714
        }
2715
    	return homotypicalGroup;
2716
    }
2717

    
2718
    /**
2719
     * @see #getHomotypicalGroup()
2720
     */
2721
    @Override
2722
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2723
        if (homotypicalGroup == null){
2724
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2725
        }
2726
        /*if (this.homotypicalGroup != null){
2727
        	this.homotypicalGroup.removeTypifiedName(this, false);
2728
        }*/
2729
        this.homotypicalGroup = homotypicalGroup;
2730
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2731
        	 this.homotypicalGroup.addTypifiedName(this);
2732
        }
2733
    }
2734

    
2735

    
2736

    
2737
// *************************************************************************//
2738

    
2739
    /**
2740
     * Returns the complete string containing the
2741
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2742
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2743
     *
2744
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2745
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2746
     * @see		#getNomenclaturalReference()
2747
     * @see		#getNomenclaturalMicroReference()
2748
     */
2749
    @Override
2750
    @Transient
2751
    public String getCitationString(){
2752
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2753
    }
2754

    
2755
    /**
2756
     * Returns the parsing problems
2757
     * @return
2758
     */
2759
    @Override
2760
    public List<ParserProblem> getParsingProblems(){
2761
        return ParserProblem.warningList(this.parsingProblem);
2762
    }
2763

    
2764
    /**
2765
     * Returns the string containing the publication date (generally only year)
2766
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2767
     * no nomenclatural reference.
2768
     *
2769
     * @return  the string containing the publication date of <i>this</i> taxon name
2770
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2771
     */
2772
    @Override
2773
    @Transient
2774
    @ValidTaxonomicYear(groups=Level3.class)
2775
    public String getReferenceYear(){
2776
        if (this.getNomenclaturalReference() != null ){
2777
            return this.getNomenclaturalReference().getYear();
2778
        }else{
2779
            return null;
2780
        }
2781
    }
2782

    
2783
    /**
2784
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2785
     * In this context a taxon base means the use of a taxon name by a reference
2786
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2787
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2788
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2789
     * within a taxonomic treatment (identified by one reference).
2790
     *
2791
     * @see	#getTaxa()
2792
     * @see	#getSynonyms()
2793
     */
2794
    @Override
2795
    public Set<TaxonBase> getTaxonBases() {
2796
        if(taxonBases == null) {
2797
            this.taxonBases = new HashSet<TaxonBase>();
2798
        }
2799
        return this.taxonBases;
2800
    }
2801

    
2802
    /**
2803
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2804
     * to the set of taxon bases using <i>this</i> taxon name.
2805
     *
2806
     * @param  taxonBase  the taxon base to be added
2807
     * @see 			  #getTaxonBases()
2808
     * @see 			  #removeTaxonBase(TaxonBase)
2809
     */
2810
    //TODO protected
2811
    @Override
2812
    public void addTaxonBase(TaxonBase taxonBase){
2813
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2814
        ReflectionUtils.makeAccessible(method);
2815
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2816
        taxonBases.add(taxonBase);
2817
    }
2818
    /**
2819
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2820
     *
2821
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2822
     * @see    				#getTaxonBases()
2823
     * @see    				#addTaxonBase(TaxonBase)
2824
     */
2825
    @Override
2826
    public void removeTaxonBase(TaxonBase taxonBase){
2827
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2828
        ReflectionUtils.makeAccessible(method);
2829
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2830

    
2831

    
2832
    }
2833

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

    
2855
    /**
2856
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2857
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2858
     * the set returned by getTaxonBases().
2859
     *
2860
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2861
     * @see	#getTaxonBases()
2862
     * @see	#getTaxa()
2863
     */
2864
    @Override
2865
    @Transient
2866
    public Set<Synonym> getSynonyms() {
2867
        Set<Synonym> result = new HashSet<>();
2868
        for (TaxonBase taxonBase : this.taxonBases){
2869
            if (taxonBase instanceof Synonym){
2870
                result.add((Synonym)taxonBase);
2871
            }
2872
        }
2873
        return result;
2874
    }
2875

    
2876
    //******* REGISTRATION *****************/
2877

    
2878
    @Override
2879
    public Set<Registration> getRegistrations() {
2880
        return this.registrations;
2881
    }
2882

    
2883

    
2884
// ************* RELATIONSHIPS *****************************/
2885

    
2886

    
2887
    /**
2888
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2889
     * by title cache of the related names.
2890
     * @see #getHybridParentRelations()
2891
     */
2892
    @Override
2893
    @Transient
2894
    public List<HybridRelationship> getOrderedChildRelationships(){
2895
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2896
        result.addAll(this.hybridChildRelations);
2897
        Collections.sort(result);
2898
        Collections.reverse(result);
2899
        return result;
2900

    
2901
    }
2902

    
2903

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

    
2926
    /**
2927
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2928
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2929
     * "is first/second parent" or "is male/female parent". By invoking this
2930
     * method <i>this</i> botanical name becomes a parent of the hybrid child
2931
     * botanical name.
2932
     *
2933
     * @param childName       the botanical name of the child for this new hybrid name relationship
2934
     * @param type            the type of this new name relationship
2935
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2936
     * @return
2937
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
2938
     * @see                   #getRelationsToThisName()
2939
     * @see                   #getNameRelations()
2940
     * @see                   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
2941
     * @see                   #addNameRelationship(NameRelationship)
2942
     */
2943
    @Override
2944
    public HybridRelationship addHybridChild(INonViralName childName, HybridRelationshipType type, String ruleConsidered){
2945
        return new HybridRelationship(childName, this, type, ruleConsidered);
2946
    }
2947

    
2948
    @Override
2949
    public void removeHybridChild(INonViralName child) {
2950
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2951
        hybridRelationships.addAll(this.getHybridChildRelations());
2952
        hybridRelationships.addAll(this.getHybridParentRelations());
2953
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2954
            // remove name relationship from this side
2955
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2956
                this.removeHybridRelationship(hybridRelationship);
2957
            }
2958
        }
2959
    }
2960

    
2961
    @Override
2962
    public void removeHybridParent(INonViralName parent) {
2963
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2964
        hybridRelationships.addAll(this.getHybridChildRelations());
2965
        hybridRelationships.addAll(this.getHybridParentRelations());
2966
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2967
            // remove name relationship from this side
2968
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2969
                this.removeHybridRelationship(hybridRelationship);
2970
            }
2971
        }
2972
    }
2973

    
2974

    
2975

    
2976
// *********** DESCRIPTIONS *************************************
2977

    
2978
    /**
2979
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2980
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2981
     * concerning the taxon name like for instance the content of its first
2982
     * publication (protolog) or a picture of this publication.
2983
     *
2984
     * @see	#addDescription(TaxonNameDescription)
2985
     * @see	#removeDescription(TaxonNameDescription)
2986
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2987
     */
2988
    @Override
2989
    public Set<TaxonNameDescription> getDescriptions() {
2990
        return descriptions;
2991
    }
2992

    
2993
    /**
2994
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2995
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2996
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2997
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2998
     *
2999
     * @param  description  the taxon name description to be added
3000
     * @see					#getDescriptions()
3001
     * @see 			  	#removeDescription(TaxonNameDescription)
3002
     */
3003
    @Override
3004
    public void addDescription(TaxonNameDescription description) {
3005
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
3006
        ReflectionUtils.makeAccessible(field);
3007
        ReflectionUtils.setField(field, description, this);
3008
        descriptions.add(description);
3009
    }
3010
    /**
3011
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
3012
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
3013
     * of the description itself will be set to "null".
3014
     *
3015
     * @param  description  the taxon name description which should be removed
3016
     * @see     		  	#getDescriptions()
3017
     * @see     		  	#addDescription(TaxonNameDescription)
3018
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
3019
     */
3020
    @Override
3021
    public void removeDescription(TaxonNameDescription description) {
3022
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonName.class);
3023
        ReflectionUtils.makeAccessible(field);
3024
        ReflectionUtils.setField(field, description, null);
3025
        descriptions.remove(description);
3026
    }
3027

    
3028
// *********** HOMOTYPIC GROUP METHODS **************************************************
3029

    
3030
    @Override
3031
    @Transient
3032
    public void mergeHomotypicGroups(TaxonName name){
3033
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
3034
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
3035
        name.setHomotypicalGroup(this.homotypicalGroup);
3036
    }
3037

    
3038
    /**
3039
     * Returns the boolean value indicating whether a given taxon name belongs
3040
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
3041
     * or not (false). Returns "true" only if the homotypical groups of both
3042
     * taxon names exist and if they are identical.
3043
     *
3044
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
3045
     * @return  			   the boolean value of the check
3046
     * @see     			   HomotypicalGroup
3047
     */
3048
    @Override
3049
    @Transient
3050
    public boolean isHomotypic(TaxonName homoTypicName) {
3051
        if (homoTypicName == null) {
3052
            return false;
3053
        }
3054
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
3055
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
3056
            return false;
3057
        }
3058
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
3059
            return true;
3060
        }
3061
        return false;
3062
    }
3063

    
3064

    
3065
    /**
3066
     * Checks whether name is a basionym for ALL names
3067
     * in its homotypical group.
3068
     * Returns <code>false</code> if there are no other names in the group
3069
     * @param name
3070
     * @return
3071
     */
3072
    @Override
3073
    @Transient
3074
    public boolean isGroupsBasionym() {
3075
    	if (homotypicalGroup == null){
3076
    		homotypicalGroup = HomotypicalGroup.NewInstance();
3077
    		homotypicalGroup.addTypifiedName(this);
3078
    	}
3079
        Set<TaxonName> typifiedNames = homotypicalGroup.getTypifiedNames();
3080

    
3081
        // Check whether there are any other names in the group
3082
        if (typifiedNames.size() == 1) {
3083
                return false;
3084
        }
3085

    
3086
        for (TaxonName taxonName : typifiedNames) {
3087
                if (!taxonName.equals(this)) {
3088
                        if (! isBasionymFor(taxonName)) {
3089
                                return false;
3090
                        }
3091
                }
3092
        }
3093
        return true;
3094
    }
3095

    
3096
    /**
3097
     * Checks whether a basionym relationship exists between fromName and toName.
3098
     *
3099
     * @param fromName
3100
     * @param toName
3101
     * @return
3102
     */
3103
    @Override
3104
    @Transient
3105
    public boolean isBasionymFor(TaxonName newCombinationName) {
3106
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
3107
            for (NameRelationship relation : relations) {
3108
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3109
                                    relation.getFromName().equals(this)) {
3110
                            return true;
3111
                    }
3112
            }
3113
            return false;
3114
    }
3115

    
3116
    /**
3117
     * Creates a basionym relationship to all other names in this names homotypical
3118
     * group.
3119
     *
3120
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
3121
     */
3122
    @Override
3123
    @Transient
3124
    public void makeGroupsBasionym() {
3125
        this.homotypicalGroup.setGroupBasionym(this);
3126
    }
3127

    
3128

    
3129
//*********  Rank comparison shortcuts   ********************//
3130
    /**
3131
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3132
     * taxon name is higher than the genus rank (true) or not (false).
3133
     * Suprageneric non viral names are monomials.
3134
     * Returns false if rank is null.
3135
     *
3136
     * @see  #isGenus()
3137
     * @see  #isInfraGeneric()
3138
     * @see  #isSpecies()
3139
     * @see  #isInfraSpecific()
3140
     */
3141
    @Override
3142
    @Transient
3143
    public boolean isSupraGeneric() {
3144
        if (rank == null){
3145
            return false;
3146
        }
3147
        return getRank().isSupraGeneric();
3148
    }
3149
    /**
3150
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3151
     * taxon name is the genus rank (true) or not (false). Non viral names with
3152
     * genus rank are monomials. Returns false if rank is null.
3153
     *
3154
     * @see  #isSupraGeneric()
3155
     * @see  #isInfraGeneric()
3156
     * @see  #isSpecies()
3157
     * @see  #isInfraSpecific()
3158
     */
3159
    @Override
3160
    @Transient
3161
    public boolean isGenus() {
3162
        if (rank == null){
3163
            return false;
3164
        }
3165
        return getRank().isGenus();
3166
    }
3167

    
3168
    @Override
3169
    @Transient
3170
    public boolean isGenusOrSupraGeneric() {
3171
        return isGenus()|| isSupraGeneric();
3172
    }
3173
    /**
3174
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3175
     * taxon name is higher than the species rank and lower than the
3176
     * genus rank (true) or not (false). Infrageneric non viral names are
3177
     * binomials. Returns false if rank is null.
3178
     *
3179
     * @see  #isSupraGeneric()
3180
     * @see  #isGenus()
3181
     * @see  #isSpecies()
3182
     * @see  #isInfraSpecific()
3183
     */
3184
    @Override
3185
    @Transient
3186
    public boolean isInfraGeneric() {
3187
        if (rank == null){
3188
            return false;
3189
        }
3190
        return getRank().isInfraGeneric();
3191
    }
3192

    
3193
    /**
3194
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3195
     * taxon name is higher than the species rank (true) or not (false).
3196
     * Returns false if rank is null.
3197
     *
3198
     * @see  #isGenus()
3199
     * @see  #isInfraGeneric()
3200
     * @see  #isSpecies()
3201
     * @see  #isInfraSpecific()
3202
     */
3203
    @Override
3204
    @Transient
3205
    public boolean isSupraSpecific(){
3206
        if (rank == null) {
3207
            return false;
3208
        }
3209
        return getRank().isHigher(Rank.SPECIES());
3210
    }
3211

    
3212
    /**
3213
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3214
     * taxon name is the species rank (true) or not (false). Non viral names
3215
     * with species rank are binomials.
3216
     * Returns false if rank is null.
3217
     *
3218
     * @see  #isSupraGeneric()
3219
     * @see  #isGenus()
3220
     * @see  #isInfraGeneric()
3221
     * @see  #isInfraSpecific()
3222
     */
3223
    @Override
3224
    @Transient
3225
    public boolean isSpecies() {
3226
        if (rank == null){
3227
            return false;
3228
        }
3229
        return getRank().isSpecies();
3230
    }
3231
    /**
3232
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3233
     * taxon name is lower than the species rank (true) or not (false).
3234
     * Infraspecific non viral names are trinomials.
3235
     * Returns false if rank is null.
3236
     *
3237
     * @see  #isSupraGeneric()
3238
     * @see  #isGenus()
3239
     * @see  #isInfraGeneric()
3240
     * @see  #isSpecies()
3241
     */
3242
    @Override
3243
    @Transient
3244
    public boolean isInfraSpecific() {
3245
        if (rank == null){
3246
            return false;
3247
        }
3248
        return getRank().isInfraSpecific();
3249
    }
3250

    
3251
    /**
3252
     * Returns true if this name's rank indicates a rank that aggregates species like species
3253
     * aggregates or species groups, false otherwise. This methods currently returns false
3254
     * for all user defined ranks.
3255
     *
3256
     *@see Rank#isSpeciesAggregate()
3257
     *
3258
     * @return
3259
     */
3260
    @Override
3261
    @Transient
3262
    public boolean isSpeciesAggregate() {
3263
        if (rank == null){
3264
            return false;
3265
        }
3266
        return getRank().isSpeciesAggregate();
3267
    }
3268

    
3269

    
3270
    /**
3271
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3272
     * the construction of <i>this</i> taxon name since there is no specific
3273
     * nomenclatural code defined. The real implementention takes place in the
3274
     * subclasses {@link IBacterialName BacterialName},
3275
     * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3276
     * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3277
     * and only one nomenclatural code.
3278
     *
3279
     * @return  null
3280
     * @see  	#isCodeCompliant()
3281
     * @see  	#getHasProblem()
3282
     * @deprecated use {@link #getNameType()} instead
3283
     */
3284
    @Override
3285
    @Deprecated
3286
    @Transient
3287
    @java.beans.Transient
3288
    public NomenclaturalCode getNomenclaturalCode() {
3289
        return nameType;
3290
    }
3291

    
3292

    
3293
    /**
3294
     * Generates and returns the string with the scientific name of <i>this</i>
3295
     * taxon name (only non viral taxon names can be generated from their
3296
     * components). This string may be stored in the inherited
3297
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3298
     * This method overrides the generic and inherited
3299
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3300
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3301
     *
3302
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
3303
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3304
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3305
     */
3306
//	@Override
3307
//	public abstract String generateTitle();
3308

    
3309
    /**
3310
     * Creates a basionym relationship between this name and
3311
     * 	each name in its homotypic group.
3312
     *
3313
     * @param basionymName
3314
     */
3315
    @Override
3316
    @Transient
3317
    public void setAsGroupsBasionym() {
3318

    
3319
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3320
        if (homotypicalGroup == null) {
3321
            return;
3322
        }
3323

    
3324
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3325
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3326

    
3327
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3328

    
3329
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3330

    
3331
            for(NameRelationship nameRelation : nameRelations){
3332
                relations.add(nameRelation);
3333
            }
3334
        }
3335

    
3336
        for (NameRelationship relation : relations) {
3337

    
3338
            // If this is a basionym relation, and toName is in the homotypical group,
3339
            //	remove the relationship.
3340
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3341
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3342
                removeRelations.add(relation);
3343
            }
3344
        }
3345

    
3346
        // Removing relations from a set through which we are iterating causes a
3347
        //	ConcurrentModificationException. Therefore, we delete the targeted
3348
        //	relations in a second step.
3349
        for (NameRelationship relation : removeRelations) {
3350
            this.removeNameRelationship(relation);
3351
        }
3352

    
3353
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3354
            if (!name.equals(this)) {
3355

    
3356
                // First check whether the relationship already exists
3357
                if (!this.isBasionymFor(name)) {
3358

    
3359
                    // Then create it
3360
                    name.addRelationshipFromName(this,
3361
                            NameRelationshipType.BASIONYM(), null);
3362
                }
3363
            }
3364
        }
3365
    }
3366

    
3367
    /**
3368
     * Removes basionym relationship between this name and
3369
     * 	each name in its homotypic group.
3370
     *
3371
     * @param basionymName
3372
     */
3373
    @Override
3374
    @Transient
3375
    public void removeAsGroupsBasionym() {
3376

    
3377
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3378

    
3379
        if (homotypicalGroup == null) {
3380
            return;
3381
        }
3382

    
3383
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3384
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3385

    
3386
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3387

    
3388
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3389

    
3390
            for(NameRelationship nameRelation : nameRelations){
3391
                relations.add(nameRelation);
3392
            }
3393
        }
3394

    
3395
        for (NameRelationship relation : relations) {
3396

    
3397
            // If this is a basionym relation, and toName is in the homotypical group,
3398
            //	and fromName is basionymName, remove the relationship.
3399
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3400
                    relation.getFromName().equals(this) &&
3401
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3402
                removeRelations.add(relation);
3403
            }
3404
        }
3405

    
3406
        // Removing relations from a set through which we are iterating causes a
3407
        //	ConcurrentModificationException. Therefore, we delete the targeted
3408
        //	relations in a second step.
3409
        for (NameRelationship relation : removeRelations) {
3410
            this.removeNameRelationship(relation);
3411
        }
3412
    }
3413

    
3414

    
3415
    /**
3416
     * Defines the last part of the name.
3417
     * This is for infraspecific taxa, the infraspecific epithet,
3418
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
3419
     * else the genusOrUninomial.
3420
     * However, the result does not depend on the rank (which may be not correctly set
3421
     * in case of dirty data) but returns the first name part which is not blank
3422
     * considering the above order.
3423
     * @return the first not blank name part in reverse order
3424
     */
3425
    @Override
3426
    public String getFamilyNamePart() {
3427
        String result =
3428
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3429
                    this.getInfraSpecificEpithet() :
3430
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3431
                    this.getSpecificEpithet():
3432
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3433
                    this.getInfraGenericEpithet():
3434
                this.getGenusOrUninomial();
3435
        return result;
3436
    }
3437

    
3438
    /**
3439
     * {@inheritDoc}
3440
     */
3441
    @Override
3442
    public boolean isHybridName() {
3443
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3444
    }
3445

    
3446
    /**
3447
     * {@inheritDoc}
3448
     */
3449
    @Override
3450
    public boolean isHybrid() {
3451
        return this.isHybridName() || this.isHybridFormula();
3452
    }
3453

    
3454
// ***************** COMPARE ********************************/
3455

    
3456
    @Override
3457
    public int compareToName(TaxonName otherName){
3458

    
3459
        int result = 0;
3460

    
3461
        if (otherName == null) {
3462
            throw new NullPointerException("Cannot compare to null.");
3463
        }
3464

    
3465
        //other
3466
        otherName = deproxy(otherName);
3467
        String otherNameCache = otherName.getNameCache();
3468
        String otherTitleCache = otherName.getTitleCache();
3469
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3470
        if (otherName.isAutonym()){
3471
            boolean isProtected = otherName.isProtectedNameCache();
3472
            String oldNameCache = otherName.getNameCache();
3473
            otherName.setProtectedNameCache(false);
3474
            otherName.setNameCache(null, false);
3475
            otherNameCache = otherName.getNameCache();
3476
            otherName.setNameCache(oldNameCache, isProtected);
3477
        }
3478

    
3479
        //this
3480
        String thisNameCache = this.getNameCache();
3481
        String thisTitleCache = this.getTitleCache();
3482

    
3483
        if (this.isAutonym()){
3484
            boolean isProtected = this.isProtectedNameCache();
3485
            String oldNameCache = this.getNameCache();
3486
            this.setProtectedNameCache(false);
3487
            this.setNameCache(null, false);
3488
            thisNameCache = this.getNameCache();
3489
            this.setNameCache(oldNameCache, isProtected);
3490
        }
3491

    
3492

    
3493
        // Compare name cache of taxon names
3494
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3495
            thisNameCache = normalizeName(thisNameCache);
3496
            otherNameCache = normalizeName(otherNameCache);
3497
            result = thisNameCache.compareTo(otherNameCache);
3498
        }
3499

    
3500
        // Compare title cache of taxon names
3501
        if (result == 0){
3502
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3503
                thisTitleCache = normalizeName(thisTitleCache);
3504
                otherTitleCache = normalizeName(otherTitleCache);
3505
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3506
            }
3507
        }
3508

    
3509
        return result;
3510
    }
3511

    
3512
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3513
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3514

    
3515
    /**
3516
     * @param thisNameCache
3517
     * @param HYBRID_SIGN
3518
     * @param QUOT_SIGN
3519
     * @return
3520
     */
3521
    private String normalizeName(String thisNameCache) {
3522
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3523
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3524
        return thisNameCache;
3525
    }
3526

    
3527
// ********************** INTERFACES ********************************************/
3528

    
3529
    /**
3530
     * Method to cast a interfaced name to a concrete name.
3531
     * The method includes a deproxy to guarantee that no
3532
     * class cast exception is thrown.
3533
     *
3534
     * @see #castAndDeproxy(Set)
3535
     * @param interfacedName
3536
     * @return
3537
     */
3538
    public static TaxonName castAndDeproxy(ITaxonNameBase interfacedName){
3539
        return deproxy(interfacedName, TaxonName.class);
3540
    }
3541

    
3542
    /**
3543
     * Method to cast a set of interfaced names to concrete namex.
3544
     * The method includes a deproxy to guarantee that no
3545
     * class cast exception is thrown.
3546
     *
3547
     * @see #castAndDeproxy(ITaxonNameBase)
3548
     * @param naminterfacedNames
3549
     * @return
3550
     */
3551
    public static Set<TaxonName> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3552
        Set<TaxonName> result = new HashSet<>();
3553
        for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3554
            result.add(castAndDeproxy(naminterfacedName));
3555
        }
3556
        return result;
3557
    }
3558

    
3559
//************************ isType ***********************************************/
3560

    
3561
    /**
3562
     * @return
3563
     */
3564
    @Override
3565
    public boolean isNonViral() {
3566
        return nameType.isNonViral();
3567
    }
3568

    
3569
    @Override
3570
    public boolean isZoological(){
3571
        return nameType.isZoological();
3572
    }
3573
    @Override
3574
    public boolean isBotanical() {
3575
        return nameType.isBotanical();
3576
    }
3577
    @Override
3578
    public boolean isCultivar() {
3579
        return nameType.isCultivar();
3580
    }
3581
    @Override
3582
    public boolean isBacterial() {
3583
        return nameType.isBacterial();
3584
    }
3585
    @Override
3586
    public boolean isViral() {
3587
        return nameType.isViral();
3588
    }
3589

    
3590

    
3591
//*********************** CLONE ********************************************************/
3592

    
3593
    /**
3594
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3595
     * a new instance that differs only slightly from <i>this</i> taxon name by
3596
     * modifying only some of the attributes.<BR><BR>
3597
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3598
     * <b>The name gets a newly created homotypical group</b><BR>
3599
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3600
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3601
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3602
     *
3603
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3604
     * @see java.lang.Object#clone()
3605
     */
3606
    @Override
3607
    public Object clone() {
3608
        TaxonName result;
3609
        try {
3610
            result = (TaxonName)super.clone();
3611

    
3612
            //taxonBases -> empty
3613
            result.taxonBases = new HashSet<>();
3614

    
3615
            //empty caches
3616
            if (! protectedFullTitleCache){
3617
                result.fullTitleCache = null;
3618
            }
3619

    
3620
            //descriptions
3621
            result.descriptions = new HashSet<>();
3622
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3623
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3624
                result.descriptions.add(newDescription);
3625
            }
3626

    
3627
            //status
3628
            result.status = new HashSet<>();
3629
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3630
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3631
                result.status.add(newStatus);
3632
            }
3633

    
3634

    
3635
            //to relations
3636
            result.relationsToThisName = new HashSet<>();
3637
            for (NameRelationship toRelationship : getRelationsToThisName()){
3638
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3639
                newRelationship.setRelatedTo(result);
3640
                result.relationsToThisName.add(newRelationship);
3641
            }
3642

    
3643
            //from relations
3644
            result.relationsFromThisName = new HashSet<>();
3645
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3646
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3647
                newRelationship.setRelatedFrom(result);
3648
                result.relationsFromThisName.add(newRelationship);
3649
            }
3650

    
3651
            //type designations
3652
            result.typeDesignations = new HashSet<>();
3653
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3654
                TypeDesignationBase<?> newDesignation = (TypeDesignationBase<?>)typeDesignation.clone();
3655
                this.removeTypeDesignation(newDesignation);
3656
                result.addTypeDesignation(newDesignation, false);
3657
            }
3658

    
3659
            //homotypicalGroup
3660
            //TODO still needs to be discussed
3661
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3662
            result.homotypicalGroup.addTypifiedName(this);
3663

    
3664

    
3665
            //HybridChildRelations
3666
            result.hybridChildRelations = new HashSet<>();
3667
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3668
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3669
                newChildRelationship.setRelatedTo(result);
3670
                result.hybridChildRelations.add(newChildRelationship);
3671
            }
3672

    
3673
            //HybridParentRelations
3674
            result.hybridParentRelations = new HashSet<>();
3675
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3676
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3677
                newParentRelationship.setRelatedFrom(result);
3678
                result.hybridParentRelations.add(newParentRelationship);
3679
            }
3680

    
3681
            //empty caches
3682
            if (! protectedNameCache){
3683
                result.nameCache = null;
3684
            }
3685

    
3686
            //empty caches
3687
            if (! protectedAuthorshipCache){
3688
                result.authorshipCache = null;
3689
            }
3690

    
3691
            //no changes to: appendedPharse, nomenclaturalReference,
3692
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3693
            //protectedFullTitleCache, rank
3694
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3695
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3696
            //protectedAuthorshipCache, protectedNameCache,
3697
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3698
            //acronym
3699
            //subGenusAuthorship, nameApprobation
3700
            //anamorphic
3701
            //cultivarName
3702
            return result;
3703
        } catch (CloneNotSupportedException e) {
3704
            logger.warn("Object does not implement cloneable");
3705
            e.printStackTrace();
3706
            return null;
3707
        }
3708

    
3709
    }
3710

    
3711

    
3712
}
3713

    
(29-29/36)