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
package eu.etaxonomy.cdm.model.name;
10

    
11
import java.beans.PropertyChangeListener;
12
import java.lang.reflect.Method;
13
import java.net.URI;
14
import java.util.ArrayList;
15
import java.util.Collections;
16
import java.util.HashSet;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Objects;
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.OneToOne;
32
import javax.persistence.Table;
33
import javax.persistence.Transient;
34
import javax.validation.Valid;
35
import javax.validation.constraints.Min;
36
import javax.validation.constraints.NotNull;
37
import javax.validation.constraints.Pattern;
38
import javax.xml.bind.annotation.XmlAccessType;
39
import javax.xml.bind.annotation.XmlAccessorType;
40
import javax.xml.bind.annotation.XmlAttribute;
41
import javax.xml.bind.annotation.XmlElement;
42
import javax.xml.bind.annotation.XmlElementWrapper;
43
import javax.xml.bind.annotation.XmlIDREF;
44
import javax.xml.bind.annotation.XmlRootElement;
45
import javax.xml.bind.annotation.XmlSchemaType;
46
import javax.xml.bind.annotation.XmlType;
47

    
48
import org.apache.log4j.Logger;
49
import org.hibernate.annotations.Cascade;
50
import org.hibernate.annotations.CascadeType;
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.EntityCollectionSetterAdapter;
67
import eu.etaxonomy.cdm.model.EntityCollectionSetterAdapter.SetterAdapterException;
68
import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
69
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
70
import eu.etaxonomy.cdm.model.common.CdmBase;
71
import eu.etaxonomy.cdm.model.common.IIntextReferenceTarget;
72
import eu.etaxonomy.cdm.model.common.IParsable;
73
import eu.etaxonomy.cdm.model.common.IRelated;
74
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
75
import eu.etaxonomy.cdm.model.common.Language;
76
import eu.etaxonomy.cdm.model.common.RelationshipBase;
77
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
78
import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
79
import eu.etaxonomy.cdm.model.description.IDescribable;
80
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
81
import eu.etaxonomy.cdm.model.media.ExternalLink;
82
import eu.etaxonomy.cdm.model.media.ExternalLinkType;
83
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
84
import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
85
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
86
import eu.etaxonomy.cdm.model.reference.Reference;
87
import eu.etaxonomy.cdm.model.taxon.Synonym;
88
import eu.etaxonomy.cdm.model.taxon.Taxon;
89
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
90
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
91
import eu.etaxonomy.cdm.strategy.cache.name.CacheUpdate;
92
import eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy;
93
import eu.etaxonomy.cdm.strategy.cache.name.TaxonNameDefaultCacheStrategy;
94
import eu.etaxonomy.cdm.strategy.match.IMatchable;
95
import eu.etaxonomy.cdm.strategy.match.Match;
96
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
97
import eu.etaxonomy.cdm.strategy.match.MatchMode;
98
import eu.etaxonomy.cdm.strategy.merge.Merge;
99
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
100
import eu.etaxonomy.cdm.strategy.parser.ParserProblem;
101
import eu.etaxonomy.cdm.validation.Level2;
102
import eu.etaxonomy.cdm.validation.Level3;
103
import eu.etaxonomy.cdm.validation.annotation.CorrectEpithetsForRank;
104
import eu.etaxonomy.cdm.validation.annotation.NameMustFollowCode;
105
import eu.etaxonomy.cdm.validation.annotation.NameMustHaveAuthority;
106
import eu.etaxonomy.cdm.validation.annotation.NoDuplicateNames;
107
import eu.etaxonomy.cdm.validation.annotation.NullOrNotEmpty;
108
import eu.etaxonomy.cdm.validation.annotation.ValidTaxonomicYear;
109

    
110
/**
111
 * The upmost (abstract) class for scientific taxon names regardless of any
112
 * particular {@link NomenclaturalCode nomenclature code}. The scientific taxon name does not depend
113
 * on the use made of it in a publication or a treatment
114
 * ({@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon concept respectively potential taxon})
115
 * as an {@link eu.etaxonomy.cdm.model.taxon.Taxon "accepted" respectively "correct" (taxon) name}
116
 * or as a {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
117
 * <P>
118
 * This class corresponds partially to: <ul>
119
 * <li> TaxonName according to the TDWG ontology
120
 * <li> ScientificName and CanonicalName according to the TCS
121
 * <li> ScientificName according to the ABCD schema
122
 * </ul>
123
 *
124
 * @author m.doering
125
 * @since 08-Nov-2007 13:06:57
126
 */
127
@XmlAccessorType(XmlAccessType.FIELD)
128
@XmlType(name = "TaxonName", propOrder = {
129
    "nameType",
130
    "appendedPhrase",
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
    //non-viral names
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
    //viral name
163
    "acronym",
164
    //bacterial names
165
    "subGenusAuthorship",
166
    "nameApprobation",
167
    //zoological name
168
    "breed",
169
    "publicationYear",
170
    "originalPublicationYear",
171
    "inCombinationAuthorship",
172
    "inBasionymAuthorship",
173
    //fungi
174
    "anamorphic",
175
    //cultivar plant names
176
    "cultivarName"
177
})
178
@XmlRootElement(name = "TaxonName")
179
@Entity
180
@Audited
181
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
182
@Table(name="TaxonName", indexes = {
183
        @javax.persistence.Index(name = "taxonNameBaseTitleCacheIndex", columnList = "titleCache"),
184
        @javax.persistence.Index(name = "taxonNameBaseNameCacheIndex", columnList = "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,
195
                IDescribable<TaxonNameDescription>,
196
                INomenclaturalStanding {
197

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

    
201
//    @XmlTransient
202
//    @Transient
203
//    private PropertyChangeListener sourceListener;
204

    
205
    /**
206
     * The {@link NomenclaturalCode nomenclatural code} this taxon name is ruled by.
207
     */
208
    @XmlAttribute(name ="NameType")
209
    @Column(name="nameType", length=15)
210
    @NotNull
211
    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
212
        parameters = {@org.hibernate.annotations.Parameter(name = "enumClass", value = "eu.etaxonomy.cdm.model.name.NomenclaturalCode")}
213
    )
214
    @Audited //needed ?
215
    private NomenclaturalCode nameType;
216

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
328
    //#6581
329
    @XmlElement(name = "NomenclaturalSource")
330
    @XmlIDREF
331
    @XmlSchemaType(name = "IDREF")
332
    @OneToOne(fetch = FetchType.LAZY, orphanRemoval=true, mappedBy="sourcedName")
333
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE,CascadeType.DELETE})
334
    @CacheUpdate(noUpdate ="titleCache")
335
    @IndexedEmbedded
336
    private NomenclaturalSource nomenclaturalSource;
337

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

    
348
//****** Non-ViralName attributes ***************************************/
349

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

    
362
    @XmlElement(name = "ProtectedNameCache")
363
    @CacheUpdate(value="nameCache")
364
    protected boolean protectedNameCache;
365

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

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

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

    
394
    @XmlElement(name = "InfraSpecificEpithet")
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 infraSpecificEpithet;
402

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

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

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

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

    
440
    @XmlElement(name = "ExBasionymAuthorship")
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<?> exBasionymAuthorship;
448

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

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

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

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

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

    
492

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

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

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

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

    
512
// ViralName attributes ************************* /
513

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

    
521
// BacterialName attributes ***********************/
522

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

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

    
533
    //ZOOLOGICAL NAME
534

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

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

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

    
554
    //Cultivar attribute(s)
555

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

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

    
568
// *************** FACTORY METHODS ********************************/
569

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

    
583
    //TODO move to TaxonNameFactory
584
    public static TaxonName NewInstance(NomenclaturalCode code, Rank rank, String genusOrUninomial,
585
            String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet,
586
            TeamOrPersonBase combinationAuthorship, Reference nomenclaturalReference,
587
            String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
588
        TaxonName result = new TaxonName(code, rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, combinationAuthorship, nomenclaturalReference, nomenclMicroRef, homotypicalGroup);
589
        return result;
590
    }
591

    
592
// ************* CONSTRUCTORS *************/
593

    
594
    /**
595
     * Class constructor: creates a new empty taxon name.
596
     * @param code
597
     *
598
     * @see #TaxonName(Rank)
599
     * @see #TaxonName(HomotypicalGroup)
600
     * @see #TaxonName(Rank, HomotypicalGroup)
601
     */
602
    protected TaxonName() {
603
        this.cacheStrategy = TaxonNameDefaultCacheStrategy.NewInstance();
604
    }
605

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

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

    
676
    @Override
677
    public void initListener(){
678
        PropertyChangeListener nameListener = event-> {
679
            String propName = event.getPropertyName();
680
            if (Objects.equals(event.getOldValue(), event.getNewValue())){
681
                return;
682
            }
683

    
684
            boolean protectedByLowerCache = false;
685
            //authorship cache
686
            if (fieldHasCacheUpdateProperty(propName, "authorshipCache")){
687
                if (protectedAuthorshipCache){
688
                    protectedByLowerCache = true;
689
                }else{
690
                    authorshipCache = null;
691
                }
692
            }
693

    
694
            //nameCache
695
            if (fieldHasCacheUpdateProperty(propName, "nameCache")){
696
                if (protectedNameCache){
697
                    protectedByLowerCache = true;
698
                }else{
699
                    nameCache = null;
700
                }
701
            }
702
            //title cache
703
            if (! fieldHasNoUpdateProperty(propName, "titleCache")){
704
                if (isProtectedTitleCache()|| protectedByLowerCache == true ){
705
                    protectedByLowerCache = true;
706
                }else{
707
                    titleCache = null;
708
                }
709
            }
710
            //full title cache
711
            if (! fieldHasNoUpdateProperty(propName, "fullTitleCache")){
712
                if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
713
                    protectedByLowerCache = true;
714
                }else{
715
                    fullTitleCache = null;
716
                }
717
            }
718

    
719
        };
720
        addPropertyChangeListener(nameListener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
721
    }
722

    
723
    private static Map<String, java.lang.reflect.Field> allFields = null;
724
    protected Map<String, java.lang.reflect.Field> getAllFields(){
725
        if (allFields == null){
726
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
727
        }
728
        return allFields;
729
    }
730

    
731
    /**
732
     * @param propertyName
733
     * @param string
734
     * @return
735
     */
736
    private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
737
        try {
738
            java.lang.reflect.Field field = getAllFields().get(propertyName);
739
            if (field != null){
740
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
741
                if (updateAnnotation != null){
742
                    for (String value : updateAnnotation.value()){
743
                        if (cacheName.equals(value)){
744
                            return true;
745
                        }
746
                    }
747
                }
748
            }
749
            return false;
750
        } catch (SecurityException e1) {
751
            throw e1;
752
        }
753
    }
