Project

General

Profile

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

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

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

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

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

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

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

    
127
    "nameCache",
128
    "genusOrUninomial",
129
    "infraGenericEpithet",
130
    "specificEpithet",
131
    "infraSpecificEpithet",
132
    "combinationAuthorship",
133
    "exCombinationAuthorship",
134
    "basionymAuthorship",
135
    "exBasionymAuthorship",
136
    "authorshipCache",
137
    "protectedAuthorshipCache",
138
    "protectedNameCache",
139
    "hybridParentRelations",
140
    "hybridChildRelations",
141
    "hybridFormula",
142
    "monomHybrid",
143
    "binomHybrid",
144
    "trinomHybrid"
145
})
146
@XmlRootElement(name = "TaxonNameBase")
147
@Entity
148
@Audited
149
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
150
@Table(appliesTo="TaxonNameBase", indexes = { @org.hibernate.annotations.Index(name = "taxonNameBaseTitleCacheIndex", columnNames = { "titleCache" }),  @org.hibernate.annotations.Index(name = "taxonNameBaseNameCacheIndex", columnNames = { "nameCache" }) })
151
public abstract class TaxonNameBase<T extends TaxonNameBase<?,?>, S extends INameCacheStrategy>
152
            extends IdentifiableEntity<S>
153
            implements ITaxonNameBase<T>, IParsable, IRelated, IMatchable, Cloneable {
154

    
155
    private static final long serialVersionUID = -4530368639601532116L;
156
    private static final Logger logger = Logger.getLogger(TaxonNameBase.class);
157

    
158
    @XmlElement(name = "FullTitleCache")
159
    @Column(length=800, name="fullTitleCache")  //see #1592
160
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
161
    @CacheUpdate(noUpdate ="titleCache")
162
    @NotEmpty(groups = Level2.class)
163
    protected String fullTitleCache;
164

    
165
    //if true titleCache will not be automatically generated/updated
166
    @XmlElement(name = "ProtectedFullTitleCache")
167
    @CacheUpdate(value ="fullTitleCache", noUpdate ="titleCache")
168
    private boolean protectedFullTitleCache;
169

    
170
    @XmlElementWrapper(name = "Descriptions")
171
    @XmlElement(name = "Description")
172
    @OneToMany(mappedBy="taxonName", fetch= FetchType.LAZY, orphanRemoval=true)
173
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
174
    @NotNull
175
    private Set<TaxonNameDescription> descriptions = new HashSet<TaxonNameDescription>();
176

    
177
    @XmlElement(name = "AppendedPhrase")
178
    @Field
179
    @CacheUpdate(value ="nameCache")
180
    //TODO Val #3379
181
//    @NullOrNotEmpty
182
    @Column(length=255)
183
    private String appendedPhrase;
184

    
185
    @XmlElement(name = "NomenclaturalMicroReference")
186
    @Field
187
    @CacheUpdate(noUpdate ="titleCache")
188
    //TODO Val #3379
189
//    @NullOrNotEmpty
190
    @Column(length=255)
191
    private String nomenclaturalMicroReference;
192

    
193
    @XmlAttribute
194
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
195
    private int parsingProblem = 0;
196

    
197
    @XmlAttribute
198
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
199
    private int problemStarts = -1;
200

    
201
    @XmlAttribute
202
    @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
203
    private int problemEnds = -1;
204

    
205
    @XmlElementWrapper(name = "TypeDesignations")
206
    @XmlElement(name = "TypeDesignation")
207
    @XmlIDREF
208
    @XmlSchemaType(name = "IDREF")
209
    @ManyToMany(fetch = FetchType.LAZY)
210
    @JoinTable(
211
        name="TaxonNameBase_TypeDesignationBase",
212
        joinColumns=@javax.persistence.JoinColumn(name="TaxonNameBase_id"),
213
        inverseJoinColumns=@javax.persistence.JoinColumn(name="typedesignations_id")
214
    )
215
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
216
    @NotNull
217
    private Set<TypeDesignationBase> typeDesignations = new HashSet<>();
218

    
219
    @XmlElement(name = "HomotypicalGroup")
220
    @XmlIDREF
221
    @XmlSchemaType(name = "IDREF")
222
    @ManyToOne(fetch = FetchType.LAZY)
223
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
224
    @Match(MatchMode.IGNORE)
225
    @CacheUpdate(noUpdate ="titleCache")
226
    //TODO Val #3379
227
//    @NotNull
228
    private HomotypicalGroup homotypicalGroup;
229

    
230
    @XmlElementWrapper(name = "RelationsFromThisName")
231
    @XmlElement(name = "RelationFromThisName")
232
    @OneToMany(mappedBy="relatedFrom", fetch= FetchType.LAZY, orphanRemoval=true)
233
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
234
    @Merge(MergeMode.RELATION)
235
    @NotNull
236
    @Valid
237
    private Set<NameRelationship> relationsFromThisName = new HashSet<>();
238

    
239
    @XmlElementWrapper(name = "RelationsToThisName")
240
    @XmlElement(name = "RelationToThisName")
241
    @XmlIDREF
242
    @XmlSchemaType(name = "IDREF")
243
    @OneToMany(mappedBy="relatedTo", fetch= FetchType.LAZY, orphanRemoval=true)
244
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
245
    @Merge(MergeMode.RELATION)
246
    @NotNull
247
    @Valid
248
    private Set<NameRelationship> relationsToThisName = new HashSet<>();
249

    
250
    @XmlElementWrapper(name = "NomenclaturalStatuses")
251
    @XmlElement(name = "NomenclaturalStatus")
252
    @OneToMany(fetch= FetchType.LAZY, orphanRemoval=true)
253
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE,CascadeType.DELETE})
254
    @NotNull
255
    @IndexedEmbedded(depth=1)
256
    private Set<NomenclaturalStatus> status = new HashSet<>();
257

    
258
    @XmlElementWrapper(name = "TaxonBases")
259
    @XmlElement(name = "TaxonBase")
260
    @XmlIDREF
261
    @XmlSchemaType(name = "IDREF")
262
    @OneToMany(mappedBy="name", fetch= FetchType.LAZY)
263
    @NotNull
264
    @IndexedEmbedded(depth=1)
265
    private Set<TaxonBase> taxonBases = new HashSet<>();
266

    
267
    @XmlElement(name = "Rank")
268
    @XmlIDREF
269
    @XmlSchemaType(name = "IDREF")
270
    @ManyToOne(fetch = FetchType.EAGER)
271
    @CacheUpdate(value ="nameCache")
272
    //TODO Val #3379, handle maybe as groups = Level2.class ??
273
//    @NotNull
274
    @IndexedEmbedded(depth=1)
275
    private Rank rank;
276

    
277
    @XmlElement(name = "NomenclaturalReference")
278
    @XmlIDREF
279
    @XmlSchemaType(name = "IDREF")
280
    @ManyToOne(fetch = FetchType.LAZY)
281
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
282
    @CacheUpdate(noUpdate ="titleCache")
283
    @IndexedEmbedded
284
    private Reference nomenclaturalReference;
285

    
286
//****** NonViralName attributes ***************************************/
287

    
288
    @XmlElement(name = "NameCache")
289
    @Fields({
290
        @Field(name = "nameCache_tokenized"),
291
        @Field(store = Store.YES, index = Index.YES, analyze = Analyze.YES)
292
    })
293
    @Analyzer(impl = org.apache.lucene.analysis.core.KeywordAnalyzer.class)
294
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
295
            cacheReplacedProperties={"genusOrUninomial", "infraGenericEpithet", "specificEpithet", "infraSpecificEpithet"} )
296
    @NotEmpty(groups = Level2.class) // implicitly NotNull
297
    @Column(length=255)
298
    private String nameCache;
299

    
300
    @XmlElement(name = "ProtectedNameCache")
301
    @CacheUpdate(value="nameCache")
302
    protected boolean protectedNameCache;
303

    
304
    @XmlElement(name = "GenusOrUninomial")
305
    @Field(analyze = Analyze.YES, indexNullAs=Field.DEFAULT_NULL_TOKEN)
306
    @Match(MatchMode.EQUAL_REQUIRED)
307
    @CacheUpdate("nameCache")
308
    @Column(length=255)
309
    @Pattern(regexp = "[A-Z][a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForUninomial.message}")
310
    @NullOrNotEmpty
311
    @NotNull(groups = Level2.class)
312
    private String genusOrUninomial;
313

    
314
    @XmlElement(name = "InfraGenericEpithet")
315
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
316
    @CacheUpdate("nameCache")
317
    //TODO Val #3379
318
//    @NullOrNotEmpty
319
    @Column(length=255)
320
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class,message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
321
    private String infraGenericEpithet;
322

    
323
    @XmlElement(name = "SpecificEpithet")
324
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
325
    @CacheUpdate("nameCache")
326
    //TODO Val #3379
327
//    @NullOrNotEmpty
328
    @Column(length=255)
329
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
330
    private String specificEpithet;
331

    
332
    @XmlElement(name = "InfraSpecificEpithet")
333
    @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
334
    @CacheUpdate("nameCache")
335
    //TODO Val #3379
336
//    @NullOrNotEmpty
337
    @Column(length=255)
338
    @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
339
    private String infraSpecificEpithet;
340

    
341
    @XmlElement(name = "CombinationAuthorship", type = TeamOrPersonBase.class)
342
    @XmlIDREF
343
    @XmlSchemaType(name = "IDREF")
344
    @ManyToOne(fetch = FetchType.LAZY)
345
//    @Target(TeamOrPersonBase.class)
346
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
347
    @JoinColumn(name="combinationAuthorship_id")
348
    @CacheUpdate("authorshipCache")
349
    @IndexedEmbedded
350
    private TeamOrPersonBase<?> combinationAuthorship;
351

    
352
    @XmlElement(name = "ExCombinationAuthorship", type = TeamOrPersonBase.class)
353
    @XmlIDREF
354
    @XmlSchemaType(name = "IDREF")
355
    @ManyToOne(fetch = FetchType.LAZY)
356
//    @Target(TeamOrPersonBase.class)
357
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
358
    @JoinColumn(name="exCombinationAuthorship_id")
359
    @CacheUpdate("authorshipCache")
360
    @IndexedEmbedded
361
    private TeamOrPersonBase<?> exCombinationAuthorship;
362

    
363
    @XmlElement(name = "BasionymAuthorship", type = TeamOrPersonBase.class)
364
    @XmlIDREF
365
    @XmlSchemaType(name = "IDREF")
366
    @ManyToOne(fetch = FetchType.LAZY)
367
//    @Target(TeamOrPersonBase.class)
368
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
369
    @JoinColumn(name="basionymAuthorship_id")
370
    @CacheUpdate("authorshipCache")
371
    @IndexedEmbedded
372
    private TeamOrPersonBase<?> basionymAuthorship;
373

    
374
    @XmlElement(name = "ExBasionymAuthorship", type = TeamOrPersonBase.class)
375
    @XmlIDREF
376
    @XmlSchemaType(name = "IDREF")
377
    @ManyToOne(fetch = FetchType.LAZY)
378
//    @Target(TeamOrPersonBase.class)
379
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
380
    @JoinColumn(name="exBasionymAuthorship_id")
381
    @CacheUpdate("authorshipCache")
382
    @IndexedEmbedded
383
    private TeamOrPersonBase<?> exBasionymAuthorship;
384

    
385
    @XmlElement(name = "AuthorshipCache")
386
    @Fields({
387
        @Field(name = "authorshipCache_tokenized"),
388
        @Field(analyze = Analyze.NO)
389
    })
390
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
391
            cacheReplacedProperties={"combinationAuthorship", "basionymAuthorship", "exCombinationAuthorship", "exBasionymAuthorship"} )
392
    //TODO Val #3379
393
//    @NotNull
394
    @Column(length=255)
395
    @Pattern(regexp = "^[A-Za-z0-9 \\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-\\&\\,\\(\\)\\.]+$", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForAuthority.message}")
396
    private String authorshipCache;
397

    
398
    @XmlElement(name = "ProtectedAuthorshipCache")
399
    @CacheUpdate("authorshipCache")
400
    protected boolean protectedAuthorshipCache;
401

    
402
    @XmlElementWrapper(name = "HybridRelationsFromThisName")
403
    @XmlElement(name = "HybridRelationsFromThisName")
404
    @OneToMany(mappedBy="relatedFrom", fetch = FetchType.LAZY)
405
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
406
    @Merge(MergeMode.RELATION)
407
    @NotNull
408
    private Set<HybridRelationship> hybridParentRelations = new HashSet<>();
409

    
410
    @XmlElementWrapper(name = "HybridRelationsToThisName")
411
    @XmlElement(name = "HybridRelationsToThisName")
412
    @OneToMany(mappedBy="relatedTo", fetch = FetchType.LAZY, orphanRemoval=true) //a hybrid relation can be deleted automatically if the child is deleted.
413
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
414
    @Merge(MergeMode.RELATION)
415
    @NotNull
416
    private Set<HybridRelationship> hybridChildRelations = new HashSet<>();
417

    
418

    
419
    //if set: this name is a hybrid formula (a hybrid that does not have an own name) and no
420
    //other hybrid flags may be set. A
421
    //hybrid name  may not have either an authorteam nor other name components.
422
    @XmlElement(name ="IsHybridFormula")
423
    @CacheUpdate("nameCache")
424
    private boolean hybridFormula = false;
425

    
426
    @XmlElement(name ="IsMonomHybrid")
427
    @CacheUpdate("nameCache")
428
    private boolean monomHybrid = false;
429

    
430
    @XmlElement(name ="IsBinomHybrid")
431
    @CacheUpdate("nameCache")
432
    private boolean binomHybrid = false;
433

    
434
    @XmlElement(name ="IsTrinomHybrid")
435
    @CacheUpdate("nameCache")
436
    private boolean trinomHybrid = false;
437

    
438
// *************** FACTORY METHODS ********************************/
439

    
440
    /**
441
     * Creates a new non viral taxon name instance
442
     * only containing its {@link common.Rank rank} and
443
      * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
444
     *
445
     * @param  rank  the rank to be assigned to <i>this</i> non viral taxon name
446
     * @see    #NewInstance(Rank, HomotypicalGroup)
447
     * @see    #NonViralName(Rank, HomotypicalGroup)
448
     * @see    #NonViralName()
449
     * @see    #NonViralName(Rank, String, String, String, String, TeamOrPersonBase, Reference, String, HomotypicalGroup)
450
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
451
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
452
     * @see    eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
453
     */
454
    public static NonViralName NewInstance(Rank rank){
455
        return new NonViralName(rank, null);
456
    }
457

    
458
    /**
459
     * Creates a new non viral taxon name instance
460
     * only containing its {@link common.Rank rank},
461
     * its {@link HomotypicalGroup homotypical group} and
462
      * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
463
     * The new non viral taxon name instance will be also added to the set of
464
     * non viral taxon names belonging to this homotypical group.
465
     *
466
     * @param  rank  the rank to be assigned to <i>this</i> non viral taxon name
467
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> non viral taxon name belongs
468
     * @see    #NewInstance(Rank)
469
     * @see    #NonViralName(Rank, HomotypicalGroup)
470
     * @see    #NonViralName()
471
     * @see    #NonViralName(Rank, String, String, String, String, TeamOrPersonBase, Reference, String, HomotypicalGroup)
472
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
473
     * @see    eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
474
     * @see    eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
475
     */
476
    public static NonViralName NewInstance(Rank rank, HomotypicalGroup homotypicalGroup){
477
        return new NonViralName(rank, homotypicalGroup);
478
    }
479

    
480
// ************* CONSTRUCTORS *************/
481
    /**
482
     * Class constructor: creates a new empty taxon name.
483
     *
484
     * @see #TaxonNameBase(Rank)
485
     * @see #TaxonNameBase(HomotypicalGroup)
486
     * @see #TaxonNameBase(Rank, HomotypicalGroup)
487
     */
488
    protected TaxonNameBase() {
489
        super();
490
        setNameCacheStrategy();
491
    }
492
    /**
493
     * Class constructor: creates a new taxon name
494
     * only containing its {@link Rank rank}.
495
     *
496
     * @param  rank  the rank to be assigned to <i>this</i> taxon name
497
     * @see    		 #TaxonNameBase()
498
     * @see    		 #TaxonNameBase(HomotypicalGroup)
499
     * @see    		 #TaxonNameBase(Rank, HomotypicalGroup)
500
     */
501
    protected TaxonNameBase(Rank rank) {
502
        this(rank, null);
503
    }
504
    /**
505
     * Class constructor: creates a new taxon name instance
506
     * only containing its {@link HomotypicalGroup homotypical group}.
507
     * The new taxon name will be also added to the set of taxon names
508
     * belonging to this homotypical group.
509
     *
510
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
511
     * @see    					 #TaxonNameBase()
512
     * @see    					 #TaxonNameBase(Rank)
513
     * @see    					 #TaxonNameBase(Rank, HomotypicalGroup)
514
     */
515
    protected TaxonNameBase(HomotypicalGroup homotypicalGroup) {
516
        this(null, homotypicalGroup);
517
    }
518

    
519
    /**
520
     * Class constructor: creates a new taxon name instance
521
     * only containing its {@link Rank rank} and
522
     * its {@link HomotypicalGroup homotypical group} and
523
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
524
     * The new taxon name will be also added to the set of taxon names
525
     * belonging to this homotypical group.
526
     *
527
     * @param  rank  			 the rank to be assigned to <i>this</i> taxon name
528
     * @param  homotypicalGroup  the homotypical group to which <i>this</i> taxon name belongs
529
     * @see    					 #TaxonNameBase()
530
     * @see    					 #TaxonNameBase(Rank)
531
     * @see    					 #TaxonNameBase(HomotypicalGroup)
532
     */
533
    protected TaxonNameBase(Rank rank, HomotypicalGroup homotypicalGroup) {
534
        super();
535
        this.setRank(rank);
536
        if (homotypicalGroup == null){
537
            homotypicalGroup = new HomotypicalGroup();
538
        }
539
        homotypicalGroup.addTypifiedName(this);
540
        this.homotypicalGroup = homotypicalGroup;
541
        setNameCacheStrategy();
542
    }
543

    
544

    
545
    /**
546
     * Class constructor: creates a new non viral taxon name instance
547
     * containing its {@link Rank rank},
548
     * its {@link HomotypicalGroup homotypical group},
549
     * its scientific name components, its {@link eu.etaxonomy.cdm.model.agent.TeamOrPersonBase author(team)},
550
     * its {@link eu.etaxonomy.cdm.model.reference.Reference nomenclatural reference} and
551
     * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
552
     * The new non viral taxon name instance will be also added to the set of
553
     * non viral taxon names belonging to this homotypical group.
554
     *
555
     * @param   rank  the rank to be assigned to <i>this</i> non viral taxon name
556
     * @param   genusOrUninomial the string for <i>this</i> non viral taxon name
557
     *          if its rank is genus or higher or for the genus part
558
     *          if its rank is lower than genus
559
     * @param   infraGenericEpithet  the string for the first epithet of
560
     *          <i>this</i> non viral taxon name if its rank is lower than genus
561
     *          and higher than species aggregate
562
     * @param   specificEpithet  the string for the first epithet of
563
     *          <i>this</i> non viral taxon name if its rank is species aggregate or lower
564
     * @param   infraSpecificEpithet  the string for the second epithet of
565
     *          <i>this</i> non viral taxon name if its rank is lower than species
566
     * @param   combinationAuthorship  the author or the team who published <i>this</i> non viral taxon name
567
     * @param   nomenclaturalReference  the nomenclatural reference where <i>this</i> non viral taxon name was published
568
     * @param   nomenclMicroRef  the string with the details for precise location within the nomenclatural reference
569
     * @param   homotypicalGroup  the homotypical group to which <i>this</i> non viral taxon name belongs
570
     * @see     #NonViralName()
571
     * @see     #NonViralName(Rank, HomotypicalGroup)
572
     * @see     #NewInstance(Rank, HomotypicalGroup)
573
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
574
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
575
     * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
576
     */
577
    protected TaxonNameBase(Rank rank, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, TeamOrPersonBase combinationAuthorship, INomenclaturalReference nomenclaturalReference, String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
578
        this(rank, homotypicalGroup);
579
        setGenusOrUninomial(genusOrUninomial);
580
        setInfraGenericEpithet (infraGenericEpithet);
581
        setSpecificEpithet(specificEpithet);
582
        setInfraSpecificEpithet(infraSpecificEpithet);
583
        setCombinationAuthorship(combinationAuthorship);
584
        setNomenclaturalReference(nomenclaturalReference);
585
        this.setNomenclaturalMicroReference(nomenclMicroRef);
586
    }
587

    
588

    
589
    private void setNameCacheStrategy(){
590
        if (getClass() == NonViralName.class){
591
            this.cacheStrategy = (S)NonViralNameDefaultCacheStrategy.NewInstance();
592
        }
593
    }
594

    
595

    
596
    @Override
597
    public void initListener(){
598
        PropertyChangeListener listener = new PropertyChangeListener() {
599
            @Override
600
            public void propertyChange(PropertyChangeEvent e) {
601
                boolean protectedByLowerCache = false;
602
                //authorship cache
603
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
604
                    if (protectedAuthorshipCache){
605
                        protectedByLowerCache = true;
606
                    }else{
607
                        authorshipCache = null;
608
                    }
609
                }
610

    
611
                //nameCache
612
                if (fieldHasCacheUpdateProperty(e.getPropertyName(), "nameCache")){
613
                    if (protectedNameCache){
614
                        protectedByLowerCache = true;
615
                    }else{
616
                        nameCache = null;
617
                    }
618
                }
619
                //title cache
620
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "titleCache")){
621
                    if (isProtectedTitleCache()|| protectedByLowerCache == true ){
622
                        protectedByLowerCache = true;
623
                    }else{
624
                        titleCache = null;
625
                    }
626
                }
627
                //full title cache
628
                if (! fieldHasNoUpdateProperty(e.getPropertyName(), "fullTitleCache")){
629
                    if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
630
                        protectedByLowerCache = true;
631
                    }else{
632
                        fullTitleCache = null;
633
                    }
634
                }
635
            }
636
        };
637
        addPropertyChangeListener(listener);  //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
638
    }
639

    
640
    private static Map<String, java.lang.reflect.Field> allFields = null;
641
    protected Map<String, java.lang.reflect.Field> getAllFields(){
642
        if (allFields == null){
643
            allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
644
        }
645
        return allFields;
646
    }
647

    
648
    /**
649
     * @param propertyName
650
     * @param string
651
     * @return
652
     */
653
    private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
654
        java.lang.reflect.Field field;
655
        try {
656
            field = getAllFields().get(propertyName);
657
            if (field != null){
658
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
659
                if (updateAnnotation != null){
660
                    for (String value : updateAnnotation.value()){
661
                        if (cacheName.equals(value)){
662
                            return true;
663
                        }
664
                    }
665
                }
666
            }
667
            return false;
668
        } catch (SecurityException e1) {
669
            throw e1;
670
        }
671
    }
672

    
673
    private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
674
        java.lang.reflect.Field field;
675
        //do not update fields with the same name
676
        if (cacheName.equals(propertyName)){
677
            return true;
678
        }
679
        //evaluate annotation
680
        try {
681
            field = getAllFields().get(propertyName);
682
            if (field != null){
683
                CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
684
                if (updateAnnotation != null){
685
                    for (String value : updateAnnotation.noUpdate()){
686
                        if (cacheName.equals(value)){
687
                            return true;
688
                        }
689
                    }
690
                }
691
            }
692
            return false;
693
        } catch (SecurityException e1) {
694
            throw e1;
695
        }
696
    }
697

    
698
// ****************** GETTER / SETTER ****************************/
699

    
700
    /**
701
     * Returns the boolean value of the flag intended to protect (true)
702
     * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
703
     * string of <i>this</i> non viral taxon name.
704
     *
705
     * @return  the boolean value of the protectedNameCache flag
706
     * @see     #getNameCache()
707
     */
708
    public boolean isProtectedNameCache() {
709
        return protectedNameCache;
710
    }
711

    
712
    /**
713
     * @see     #isProtectedNameCache()
714
     */
715
    public void setProtectedNameCache(boolean protectedNameCache) {
716
        this.protectedNameCache = protectedNameCache;
717
    }
718

    
719
    /**
720
     * Returns either the scientific name string (without authorship) for <i>this</i>
721
     * non viral taxon name if its rank is genus or higher (monomial) or the string for
722
     * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
723
     * Genus or uninomial strings begin with an upper case letter.
724
     *
725
     * @return  the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
726
     * @see     #getNameCache()
727
     */
728
    public String getGenusOrUninomial() {
729
        return genusOrUninomial;
730
    }
731

    
732
    /**
733
     * @see  #getGenusOrUninomial()
734
     */
735
    public void setGenusOrUninomial(String genusOrUninomial) {
736
        this.genusOrUninomial = StringUtils.isBlank(genusOrUninomial) ? null : genusOrUninomial;
737
    }
738

    
739
    /**
740
     * Returns the genus subdivision epithet string (infrageneric part) for
741
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
742
     * higher than species aggregate: binomial). Genus subdivision epithet
743
     * strings begin with an upper case letter.
744
     *
745
     * @return  the string containing the infrageneric part of <i>this</i> non viral taxon name
746
     * @see     #getNameCache()
747
     */
748
    public String getInfraGenericEpithet(){
749
        return this.infraGenericEpithet;
750
    }
751

    
752
    /**
753
     * @see  #getInfraGenericEpithet()
754
     */
755
    public void setInfraGenericEpithet(String infraGenericEpithet){
756
        this.infraGenericEpithet = StringUtils.isBlank(infraGenericEpithet)? null : infraGenericEpithet;
757
    }
758

    
759
    /**
760
     * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
761
     * species aggregate or lower (bi- or trinomial). Species epithet strings
762
     * begin with a lower case letter.
763
     *
764
     * @return  the string containing the species epithet of <i>this</i> non viral taxon name
765
     * @see     #getNameCache()
766
     */
767
    public String getSpecificEpithet(){
768
        return this.specificEpithet;
769
    }
770

    
771
    /**
772
     * @see  #getSpecificEpithet()
773
     */
774
    public void setSpecificEpithet(String specificEpithet){
775
        this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
776
    }
777

    
778
    /**
779
     * Returns the species subdivision epithet string (infraspecific part) for
780
     * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
781
     * (lower than species: trinomial). Species subdivision epithet strings
782
     * begin with a lower case letter.
783
     *
784
     * @return  the string containing the infraspecific part of <i>this</i> non viral taxon name
785
     * @see     #getNameCache()
786
     */