754

    
755
    private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
756
        java.lang.reflect.Field field;
757
        //do not update fields with the same name
758
        if (cacheName.equals(propertyName)){
759
            return true;
760
        }
761
        //evaluate annotation
762
        try {
763
            field = getAllFields().get(propertyName);
764
            if (field != null){
765
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
766
                if (updateAnnotation != null){
767
                    for (String value : updateAnnotation.noUpdate()){
768
                        if (cacheName.equals(value)){
769
                            return true;
770
                        }
771
                    }
772
                }
773
            }
774
            return false;
775
        } catch (SecurityException e1) {
776
            throw e1;
777
        }
778
    }
779

    
780
// ****************** GETTER / SETTER ****************************/
781

    
782
    @Override
783
    public NomenclaturalCode getNameType() {
784
        return nameType;
785
    }
786

    
787
    @Override
788
    public void setNameType(NomenclaturalCode nameType) {
789
        this.nameType = nameType;
790
    }
791

    
792
    /**
793
     * Returns the boolean value of the flag intended to protect (true)
794
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
795
     * string of <i>this</i> non viral taxon name.
796
     *
797
     * @return  the boolean value of the protectedNameCache flag
798
     * @see     #getNameCache()
799
     */
800
    @Override
801
    public boolean isProtectedNameCache() {
802
        return protectedNameCache;
803
    }
804

    
805
    /**
806
     * @see     #isProtectedNameCache()
807
     */
808
    @Override
809
    public void setProtectedNameCache(boolean protectedNameCache) {
810
        this.protectedNameCache = protectedNameCache;
811
    }
812

    
813
    /**
814
     * Returns either the scientific name string (without authorship) for <i>this</i>
815
     * non viral taxon name if its rank is genus or higher (monomial) or the string for
816
     * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
817
     * Genus or uninomial strings begin with an upper case letter.
818
     *
819
     * @return  the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
820
     * @see     #getNameCache()
821
     */
822
    @Override
823
    public String getGenusOrUninomial() {
824
        return genusOrUninomial;
825
    }
826

    
827
    /**
828
     * @see  #getGenusOrUninomial()
829
     */
830
    @Override
831
    public void setGenusOrUninomial(String genusOrUninomial) {
832
        this.genusOrUninomial = isBlank(genusOrUninomial) ? null : genusOrUninomial;
833
    }
834

    
835
    /**
836
     * Returns the genus subdivision epithet string (infrageneric part) for
837
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
838
     * higher than species aggregate: binomial). Genus subdivision epithet
839
     * strings begin with an upper case letter.
840
     *
841
     * @return  the string containing the infrageneric part of <i>this</i> non viral taxon name
842
     * @see     #getNameCache()
843
     */
844
    @Override
845
    public String getInfraGenericEpithet(){
846
        return this.infraGenericEpithet;
847
    }
848

    
849
    /**
850
     * @see  #getInfraGenericEpithet()
851
     */
852
    @Override
853
    public void setInfraGenericEpithet(String infraGenericEpithet){
854
        this.infraGenericEpithet = isBlank(infraGenericEpithet)? null : infraGenericEpithet;
855
    }
856

    
857
    /**
858
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
859
     * species aggregate or lower (bi- or trinomial). Species epithet strings
860
     * begin with a lower case letter.
861
     *
862
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
863
     * @see     #getNameCache()
864
     */
865
    @Override
866
    public String getSpecificEpithet(){
867
        return this.specificEpithet;
868
    }
869

    
870
    /**
871
     * @see  #getSpecificEpithet()
872
     */
873
    @Override
874
    public void setSpecificEpithet(String specificEpithet){
875
        this.specificEpithet = isBlank(specificEpithet) ? null : specificEpithet;
876
    }
877

    
878
    /**
879
     * Returns the species subdivision epithet string (infraspecific part) for
880
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
881
     * (lower than species: trinomial). Species subdivision epithet strings
882
     * begin with a lower case letter.
883
     *
884
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
885
     * @see     #getNameCache()
886
     */
887
    @Override
888
    public String getInfraSpecificEpithet(){
889
        return this.infraSpecificEpithet;
890
    }
891

    
892
    /**
893
     * @see  #getInfraSpecificEpithet()
894
     */
895
    @Override
896
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
897
        this.infraSpecificEpithet = isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
898
    }
899

    
900
    /**
901
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
902
     * taxon name.
903
     *
904
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
905
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
906
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
907
     */
908
    @Override
909
    public TeamOrPersonBase<?> getCombinationAuthorship(){
910
        return this.combinationAuthorship;
911
    }
912

    
913
    /**
914
     * @see  #getCombinationAuthorship()
915
     */
916
    @Override
917
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
918
        this.combinationAuthorship = combinationAuthorship;
919
    }
920

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

    
956
    @Override
957
    public TeamOrPersonBase<?> getInCombinationAuthorship(){
958
        return this.inCombinationAuthorship;
959
    }
960
    @Override
961
    public void setInCombinationAuthorship(TeamOrPersonBase<?> inCombinationAuthorship) {
962
        this.inCombinationAuthorship = inCombinationAuthorship;
963
    }
964

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

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

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

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

    
1018
    @Override
1019
    public TeamOrPersonBase<?> getInBasionymAuthorship(){
1020
        return this.inBasionymAuthorship;
1021
    }
1022
    @Override
1023
    public void setInBasionymAuthorship(TeamOrPersonBase<?> inBasionymAuthorship) {
1024
        this.inBasionymAuthorship = inBasionymAuthorship;
1025
    }
1026

    
1027
    /**
1028
     * Returns the boolean value of the flag intended to protect (true)
1029
     * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
1030
     * of <i>this</i> non viral taxon name.
1031
     *
1032
     * @return  the boolean value of the protectedAuthorshipCache flag
1033
     * @see     #getAuthorshipCache()
1034
     */
1035
    @Override
1036
    public boolean isProtectedAuthorshipCache() {
1037
        return protectedAuthorshipCache;
1038
    }
1039

    
1040
    /**
1041
     * @see     #isProtectedAuthorshipCache()
1042
     * @see     #getAuthorshipCache()
1043
     */
1044
    @Override
1045
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
1046
        this.protectedAuthorshipCache = protectedAuthorshipCache;
1047
    }
1048

    
1049
    /**
1050
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1051
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
1052
     *
1053
     * @see    #getHybridRelationships()
1054
     * @see    #getChildRelationships()
1055
     * @see    HybridRelationshipType
1056
     */
1057
    @Override
1058
    public Set<HybridRelationship> getHybridParentRelations() {
1059
        if(hybridParentRelations == null) {
1060
            this.hybridParentRelations = new HashSet<>();
1061
        }
1062
        return hybridParentRelations;
1063
    }
1064

    
1065
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1066
        this.hybridParentRelations = hybridParentRelations;
1067
    }
1068

    
1069

    
1070
    /**
1071
     * Returns the set of all {@link HybridRelationship hybrid relationships}
1072
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
1073
     *
1074
     * @see    #getHybridRelationships()
1075
     * @see    #getParentRelationships()
1076
     * @see    HybridRelationshipType
1077
     */
1078
    @Override
1079
    public Set<HybridRelationship> getHybridChildRelations() {
1080
        if(hybridChildRelations == null) {
1081
            this.hybridChildRelations = new HashSet<>();
1082
        }
1083
        return hybridChildRelations;
1084
    }
1085

    
1086
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1087
        this.hybridChildRelations = hybridChildRelations;
1088
    }
1089

    
1090
    @Override
1091
    public boolean isProtectedFullTitleCache() {
1092
        return protectedFullTitleCache;
1093
    }
1094

    
1095
    @Override
1096
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1097
        this.protectedFullTitleCache = protectedFullTitleCache;
1098
    }
1099

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

    
1121
    /**
1122
     * @see  #isHybridFormula()
1123
     */
1124
    @Override
1125
    public void setHybridFormula(boolean hybridFormula){
1126
        this.hybridFormula = hybridFormula;
1127
    }
1128

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

    
1145
    /**
1146
     * @see  #isMonomHybrid()
1147
     * @see  #isBinomHybrid()
1148
     * @see  #isTrinomHybrid()
1149
     */
1150
    @Override
1151
    public void setMonomHybrid(boolean monomHybrid){
1152
        this.monomHybrid = monomHybrid;
1153
    }
1154

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

    
1171
    /**
1172
     * @see  #isBinomHybrid()
1173
     * @see  #isMonomHybrid()
1174
     * @see  #isTrinomHybrid()
1175
     */
1176
    @Override
1177
    public void setBinomHybrid(boolean binomHybrid){
1178
        this.binomHybrid = binomHybrid;
1179
    }
1180

    
1181
    @Override
1182
    public boolean isTrinomHybrid(){
1183
        return this.trinomHybrid;
1184
    }
1185

    
1186
    /**
1187
     * @see  #isTrinomHybrid()
1188
     * @see  #isBinomHybrid()
1189
     * @see  #isMonomHybrid()
1190
     */
1191
    @Override
1192
    public void setTrinomHybrid(boolean trinomHybrid){
1193
        this.trinomHybrid = trinomHybrid;
1194
    }
1195

    
1196
    // ****************** VIRAL NAME ******************/
1197

    
1198
    @Override
1199
    public String getAcronym(){
1200
        return this.acronym;
1201
    }
1202

    
1203
    /**
1204
     * @see  #getAcronym()
1205
     */
1206
    @Override
1207
    public void setAcronym(String acronym){
1208
        this.acronym = isBlank(acronym)? null : acronym;
1209
    }
1210

    
1211
    // ****************** BACTERIAL NAME ******************/
1212

    
1213
    @Override
1214
    public String getSubGenusAuthorship(){
1215
        return this.subGenusAuthorship;
1216
    }
1217

    
1218
    @Override
1219
    public void setSubGenusAuthorship(String subGenusAuthorship){
1220
        this.subGenusAuthorship = subGenusAuthorship;
1221
    }
1222

    
1223

    
1224
    @Override
1225
    public String getNameApprobation(){
1226
        return this.nameApprobation;
1227
    }
1228

    
1229
    /**
1230
     * @see  #getNameApprobation()
1231
     */
1232
    @Override
1233
    public void setNameApprobation(String nameApprobation){
1234
        this.nameApprobation = nameApprobation;
1235
    }
1236

    
1237
    //************ Zoological Name
1238

    
1239

    
1240
    @Override
1241
    public String getBreed(){
1242
        return this.breed;
1243
    }
1244
    /**
1245
     * @see  #getBreed()
1246
     */
1247
    @Override
1248
    public void setBreed(String breed){
1249
        this.breed = isBlank(breed) ? null : breed;
1250
    }
1251

    
1252

    
1253
    @Override
1254
    public Integer getPublicationYear() {
1255
        return publicationYear;
1256
    }
1257
    /**
1258
     * @see  #getPublicationYear()
1259
     */
1260
    @Override
1261
    public void setPublicationYear(Integer publicationYear) {
1262
        this.publicationYear = publicationYear;
1263
    }
1264

    
1265

    
1266
    @Override
1267
    public Integer getOriginalPublicationYear() {
1268
        return originalPublicationYear;
1269
    }
1270
    /**
1271
     * @see  #getOriginalPublicationYear()
1272
     */
1273
    @Override
1274
    public void setOriginalPublicationYear(Integer originalPublicationYear) {
1275
        this.originalPublicationYear = originalPublicationYear;
1276
    }
1277

    
1278
    // **** Cultivar Name ************
1279

    
1280

    
1281
    @Override
1282
    public String getCultivarName(){
1283
        return this.cultivarName;
1284
    }
1285

    
1286
    /**
1287
     * @see  #getCultivarName()
1288
     */
1289
    @Override
1290
    public void setCultivarName(String cultivarName){
1291
        this.cultivarName = isBlank(cultivarName) ? null : cultivarName;
1292
    }
1293

    
1294
    // **************** Fungus Name
1295
    @Override
1296
    public boolean isAnamorphic(){
1297
        return this.anamorphic;
1298
    }
1299

    
1300
    /**
1301
     * @see  #isAnamorphic()
1302
     */
1303
    @Override
1304
    public void setAnamorphic(boolean anamorphic){
1305
        this.anamorphic = anamorphic;
1306
    }
1307

    
1308

    
1309
// **************** ADDER / REMOVE *************************/
1310

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

    
1340

    
1341
    /**
1342
     * Removes one {@link HybridRelationship hybrid relationship} from the set of
1343
     * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1344
     * is involved. The hybrid relationship will also be removed from the set
1345
     * belonging to the second botanical taxon name involved.
1346
     *
1347
     * @param  relationship  the hybrid relationship which should be deleted from the corresponding sets
1348
     * @see                  #getHybridRelationships()
1349
     */
1350
    @Override
1351
    public void removeHybridRelationship(HybridRelationship hybridRelation) {
1352
        if (hybridRelation == null) {
1353
            return;
1354
        }
1355

    
1356
        TaxonName parent = hybridRelation.getParentName();
1357
        TaxonName child = hybridRelation.getHybridName();
1358
        if (this.equals(parent)){
1359
            this.hybridParentRelations.remove(hybridRelation);
1360
            child.hybridChildRelations.remove(hybridRelation);
1361
            hybridRelation.setHybridName(null);
1362
            hybridRelation.setParentName(null);
1363
        }
1364
        if (this.equals(child)){
1365
            parent.hybridParentRelations.remove(hybridRelation);
1366
            this.hybridChildRelations.remove(hybridRelation);
1367
            hybridRelation.setHybridName(null);
1368
            hybridRelation.setParentName(null);
1369
        }
1370
    }
1371

    
1372
//********* METHODS **************************************/
1373

    
1374
    @Override
1375
    public INameCacheStrategy getCacheStrategy() {
1376
        return this.cacheStrategy;
1377
    }
1378

    
1379
    @Override
1380
    public String generateFullTitle(){
1381
        if (getCacheStrategy() == null){
1382
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1383
            return null;
1384
        }else{
1385
            return cacheStrategy.getFullTitleCache(this);
1386
        }
1387
    }
1388

    
1389
    @Override
1390
    public void setFullTitleCache(String fullTitleCache){
1391
        setFullTitleCache(fullTitleCache, PROTECTED);
1392
    }
1393

    
1394
    @Override
1395
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1396
        fullTitleCache = getTruncatedCache(fullTitleCache);
1397
        this.fullTitleCache = fullTitleCache;
1398
        this.setProtectedFullTitleCache(protectCache);
1399
    }
1400

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

    
1426
    @Override
1427
    @Transient
1428
    public List<TaggedText> getTaggedName(){
1429
        INameCacheStrategy strat = getCacheStrategy();
1430
        return strat.getTaggedTitle(this);
1431
    }
1432

    
1433
    @Override
1434
    @Transient
1435
    public List<TaggedText> getTaggedFullTitle() {
1436
        INameCacheStrategy strat = getCacheStrategy();
1437
        return strat.getTaggedFullTitle(this);
1438
    }
1439

    
1440
    @Override
1441
    @Transient
1442
    public String getFullTitleCache(){
1443
        if (protectedFullTitleCache){
1444
            return this.fullTitleCache;
1445
        }
1446
        updateAuthorshipCache();
1447
        if (fullTitleCache == null ){
1448
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1449
        }
1450
        return fullTitleCache;
1451
    }
1452

    
1453
    @Override
1454
    public String getTitleCache(){
1455
        if(!protectedTitleCache) {
1456
            updateAuthorshipCache();
1457
        }
1458
        return super.getTitleCache();
1459
    }
1460

    
1461
    @Override
1462
    public void setTitleCache(String titleCache, boolean protectCache){
1463
        super.setTitleCache(titleCache, protectCache);
1464
    }
1465

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

    
1489
        }
1490
        return authorshipCache;
1491
    }
1492

    
1493
    /**
1494
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1495
     * flag to <code>true</code>.
1496
     *
1497
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1498
     * @see    #getAuthorshipCache()
1499
     */
1500
    @Override
1501
    public void setAuthorshipCache(String authorshipCache) {
1502
        setAuthorshipCache(authorshipCache, true);
1503
    }
1504

    
1505
    /**
1506
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1507
     *
1508
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1509
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1510
     * the flag is set to <code>false</code>.
1511
     * @see    #getAuthorshipCache()
1512
     */
1513
    @Override
1514
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1515
        this.authorshipCache = authorshipCache;
1516
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1517
    }
1518

    
1519
    /**
1520
     * Generates and returns a concatenated and formated author team string
1521
     * including basionym and combination authors of <i>this</i> taxon name
1522
     * according to the strategy defined in
1523
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName) INonViralNameCacheStrategy}.
1524
     *
1525
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1526
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonName)
1527
     */
1528
    @Override
1529
    public String generateAuthorship(){
1530
        if (getCacheStrategy() == null){
1531
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1532
            return null;
1533
        }else{
1534
            return cacheStrategy.getAuthorshipCache(this);
1535
        }
1536
    }
1537

    
1538
    /**
1539
     * Tests if the given name has any authors.
1540
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1541
     */
1542
    @Override
1543
    public boolean hasAuthors() {
1544
        return (this.getCombinationAuthorship() != null ||
1545
                this.getExCombinationAuthorship() != null ||
1546
                this.getBasionymAuthorship() != null ||
1547
                this.getExBasionymAuthorship() != null);
1548
    }
1549

    
1550
    /**
1551
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1552
     */
1553
    @Override
1554
    public String computeCombinationAuthorNomenclaturalTitle() {
1555
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1556
    }