787
    public String getInfraSpecificEpithet(){
788
        return this.infraSpecificEpithet;
789
    }
790

    
791
    /**
792
     * @see  #getInfraSpecificEpithet()
793
     */
794
    public void setInfraSpecificEpithet(String infraSpecificEpithet){
795
        this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
796
    }
797

    
798
    /**
799
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
800
     * taxon name.
801
     *
802
     * @return  the nomenclatural author (team) of <i>this</i> non viral taxon name
803
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
804
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
805
     */
806
    public TeamOrPersonBase<?> getCombinationAuthorship(){
807
        return this.combinationAuthorship;
808
    }
809

    
810
    /**
811
     * @see  #getCombinationAuthorship()
812
     */
813
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
814
        this.combinationAuthorship = combinationAuthorship;
815
    }
816

    
817
    /**
818
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
819
     * the publication of <i>this</i> non viral taxon name as generally stated by
820
     * the {@link #getCombinationAuthorship() combination author (team)} itself.<BR>
821
     * An ex-author(-team) is an author(-team) to whom a taxon name was ascribed
822
     * although it is not the author(-team) of a valid publication (for instance
823
     * without the validating description or diagnosis in case of a name for a
824
     * new taxon). The name of this ascribed authorship, followed by "ex", may
825
     * be inserted before the name(s) of the publishing author(s) of the validly
826
     * published name:<BR>
827
     * <i>Lilium tianschanicum</i> was described by Grubov (1977) as a new species and
828
     * its name was ascribed to Ivanova; since there is no indication that
829
     * Ivanova provided the validating description, the name may be cited as
830
     * <i>Lilium tianschanicum</i> N. A. Ivanova ex Grubov or <i>Lilium tianschanicum</i> Grubov.
831
     * <P>
832
     * The presence of an author (team) of <i>this</i> non viral taxon name is a
833
     * condition for the existence of an ex author (team) for <i>this</i> same name.
834
     *
835
     * @return  the nomenclatural ex author (team) of <i>this</i> non viral taxon name
836
     * @see     #getCombinationAuthorship()
837
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
838
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
839
     */
840
    public TeamOrPersonBase<?> getExCombinationAuthorship(){
841
        return this.exCombinationAuthorship;
842
    }
843

    
844
    /**
845
     * @see  #getExCombinationAuthorship()
846
     */
847
    public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
848
        this.exCombinationAuthorship = exCombinationAuthorship;
849
    }
850

    
851
    /**
852
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
853
     * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
854
     * author (team) can only exist if <i>this</i> non viral taxon name is a new
855
     * combination due to a taxonomical revision.
856
     *
857
     * @return  the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
858
     * @see     #getCombinationAuthorship()
859
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
860
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
861
     */
862
    public TeamOrPersonBase<?> getBasionymAuthorship(){
863
        return basionymAuthorship;
864
    }
865

    
866
    /**
867
     * @see  #getBasionymAuthorship()
868
     */
869
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
870
        this.basionymAuthorship = basionymAuthorship;
871
    }
872

    
873
    /**
874
     * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
875
     * the publication of the original combination <i>this</i> non viral taxon name is
876
     * based on. This should have been generally stated by
877
     * the {@link #getBasionymAuthorship() basionym author (team)} itself.
878
     * The presence of a basionym author (team) of <i>this</i> non viral taxon name is a
879
     * condition for the existence of an ex basionym author (team)
880
     * for <i>this</i> same name.
881
     *
882
     * @return  the nomenclatural ex basionym author (team) of <i>this</i> non viral taxon name
883
     * @see     #getBasionymAuthorship()
884
     * @see     #getExCombinationAuthorship()
885
     * @see     #getCombinationAuthorship()
886
     * @see     eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
887
     * @see     eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
888
     */
889
    public TeamOrPersonBase<?> getExBasionymAuthorship(){
890
        return exBasionymAuthorship;
891
    }
892

    
893
    /**
894
     * @see  #getExBasionymAuthorship()
895
     */
896
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
897
        this.exBasionymAuthorship = exBasionymAuthorship;
898
    }
899

    
900
    /**
901
     * Returns the boolean value of the flag intended to protect (true)
902
     * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
903
     * of <i>this</i> non viral taxon name.
904
     *
905
     * @return  the boolean value of the protectedAuthorshipCache flag
906
     * @see     #getAuthorshipCache()
907
     */
908
    public boolean isProtectedAuthorshipCache() {
909
        return protectedAuthorshipCache;
910
    }
911

    
912
    /**
913
     * @see     #isProtectedAuthorshipCache()
914
     * @see     #getAuthorshipCache()
915
     */
916
    public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
917
        this.protectedAuthorshipCache = protectedAuthorshipCache;
918
    }
919

    
920
    /**
921
     * Returns the set of all {@link HybridRelationship hybrid relationships}
922
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
923
     *
924
     * @see    #getHybridRelationships()
925
     * @see    #getChildRelationships()
926
     * @see    HybridRelationshipType
927
     */
928
    public Set<HybridRelationship> getHybridParentRelations() {
929
        if(hybridParentRelations == null) {
930
            this.hybridParentRelations = new HashSet<>();
931
        }
932
        return hybridParentRelations;
933
    }
934

    
935
    private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
936
        this.hybridParentRelations = hybridParentRelations;
937
    }
938

    
939

    
940
    /**
941
     * Returns the set of all {@link HybridRelationship hybrid relationships}
942
     * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
943
     *
944
     * @see    #getHybridRelationships()
945
     * @see    #getParentRelationships()
946
     * @see    HybridRelationshipType
947
     */
948
    public Set<HybridRelationship> getHybridChildRelations() {
949
        if(hybridChildRelations == null) {
950
            this.hybridChildRelations = new HashSet<>();
951
        }
952
        return hybridChildRelations;
953
    }
954

    
955
    private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
956
        this.hybridChildRelations = hybridChildRelations;
957
    }
958

    
959
    @Override
960
    public boolean isProtectedFullTitleCache() {
961
        return protectedFullTitleCache;
962
    }
963

    
964
    @Override
965
    public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
966
        this.protectedFullTitleCache = protectedFullTitleCache;
967
    }
968

    
969
    /**
970
     * Returns the boolean value of the flag indicating whether the name of <i>this</i>
971
     * botanical taxon name is a hybrid formula (true) or not (false). A hybrid
972
     * named by a hybrid formula (composed with its parent names by placing the
973
     * multiplication sign between them) does not have an own published name
974
     * and therefore has neither an {@link NonViralName#getAuthorshipCache() autorship}
975
     * nor other name components. If this flag is set no other hybrid flags may
976
     * be set.
977
     *
978
     * @return  the boolean value of the isHybridFormula flag
979
     * @see     #isMonomHybrid()
980
     * @see     #isBinomHybrid()
981
     * @see     #isTrinomHybrid()
982
     */
983
    public boolean isHybridFormula(){
984
        return this.hybridFormula;
985
    }
986

    
987
    /**
988
     * @see  #isHybridFormula()
989
     */
990
    public void setHybridFormula(boolean hybridFormula){
991
        this.hybridFormula = hybridFormula;
992
    }
993

    
994
    /**
995
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
996
     * taxon name is the name of an intergeneric hybrid (true) or not (false).
997
     * In this case the multiplication sign is placed before the scientific
998
     * name. If this flag is set no other hybrid flags may be set.
999
     *
1000
     * @return  the boolean value of the isMonomHybrid flag
1001
     * @see     #isHybridFormula()
1002
     * @see     #isBinomHybrid()
1003
     * @see     #isTrinomHybrid()
1004
     */
1005
    public boolean isMonomHybrid(){
1006
        return this.monomHybrid;
1007
    }
1008

    
1009
    /**
1010
     * @see  #isMonomHybrid()
1011
     * @see  #isBinomHybrid()
1012
     * @see  #isTrinomHybrid()
1013
     */
1014
    public void setMonomHybrid(boolean monomHybrid){
1015
        this.monomHybrid = monomHybrid;
1016
    }
1017

    
1018
    /**
1019
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1020
     * taxon name is the name of an interspecific hybrid (true) or not (false).
1021
     * In this case the multiplication sign is placed before the species
1022
     * epithet. If this flag is set no other hybrid flags may be set.
1023
     *
1024
     * @return  the boolean value of the isBinomHybrid flag
1025
     * @see     #isHybridFormula()
1026
     * @see     #isMonomHybrid()
1027
     * @see     #isTrinomHybrid()
1028
     */
1029
    public boolean isBinomHybrid(){
1030
        return this.binomHybrid;
1031
    }
1032

    
1033
    /**
1034
     * @see  #isBinomHybrid()
1035
     * @see  #isMonomHybrid()
1036
     * @see  #isTrinomHybrid()
1037
     */
1038
    public void setBinomHybrid(boolean binomHybrid){
1039
        this.binomHybrid = binomHybrid;
1040
    }
1041

    
1042
    /**
1043
     * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1044
     * taxon name is the name of an infraspecific hybrid (true) or not (false).
1045
     * In this case the term "notho-" (optionally abbreviated "n-") is used as
1046
     * a prefix to the term denoting the infraspecific rank of <i>this</i> botanical
1047
     * taxon name. If this flag is set no other hybrid flags may be set.
1048
     *
1049
     * @return  the boolean value of the isTrinomHybrid flag
1050
     * @see     #isHybridFormula()
1051
     * @see     #isMonomHybrid()
1052
     * @see     #isBinomHybrid()
1053
     */
1054
    public boolean isTrinomHybrid(){
1055
        return this.trinomHybrid;
1056
    }
1057

    
1058
    /**
1059
     * @see  #isTrinomHybrid()
1060
     * @see  #isBinomHybrid()
1061
     * @see  #isMonomHybrid()
1062
     */
1063
    public void setTrinomHybrid(boolean trinomHybrid){
1064
        this.trinomHybrid = trinomHybrid;
1065
    }
1066

    
1067
// **************** ADDER / REMOVE *************************/
1068

    
1069
    /**
1070
     * Adds the given {@link HybridRelationship hybrid relationship} to the set
1071
     * of {@link #getHybridRelationships() hybrid relationships} of both non-viral names
1072
     * involved in this hybrid relationship. One of both non-viral names
1073
     * must be <i>this</i> non-viral name otherwise no addition will be carried
1074
     * out. The {@link eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedTo() child
1075
     * non viral taxon name} must be a hybrid, which means that one of its four hybrid flags must be set.
1076
     *
1077
     * @param relationship  the hybrid relationship to be added
1078
     * @see                 #isHybridFormula()
1079
     * @see                 #isMonomHybrid()
1080
     * @see                 #isBinomHybrid()
1081
     * @see                 #isTrinomHybrid()
1082
     * @see                 #getHybridRelationships()
1083
     * @see                 #getParentRelationships()
1084
     * @see                 #getChildRelationships()
1085
     * @see                 #addRelationship(RelationshipBase)
1086
     * @throws              IllegalArgumentException
1087
     */
1088
    protected void addHybridRelationship(HybridRelationship rel) {
1089
        if (rel!=null && rel.getHybridName().equals(this)){
1090
            this.hybridChildRelations.add(rel);
1091
        }else if(rel!=null && rel.getParentName().equals(this)){
1092
            this.hybridParentRelations.add(rel);
1093
        }else{
1094
            throw new IllegalArgumentException("Hybrid relationship is either null or the relationship does not reference this name");
1095
        }
1096
    }
1097

    
1098

    
1099
    /**
1100
     * Removes one {@link HybridRelationship hybrid relationship} from the set of
1101
     * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1102
     * is involved. The hybrid relationship will also be removed from the set
1103
     * belonging to the second botanical taxon name involved.
1104
     *
1105
     * @param  relationship  the hybrid relationship which should be deleted from the corresponding sets
1106
     * @see                  #getHybridRelationships()
1107
     */
1108
    public void removeHybridRelationship(HybridRelationship hybridRelation) {
1109
        if (hybridRelation == null) {
1110
            return;
1111
        }
1112

    
1113
        TaxonNameBase<?,?> parent = hybridRelation.getParentName();
1114
        TaxonNameBase<?,?> child = hybridRelation.getHybridName();
1115
        if (this.equals(parent)){
1116
            this.hybridParentRelations.remove(hybridRelation);
1117
            child.hybridChildRelations.remove(hybridRelation);
1118
            hybridRelation.setHybridName(null);
1119
            hybridRelation.setParentName(null);
1120
        }
1121
        if (this.equals(child)){
1122
            parent.hybridParentRelations.remove(hybridRelation);
1123
            this.hybridChildRelations.remove(hybridRelation);
1124
            hybridRelation.setHybridName(null);
1125
            hybridRelation.setParentName(null);
1126
        }
1127
    }
1128

    
1129
//********* METHODS **************************************/
1130

    
1131
    @Override
1132
    public String generateFullTitle(){
1133
        if (cacheStrategy == null){
1134
            logger.warn("No CacheStrategy defined for nonViralName: " + this.getUuid());
1135
            return null;
1136
        }else{
1137
            return cacheStrategy.getFullTitleCache(this);
1138
        }
1139
    }
1140

    
1141

    
1142
    @Override
1143
    public void setFullTitleCache(String fullTitleCache){
1144
        setFullTitleCache(fullTitleCache, PROTECTED);
1145
    }
1146

    
1147
    @Override
1148
    public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1149
        fullTitleCache = getTruncatedCache(fullTitleCache);
1150
        this.fullTitleCache = fullTitleCache;
1151
        this.setProtectedFullTitleCache(protectCache);
1152
    }
1153

    
1154
    /**
1155
      * Needs to be implemented by those classes that handle autonyms (e.g. botanical names).
1156
      **/
1157
    @Transient
1158
    public boolean isAutonym(){
1159
        return false;
1160
    }
1161

    
1162

    
1163
    /**
1164
     * Returns the boolean value "false" since the components of <i>this</i> taxon name
1165
     * cannot follow the rules of a corresponding {@link NomenclaturalCode nomenclatural code}
1166
     * which is not defined for this class. The nomenclature code depends on
1167
     * the concrete name subclass ({@link BacterialName BacterialName},
1168
     * {@link BotanicalName BotanicalName}, {@link CultivarPlantName CultivarPlantName} or
1169
     * {@link ZoologicalName ZoologicalName} to which <i>this</i> non taxon name.
1170
     *
1171
     * @return  false
1172
     */
1173
    @Override
1174
    @Transient
1175
    public boolean isCodeCompliant() {
1176
        //FIXME
1177
        logger.warn("is CodeCompliant not implemented for TaxonNameBase");
1178
        return false;
1179
    }
1180

    
1181
    @Override
1182
    @Transient
1183
    public List<TaggedText> getTaggedName(){
1184
        return getCacheStrategy().getTaggedTitle(this);
1185
    }
1186

    
1187
    @Override
1188
    @Transient
1189
    public String getFullTitleCache(){
1190
        if (protectedFullTitleCache){
1191
            return this.fullTitleCache;
1192
        }
1193
        updateAuthorshipCache();
1194
        if (fullTitleCache == null ){
1195
            this.fullTitleCache = getTruncatedCache(generateFullTitle());
1196
        }
1197
        return fullTitleCache;
1198
    }
1199

    
1200

    
1201
    @Override
1202
    public String getTitleCache(){
1203
        if(!protectedTitleCache) {
1204
            updateAuthorshipCache();
1205
        }
1206
        return super.getTitleCache();
1207
    }
1208

    
1209
    @Override
1210
    public void setTitleCache(String titleCache, boolean protectCache){
1211
        super.setTitleCache(titleCache, protectCache);
1212
    }
1213

    
1214
    /**
1215
     * Returns the concatenated and formated authorteams string including
1216
     * basionym and combination authors of <i>this</i> non viral taxon name.
1217
     * If the protectedAuthorshipCache flag is set this method returns the
1218
     * string stored in the the authorshipCache attribute, otherwise it
1219
     * generates the complete authorship string, returns it and stores it in
1220
     * the authorshipCache attribute.
1221
     *
1222
     * @return  the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
1223
     * @see     #generateAuthorship()
1224
     */
1225
    @Transient
1226
    public String getAuthorshipCache() {
1227
        if (protectedAuthorshipCache){
1228
            return this.authorshipCache;
1229
        }
1230
        if (this.authorshipCache == null ){
1231
            this.authorshipCache = generateAuthorship();
1232
        }else{
1233
            //TODO get isDirty of authors, make better if possible
1234
            this.setAuthorshipCache(generateAuthorship(), protectedAuthorshipCache); //throw change event to inform higher caches
1235

    
1236
        }
1237
        return authorshipCache;
1238
    }
1239

    
1240

    
1241

    
1242
    /**
1243
     * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
1244
     * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
1245
     * @return
1246
     */
1247
    private void updateAuthorshipCache() {
1248
        //updates the authorship cache if necessary and via the listener updates all higher caches
1249
        if (protectedAuthorshipCache == false){
1250
            String oldCache = this.authorshipCache;
1251
            String newCache = this.getAuthorshipCache();
1252
            if ( (oldCache == null && newCache != null)  ||  CdmUtils.nullSafeEqual(oldCache,newCache)){
1253
                this.setAuthorshipCache(this.getAuthorshipCache(), false);
1254
            }
1255
        }
1256
    }
1257

    
1258

    
1259
    /**
1260
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1261
     * flag to <code>true</code>.
1262
     *
1263
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1264
     * @see    #getAuthorshipCache()
1265
     */
1266
    public void setAuthorshipCache(String authorshipCache) {
1267
        setAuthorshipCache(authorshipCache, true);
1268
    }
1269

    
1270

    
1271
    /**
1272
     * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1273
     *
1274
     * @param  authorshipCache  the string which identifies the complete authorship of <i>this</i> non viral taxon name
1275
     * @param  protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1276
     * the flag is set to <code>false</code>.
1277
     * @see    #getAuthorshipCache()
1278
     */
1279
    public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1280
        this.authorshipCache = authorshipCache;
1281
        this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1282
    }
1283

    
1284
    /**
1285
     * Generates and returns a concatenated and formated authorteams string
1286
     * including basionym and combination authors of <i>this</i> non viral taxon name
1287
     * according to the strategy defined in
1288
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonNameBase) INonViralNameCacheStrategy}.
1289
     *
1290
     * @return  the string with the concatenated and formatted author teams for <i>this</i> taxon name
1291
     * @see     eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonNameBase)
1292
     */
1293
    public String generateAuthorship(){
1294
        if (cacheStrategy == null){
1295
            logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1296
            return null;
1297
        }else{
1298
            return ((INonViralNameCacheStrategy)cacheStrategy).getAuthorshipCache(this);
1299
        }
1300
    }
1301

    
1302

    
1303

    
1304
    /**
1305
     * Tests if the given name has any authors.
1306
     * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1307
     */
1308
    public boolean hasAuthors() {
1309
        return (this.getCombinationAuthorship() != null ||
1310
                this.getExCombinationAuthorship() != null ||
1311
                this.getBasionymAuthorship() != null ||
1312
                this.getExBasionymAuthorship() != null);
1313
    }
1314

    
1315
    /**
1316
     * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1317
     * @return
1318
     */
1319
    public String computeCombinationAuthorNomenclaturalTitle() {
1320
        return computeNomenclaturalTitle(this.getCombinationAuthorship());
1321
    }
1322

    
1323
    /**
1324
     * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1325
     * @return
1326
     */
1327
    public String computeBasionymAuthorNomenclaturalTitle() {
1328
        return computeNomenclaturalTitle(this.getBasionymAuthorship());
1329
    }
1330

    
1331

    
1332
    /**
1333
     * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1334
     * @return
1335
     */
1336
    public String computeExCombinationAuthorNomenclaturalTitle() {
1337
        return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1338
    }
1339

    
1340
    /**
1341
     * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1342
     * @return
1343
     */
1344
    public String computeExBasionymAuthorNomenclaturalTitle() {
1345
        return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1346
    }
1347

    
1348
    private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1349
        if (author == null){
1350
            return null;
1351
        }else{
1352
            return author.getNomenclaturalTitle();
1353
        }
1354
    }
1355

    
1356
    /**
1357
     * Returns the set of all {@link NameRelationship name relationships}
1358
     * in which <i>this</i> taxon name is involved. A taxon name can be both source
1359
     * in some name relationships or target in some others.
1360
     *
1361
     * @see    #getRelationsToThisName()
1362
     * @see    #getRelationsFromThisName()
1363
     * @see    #addNameRelationship(NameRelationship)
1364
     * @see    #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1365
     * @see    #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1366
     */
1367
    @Override
1368
    @Transient
1369
    public Set<NameRelationship> getNameRelations() {
1370
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
1371
        rels.addAll(getRelationsFromThisName());
1372
        rels.addAll(getRelationsToThisName());
1373
        return rels;
1374
    }
1375

    
1376
    /**
1377
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1378
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1379
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1380
     *
1381
     * @param toName		  the taxon name of the target for this new name relationship
1382
     * @param type			  the type of this new name relationship
1383
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1384
     * @see    				  #getRelationsToThisName()
1385
     * @see    				  #getNameRelations()
1386
     * @see    				  #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1387
     * @see    				  #addNameRelationship(NameRelationship)
1388
     */
1389
    @Override
1390
    public void addRelationshipToName(TaxonNameBase toName, NameRelationshipType type, String ruleConsidered){
1391
        addRelationshipToName(toName, type, null, null, ruleConsidered);
1392
        //		NameRelationship rel = new NameRelationship(toName, this, type, ruleConsidered);
1393
    }
1394

    
1395
    /**
1396
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1397
     * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1398
     * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1399
     *
1400
     * @param toName		  the taxon name of the target for this new name relationship
1401
     * @param type			  the type of this new name relationship
1402
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1403
     * @return
1404
     * @see    				  #getRelationsToThisName()
1405
     * @see    				  #getNameRelations()
1406
     * @see    				  #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1407
     * @see    				  #addNameRelationship(NameRelationship)
1408
     */
1409
    @Override
1410
    public NameRelationship addRelationshipToName(TaxonNameBase toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1411
        if (toName == null){
1412
            throw new NullPointerException("Null is not allowed as name for a name relationship");
1413
        }
1414
        NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered);
1415
        return rel;
1416
    }
1417

    
1418
    /**
1419
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1420
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1421
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1422
     *
1423
     * @param fromName		  the taxon name of the source for this new name relationship
1424
     * @param type			  the type of this new name relationship
1425
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1426
     * @param citation		  the reference in which this relation was described
1427
     * @param microCitation	  the reference detail for this relation (e.g. page)
1428
     * @see    				  #getRelationsFromThisName()
1429
     * @see    				  #getNameRelations()
1430
     * @see    				  #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1431
     * @see    				  #addNameRelationship(NameRelationship)
1432
     */
1433
    @Override
1434
    public NameRelationship addRelationshipFromName(TaxonNameBase fromName, NameRelationshipType type, String ruleConsidered){
1435
        //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1436
        return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
1437
    }
1438
    /**
1439
     * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1440
     * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1441
     * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1442
     *
1443
     * @param fromName		  the taxon name of the source for this new name relationship
1444
     * @param type			  the type of this new name relationship
1445
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
1446
     * @param citation		  the reference in which this relation was described
1447
     * @param microCitation	  the reference detail for this relation (e.g. page)
1448
     * @see    				  #getRelationsFromThisName()
1449
     * @see    				  #getNameRelations()
1450
     * @see    				  #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1451
     * @see    				  #addNameRelationship(NameRelationship)
1452
     */
1453
    @Override
1454
    public NameRelationship addRelationshipFromName(TaxonNameBase fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1455
        return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1456
    }
1457

    
1458
    /**
1459
     * Adds an existing {@link NameRelationship name relationship} either to the set of
1460
     * {@link #getRelationsToThisName() relations to <i>this</i> taxon name} or to the set of
1461
     * {@link #getRelationsFromThisName() relations from <i>this</i> taxon name}. If neither the
1462
     * source nor the target of the name relationship match with <i>this</i> taxon name
1463
     * no addition will be carried out.
1464
     *
1465
     * @param rel  the name relationship to be added to one of <i>this</i> taxon name's name relationships sets
1466
     * @see    	   #getNameRelations()
1467
     * @see    	   #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1468
     * @see    	   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1469
     */
1470
    protected void addNameRelationship(NameRelationship rel) {
1471
        if (rel != null ){
1472
            if (rel.getToName().equals(this)){
1473
                this.relationsToThisName.add(rel);
1474
            }else if(rel.getFromName().equals(this)){
1475
                this.relationsFromThisName.add(rel);
1476
            }
1477
            NameRelationshipType type = rel.getType();
1478
            if (type != null && ( type.isBasionymRelation() || type.isReplacedSynonymRelation() ) ){
1479
                rel.getFromName().mergeHomotypicGroups(rel.getToName());
1480
            }
1481
        }else{
1482
            throw new RuntimeException("NameRelationship is either null or the relationship does not reference this name");
1483
        }
1484
    }
1485
    /**
1486
     * Removes one {@link NameRelationship name relationship} from one of both sets of
1487
     * {@link #getNameRelations() name relationships} in which <i>this</i> taxon name is involved.
1488
     * The name relationship will also be removed from one of both sets belonging
1489
     * to the second taxon name involved. Furthermore the fromName and toName
1490
     * attributes of the name relationship object will be nullified.
1491
     *
1492
     * @param  nameRelation  the name relationship which should be deleted from one of both sets
1493
     * @see    				 #getNameRelations()
1494
     */
1495
    @Override
1496
    public void removeNameRelationship(NameRelationship nameRelation) {
1497

    
1498
        TaxonNameBase fromName = nameRelation.getFromName();
1499
        TaxonNameBase toName = nameRelation.getToName();
1500

    
1501
        if (nameRelation != null) {
1502
            nameRelation.setToName(null);
1503
            nameRelation.setFromName(null);
1504
        }
1505

    
1506
        if (fromName != null) {
1507
            fromName.removeNameRelationship(nameRelation);
1508
        }
1509

    
1510
        if (toName != null) {
1511
            toName.removeNameRelationship(nameRelation);
1512
        }
1513

    
1514
        this.relationsToThisName.remove(nameRelation);
1515
        this.relationsFromThisName.remove(nameRelation);
1516
    }
1517

    
1518
    @Override