1557

    
1558
    /**
1559
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1560
     */
1561
    @Override
1562
    public String computeBasionymAuthorNomenclaturalTitle() {
1563
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1564
    }
1565

    
1566

    
1567
    /**
1568
     * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1569
     */
1570
    @Override
1571
    public String computeExCombinationAuthorNomenclaturalTitle() {
1572
        return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1573
    }
1574

    
1575
    /**
1576
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1577
     */
1578
    @Override
1579
    public String computeExBasionymAuthorNomenclaturalTitle() {
1580
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1581
    }
1582

    
1583
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1584
        if (author == null){
1585
            return null;
1586
        }else{
1587
            return author.getNomenclaturalTitle();
1588
        }
1589
    }
1590

    
1591
    /**
1592
     * Returns the set of all {@link NameRelationship name relationships}
1593
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1594
     * in some name relationships or target in some others.
1595
     *
1596
     * @see    #getRelationsToThisName()
1597
     * @see    #getRelationsFromThisName()
1598
     * @see    #addNameRelationship(NameRelationship)
1599
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1600
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1601
     */
1602
    @Override
1603
    @Transient
1604
    public Set<NameRelationship> getNameRelations() {
1605
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1606
        rels.addAll(getRelationsFromThisName());
1607
        rels.addAll(getRelationsToThisName());
1608
        return rels;
1609
    }
1610

    
1611
    @Override
1612
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1613
        return addRelationshipToName(toName, type, null, null, ruleConsidered, codeEdition);
1614
    }
1615

    
1616
    @Override
1617
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type){
1618
        return addRelationshipToName(toName, type, null, null, null, null);
1619
    }
1620

    
1621
    @Override
1622
    public NameRelationship addRelationshipToName(TaxonName toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1623
        if (toName == null){
1624
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1625
        }
1626
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered, codeEdition);
1627
        return rel;
1628
    }
1629

    
1630
    @Override
1631
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type){
1632
        return addRelationshipFromName(fromName, type, null, null, null, null);
1633
    }
1634

    
1635
    @Override
1636
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1637
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1638
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered, codeEdition);
1639
    }
1640

    
1641
    @Override
1642
    public NameRelationship addRelationshipFromName(TaxonName fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
1643
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered, codeEdition);
1644
    }
1645

    
1646
    /**
1647
     * Adds an existing {@link NameRelationship name relationship} either to the set of
1648
     * {@link #getRelationsToThisName() relations to <i>this</i> taxon name} or to the set of
1649
     * {@link #getRelationsFromThisName() relations from <i>this</i> taxon name}. If neither the
1650
     * source nor the target of the name relationship match with <i>this</i> taxon name
1651
     * no addition will be carried out.
1652
     *
1653
     * @param rel  the name relationship to be added to one of <i>this</i> taxon name's name relationships sets
1654
     * @see    	   #getNameRelations()
1655
     * @see    	   #addRelationshipToName(TaxonName, NameRelationshipType, String)
1656
     * @see    	   #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1657
     */
1658
    protected void addNameRelationship(NameRelationship rel) {
1659
        if (rel != null ){
1660
            if (rel.getToName().equals(this)){
1661
                this.relationsToThisName.add(rel);
1662
            }else if(rel.getFromName().equals(this)){
1663
                this.relationsFromThisName.add(rel);
1664
            }
1665
            NameRelationshipType type = rel.getType();
1666
            if (type != null && ( type.isBasionymRelation() || type.isReplacedSynonymRelation() ) ){
1667
                rel.getFromName().mergeHomotypicGroups(rel.getToName());
1668
            }
1669
        }else{
1670
            throw new RuntimeException("NameRelationship is either null or the relationship does not reference this name");
1671
        }
1672
    }
1673
    /**
1674
     * Removes one {@link NameRelationship name relationship} from one of both sets of
1675
     * {@link #getNameRelations() name relationships} in which <i>this</i> taxon name is involved.
1676
     * The name relationship will also be removed from one of both sets belonging
1677
     * to the second taxon name involved. Furthermore the fromName and toName
1678
     * attributes of the name relationship object will be nullified.
1679
     *
1680
     * @param  nameRelation  the name relationship which should be deleted from one of both sets
1681
     * @see    				 #getNameRelations()
1682
     */
1683
    @Override
1684
    public void removeNameRelationship(NameRelationship nameRelation) {
1685

    
1686
        TaxonName fromName = nameRelation.getFromName();
1687
        TaxonName toName = nameRelation.getToName();
1688

    
1689
        if (nameRelation != null) {
1690
            nameRelation.setToName(null);
1691
            nameRelation.setFromName(null);
1692
        }
1693

    
1694
        if (fromName != null) {
1695
            fromName.removeNameRelationship(nameRelation);
1696
        }
1697

    
1698
        if (toName != null) {
1699
            toName.removeNameRelationship(nameRelation);
1700
        }
1701

    
1702
        this.relationsToThisName.remove(nameRelation);
1703
        this.relationsFromThisName.remove(nameRelation);
1704
    }
1705

    
1706
    @Override
1707
    public void removeRelationToTaxonName(TaxonName toTaxonName) {
1708
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1709
//		nameRelationships.addAll(this.getNameRelations());
1710
        nameRelationships.addAll(this.getRelationsFromThisName());
1711
        nameRelationships.addAll(this.getRelationsToThisName());
1712
        for(NameRelationship nameRelationship : nameRelationships) {
1713
            // remove name relationship from this side
1714
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1715
                this.removeNameRelationship(nameRelationship);
1716
            }
1717
        }
1718
    }
1719

    
1720
    public void removeRelationWithTaxonName(TaxonName otherTaxonName, Direction direction, NameRelationshipType type) {
1721

    
1722
        Set<NameRelationship> tmpRels = new HashSet<>(relationsWithThisName(direction));
1723
        for(NameRelationship nameRelationship : tmpRels) {
1724
            if (direction.equals(Direction.relatedFrom) && nameRelationship.getToName().equals(otherTaxonName) ||
1725
                    direction.equals(Direction.relatedTo) && nameRelationship.getFromName().equals(otherTaxonName)) {
1726
                if (type == null || type.equals(nameRelationship.getType())){
1727
                    this.removeNameRelationship(nameRelationship);
1728
                }
1729
            }
1730
        }
1731
    }
1732

    
1733
    /**
1734
     * If relation is of type NameRelationship, addNameRelationship is called;
1735
     * if relation is of type HybridRelationship addHybridRelationship is called,
1736
     * otherwise an IllegalArgumentException is thrown.
1737
     *
1738
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1739
     * @see    	   		#addNameRelationship(NameRelationship)
1740
     * @see    	   		#getNameRelations()
1741
     * @see    	   		NameRelationship
1742
     * @see    	   		RelationshipBase
1743
     * @see             #addHybridRelationship(HybridRelationship)
1744

    
1745
     * @deprecated to be used by RelationshipBase only
1746
     */
1747
    @Deprecated
1748
    @Override
1749
    public void addRelationship(RelationshipBase relation) {
1750
        if (relation instanceof NameRelationship){
1751
            addNameRelationship((NameRelationship)relation);
1752

    
1753
        }else if (relation instanceof HybridRelationship){
1754
            addHybridRelationship((HybridRelationship)relation);
1755
        }else{
1756
            logger.warn("Relationship not of type NameRelationship!");
1757
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1758
        }
1759
    }
1760

    
1761
    /**
1762
     * Returns the set of all {@link NameRelationship name relationships}
1763
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1764
     *
1765
     * @see    #getNameRelations()
1766
     * @see    #getRelationsToThisName()
1767
     * @see    #addRelationshipFromName(TaxonName, NameRelationshipType, String)
1768
     */
1769
    @Override
1770
    public Set<NameRelationship> getRelationsFromThisName() {
1771
        if(relationsFromThisName == null) {
1772
            this.relationsFromThisName = new HashSet<>();
1773
        }
1774
        return relationsFromThisName;
1775
    }
1776

    
1777
    /**
1778
     * Returns the set of all {@link NameRelationship name relationships}
1779
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1780
     *
1781
     * @see    #getNameRelations()
1782
     * @see    #getRelationsFromThisName()
1783
     * @see    #addRelationshipToName(TaxonName, NameRelationshipType, String)
1784
     */
1785
    @Override
1786
    public Set<NameRelationship> getRelationsToThisName() {
1787
        if(relationsToThisName == null) {
1788
            this.relationsToThisName = new HashSet<>();
1789
        }
1790
        return relationsToThisName;
1791
    }
1792

    
1793
    /**
1794
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1795
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1796
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1797
     * and the nomenclatural code rule considered.
1798
     *
1799
     * @see     NomenclaturalStatus
1800
     * @see     NomenclaturalStatusType
1801
     */
1802
    @Override
1803
    public Set<NomenclaturalStatus> getStatus() {
1804
        if(status == null) {
1805
            this.status = new HashSet<>();
1806
        }
1807
        return status;
1808
    }
1809

    
1810
    /**
1811
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1812
     * to <i>this</i> taxon name's set of nomenclatural status.
1813
     *
1814
     * @param  nomStatus  the nomenclatural status to be added
1815
     * @see 			  #getStatus()
1816
     */
1817
    @Override
1818
    public void addStatus(NomenclaturalStatus nomStatus) {
1819
        this.status.add(nomStatus);
1820
    }
1821
    @Override
1822
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1823
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1824
        this.status.add(newStatus);
1825
        return newStatus;
1826
    }