1519
    public void removeRelationToTaxonName(TaxonNameBase toTaxonName) {
1520
        Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1521
//		nameRelationships.addAll(this.getNameRelations());
1522
        nameRelationships.addAll(this.getRelationsFromThisName());
1523
        nameRelationships.addAll(this.getRelationsToThisName());
1524
        for(NameRelationship nameRelationship : nameRelationships) {
1525
            // remove name relationship from this side
1526
            if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1527
                this.removeNameRelationship(nameRelationship);
1528
            }
1529
        }
1530
    }
1531

    
1532

    
1533
    /**
1534
     * If relation is of type NameRelationship, addNameRelationship is called;
1535
     * if relation is of type HybridRelationship addHybridRelationship is called,
1536
     * otherwise an IllegalArgumentException is thrown.
1537
     *
1538
     * @param relation  the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1539
     * @see    	   		#addNameRelationship(NameRelationship)
1540
     * @see    	   		#getNameRelations()
1541
     * @see    	   		NameRelationship
1542
     * @see    	   		RelationshipBase
1543
     * @see             #addHybridRelationship(HybridRelationship)
1544

    
1545
     * @deprecated to be used by RelationshipBase only
1546
     */
1547
    @Deprecated
1548
    @Override
1549
    public void addRelationship(RelationshipBase relation) {
1550
        if (relation instanceof NameRelationship){
1551
            addNameRelationship((NameRelationship)relation);
1552

    
1553
        }else if (relation instanceof HybridRelationship){
1554
            addHybridRelationship((HybridRelationship)relation);
1555
        }else{
1556
            logger.warn("Relationship not of type NameRelationship!");
1557
            throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1558
        }
1559
    }
1560

    
1561
    /**
1562
     * Returns the set of all {@link NameRelationship name relationships}
1563
     * in which <i>this</i> taxon name is involved as a source ("from"-side).
1564
     *
1565
     * @see    #getNameRelations()
1566
     * @see    #getRelationsToThisName()
1567
     * @see    #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1568
     */
1569
    @Override
1570
    public Set<NameRelationship> getRelationsFromThisName() {
1571
        if(relationsFromThisName == null) {
1572
            this.relationsFromThisName = new HashSet<>();
1573
        }
1574
        return relationsFromThisName;
1575
    }
1576

    
1577
    /**
1578
     * Returns the set of all {@link NameRelationship name relationships}
1579
     * in which <i>this</i> taxon name is involved as a target ("to"-side).
1580
     *
1581
     * @see    #getNameRelations()
1582
     * @see    #getRelationsFromThisName()
1583
     * @see    #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1584
     */
1585
    @Override
1586
    public Set<NameRelationship> getRelationsToThisName() {
1587
        if(relationsToThisName == null) {
1588
            this.relationsToThisName = new HashSet<>();
1589
        }
1590
        return relationsToThisName;
1591
    }
1592

    
1593
    /**
1594
     * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1595
     * to <i>this</i> taxon name according to its corresponding nomenclature code.
1596
     * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1597
     * and the nomenclatural code rule considered.
1598
     *
1599
     * @see     NomenclaturalStatus
1600
     * @see     NomenclaturalStatusType
1601
     */
1602
    @Override
1603
    public Set<NomenclaturalStatus> getStatus() {
1604
        if(status == null) {
1605
            this.status = new HashSet<>();
1606
        }
1607
        return status;
1608
    }
1609

    
1610
    /**
1611
     * Adds a new {@link NomenclaturalStatus nomenclatural status}
1612
     * to <i>this</i> taxon name's set of nomenclatural status.
1613
     *
1614
     * @param  nomStatus  the nomenclatural status to be added
1615
     * @see 			  #getStatus()
1616
     */
1617
    @Override
1618
    public void addStatus(NomenclaturalStatus nomStatus) {
1619
        this.status.add(nomStatus);
1620
    }
1621
    @Override
1622
    public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1623
        NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1624
        this.status.add(newStatus);
1625
        return newStatus;
1626
    }
1627

    
1628
    /**
1629
     * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1630
     * Type and ruleConsidered attributes of the nomenclatural status object
1631
     * will be nullified.
1632
     *
1633
     * @param  nomStatus  the nomenclatural status of <i>this</i> taxon name which should be deleted
1634
     * @see     		  #getStatus()
1635
     */
1636
    @Override
1637
    public void removeStatus(NomenclaturalStatus nomStatus) {
1638
        //TODO to be implemented?
1639
        logger.warn("not yet fully implemented?");
1640
        this.status.remove(nomStatus);
1641
    }
1642

    
1643

    
1644
    /**
1645
     * Generates the composed name string of <i>this</i> non viral taxon name without author
1646
     * strings or year according to the strategy defined in
1647
     * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1648
     * The result might be stored in {@link #getNameCache() nameCache} if the
1649
     * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1650
     *
1651
     * @return  the string with the composed name of <i>this</i> non viral taxon name without authors or year
1652
     * @see     #getNameCache()
1653
     */
1654
    protected String generateNameCache(){
1655
        if (cacheStrategy == null){
1656
            logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1657
            return null;
1658
        }else{
1659
            return cacheStrategy.getNameCache(this);
1660
        }
1661
    }
1662

    
1663
    /**
1664
     * Returns or generates the nameCache (scientific name
1665
     * without author strings and year) string for <i>this</i> non viral taxon name. If the
1666
     * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1667
     * the string will be generated according to a defined strategy,
1668
     * otherwise the value of the actual nameCache string will be returned.
1669
     *
1670
     * @return  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1671
     * @see     #generateNameCache()
1672
     */
1673
    @Transient
1674
    public String getNameCache() {
1675
        if (protectedNameCache){
1676
            return this.nameCache;
1677
        }
1678
        // is title dirty, i.e. equal NULL?
1679
        if (nameCache == null){
1680
            this.nameCache = generateNameCache();
1681
        }
1682
        return nameCache;
1683
    }
1684

    
1685
    /**
1686
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1687
     * Sets the protectedNameCache flag to <code>true</code>.
1688
     *
1689
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1690
     * @see    #getNameCache()
1691
     */
1692
    public void setNameCache(String nameCache){
1693
        setNameCache(nameCache, true);
1694
    }
1695

    
1696
    /**
1697
     * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1698
     * Sets the protectedNameCache flag to <code>true</code>.
1699
     *
1700
     * @param  nameCache  the string which identifies <i>this</i> non viral taxon name (without authors or year)
1701
     * @param  protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
1702
     * <code>false</code>
1703
     * @see    #getNameCache()
1704
     */
1705
    public void setNameCache(String nameCache, boolean protectedNameCache){
1706
        this.nameCache = nameCache;
1707
        this.setProtectedNameCache(protectedNameCache);
1708
    }
1709

    
1710

    
1711
    /**
1712
     * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
1713
     * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1714
     * of any other taxon name. Returns "true", if a basionym or a replaced
1715
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1716
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1717
     * homotypical group).
1718
     */
1719
    @Override
1720
    @Transient
1721
    public boolean isOriginalCombination(){
1722
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1723
        for (NameRelationship relation : relationsFromThisName) {
1724
            if (relation.getType().isBasionymRelation() ||
1725
                    relation.getType().isReplacedSynonymRelation()) {
1726
                return true;
1727
            }
1728
        }
1729
        return false;
1730
    }
1731

    
1732
    /**
1733
     * Indicates <i>this</i> taxon name is a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1734
     * of any other taxon name. Returns "true", if a replaced
1735
     * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1736
     * false otherwise (also in case <i>this</i> taxon name is the only one in the
1737
     * homotypical group).
1738
     */
1739
    @Override
1740
    @Transient
1741
    public boolean isReplacedSynonym(){
1742
        Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1743
        for (NameRelationship relation : relationsFromThisName) {
1744
            if (relation.getType().isReplacedSynonymRelation()) {
1745
                return true;
1746
            }
1747
        }
1748
        return false;
1749
    }
1750

    
1751
    /**
1752
     * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
1753
     * The basionym of a taxon name is its epithet-bringing synonym.
1754
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
1755
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
1756
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
1757
     *
1758
     * If more than one basionym exists one is choosen at radom.
1759
     *
1760
     * If no basionym exists null is returned.
1761
     */
1762
    @Override
1763
    @Transient
1764
    public TaxonNameBase getBasionym(){
1765
        Set<TaxonNameBase> basionyms = getBasionyms();
1766
        if (basionyms.size() == 0){
1767
            return null;
1768
        }else{
1769
            return basionyms.iterator().next();
1770
        }
1771
    }
1772

    
1773
    /**
1774
     * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
1775
     * The basionym of a taxon name is its epithet-bringing synonym.
1776
     * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
1777
     * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
1778
     * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
1779
     */
1780
    @Override
1781
    @Transient
1782
    public Set<TaxonNameBase> getBasionyms(){
1783
        Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
1784
        Set<NameRelationship> rels = this.getRelationsToThisName();
1785
        for (NameRelationship rel : rels){
1786
            if (rel.getType()!= null && rel.getType().isBasionymRelation()){
1787
                TaxonNameBase<?,?> basionym = rel.getFromName();
1788
                result.add(basionym);
1789
            }
1790
        }
1791
        return result;
1792
    }
1793

    
1794
    /**
1795
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
1796
     * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
1797
     * and to the basionym. The basionym cannot have itself a basionym.
1798
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
1799
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
1800
     *
1801
     * @param  basionym		the taxon name to be set as the basionym of <i>this</i> taxon name
1802
     * @see  				#getBasionym()
1803
     * @see  				#addBasionym(TaxonNameBase, String)
1804
     */
1805
    @Override
1806
    public void addBasionym(T basionym){
1807
        addBasionym(basionym, null, null, null);
1808
    }
1809
    /**
1810
     * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
1811
     * and keeps the nomenclatural rule considered for it. The basionym
1812
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
1813
     * The basionym cannot have itself a basionym.
1814
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
1815
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
1816
     *
1817
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
1818
     * @param  ruleConsidered	the string identifying the nomenclatural rule
1819
     * @return
1820
     * @see  					#getBasionym()
1821
     * @see  					#addBasionym(TaxonNameBase)
1822
     */
1823
    @Override
1824
    public NameRelationship addBasionym(T basionym, Reference citation, String microcitation, String ruleConsidered){
1825
        if (basionym != null){
1826
            return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
1827
        }else{
1828
            return null;
1829
        }
1830
    }
1831

    
1832
    /**
1833
     * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
1834
     *
1835
     */
1836
    @Override
1837
    @Transient
1838
    public Set<TaxonNameBase> getReplacedSynonyms(){
1839
        Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
1840
        Set<NameRelationship> rels = this.getRelationsToThisName();
1841
        for (NameRelationship rel : rels){
1842
            if (rel.getType().isReplacedSynonymRelation()){
1843
                TaxonNameBase replacedSynonym = rel.getFromName();
1844
                result.add(replacedSynonym);
1845
            }
1846
        }
1847
        return result;
1848
    }
1849

    
1850
    /**
1851
     * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
1852
     * and keeps the nomenclatural rule considered for it. The replaced synonym
1853
     * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
1854
     * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
1855
     * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
1856
     *
1857
     * @param  basionym			the taxon name to be set as the basionym of <i>this</i> taxon name
1858
     * @param  ruleConsidered	the string identifying the nomenclatural rule
1859
     * @see  					#getBasionym()
1860
     * @see  					#addBasionym(TaxonNameBase)
1861
     */
1862
    //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
1863
    @Override
1864
    public void addReplacedSynonym(T replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
1865
        if (replacedSynonym != null){
1866
            replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
1867
        }
1868
    }
1869

    
1870
    /**
1871
     * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
1872
     * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
1873
     * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
1874
     * previously used as basionym.
1875
     *
1876
     * @see   #getBasionym()
1877
     * @see   #addBasionym(TaxonNameBase)
1878
     */
1879
    @Override
1880
    public void removeBasionyms(){
1881
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
1882
        for (NameRelationship nameRelation : this.getRelationsToThisName()){
1883
            if (nameRelation.getType().isBasionymRelation()){
1884
                removeRelations.add(nameRelation);
1885
            }
1886
        }
1887
        // Removing relations from a set through which we are iterating causes a
1888
        // ConcurrentModificationException. Therefore, we delete the targeted
1889
        // relations in a second step.
1890
        for (NameRelationship relation : removeRelations){
1891
            this.removeNameRelationship(relation);
1892
        }
1893
    }
1894

    
1895
    /**
1896
     * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
1897
     *
1898
     * @see 	Rank
1899
     */
1900
    @Override
1901
    public Rank getRank(){
1902
        return this.rank;
1903
    }
1904

    
1905
    /**
1906
     * @see  #getRank()
1907
     */
1908
    @Override
1909
    public void setRank(Rank rank){
1910
        this.rank = rank;
1911
    }
1912

    
1913
    /**
1914
     * Returns the {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} of <i>this</i> taxon name.
1915
     * The nomenclatural reference is here meant to be the one publication
1916
     * <i>this</i> taxon name was originally published in while fulfilling the formal
1917
     * requirements as specified by the corresponding {@link NomenclaturalCode nomenclatural code}.
1918
     *
1919
     * @see 	eu.etaxonomy.cdm.model.reference.INomenclaturalReference
1920
     * @see 	eu.etaxonomy.cdm.model.reference.Reference
1921
     */
1922
    @Override
1923
    public INomenclaturalReference getNomenclaturalReference(){
1924
        return this.nomenclaturalReference;
1925
    }
1926
    /**
1927
     * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
1928
     * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
1929
     * as it is obviously used for nomenclatural purposes.
1930
     *
1931
     * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
1932
     * @see  #getNomenclaturalReference()
1933
     */