1827

    
1828
    /**
1829
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1830
     * Type and ruleConsidered attributes of the nomenclatural status object
1831
     * will be nullified.
1832
     *
1833
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1834
     * @see     		  #getStatus()
1835
     */
1836
    @Override
1837
    public void removeStatus(NomenclaturalStatus nomStatus) {
1838
        //TODO to be implemented?
1839
        logger.warn("not yet fully implemented?");
1840
        this.status.remove(nomStatus);
1841
    }
1842

    
1843
    public void setStatus(Set<NomenclaturalStatus> nomStatus) throws SetterAdapterException {
1844
        new EntityCollectionSetterAdapter<TaxonName, NomenclaturalStatus>(TaxonName.class, NomenclaturalStatus.class, "status", "addStatus", "removeStatus").setCollection(this, status);
1845
    }
1846

    
1847
    /**
1848
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1849
     * strings or year according to the strategy defined in
1850
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1851
     * The result might be stored in {@link #getNameCache() nameCache} if the
1852
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1853
     *
1854
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1855
     * @see     #getNameCache()
1856
     */
1857
    protected String generateNameCache(){
1858
        if (getCacheStrategy() == null){
1859
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1860
            return null;
1861
        }else{
1862
            return cacheStrategy.getNameCache(this);
1863
        }
1864
    }
1865

    
1866
    /**
1867
     * Returns or generates the nameCache (scientific name
1868
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1869
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1870
     * the string will be generated according to a defined strategy,
1871
     * otherwise the value of the actual nameCache string will be returned.
1872
     *
1873
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1874
     * @see     #generateNameCache()
1875
     */
1876
    @Override
1877
    @Transient
1878
    public String getNameCache() {
1879
        if (protectedNameCache){
1880
            return this.nameCache;
1881
        }
1882
        // is title dirty, i.e. equal NULL?
1883
        if (nameCache == null){
1884
            this.nameCache = generateNameCache();
1885
        }
1886
        return nameCache;
1887
    }
1888

    
1889
    /**
1890
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1891
     * Sets the protectedNameCache flag to <code>true</code>.
1892
     *
1893
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1894
     * @see    #getNameCache()
1895
     */
1896
    @Override
1897
    public void setNameCache(String nameCache){
1898
        setNameCache(nameCache, true);
1899
    }
1900

    
1901
    /**
1902
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1903
     * Sets the protectedNameCache flag to <code>true</code>.
1904
     *
1905
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1906
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
1907
     * <code>false</code>
1908
     * @see    #getNameCache()
1909
     */
1910
    @Override
1911
    public void setNameCache(String nameCache, boolean protectedNameCache){
1912
        this.nameCache = nameCache;
1913
        this.setProtectedNameCache(protectedNameCache);
1914
    }
1915

    
1916

    
1917
    /**
1918
     * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
1919
     * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1920
     * of any other taxon name. Returns "true", if a basionym or a replaced
1921
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1922
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1923
     * homotypical group).
1924
     */
1925
    @Override
1926
    @Transient
1927
    public boolean isOriginalCombination(){
1928
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1929
        for (NameRelationship relation : relationsFromThisName) {
1930
            if (relation.getType().isBasionymRelation() ||
1931
                    relation.getType().isReplacedSynonymRelation()) {
1932
                return true;
1933
            }
1934
        }
1935
        return false;
1936
    }
1937

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

    
1957
    /**
1958
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
1959
     * The basionym of a taxon name is its epithet-bringing synonym.
1960
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
1961
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
1962
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
1963
     *
1964
     * If more than one basionym exists one is choosen at radom.
1965
     *
1966
     * If no basionym exists null is returned.
1967
     */
1968
    @Override
1969
    @Transient
1970
    public TaxonName getBasionym(){
1971
        Set<TaxonName> basionyms = getBasionyms();
1972
        if (basionyms.size() == 0){
1973
            return null;
1974
        }else{
1975
            return basionyms.iterator().next();
1976
        }
1977
    }
1978

    
1979
    /**
1980
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
1981
     * The basionym of a taxon name is its epithet-bringing synonym.
1982
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
1983
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
1984
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
1985
     */
1986
    @Override
1987
    @Transient
1988
    public Set<TaxonName> getBasionyms(){
1989
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.BASIONYM());
1990
    }
1991

    
1992
    /**
1993
     *
1994
     * @param direction
1995
     * @param type
1996
     * @return
1997
     */
1998
    public Set<TaxonName> getRelatedNames(Direction direction, NameRelationshipType type) {
1999
        return getRelatedNames(relationsWithThisName(direction), type);
2000
    }
2001

    
2002
    private Set<TaxonName> getRelatedNames(Set<NameRelationship> rels, NameRelationshipType type) {
2003
        Set<TaxonName> result = new HashSet<>();
2004
        for (NameRelationship rel : rels){
2005
            if (rel.getType()!= null && rel.getType().isRelationshipType(type)){
2006
                TaxonName basionym = rel.getFromName();
2007
                result.add(basionym);
2008
            }
2009
        }
2010
        return result;
2011
    }
2012

    
2013
    @Override
2014
    @Transient
2015
    public TaxonName getOriginalSpelling(){
2016
        if (this.getNomenclaturalSource() != null){
2017
            return this.getNomenclaturalSource().getNameUsedInSource();
2018
        }else{
2019
            return null;
2020
        }
2021
    }
2022

    
2023
    @Override
2024
    @Transient
2025
    public void setOriginalSpelling(TaxonName originalSpelling){
2026
        if (originalSpelling != null){
2027
            this.getNomenclaturalSource(true).setNameUsedInSource(originalSpelling);
2028
        }else if (this.getNomenclaturalSource() != null){
2029
            this.getNomenclaturalSource().setNameUsedInSource(null);
2030
            checkNullSource();
2031
        }
2032
    }
2033

    
2034
    /**
2035
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2036
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2037
     * and to the basionym. The basionym cannot have itself as a basionym.
2038
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2039
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2040
     *
2041
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
2042
     * @see  				#getBasionym()
2043
     * @see  				#addBasionym(TaxonName, String)
2044
     */
2045
    @Override
2046
    public NameRelationship addBasionym(TaxonName basionym){
2047
        return addBasionym(basionym, null, null, null, null);
2048
    }
2049
    /**
2050
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2051
     * and keeps the nomenclatural rule considered for it. The basionym
2052
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2053
     * The basionym cannot have itself as a basionym.
2054
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2055
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2056
     *
2057
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
2058
     * @param  ruleConsidered	the string identifying the nomenclatural rule
2059
     * @param codeEdition     the edition of the nomenclatural code where the <code>ruleConsidered</code> has been published.
2060
     * @return
2061
     * @see  					#getBasionym()
2062
     * @see  					#addBasionym(TaxonName)
2063
     */
2064
    @Override
2065
    public NameRelationship addBasionym(TaxonName basionym, Reference citation, String microcitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
2066
        if (basionym != null){
2067
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered, codeEdition);
2068
        }else{
2069
            return null;
2070
        }
2071
    }
2072

    
2073
    /**
2074
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2075
     */
2076
    @Override
2077
    @Transient
2078
    public Set<TaxonName> getReplacedSynonyms(){
2079
        return getRelatedNames(Direction.relatedTo, NameRelationshipType.REPLACED_SYNONYM());
2080
    }
2081

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

    
2103
    /**
2104
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2105
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2106
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2107
     * previously used as basionym.
2108
     *
2109
     * @see   #getBasionym()
2110
     * @see   #addBasionym(TaxonName)
2111
     */
2112
    @Override
2113
    public void removeBasionyms(){
2114
        removeNameRelations(Direction.relatedTo, NameRelationshipType.BASIONYM());
2115
    }
2116

    
2117

    
2118
    /**
2119
     * Removes all {@link NameRelationship relationships} of the given <code>type</code> from the set of
2120
     * relations in the specified <code>direction</code> direction wich are related from or to this
2121
     * <i>this</i> taxon name. The same relationship will be removed from the set of
2122
     * reverse relations of the other taxon name.
2123
     *
2124
     * @param direction
2125
     * @param type
2126
     */
2127
    public void removeNameRelations(Direction direction, NameRelationshipType type) {
2128
        Set<NameRelationship> relationsWithThisName = relationsWithThisName(direction);
2129
        Set<NameRelationship> removeRelations = new HashSet<>();
2130
        for (NameRelationship nameRelation : relationsWithThisName){
2131
            if (nameRelation.getType().isRelationshipType(type)){
2132
                removeRelations.add(nameRelation);
2133
            }
2134
        }
2135
        // Removing relations from a set through which we are iterating causes a
2136
        // ConcurrentModificationException. Therefore, we delete the targeted
2137
        // relations in a second step.
2138
        for (NameRelationship relation : removeRelations){
2139
            this.removeNameRelationship(relation);
2140
        }
2141
    }
2142

    
2143
    protected Set<NameRelationship> relationsWithThisName(Direction direction) {
2144
        switch(direction) {
2145
            case relatedTo:
2146
                return this.getRelationsToThisName();
2147
            case relatedFrom:
2148
                return this.getRelationsFromThisName();
2149
            default: throw new RuntimeException("invalid Direction:" + direction);
2150
        }
2151
    }
2152

    
2153
    /**
2154
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2155
     *
2156
     * @see 	Rank
2157
     */
2158
    @Override
2159
    public Rank getRank(){
2160
        return this.rank;
2161
    }
2162
    /**
2163
     * @see  #getRank()
2164
     */