1934
    @Override
1935
    public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
1936
        if(nomenclaturalReference != null){
1937
            if(!INomenclaturalReference.class.isAssignableFrom(nomenclaturalReference.getClass())){
1938
                throw new IllegalArgumentException("Parameter nomenclaturalReference is not assignable from INomenclaturalReference");
1939
            }
1940
            this.nomenclaturalReference = (Reference)nomenclaturalReference;
1941
        } else {
1942
            this.nomenclaturalReference = null;
1943
        }
1944
    }
1945

    
1946
    /**
1947
     * Returns the appended phrase string assigned to <i>this</i> taxon name.
1948
     * The appended phrase is a non-atomised addition to a name. It is
1949
     * not ruled by a nomenclatural code.
1950
     */
1951
    @Override
1952
    public String getAppendedPhrase(){
1953
        return this.appendedPhrase;
1954
    }
1955

    
1956
    /**
1957
     * @see  #getAppendedPhrase()
1958
     */
1959
    @Override
1960
    public void setAppendedPhrase(String appendedPhrase){
1961
        this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
1962
    }
1963

    
1964
    /**
1965
     * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
1966
     * to <i>this</i> taxon name. The details describe the exact localisation within
1967
     * the publication used as nomenclature reference. These are mostly
1968
     * (implicitly) pages but can also be figures or tables or any other
1969
     * element of a publication. A nomenclatural micro reference (details)
1970
     * requires the existence of a nomenclatural reference.
1971
     */
1972
    //Details of the nomenclatural reference (protologue).
1973
    @Override
1974
    public String getNomenclaturalMicroReference(){
1975
        return this.nomenclaturalMicroReference;
1976
    }
1977
    /**
1978
     * @see  #getNomenclaturalMicroReference()
1979
     */
1980
    @Override
1981
    public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
1982
        this.nomenclaturalMicroReference = StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
1983
    }
1984

    
1985
    @Override
1986
    public int getParsingProblem(){
1987
        return this.parsingProblem;
1988
    }
1989

    
1990
    @Override
1991
    public void setParsingProblem(int parsingProblem){
1992
        this.parsingProblem = parsingProblem;
1993
    }
1994

    
1995
    @Override
1996
    public void addParsingProblem(ParserProblem problem){
1997
        parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
1998
    }
1999

    
2000
    @Override
2001
    public void removeParsingProblem(ParserProblem problem) {
2002
        parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2003
    }
2004

    
2005
    /**
2006
     * @param warnings
2007
     */
2008
    @Override
2009
    public void addParsingProblems(int problems){
2010
        parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2011
    }
2012

    
2013
    @Override
2014
    public boolean hasProblem(){
2015
        return parsingProblem != 0;
2016
    }
2017

    
2018
    @Override
2019
    public boolean hasProblem(ParserProblem problem) {
2020
        return getParsingProblems().contains(problem);
2021
    }
2022

    
2023
    @Override
2024
    public int getProblemStarts(){
2025
        return this.problemStarts;
2026
    }
2027

    
2028
    @Override
2029
    public void setProblemStarts(int start) {
2030
        this.problemStarts = start;
2031
    }
2032

    
2033
    @Override
2034
    public int getProblemEnds(){
2035
        return this.problemEnds;
2036
    }
2037

    
2038
    @Override
2039
    public void setProblemEnds(int end) {
2040
        this.problemEnds = end;
2041
    }
2042

    
2043
//*********************** TYPE DESIGNATION *********************************************//
2044

    
2045
    /**
2046
     * Returns the set of {@link TypeDesignationBase type designations} assigned
2047
     * to <i>this</i> taxon name.
2048
     * @see     NameTypeDesignation
2049
     * @see     SpecimenTypeDesignation
2050
     */
2051
    @Override
2052
    public Set<TypeDesignationBase> getTypeDesignations() {
2053
        if(typeDesignations == null) {
2054
            this.typeDesignations = new HashSet<TypeDesignationBase>();
2055
        }
2056
        return typeDesignations;
2057
    }
2058

    
2059
    /**
2060
     * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2061
     * <i>this</i> taxon name. The type designation itself will be nullified.
2062
     *
2063
     * @param  typeDesignation  the type designation which should be deleted
2064
     */
2065
    @Override
2066
    @SuppressWarnings("deprecation")
2067
    public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2068
        this.typeDesignations.remove(typeDesignation);
2069
        typeDesignation.removeTypifiedName(this);
2070
    }
2071

    
2072
    /**
2073
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2074
     * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2075
     * "species" or below. The specimen type designations include all the
2076
     * specimens on which the typification of this name is based (which are
2077
     * exclusively used to typify taxon names belonging to the same
2078
     * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2079
     * belongs) and eventually the status of these designations.
2080
     *
2081
     * @see     SpecimenTypeDesignation
2082
     * @see     NameTypeDesignation
2083
     * @see     HomotypicalGroup
2084
     */
2085
    @Override
2086
    @Transient
2087
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2088
        return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2089
    }
2090

    
2091
//*********************** NAME TYPE DESIGNATION *********************************************//
2092

    
2093
    /**
2094
     * Returns the set of {@link NameTypeDesignation name type designations} assigned
2095
     * to <i>this</i> taxon name the rank of which must be above "species".
2096
     * The name type designations include all the taxon names used to typify
2097
     * <i>this</i> taxon name and eventually the rejected or conserved status
2098
     * of these designations.
2099
     *
2100
     * @see     NameTypeDesignation
2101
     * @see     SpecimenTypeDesignation
2102
     */
2103
    @Override
2104
    @Transient
2105
    public Set<NameTypeDesignation> getNameTypeDesignations() {
2106
        Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2107
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2108
            if (typeDesignation instanceof NameTypeDesignation){
2109
                result.add((NameTypeDesignation)typeDesignation);
2110
            }
2111
        }
2112
        return result;
2113
    }
2114

    
2115
    /**
2116
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2117
     * to <i>this</i> taxon name's set of type designations.
2118
     *
2119
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2120
     * @param  citation					the reference for this new designation
2121
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2122
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2123
     * @param  isRejectedType			the boolean status for a rejected name type designation
2124
     * @param  isConservedType			the boolean status for a conserved name type designation
2125
     * @param  isLectoType				the boolean status for a lectotype name type designation
2126
     * @param  isNotDesignated			the boolean status for a name type designation without name type
2127
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2128
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2129
     * @return
2130
     * @see 			  				#getNameTypeDesignations()
2131
     * @see 			  				NameTypeDesignation
2132
     * @see 			  				TypeDesignationBase#isNotDesignated()
2133
     */
2134
    @Override
2135
    public NameTypeDesignation addNameTypeDesignation(TaxonNameBase typeSpecies,
2136
                Reference citation,
2137
                String citationMicroReference,
2138
                String originalNameString,
2139
                NameTypeDesignationStatus status,
2140
                boolean isRejectedType,
2141
                boolean isConservedType,
2142
                /*boolean isLectoType, */
2143
                boolean isNotDesignated,
2144
                boolean addToAllHomotypicNames) {
2145
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalNameString, status, isRejectedType, isConservedType, isNotDesignated);
2146
        //nameTypeDesignation.setLectoType(isLectoType);
2147
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2148
        return nameTypeDesignation;
2149
    }
2150

    
2151
    /**
2152
     * Creates and adds a new {@link NameTypeDesignation name type designation}
2153
     * to <i>this</i> taxon name's set of type designations.
2154
     *
2155
     * @param  typeSpecies				the taxon name to be used as type of <i>this</i> taxon name
2156
     * @param  citation					the reference for this new designation
2157
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2158
     * @param  originalNameString		the taxon name string used in the reference to assert this designation
2159
     * @param  status                   the name type designation status
2160
     * @param  addToAllHomotypicNames	the boolean indicating whether the name type designation should be
2161
     * 									added to all taxon names of the homotypical group this taxon name belongs to
2162
     * @return
2163
     * @see 			  				#getNameTypeDesignations()
2164
     * @see 			  				NameTypeDesignation
2165
     * @see 			  				TypeDesignationBase#isNotDesignated()
2166
     */
2167
    @Override
2168
    public NameTypeDesignation addNameTypeDesignation(TaxonNameBase typeSpecies,
2169
                Reference citation,
2170
                String citationMicroReference,
2171
                String originalNameString,
2172
                NameTypeDesignationStatus status,
2173
                boolean addToAllHomotypicNames) {
2174
        NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2175
        addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2176
        return nameTypeDesignation;
2177
    }
2178

    
2179
//*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2180

    
2181
    /**
2182
     * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2183
     * that typify <i>this</i> taxon name.
2184
     */
2185
    @Override
2186
    @Transient
2187
    public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2188
        Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2189
        for (TypeDesignationBase typeDesignation : this.typeDesignations){
2190
            if (typeDesignation instanceof SpecimenTypeDesignation){
2191
                result.add((SpecimenTypeDesignation)typeDesignation);
2192
            }
2193
        }
2194
        return result;
2195
    }
2196

    
2197

    
2198
    /**
2199
     * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2200
     * to <i>this</i> taxon name's set of type designations.
2201
     *
2202
     * @param  typeSpecimen				the specimen to be used as a type for <i>this</i> taxon name
2203
     * @param  status					the specimen type designation status
2204
     * @param  citation					the reference for this new specimen type designation
2205
     * @param  citationMicroReference	the string with the details (generally pages) within the reference
2206
     * @param  originalNameString		the taxon name used in the reference to assert this designation
2207
     * @param  isNotDesignated			the boolean status for a specimen type designation without specimen type
2208
     * @param  addToAllHomotypicNames	the boolean indicating whether the specimen type designation should be
2209
     * 									added to all taxon names of the homotypical group the typified
2210
     * 									taxon name belongs to
2211
     * @return
2212
     * @see 			  				#getSpecimenTypeDesignations()
2213
     * @see 			  				SpecimenTypeDesignationStatus
2214
     * @see 			  				SpecimenTypeDesignation
2215
     * @see 			  				TypeDesignationBase#isNotDesignated()
2216
     */
2217
    @Override
2218
    public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2219
                SpecimenTypeDesignationStatus status,
2220
                Reference citation,
2221
                String citationMicroReference,
2222
                String originalNameString,
2223
                boolean isNotDesignated,
2224
                boolean addToAllHomotypicNames) {
2225
        SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2226
        addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2227
        return specimenTypeDesignation;
2228
    }
2229

    
2230
    //used by merge strategy
2231
    private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2232
        return addTypeDesignation(typeDesignation, true);
2233
    }
2234

    
2235
    /**
2236
     * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2237
     *
2238
     * @param typeDesignation			the typeDesignation to be added to <code>this</code> taxon name
2239
     * @param addToAllNames				the boolean indicating whether the type designation should be
2240
     * 									added to all taxon names of the homotypical group the typified
2241
     * 									taxon name belongs to
2242
     * @return							true if the operation was succesful
2243
     *
2244
     * @throws IllegalArgumentException	if the type designation already has typified names, an {@link IllegalArgumentException exception}
2245
     * 									is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2246
     *
2247
     */
2248
    @Override
2249
    public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2250
        //currently typeDesignations are not persisted with the homotypical group
2251
        //so explicit adding to the homotypical group is not necessary.
2252
        if (typeDesignation != null){
2253
            checkHomotypicalGroup(typeDesignation);
2254
            this.typeDesignations.add(typeDesignation);
2255
            typeDesignation.addTypifiedName(this);
2256

    
2257
            if (addToAllNames){
2258
                for (TaxonNameBase taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2259
                    if (taxonName != this){
2260
                        taxonName.addTypeDesignation(typeDesignation, false);
2261
                    }
2262
                }
2263
            }
2264
        }
2265
        return true;
2266
    }
2267

    
2268
    /**
2269
     * Throws an Exception this type designation already has typified names from another homotypical group.
2270
     * @param typeDesignation
2271
     */
2272
    private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2273
        if(typeDesignation.getTypifiedNames().size() > 0){
2274
            Set<HomotypicalGroup> groups = new HashSet<HomotypicalGroup>();
2275
            Set<TaxonNameBase> names = typeDesignation.getTypifiedNames();
2276
            for (TaxonNameBase taxonName: names){
2277
                groups.add(taxonName.getHomotypicalGroup());
2278
            }
2279
            if (groups.size() > 1){
2280
                throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2281
            }
2282
        }
2283
    }
2284

    
2285

    
2286

    
2287
//*********************** HOMOTYPICAL GROUP *********************************************//
2288

    
2289

    
2290
    /**
2291
     * Returns the {@link HomotypicalGroup homotypical group} to which
2292
     * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2293
     * that share the same types.
2294
     *
2295
     * @see 	HomotypicalGroup
2296
     */
2297

    
2298
    @Override
2299
    public HomotypicalGroup getHomotypicalGroup() {
2300
        if (homotypicalGroup == null){
2301
            homotypicalGroup = new HomotypicalGroup();
2302
            homotypicalGroup.typifiedNames.add(this);
2303
        }
2304
    	return homotypicalGroup;
2305
    }
2306

    
2307
    /**
2308
     * @see #getHomotypicalGroup()
2309
     */
2310
    @Override
2311
    public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2312
        if (homotypicalGroup == null){
2313
            throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2314
        }
2315
        /*if (this.homotypicalGroup != null){
2316
        	this.homotypicalGroup.removeTypifiedName(this, false);
2317
        }*/