2165
    @Override
2166
    public void setRank(Rank rank){
2167
        this.rank = rank;
2168
    }
2169

    
2170
//*************** nom ref/source *******************/
2171

    
2172
    @Override
2173
    public NomenclaturalSource getNomenclaturalSource(){
2174
        return this.nomenclaturalSource;
2175
    }
2176

    
2177
    protected DescriptionElementSource getNomenclaturalSource(boolean createIfNotExist){
2178
        if (this.nomenclaturalSource == null && createIfNotExist){
2179
            setNomenclaturalSource(NomenclaturalSource.NewNomenclaturalInstance(this));
2180
        }
2181
        return nomenclaturalSource;
2182
    }
2183

    
2184
    @Override
2185
    public void setNomenclaturalSource(NomenclaturalSource nomenclaturalSource) throws IllegalArgumentException {
2186
        //check state
2187
        if (nomenclaturalSource != null && !OriginalSourceType.NomenclaturalReference.equals(nomenclaturalSource.getType())
2188
                ){
2189
            throw new IllegalArgumentException("Nomenclatural source must be of type " + OriginalSourceType.NomenclaturalReference.getLabel());
2190
        }
2191
        this.nomenclaturalSource = nomenclaturalSource;
2192
        if (nomenclaturalSource != null && nomenclaturalSource.getSourcedName() != this){
2193
            nomenclaturalSource.setSourcedName(this);
2194
        }
2195
    }
2196

    
2197
    @Override
2198
    @Transient
2199
    public Reference getNomenclaturalReference(){
2200
        return this.nomenclaturalSource == null? null:this.nomenclaturalSource.getCitation();
2201
    }
2202

    
2203
    @Override
2204
    @Transient
2205
    public void setNomenclaturalReference(Reference nomenclaturalReference){
2206
        if (nomenclaturalReference == null && this.getNomenclaturalSource()==null){
2207
            return;
2208
        }else{
2209
            getNomenclaturalSource(true).setCitation(nomenclaturalReference);
2210
        }
2211
    }
2212

    
2213
    @Override
2214
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2215
        setNomenclaturalReference(CdmBase.deproxy(nomenclaturalReference, Reference.class));
2216
    }
2217

    
2218
    /**
2219
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2220
     * to <i>this</i> taxon name. The details describe the exact localisation within
2221
     * the publication used as nomenclature reference. These are mostly
2222
     * (implicitly) pages but can also be figures or tables or any other
2223
     * element of a publication. A nomenclatural micro reference (details)
2224
     * requires the existence of a nomenclatural reference.
2225
     */
2226
    //Details of the nomenclatural reference (protologue).
2227
    @Override
2228
    @Transient
2229
    public String getNomenclaturalMicroReference(){
2230
        return this.nomenclaturalSource == null? null: this.nomenclaturalSource.getCitationMicroReference();
2231
    }
2232

    
2233
    /**
2234
     * @see  #getNomenclaturalMicroReference()
2235
     */
2236
    @Override
2237
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2238
        nomenclaturalMicroReference = isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
2239
        if (nomenclaturalMicroReference == null && this.getNomenclaturalSource()==null){
2240
            return;
2241
        }else{
2242
            this.getNomenclaturalSource(true).setCitationMicroReference(nomenclaturalMicroReference);
2243
        }
2244
    }
2245

    
2246
    /**
2247
     * Checks if the source is completely empty and if empty removes it from the name.
2248
     */
2249
    //TODO maybe this should be moved to a hibernate listener, but the listener solution may
2250
    //work only for nomenclatural single sources as they are the only which are bidirectional
2251
    protected void checkNullSource() {
2252
        if (this.nomenclaturalSource != null && this.nomenclaturalSource.checkEmpty(true)){
2253
            this.nomenclaturalSource = null;
2254
        }
2255
    }
2256

    
2257
    /**
2258
     * Creates a new external link for given uri with default language description if description exists
2259
     * and adds it to the nomenclatural source.
2260
     * If no nomenclatural source exists a new one is created.
2261
     * @param uri the url to the protogue
2262
     * @param description an additional description for the link for the default language
2263
     * @param type see {@link ExternalLinkType}
2264
     * @return the newly created {@link ExternalLink external link}
2265
     */
2266
    public ExternalLink addProtologue(URI uri, String description, ExternalLinkType type){
2267
        ExternalLink newProtologue = ExternalLink.NewInstance(type, uri, description, null);
2268
        getNomenclaturalSource(true).addLink(newProtologue);
2269
        return newProtologue;
2270
    }
2271

    
2272
    /**
2273
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
2274
     * The appended phrase is a non-atomised addition to a name. It is
2275
     * not ruled by a nomenclatural code.
2276
     */
2277
    @Override
2278
    public String getAppendedPhrase(){
2279
        return this.appendedPhrase;
2280
    }
2281

    
2282
    /**
2283
     * @see  #getAppendedPhrase()
2284
     */
2285
    @Override
2286
    public void setAppendedPhrase(String appendedPhrase){
2287
        this.appendedPhrase = isBlank(appendedPhrase)? null : appendedPhrase;
2288
    }
2289

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

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

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

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

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

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

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

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

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

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

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

    
2348
//*********************** TYPE DESIGNATION *********************************************//
2349

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

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

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

    
2396
//*********************** NAME TYPE DESIGNATION *********************************************//
2397

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

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

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

    
2484
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2485

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

    
2502

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

    
2535
    @Override
2536
    public TextualTypeDesignation addTextualTypeDesignation(
2537
                String text,
2538
                Language language,
2539
                boolean isVerbatim,
2540
                Reference citation,
2541
                String citationMicroReference,
2542
                String originalNameString,
2543
                boolean addToAllHomotypicNames) {
2544
        TextualTypeDesignation textualTypeDesignation = TextualTypeDesignation.NewInstance(text, language, isVerbatim, citation, citationMicroReference, originalNameString);
2545
        addTypeDesignation(textualTypeDesignation, addToAllHomotypicNames);
2546
        return textualTypeDesignation;
2547
    }
2548

    
2549
    //used by merge strategy
2550
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2551
        return addTypeDesignation(typeDesignation, true);
2552
    }
2553

    
2554
    /**
2555
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2556
     *
2557
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2558
     * @param addToAllNames				the boolean indicating whether the type designation should be
2559
     * 									added to all taxon names of the homotypical group the typified
2560
     * 									taxon name belongs to
2561
     * @return							true if the operation was successful
2562
     *
2563
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2564
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2565
     *
2566
     */
2567
    @Override
2568
    public boolean addTypeDesignation(TypeDesignationBase<?> typeDesignation, boolean addToAllNames){
2569
        //currently typeDesignations are not persisted with the homotypical group
2570
        //so explicit adding to the homotypical group is not necessary.
2571
        if (typeDesignation != null){
2572
            checkHomotypicalGroup(typeDesignation);
2573
            this.typeDesignations.add(typeDesignation);
2574
            typeDesignation.addTypifiedName(this);
2575

    
2576
            if (addToAllNames){
2577
                for (TaxonName taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2578
                    if (taxonName != this){
2579
                        taxonName.addTypeDesignation(typeDesignation, false);
2580
                    }
2581
                }
2582
            }
2583
        }
2584
        return true;
2585
    }
2586

    
2587
    /**
2588
     * Throws an Exception this type designation already has typified names from another homotypical group.
2589
     * @param typeDesignation
2590
     */
2591
    private void checkHomotypicalGroup(TypeDesignationBase<?> typeDesignation) {
2592
        if(typeDesignation.getTypifiedNames().size() > 0){
2593
            Set<HomotypicalGroup> groups = new HashSet<>();
2594
            Set<TaxonName> names = typeDesignation.getTypifiedNames();
2595
            for (TaxonName taxonName: names){
2596
                groups.add(taxonName.getHomotypicalGroup());
2597
            }
2598
            if (groups.size() > 1){
2599
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2600
            }
2601
        }
2602
    }
2603

    
2604

    
2605

    
2606
//*********************** HOMOTYPICAL GROUP *********************************************//
2607

    
2608

    
2609
    /**
2610
     * Returns the {@link HomotypicalGroup homotypical group} to which
2611
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2612
     * that share the same types.
2613
     *
2614
     * @see 	HomotypicalGroup
2615
     */
2616

    
2617
    @Override
2618
    public HomotypicalGroup getHomotypicalGroup() {
2619
        if (homotypicalGroup == null){
2620
            homotypicalGroup = new HomotypicalGroup();
2621
            homotypicalGroup.typifiedNames.add(this);
2622
        }
2623
    	return homotypicalGroup;
2624
    }
2625

    
2626
    /**
2627
     * @see #getHomotypicalGroup()
2628
     */
2629
    @Override
2630
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2631
        if (homotypicalGroup == null){
2632
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2633
        }
2634
        /*if (this.homotypicalGroup != null){
2635
        	this.homotypicalGroup.removeTypifiedName(this, false);
2636
        }*/
2637
        this.homotypicalGroup = homotypicalGroup;
2638
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2639
        	 this.homotypicalGroup.addTypifiedName(this);
2640
        }
2641
    }
2642

    
2643

    
2644

    
2645
// *************************************************************************//
2646

    
2647
    /**
2648
     * Returns the complete string containing the
2649
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2650
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2651
     *
2652
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2653
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2654
     * @see		#getNomenclaturalReference()
2655
     * @see		#getNomenclaturalMicroReference()
2656
     */
2657
    @Override
2658
    @Transient