2318
        this.homotypicalGroup = homotypicalGroup;
2319
        if (!this.homotypicalGroup.typifiedNames.contains(this)){
2320
        	 this.homotypicalGroup.addTypifiedName(this);
2321
        }
2322
    }
2323

    
2324

    
2325

    
2326
// *************************************************************************//
2327

    
2328
    /**
2329
     * Returns the complete string containing the
2330
     * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2331
     * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2332
     *
2333
     * @return  the string containing the nomenclatural reference of <i>this</i> taxon name
2334
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2335
     * @see		#getNomenclaturalReference()
2336
     * @see		#getNomenclaturalMicroReference()
2337
     */
2338
    @Override
2339
    @Transient
2340
    public String getCitationString(){
2341
        return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2342
    }
2343

    
2344
    /**
2345
     * Returns the parsing problems
2346
     * @return
2347
     */
2348
    @Override
2349
    public List<ParserProblem> getParsingProblems(){
2350
        return ParserProblem.warningList(this.parsingProblem);
2351
    }
2352

    
2353
    /**
2354
     * Returns the string containing the publication date (generally only year)
2355
     * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2356
     * no nomenclatural reference.
2357
     *
2358
     * @return  the string containing the publication date of <i>this</i> taxon name
2359
     * @see		eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2360
     */
2361
    @Override
2362
    @Transient
2363
    @ValidTaxonomicYear(groups=Level3.class)
2364
    public String getReferenceYear(){
2365
        if (this.getNomenclaturalReference() != null ){
2366
            return this.getNomenclaturalReference().getYear();
2367
        }else{
2368
            return null;
2369
        }
2370
    }
2371

    
2372
    /**
2373
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2374
     * In this context a taxon base means the use of a taxon name by a reference
2375
     * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2376
     * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2377
     * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2378
     * within a taxonomic treatment (identified by one reference).
2379
     *
2380
     * @see	#getTaxa()
2381
     * @see	#getSynonyms()
2382
     */
2383
    @Override
2384
    public Set<TaxonBase> getTaxonBases() {
2385
        if(taxonBases == null) {
2386
            this.taxonBases = new HashSet<TaxonBase>();
2387
        }
2388
        return this.taxonBases;
2389
    }
2390

    
2391
    /**
2392
     * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2393
     * to the set of taxon bases using <i>this</i> taxon name.
2394
     *
2395
     * @param  taxonBase  the taxon base to be added
2396
     * @see 			  #getTaxonBases()
2397
     * @see 			  #removeTaxonBase(TaxonBase)
2398
     */
2399
    //TODO protected
2400
    @Override
2401
    public void addTaxonBase(TaxonBase taxonBase){
2402
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonNameBase.class});
2403
        ReflectionUtils.makeAccessible(method);
2404
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2405
        taxonBases.add(taxonBase);
2406
    }
2407
    /**
2408
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2409
     *
2410
     * @param  taxonBase	the taxon base which should be removed from the corresponding set
2411
     * @see    				#getTaxonBases()
2412
     * @see    				#addTaxonBase(TaxonBase)
2413
     */
2414
    @Override
2415
    public void removeTaxonBase(TaxonBase taxonBase){
2416
        Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonNameBase.class});
2417
        ReflectionUtils.makeAccessible(method);
2418
        ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2419

    
2420

    
2421
    }
2422

    
2423
    /**
2424
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2425
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2426
     * the set returned by getTaxonBases().
2427
     *
2428
     * @see	eu.etaxonomy.cdm.model.taxon.Taxon
2429
     * @see	#getTaxonBases()
2430
     * @see	#getSynonyms()
2431
     */
2432
    @Override
2433
    @Transient
2434
    public Set<Taxon> getTaxa(){
2435
        Set<Taxon> result = new HashSet<Taxon>();
2436
        for (TaxonBase taxonBase : this.taxonBases){
2437
            if (taxonBase instanceof Taxon){
2438
                result.add((Taxon)taxonBase);
2439
            }
2440
        }
2441
        return result;
2442
    }
2443

    
2444
    /**
2445
     * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2446
     * reference) that are based on <i>this</i> taxon name. This set is a subset of
2447
     * the set returned by getTaxonBases().
2448
     *
2449
     * @see	eu.etaxonomy.cdm.model.taxon.Synonym
2450
     * @see	#getTaxonBases()
2451
     * @see	#getTaxa()
2452
     */
2453
    @Override
2454
    @Transient
2455
    public Set<Synonym> getSynonyms() {
2456
        Set<Synonym> result = new HashSet<Synonym>();
2457
        for (TaxonBase taxonBase : this.taxonBases){
2458
            if (taxonBase instanceof Synonym){
2459
                result.add((Synonym)taxonBase);
2460
            }
2461
        }
2462
        return result;
2463
    }
2464

    
2465
// ************* RELATIONSHIPS *****************************/
2466

    
2467

    
2468
    /**
2469
     * Returns the hybrid child relationships ordered by relationship type, or if equal
2470
     * by title cache of the related names.
2471
     * @see #getHybridParentRelations()
2472
     */
2473
    @Transient
2474
    public List<HybridRelationship> getOrderedChildRelationships(){
2475
        List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2476
        result.addAll(this.hybridChildRelations);
2477
        Collections.sort(result);
2478
        Collections.reverse(result);
2479
        return result;
2480

    
2481
    }
2482

    
2483

    
2484
    /**
2485
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2486
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2487
     * "is first/second parent" or "is male/female parent". By invoking this
2488
     * method <i>this</i> botanical name becomes a hybrid child of the parent
2489
     * botanical name.
2490
     *
2491
     * @param parentName      the botanical name of the parent for this new hybrid name relationship
2492
     * @param type            the type of this new name relationship
2493
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2494
     * @return
2495
     * @see                   #addHybridChild(BotanicalName, HybridRelationshipType,String )
2496
     * @see                   #getRelationsToThisName()
2497
     * @see                   #getNameRelations()
2498
     * @see                   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
2499
     * @see                   #addNameRelationship(NameRelationship)
2500
     */
2501
    public HybridRelationship addHybridParent(NonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2502
        return new HybridRelationship(this, parentName, type, ruleConsidered);
2503
    }
2504

    
2505
    /**
2506
     * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2507
     * to <i>this</i> botanical name. A HybridRelationship may be of type
2508
     * "is first/second parent" or "is male/female parent". By invoking this
2509
     * method <i>this</i> botanical name becomes a parent of the hybrid child
2510
     * botanical name.
2511
     *
2512
     * @param childName       the botanical name of the child for this new hybrid name relationship
2513
     * @param type            the type of this new name relationship
2514
     * @param ruleConsidered  the string which specifies the rule on which this name relationship is based
2515
     * @return
2516
     * @see                   #addHybridParent(BotanicalName, HybridRelationshipType,String )
2517
     * @see                   #getRelationsToThisName()
2518
     * @see                   #getNameRelations()
2519
     * @see                   #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
2520
     * @see                   #addNameRelationship(NameRelationship)
2521
     */
2522
    public HybridRelationship addHybridChild(NonViralName childName, HybridRelationshipType type, String ruleConsidered){
2523
        return new HybridRelationship(childName, this, type, ruleConsidered);
2524
    }
2525

    
2526
    public void removeHybridChild(NonViralName child) {
2527
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2528
        hybridRelationships.addAll(this.getHybridChildRelations());
2529
        hybridRelationships.addAll(this.getHybridParentRelations());
2530
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2531
            // remove name relationship from this side
2532
            if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2533
                this.removeHybridRelationship(hybridRelationship);
2534
            }
2535
        }
2536
    }
2537

    
2538
    public void removeHybridParent(NonViralName parent) {
2539
        Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2540
        hybridRelationships.addAll(this.getHybridChildRelations());
2541
        hybridRelationships.addAll(this.getHybridParentRelations());
2542
        for(HybridRelationship hybridRelationship : hybridRelationships) {
2543
            // remove name relationship from this side
2544
            if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2545
                this.removeHybridRelationship(hybridRelationship);
2546
            }
2547
        }
2548
    }
2549

    
2550

    
2551

    
2552
// *********** DESCRIPTIONS *************************************
2553

    
2554
    /**
2555
     * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2556
     * to <i>this</i> taxon name. A taxon name description is a piece of information
2557
     * concerning the taxon name like for instance the content of its first
2558
     * publication (protolog) or a picture of this publication.
2559
     *
2560
     * @see	#addDescription(TaxonNameDescription)
2561
     * @see	#removeDescription(TaxonNameDescription)
2562
     * @see	eu.etaxonomy.cdm.model.description.TaxonNameDescription
2563
     */
2564
    @Override
2565
    public Set<TaxonNameDescription> getDescriptions() {
2566
        return descriptions;
2567
    }
2568

    
2569
    /**
2570
     * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2571
     * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2572
     * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2573
     * taxon name description itself will be replaced with <i>this</i> taxon name.
2574
     *
2575
     * @param  description  the taxon name description to be added
2576
     * @see					#getDescriptions()
2577
     * @see 			  	#removeDescription(TaxonNameDescription)
2578
     */
2579
    @Override
2580
    public void addDescription(TaxonNameDescription description) {
2581
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonNameBase.class);
2582
        ReflectionUtils.makeAccessible(field);
2583
        ReflectionUtils.setField(field, description, this);
2584
        descriptions.add(description);
2585
    }
2586
    /**
2587
     * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2588
     * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2589
     * of the description itself will be set to "null".
2590
     *
2591
     * @param  description  the taxon name description which should be removed
2592
     * @see     		  	#getDescriptions()
2593
     * @see     		  	#addDescription(TaxonNameDescription)
2594
     * @see 			  	eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
2595
     */
2596
    @Override
2597
    public void removeDescription(TaxonNameDescription description) {
2598
        java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonNameBase.class);
2599
        ReflectionUtils.makeAccessible(field);
2600
        ReflectionUtils.setField(field, description, null);
2601
        descriptions.remove(description);
2602
    }
2603

    
2604
// *********** HOMOTYPIC GROUP METHODS **************************************************
2605

    
2606
    @Override
2607
    @Transient
2608
    public void mergeHomotypicGroups(TaxonNameBase name){
2609
        this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2610
        //HomotypicalGroup thatGroup = name.homotypicalGroup;
2611
        name.setHomotypicalGroup(this.homotypicalGroup);
2612
    }
2613

    
2614
    /**
2615
     * Returns the boolean value indicating whether a given taxon name belongs
2616
     * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
2617
     * or not (false). Returns "true" only if the homotypical groups of both
2618
     * taxon names exist and if they are identical.
2619
     *
2620
     * @param	homoTypicName  the taxon name the homotypical group of which is to be checked
2621
     * @return  			   the boolean value of the check
2622
     * @see     			   HomotypicalGroup
2623
     */
2624
    @Override
2625
    @Transient
2626
    public boolean isHomotypic(TaxonNameBase homoTypicName) {
2627
        if (homoTypicName == null) {
2628
            return false;
2629
        }
2630
        HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
2631
        if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
2632
            return false;
2633
        }
2634
        if (homotypicGroup.equals(this.getHomotypicalGroup())) {
2635
            return true;
2636
        }
2637
        return false;
2638
    }
2639

    
2640

    
2641
    /**
2642
     * Checks whether name is a basionym for ALL names
2643
     * in its homotypical group.
2644
     * Returns <code>false</code> if there are no other names in the group
2645
     * @param name
2646
     * @return
2647
     */
2648
    @Override
2649
    @Transient
2650
    public boolean isGroupsBasionym() {
2651
    	if (homotypicalGroup == null){
2652
    		homotypicalGroup = HomotypicalGroup.NewInstance();
2653
    		homotypicalGroup.addTypifiedName(this);
2654
    	}
2655
        Set<TaxonNameBase> typifiedNames = homotypicalGroup.getTypifiedNames();
2656

    
2657
        // Check whether there are any other names in the group
2658
        if (typifiedNames.size() == 1) {
2659
                return false;
2660
        }
2661

    
2662
        boolean isBasionymToAll = true;
2663

    
2664
        for (TaxonNameBase taxonName : typifiedNames) {
2665
                if (!taxonName.equals(this)) {
2666
                        if (! isBasionymFor(taxonName)) {
2667
                                return false;
2668
                        }
2669
                }
2670
        }
2671
        return true;
2672
    }
2673

    
2674
    /**
2675
     * Checks whether a basionym relationship exists between fromName and toName.
2676
     *
2677
     * @param fromName
2678
     * @param toName
2679
     * @return
2680
     */
2681
    @Override
2682
    @Transient
2683
    public boolean isBasionymFor(TaxonNameBase newCombinationName) {
2684
            Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
2685
            for (NameRelationship relation : relations) {
2686
                    if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
2687
                                    relation.getFromName().equals(this)) {
2688
                            return true;
2689
                    }
2690
            }
2691
            return false;
2692
    }
2693

    
2694
    /**
2695
     * Creates a basionym relationship to all other names in this names homotypical
2696
     * group.
2697
     *
2698
     * @see HomotypicalGroup.setGroupBasionym(TaxonNameBase basionymName)
2699
     */
2700
    @Override
2701
    @Transient
2702
    public void makeGroupsBasionym() {
2703
        this.homotypicalGroup.setGroupBasionym(this);
2704
    }