2659
    public String getCitationString(){
2660
        Reference nomRef = getNomenclaturalReference();
2661
        return nomRef == null ? null : nomRef.getNomenclaturalCitation(getNomenclaturalMicroReference());
2662
    }
2663

    
2664
    /**
2665
     * Returns the parsing problems
2666
     * @return
2667
     */
2668
    @Override
2669
    public List<ParserProblem> getParsingProblems(){
2670
        return ParserProblem.warningList(this.parsingProblem);
2671
    }
2672

    
2673
    /**
2674
     * Returns the string containing the publication date (generally only year)
2675
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2676
     * no nomenclatural reference.
2677
     *
2678
     * @return  the string containing the publication date of <i>this</i> taxon name
2679
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2680
     */
2681
    @Override
2682
    @Transient
2683
    @ValidTaxonomicYear(groups=Level3.class)
2684
    public String getReferenceYear(){
2685
        if (this.getNomenclaturalReference() != null ){
2686
            return this.getNomenclaturalReference().getYear();
2687
        }else{
2688
            return null;
2689
        }
2690
    }
2691

    
2692
    /**
2693
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2694
     * In this context a taxon base means the use of a taxon name by a reference
2695
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2696
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2697
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2698
     * within a taxonomic treatment (identified by one reference).
2699
     *
2700
     * @see	#getTaxa()
2701
     * @see	#getSynonyms()
2702
     */
2703
    @Override
2704
    public Set<TaxonBase> getTaxonBases() {
2705
        if(taxonBases == null) {
2706
            this.taxonBases = new HashSet<>();
2707
        }
2708
        return this.taxonBases;
2709
    }
2710

    
2711
    /**
2712
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2713
     * to the set of taxon bases using <i>this</i> taxon name.
2714
     *
2715
     * @param  taxonBase  the taxon base to be added
2716
     * @see 			  #getTaxonBases()
2717
     * @see 			  #removeTaxonBase(TaxonBase)
2718
     */
2719
    //TODO protected
2720
    @Override
2721
    public void addTaxonBase(TaxonBase taxonBase){
2722
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2723
        ReflectionUtils.makeAccessible(method);
2724
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2725
        taxonBases.add(taxonBase);
2726
    }
2727
    /**
2728
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2729
     *
2730
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2731
     * @see    				#getTaxonBases()
2732
     * @see    				#addTaxonBase(TaxonBase)
2733
     */
2734
    @Override
2735
    public void removeTaxonBase(TaxonBase taxonBase){
2736
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonName.class});
2737
        ReflectionUtils.makeAccessible(method);
2738
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2739

    
2740

    
2741
    }
2742

    
2743
    /**
2744
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2745
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2746
     * the set returned by getTaxonBases().
2747
     *
2748
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2749
     * @see	#getTaxonBases()
2750
     * @see	#getSynonyms()
2751
     */
2752
    @Override
2753
    @Transient
2754
    public Set<Taxon> getTaxa(){
2755
        Set<Taxon> result = new HashSet<>();
2756
        for (TaxonBase<?> taxonBase : this.taxonBases){
2757
            if (taxonBase instanceof Taxon){
2758
                result.add((Taxon)taxonBase);
2759
            }
2760
        }
2761
        return result;
2762
    }
2763

    
2764
    /**
2765
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2766
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2767
     * the set returned by getTaxonBases().
2768
     *
2769
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2770
     * @see	#getTaxonBases()
2771
     * @see	#getTaxa()
2772
     */
2773
    @Override
2774
    @Transient
2775
    public Set<Synonym> getSynonyms() {
2776
        Set<Synonym> result = new HashSet<>();
2777
        for (TaxonBase<?> taxonBase : this.taxonBases){
2778
            if (taxonBase instanceof Synonym){
2779
                result.add((Synonym)taxonBase);
2780
            }
2781
        }
2782
        return result;
2783
    }
2784

    
2785
//***************** REGISTRATION *****************/
2786

    
2787
    @Override
2788
    public Set<Registration> getRegistrations() {
2789
        return this.registrations;
2790
    }
2791

    
2792

    
2793
// ************* RELATIONSHIPS *****************************/
2794

    
2795
    /**
2796
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2797
     * by title cache of the related names.
2798
     * @see #getHybridParentRelations()
2799
     */
2800
    @Override
2801
    @Transient
2802
    public List<HybridRelationship> getOrderedChildRelationships(){
2803
        List<HybridRelationship> result = new ArrayList<>();
2804
        result.addAll(this.hybridChildRelations);
2805
        Collections.sort(result);
2806
        Collections.reverse(result);
2807
        return result;
2808
    }
2809

    
2810
    @Override
2811
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2812
        return addHybridParent(parentName, type, null, null, ruleConsidered, null);
2813
    }
2814

    
2815
    @Override
2816
    public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, Reference reference,
2817
            String microReference, String ruleConsidered, NomenclaturalCodeEdition codeEdition){
2818
        return new HybridRelationship(this, parentName, type, reference, microReference, ruleConsidered, codeEdition);
2819
    }
2820

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

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

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

    
2869

    
2870

    
2871
// *********** DESCRIPTIONS *************************************
2872

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

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

    
2923
// *********** HOMOTYPIC GROUP METHODS **************************************************
2924

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

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

    
2959

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

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

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

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

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

    
3023

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

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

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

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

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

    
3164

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

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

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

    
3212
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3213
        if (homotypicalGroup == null) {
3214
            return;
3215
        }
3216

    
3217
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3218
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3219

    
3220
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3221

    
3222
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3223

    
3224
            for(NameRelationship nameRelation : nameRelations){
3225
                relations.add(nameRelation);
3226
            }
3227
        }
3228

    
3229
        for (NameRelationship relation : relations) {
3230

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

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

    
3246
        for (TaxonName name : homotypicalGroup.getTypifiedNames()) {
3247
            if (!name.equals(this)) {
3248

    
3249
                // First check whether the relationship already exists
3250
                if (!this.isBasionymFor(name)) {
3251

    
3252
                    // Then create it
3253
                    name.addRelationshipFromName(this,
3254
                            NameRelationshipType.BASIONYM(), null, null);
3255
                }
3256
            }
3257
        }
3258
    }
3259

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

    
3270
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3271

    
3272
        if (homotypicalGroup == null) {
3273
            return;
3274
        }
3275

    
3276
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
3277
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3278

    
3279
        for(TaxonName typifiedName : homotypicalGroup.getTypifiedNames()){
3280

    
3281
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3282

    
3283
            for(NameRelationship nameRelation : nameRelations){
3284
                relations.add(nameRelation);
3285
            }
3286
        }
3287

    
3288
        for (NameRelationship relation : relations) {
3289

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

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

    
3307

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

    
3331
    @Override
3332
    public boolean isHybridName() {
3333
        return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3334
    }
3335

    
3336
    @Override
3337
    public boolean isHybrid() {
3338
        return this.isHybridName() || this.isHybridFormula();
3339
    }
3340

    
3341
// ******************** NOMENCLATURAL STANDING ****************/
3342

    
3343
    /**
3344
     * Computes the highest priority nomenclatural standing
3345
     * from nomenclatural status and name relationships.
3346
     */
3347
    private NomenclaturalStanding computeNomenclaturalStanding() {
3348
        Set<NomenclaturalStanding> standings = computeNomenclaturalStandings();
3349
        return NomenclaturalStanding.highest(standings);
3350
    }
3351

    
3352
    /**
3353
     * Computes all nomenclatural standings form the nomenclatural status and the name relationships.
3354
     */
3355
    private Set<NomenclaturalStanding> computeNomenclaturalStandings() {
3356
        Set<NomenclaturalStanding> standings = new HashSet<>();
3357
        for (NomenclaturalStatus status : this.status){
3358
            if (status.getType() != null){
3359
                NomenclaturalStanding standing = status.getType().getNomenclaturalStanding();
3360
                standings.add(standing);
3361
            }
3362
        }
3363
        for (NameRelationship nameRel : this.relationsFromThisName){
3364
            if (nameRel.getType() != null){
3365
                NomenclaturalStanding standing = nameRel.getType().getNomenclaturalStanding();
3366
                standings.add(standing);
3367
            }
3368
        }
3369
        for (NameRelationship nameRel : this.relationsToThisName){
3370
            if (nameRel.getType() != null){
3371
                NomenclaturalStanding standing = nameRel.getType().getNomenclaturalStandingInverse();
3372
                standings.add(standing);
3373
            }
3374
        }
3375
        return standings;
3376
    }
3377

    
3378
    @Override
3379
    @Transient
3380
    public boolean isDesignationOnly() {
3381
        return computeNomenclaturalStanding().isDesignationOnly();
3382
    }
3383

    
3384
    @Override
3385
    @Transient
3386
    public boolean isInvalidExplicit() {
3387
        return computeNomenclaturalStanding().isInvalidExplicit();
3388
    }
3389

    
3390
    @Override
3391
    @Transient
3392
    public boolean isIllegitimate() {
3393
        return computeNomenclaturalStanding().isIllegitimate();
3394
    }
3395

    
3396
    @Override
3397
    @Transient
3398
    public boolean isValidExplicit() {
3399
        return computeNomenclaturalStanding().isValidExplicit();
3400
    }
3401

    
3402
    @Override
3403
    @Transient
3404
    public boolean isNoStatus() {
3405
        return computeNomenclaturalStanding().isNoStatus();
3406
    }
3407

    
3408
    @Override
3409
    @Transient
3410
    public boolean isInvalid() {
3411
        return computeNomenclaturalStanding().isInvalid();
3412
    }
3413

    
3414
    @Override
3415
    @Transient
3416
    public boolean isLegitimate() {
3417
        return computeNomenclaturalStanding().isLegitimate();
3418
    }
3419

    
3420
    @Override
3421
    @Transient
3422
    public boolean isValid() {
3423
        return computeNomenclaturalStanding().isValid();
3424
    }
3425

    
3426
// ***************** COMPARE ********************************/
3427

    
3428
    @Override
3429
    public int compareToName(TaxonName otherName){
3430

    
3431
        int result = 0;
3432

    
3433
        if (otherName == null) {
3434
            throw new NullPointerException("Cannot compare to null.");
3435
        }
3436

    
3437
        //other
3438
        otherName = deproxy(otherName);
3439
        String otherNameCache = otherName.getNameCache();
3440
        String otherTitleCache = otherName.getTitleCache();
3441
        //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3442
        if (otherName.isAutonym()){
3443
            boolean isProtected = otherName.isProtectedNameCache();
3444
            String oldNameCache = otherName.getNameCache();
3445
            otherName.setProtectedNameCache(false);
3446
            otherName.setNameCache(null, false);
3447
            otherNameCache = otherName.getNameCache();
3448
            otherName.setNameCache(oldNameCache, isProtected);
3449
        }
3450

    
3451
        //this
3452
        String thisNameCache = this.getNameCache();
3453
        String thisTitleCache = this.getTitleCache();
3454

    
3455
        if (this.isAutonym()){
3456
            boolean isProtected = this.isProtectedNameCache();
3457
            String oldNameCache = this.getNameCache();
3458
            this.setProtectedNameCache(false);
3459
            this.setNameCache(null, false);
3460
            thisNameCache = this.getNameCache();
3461
            this.setNameCache(oldNameCache, isProtected);
3462
        }
3463

    
3464
        // Compare name cache of taxon names
3465
        if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3466
            thisNameCache = normalizeName(thisNameCache);
3467
            otherNameCache = normalizeName(otherNameCache);
3468
            result = thisNameCache.compareTo(otherNameCache);
3469
        }
3470

    
3471
        // Compare title cache of taxon names
3472
        if (result == 0){
3473
            if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3474
                thisTitleCache = normalizeName(thisTitleCache);
3475
                otherTitleCache = normalizeName(otherTitleCache);
3476
                result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3477
            }
3478
        }