2705

    
2706

    
2707
//*********  Rank comparison shortcuts   ********************//
2708
    /**
2709
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2710
     * taxon name is higher than the genus rank (true) or not (false).
2711
     * Suprageneric non viral names are monomials.
2712
     * Returns false if rank is null.
2713
     *
2714
     * @see  #isGenus()
2715
     * @see  #isInfraGeneric()
2716
     * @see  #isSpecies()
2717
     * @see  #isInfraSpecific()
2718
     */
2719
    @Override
2720
    @Transient
2721
    public boolean isSupraGeneric() {
2722
        if (rank == null){
2723
            return false;
2724
        }
2725
        return getRank().isSupraGeneric();
2726
    }
2727
    /**
2728
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2729
     * taxon name is the genus rank (true) or not (false). Non viral names with
2730
     * genus rank are monomials. Returns false if rank is null.
2731
     *
2732
     * @see  #isSupraGeneric()
2733
     * @see  #isInfraGeneric()
2734
     * @see  #isSpecies()
2735
     * @see  #isInfraSpecific()
2736
     */
2737
    @Override
2738
    @Transient
2739
    public boolean isGenus() {
2740
        if (rank == null){
2741
            return false;
2742
        }
2743
        return getRank().isGenus();
2744
    }
2745
    /**
2746
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2747
     * taxon name is higher than the species rank and lower than the
2748
     * genus rank (true) or not (false). Infrageneric non viral names are
2749
     * binomials. Returns false if rank is null.
2750
     *
2751
     * @see  #isSupraGeneric()
2752
     * @see  #isGenus()
2753
     * @see  #isSpecies()
2754
     * @see  #isInfraSpecific()
2755
     */
2756
    @Override
2757
    @Transient
2758
    public boolean isInfraGeneric() {
2759
        if (rank == null){
2760
            return false;
2761
        }
2762
        return getRank().isInfraGeneric();
2763
    }
2764

    
2765
    /**
2766
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2767
     * taxon name is higher than the species rank (true) or not (false).
2768
     * Returns false if rank is null.
2769
     *
2770
     * @see  #isGenus()
2771
     * @see  #isInfraGeneric()
2772
     * @see  #isSpecies()
2773
     * @see  #isInfraSpecific()
2774
     */
2775
    @Override
2776
    @Transient
2777
    public boolean isSupraSpecific(){
2778
        if (rank == null) {
2779
            return false;
2780
        }
2781
        return getRank().isHigher(Rank.SPECIES());
2782
    }
2783

    
2784
    /**
2785
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2786
     * taxon name is the species rank (true) or not (false). Non viral names
2787
     * with species rank are binomials.
2788
     * Returns false if rank is null.
2789
     *
2790
     * @see  #isSupraGeneric()
2791
     * @see  #isGenus()
2792
     * @see  #isInfraGeneric()
2793
     * @see  #isInfraSpecific()
2794
     */
2795
    @Override
2796
    @Transient
2797
    public boolean isSpecies() {
2798
        if (rank == null){
2799
            return false;
2800
        }
2801
        return getRank().isSpecies();
2802
    }
2803
    /**
2804
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2805
     * taxon name is lower than the species rank (true) or not (false).
2806
     * Infraspecific non viral names are trinomials.
2807
     * Returns false if rank is null.
2808
     *
2809
     * @see  #isSupraGeneric()
2810
     * @see  #isGenus()
2811
     * @see  #isInfraGeneric()
2812
     * @see  #isSpecies()
2813
     */
2814
    @Override
2815
    @Transient
2816
    public boolean isInfraSpecific() {
2817
        if (rank == null){
2818
            return false;
2819
        }
2820
        return getRank().isInfraSpecific();
2821
    }
2822

    
2823
    /**
2824
     * Returns true if this name's rank indicates a rank that aggregates species like species
2825
     * aggregates or species groups, false otherwise. This methods currently returns false
2826
     * for all user defined ranks.
2827
     *
2828
     *@see Rank#isSpeciesAggregate()
2829
     *
2830
     * @return
2831
     */
2832
    @Override
2833
    @Transient
2834
    public boolean isSpeciesAggregate() {
2835
        if (rank == null){
2836
            return false;
2837
        }
2838
        return getRank().isSpeciesAggregate();
2839
    }
2840

    
2841

    
2842
    /**
2843
     * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
2844
     * the construction of <i>this</i> taxon name since there is no specific
2845
     * nomenclatural code defined. The real implementention takes place in the
2846
     * subclasses {@link BacterialName BacterialName},
2847
     * {@link BotanicalName BotanicalName}, {@link CultivarPlantName CultivarPlantName} and
2848
     * {@link ZoologicalName ZoologicalName}. Each taxon name is governed by one
2849
     * and only one nomenclatural code.
2850
     *
2851
     * @return  null
2852
     * @see  	#isCodeCompliant()
2853
     * @see  	#getHasProblem()
2854
     */
2855
    @Override
2856
    public NomenclaturalCode getNomenclaturalCode() {
2857
        logger.warn("TaxonNameBase has no specific Code defined. Use subclasses");
2858
        return null;
2859
    }
2860

    
2861

    
2862
    /**
2863
     * Generates and returns the string with the scientific name of <i>this</i>
2864
     * taxon name (only non viral taxon names can be generated from their
2865
     * components). This string may be stored in the inherited
2866
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
2867
     * This method overrides the generic and inherited
2868
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
2869
     * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
2870
     *
2871
     * @return  the string with the composed name of this non viral taxon name with authorship (and maybe year)
2872
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
2873
     * @see  	eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
2874
     */
2875
//	@Override
2876
//	public abstract String generateTitle();
2877

    
2878
    /**
2879
     * Creates a basionym relationship between this name and
2880
     * 	each name in its homotypic group.
2881
     *
2882
     * @param basionymName
2883
     */
2884
    @Override
2885
    @Transient
2886
    public void setAsGroupsBasionym() {
2887

    
2888
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
2889
        if (homotypicalGroup == null) {
2890
            return;
2891
        }
2892

    
2893
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
2894
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
2895

    
2896
        for(TaxonNameBase<?, ?> typifiedName : homotypicalGroup.getTypifiedNames()){
2897

    
2898
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
2899

    
2900
            for(NameRelationship nameRelation : nameRelations){
2901
                relations.add(nameRelation);
2902
            }
2903
        }
2904

    
2905
        for (NameRelationship relation : relations) {
2906

    
2907
            // If this is a basionym relation, and toName is in the homotypical group,
2908
            //	remove the relationship.
2909
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
2910
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
2911
                removeRelations.add(relation);
2912
            }
2913
        }
2914

    
2915
        // Removing relations from a set through which we are iterating causes a
2916
        //	ConcurrentModificationException. Therefore, we delete the targeted
2917
        //	relations in a second step.
2918
        for (NameRelationship relation : removeRelations) {
2919
            this.removeNameRelationship(relation);
2920
        }
2921

    
2922
        for (TaxonNameBase<?, ?> name : homotypicalGroup.getTypifiedNames()) {
2923
            if (!name.equals(this)) {
2924

    
2925
                // First check whether the relationship already exists
2926
                if (!this.isBasionymFor(name)) {
2927

    
2928
                    // Then create it
2929
                    name.addRelationshipFromName(this,
2930
                            NameRelationshipType.BASIONYM(), null);
2931
                }
2932
            }
2933
        }
2934
    }
2935

    
2936
    /**
2937
     * Removes basionym relationship between this name and
2938
     * 	each name in its homotypic group.
2939
     *
2940
     * @param basionymName
2941
     */
2942
    @Override
2943
    @Transient
2944
    public void removeAsGroupsBasionym() {
2945

    
2946
        HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
2947

    
2948
        if (homotypicalGroup == null) {
2949
            return;
2950
        }
2951

    
2952
        Set<NameRelationship> relations = new HashSet<NameRelationship>();
2953
        Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
2954

    
2955
        for(TaxonNameBase<?, ?> typifiedName : homotypicalGroup.getTypifiedNames()){
2956

    
2957
            Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
2958

    
2959
            for(NameRelationship nameRelation : nameRelations){
2960
                relations.add(nameRelation);
2961
            }
2962
        }
2963

    
2964
        for (NameRelationship relation : relations) {
2965

    
2966
            // If this is a basionym relation, and toName is in the homotypical group,
2967
            //	and fromName is basionymName, remove the relationship.
2968
            if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
2969
                    relation.getFromName().equals(this) &&
2970
                    relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
2971
                removeRelations.add(relation);
2972
            }
2973
        }
2974

    
2975
        // Removing relations from a set through which we are iterating causes a
2976
        //	ConcurrentModificationException. Therefore, we delete the targeted
2977
        //	relations in a second step.
2978
        for (NameRelationship relation : removeRelations) {
2979
            this.removeNameRelationship(relation);
2980
        }
2981
    }
2982

    
2983

    
2984
    /**
2985
     * Defines the last part of the name.
2986
     * This is for infraspecific taxa, the infraspecific epithet,
2987
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
2988
     * else the genusOrUninomial.
2989
     * However, the result does not depend on the rank (which may be not correctly set
2990
     * in case of dirty data) but returns the first name part which is not blank
2991
     * considering the above order.
2992
     * @return the first not blank name part in reverse order
2993
     */
2994
    public String getLastNamePart() {
2995
        String result =
2996
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
2997
                    this.getInfraSpecificEpithet() :
2998
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
2999
                    this.getSpecificEpithet():
3000
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3001
                    this.getInfraGenericEpithet():
3002
                this.getGenusOrUninomial();
3003
        return result;
3004
    }
3005

    
3006
//*********************** CLONE ********************************************************/
3007

    
3008
    /**
3009
     * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3010
     * a new instance that differs only slightly from <i>this</i> taxon name by
3011
     * modifying only some of the attributes.<BR><BR>
3012
     * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3013
     * <b>The name gets a newly created homotypical group</b><BR>
3014
     * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3015
     * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3016
     * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3017
     *
3018
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3019
     * @see java.lang.Object#clone()
3020
     */
3021
    @Override
3022
    public Object clone() {
3023
        TaxonNameBase<?,?> result;
3024
        try {
3025
            result = (TaxonNameBase)super.clone();
3026

    
3027
            //taxonBases -> empty
3028
            result.taxonBases = new HashSet<TaxonBase>();
3029

    
3030
            //empty caches
3031
            if (! protectedFullTitleCache){
3032
                result.fullTitleCache = null;
3033
            }
3034

    
3035
            //descriptions
3036
            result.descriptions = new HashSet<TaxonNameDescription>();
3037
            for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3038
                TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3039
                result.descriptions.add(newDescription);
3040
            }
3041

    
3042
            //status
3043
            result.status = new HashSet<NomenclaturalStatus>();
3044
            for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3045
                NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3046
                result.status.add(newStatus);
3047
            }
3048

    
3049

    
3050
            //To Relations
3051
            result.relationsToThisName = new HashSet<NameRelationship>();
3052
            for (NameRelationship toRelationship : getRelationsToThisName()){
3053
                NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3054
                newRelationship.setRelatedTo(result);
3055
                result.relationsToThisName.add(newRelationship);
3056
            }
3057

    
3058
            //From Relations
3059
            result.relationsFromThisName = new HashSet<NameRelationship>();
3060
            for (NameRelationship fromRelationship : getRelationsFromThisName()){
3061
                NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3062
                newRelationship.setRelatedFrom(result);
3063
                result.relationsFromThisName.add(newRelationship);
3064
            }
3065

    
3066
            //type designations
3067
            result.typeDesignations = new HashSet<TypeDesignationBase>();
3068
            for (TypeDesignationBase typeDesignation : getTypeDesignations()){
3069
                TypeDesignationBase newDesignation = (TypeDesignationBase)typeDesignation.clone();
3070
                result.typeDesignations.add(newDesignation);
3071
                newDesignation.addTypifiedName(result);
3072
            }
3073

    
3074
            //homotypicalGroup
3075
            //TODO still needs to be discussed
3076
            result.homotypicalGroup = HomotypicalGroup.NewInstance();
3077
            result.homotypicalGroup.addTypifiedName(this);
3078

    
3079

    
3080
            //HybridChildRelations
3081
            result.hybridChildRelations = new HashSet<HybridRelationship>();
3082
            for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3083
                HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3084
                newChildRelationship.setRelatedTo(result);
3085
                result.hybridChildRelations.add(newChildRelationship);
3086
            }
3087

    
3088
            //HybridParentRelations
3089
            result.hybridParentRelations = new HashSet<HybridRelationship>();
3090
            for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3091
                HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3092
                newParentRelationship.setRelatedFrom(result);
3093
                result.hybridParentRelations.add(newParentRelationship);
3094
            }
3095

    
3096
            //empty caches
3097
            if (! protectedNameCache){
3098
                result.nameCache = null;
3099
            }
3100

    
3101
            //empty caches
3102
            if (! protectedAuthorshipCache){
3103
                result.authorshipCache = null;
3104
            }
3105

    
3106
            //no changes to: appendedPharse, nomenclaturalReference,
3107
            //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3108
            //protectedFullTitleCache, rank
3109
            //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3110
            //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3111
            //protectedAuthorshipCache, protectedNameCache
3112
            //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3113
            return result;
3114
        } catch (CloneNotSupportedException e) {
3115
            logger.warn("Object does not implement cloneable");
3116
            e.printStackTrace();
3117
            return null;
3118
        }
3119

    
3120
    }
3121
}
(23-23/30)