3479

    
3480
        return result;
3481
    }
3482

    
3483
    static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3484
    static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3485

    
3486
    private String normalizeName(String thisNameCache) {
3487
        thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3488
        thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3489
        return thisNameCache;
3490
    }
3491

    
3492
// ********************** INTERFACES ********************************************/
3493

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

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

    
3524
//************************ isType ***********************************************/
3525

    
3526
    @Override
3527
    public boolean isNonViral() {
3528
        return nameType.isNonViral();
3529
    }
3530
    @Override
3531
    public boolean isZoological(){
3532
        return nameType.isZoological();
3533
    }
3534
    @Override
3535
    public boolean isBotanical() {
3536
        return nameType.isBotanical();
3537
    }
3538
    @Override
3539
    public boolean isCultivar() {
3540
        return nameType.isCultivar();
3541
    }
3542
    @Override
3543
    public boolean isBacterial() {
3544
        return nameType.isBacterial();
3545
    }
3546
    @Override
3547
    public boolean isViral() {
3548
        return nameType != null? nameType.isViral(): false;
3549
    }
3550

    
3551
// *********************** CACHES ***************************************************/
3552

    
3553
    @Override
3554
    public boolean updateCaches() {
3555
        boolean result = updateAuthorshipCache();
3556
        result |= updateNameCache();
3557
        result |= super.updateCaches();
3558
        result |= updateFullTitleCache();
3559
        return result;
3560
    }
3561

    
3562
    /**
3563
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
3564
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened.
3565
     * @return <code>true</code> if something changed
3566
     */
3567
    private boolean updateAuthorshipCache() {
3568
        //updates the authorship cache if necessary and via the listener updates all higher caches
3569
        if (protectedAuthorshipCache == false){
3570
            String oldCache = this.authorshipCache;
3571
            String newCache = cacheStrategy.getAuthorshipCache(this);
3572
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3573
                this.setAuthorshipCache(null, false);
3574
                this.getAuthorshipCache();
3575
                return true;
3576
            }
3577
        }
3578
        return false;
3579
    }
3580

    
3581
    private boolean updateNameCache() {
3582
        //updates the name cache if necessary and via the listener updates all higher caches
3583
        if (protectedNameCache == false){
3584
            String oldCache = this.nameCache;
3585
            String newCache = cacheStrategy.getNameCache(this);
3586
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3587
                this.setNameCache(null, false);
3588
                this.getNameCache();
3589
                return true;
3590
            }
3591
        }
3592
        return false;
3593
    }
3594

    
3595
    private boolean updateFullTitleCache() {
3596
        if (protectedFullTitleCache == false){
3597
            String oldCache = this.fullTitleCache;
3598
            String newCache = getTruncatedCache(cacheStrategy.getFullTitleCache(this));
3599
            if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
3600
                this.setFullTitleCache(null, false);
3601
                this.getFullTitleCache();
3602
                return true;
3603
            }
3604
        }
3605
        return false;
3606
    }
3607

    
3608
//*********************** CLONE ********************************************************/
3609

    
3610
    @Override
3611
    public TaxonName clone() {
3612
        return this.clone(true);
3613
    }
3614

    
3615
    @Override
3616
    public TaxonName clone(boolean newHomotypicGroup) {
3617
        try {
3618
            TaxonName result = (TaxonName)super.clone();
3619

    
3620
            //taxonBases -> empty
3621
            result.taxonBases = new HashSet<>();
3622

    
3623
            //empty caches
3624
            if (! protectedFullTitleCache){
3625
                result.fullTitleCache = null;
3626
            }
3627

    
3628
            //descriptions
3629
            result.descriptions = new HashSet<>();
3630
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3631
                TaxonNameDescription newDescription = taxonNameDescription.clone();
3632
                result.descriptions.add(newDescription);
3633
            }
3634

    
3635
            //status
3636
            result.status = new HashSet<>();
3637
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3638
                NomenclaturalStatus newStatus = nomenclaturalStatus.clone();
3639
                result.status.add(newStatus);
3640
            }
3641

    
3642
            //to relations
3643
            result.relationsToThisName = new HashSet<>();
3644
            for (NameRelationship toRelationship : getRelationsToThisName()){
3645
                NameRelationship newRelationship = toRelationship.clone();
3646
                newRelationship.setRelatedTo(result);
3647
                result.relationsToThisName.add(newRelationship);
3648
            }
3649

    
3650
            //from relations
3651
            result.relationsFromThisName = new HashSet<>();
3652
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3653
                NameRelationship newRelationship = fromRelationship.clone();
3654
                newRelationship.setRelatedFrom(result);
3655
                result.relationsFromThisName.add(newRelationship);
3656
            }
3657

    
3658
            //type designations
3659
            result.typeDesignations = new HashSet<>();
3660
            for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3661
                TypeDesignationBase<?> newDesignation = typeDesignation.clone();
3662
                this.removeTypeDesignation(newDesignation);
3663
                result.addTypeDesignation(newDesignation, false);
3664
            }
3665

    
3666
            //homotypicalGroup
3667
            if (newHomotypicGroup){
3668
                HomotypicalGroup homotypicalGroup = HomotypicalGroup.NewInstance();
3669
                homotypicalGroup.addTypifiedName(result);
3670
            }else{
3671
                result.homotypicalGroup.addTypifiedName(result);  //to immediately handle bidirectionality
3672
            }
3673

    
3674
            //HybridChildRelations
3675
            result.hybridChildRelations = new HashSet<>();
3676
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3677
                HybridRelationship newChildRelationship = hybridRelationship.clone();
3678
                newChildRelationship.setRelatedTo(result);
3679
                result.hybridChildRelations.add(newChildRelationship);
3680
            }
3681

    
3682
            //HybridParentRelations
3683
            result.hybridParentRelations = new HashSet<>();
3684
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3685
                HybridRelationship newParentRelationship = hybridRelationship.clone();
3686
                newParentRelationship.setRelatedFrom(result);
3687
                result.hybridParentRelations.add(newParentRelationship);
3688
            }
3689

    
3690
            //empty caches
3691
            if (! protectedNameCache){
3692
                result.nameCache = null;
3693
            }
3694

    
3695
            //empty caches
3696
            if (! protectedAuthorshipCache){
3697
                result.authorshipCache = null;
3698
            }
3699

    
3700
            //registrations
3701
            result.registrations = new HashSet<>();
3702
            for (Registration registration : getRegistrations()){
3703
                result.registrations.add(registration);
3704
            }
3705

    
3706
            //no changes to: appendedPharse, nomenclaturalReference,
3707
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3708
            //protectedFullTitleCache, rank
3709
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3710
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3711
            //protectedAuthorshipCache, protectedNameCache,
3712
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3713
            //acronym
3714
            //subGenusAuthorship, nameApprobation
3715
            //anamorphic
3716
            //cultivarName
3717
            return result;
3718
        } catch (CloneNotSupportedException e) {
3719
            logger.warn("Object does not implement cloneable");
3720
            e.printStackTrace();
3721
            return null;
3722
        }
3723
    }
3724

    
3725
}
(35-35/43)