remove required from TaxonNAMEBase.cultivarName JAXB definition
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / name / TaxonNameBase.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.cdm.model.name;
11
12 import java.beans.PropertyChangeEvent;
13 import java.beans.PropertyChangeListener;
14 import java.lang.reflect.Method;
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21
22 import javax.persistence.Column;
23 import javax.persistence.Entity;
24 import javax.persistence.FetchType;
25 import javax.persistence.Inheritance;
26 import javax.persistence.InheritanceType;
27 import javax.persistence.JoinColumn;
28 import javax.persistence.JoinTable;
29 import javax.persistence.ManyToMany;
30 import javax.persistence.ManyToOne;
31 import javax.persistence.OneToMany;
32 import javax.persistence.Transient;
33 import javax.validation.Valid;
34 import javax.validation.constraints.Min;
35 import javax.validation.constraints.NotNull;
36 import javax.validation.constraints.Pattern;
37 import javax.xml.bind.annotation.XmlAccessType;
38 import javax.xml.bind.annotation.XmlAccessorType;
39 import javax.xml.bind.annotation.XmlAttribute;
40 import javax.xml.bind.annotation.XmlElement;
41 import javax.xml.bind.annotation.XmlElementWrapper;
42 import javax.xml.bind.annotation.XmlIDREF;
43 import javax.xml.bind.annotation.XmlRootElement;
44 import javax.xml.bind.annotation.XmlSchemaType;
45 import javax.xml.bind.annotation.XmlType;
46
47 import org.apache.commons.lang.StringUtils;
48 import org.apache.log4j.Logger;
49 import org.hibernate.annotations.Cascade;
50 import org.hibernate.annotations.CascadeType;
51 import org.hibernate.annotations.Table;
52 import org.hibernate.envers.Audited;
53 import org.hibernate.search.annotations.Analyze;
54 import org.hibernate.search.annotations.Analyzer;
55 import org.hibernate.search.annotations.Field;
56 import org.hibernate.search.annotations.Fields;
57 import org.hibernate.search.annotations.Index;
58 import org.hibernate.search.annotations.IndexedEmbedded;
59 import org.hibernate.search.annotations.Store;
60 import org.hibernate.validator.constraints.NotEmpty;
61 import org.springframework.util.ReflectionUtils;
62
63 import eu.etaxonomy.cdm.common.CdmUtils;
64 import eu.etaxonomy.cdm.common.UTF8;
65 import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
66 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
67 import eu.etaxonomy.cdm.model.common.CdmBase;
68 import eu.etaxonomy.cdm.model.common.IIntextReferenceTarget;
69 import eu.etaxonomy.cdm.model.common.IParsable;
70 import eu.etaxonomy.cdm.model.common.IRelated;
71 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
72 import eu.etaxonomy.cdm.model.common.RelationshipBase;
73 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
74 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
75 import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
76 import eu.etaxonomy.cdm.model.reference.Reference;
77 import eu.etaxonomy.cdm.model.taxon.Synonym;
78 import eu.etaxonomy.cdm.model.taxon.Taxon;
79 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
80 import eu.etaxonomy.cdm.strategy.cache.TaggedText;
81 import eu.etaxonomy.cdm.strategy.cache.name.CacheUpdate;
82 import eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy;
83 import eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy;
84 import eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy;
85 import eu.etaxonomy.cdm.strategy.match.IMatchable;
86 import eu.etaxonomy.cdm.strategy.match.Match;
87 import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
88 import eu.etaxonomy.cdm.strategy.match.MatchMode;
89 import eu.etaxonomy.cdm.strategy.merge.Merge;
90 import eu.etaxonomy.cdm.strategy.merge.MergeMode;
91 import eu.etaxonomy.cdm.strategy.parser.ParserProblem;
92 import eu.etaxonomy.cdm.validation.Level2;
93 import eu.etaxonomy.cdm.validation.Level3;
94 import eu.etaxonomy.cdm.validation.annotation.NameMustFollowCode;
95 import eu.etaxonomy.cdm.validation.annotation.NullOrNotEmpty;
96 import eu.etaxonomy.cdm.validation.annotation.ValidTaxonomicYear;
97
98 /**
99 * The upmost (abstract) class for scientific taxon names regardless of any
100 * particular {@link NomenclaturalCode nomenclature code}. The scientific taxon name does not depend
101 * on the use made of it in a publication or a treatment
102 * ({@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon concept respectively potential taxon})
103 * as an {@link eu.etaxonomy.cdm.model.taxon.Taxon "accepted" respectively "correct" (taxon) name}
104 * or as a {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
105 * <P>
106 * This class corresponds partially to: <ul>
107 * <li> TaxonName according to the TDWG ontology
108 * <li> ScientificName and CanonicalName according to the TCS
109 * <li> ScientificName according to the ABCD schema
110 * </ul>
111 *
112 * @author m.doering
113 * @created 08-Nov-2007 13:06:57
114 */
115 @XmlAccessorType(XmlAccessType.FIELD)
116 @XmlType(name = "TaxonNameBase", propOrder = {
117 "appendedPhrase",
118 "nomenclaturalMicroReference",
119 "nomenclaturalReference",
120 "rank",
121 "fullTitleCache",
122 "protectedFullTitleCache",
123 "homotypicalGroup",
124 "typeDesignations",
125 "relationsFromThisName",
126 "relationsToThisName",
127 "status",
128 "descriptions",
129 "taxonBases",
130
131 "nameCache",
132 "genusOrUninomial",
133 "infraGenericEpithet",
134 "specificEpithet",
135 "infraSpecificEpithet",
136 "combinationAuthorship",
137 "exCombinationAuthorship",
138 "basionymAuthorship",
139 "exBasionymAuthorship",
140 "authorshipCache",
141 "protectedAuthorshipCache",
142 "protectedNameCache",
143 "hybridParentRelations",
144 "hybridChildRelations",
145 "hybridFormula",
146 "monomHybrid",
147 "binomHybrid",
148 "trinomHybrid",
149
150 "acronym",
151
152 "subGenusAuthorship",
153 "nameApprobation",
154
155 "breed",
156 "publicationYear",
157 "originalPublicationYear",
158
159 // "anamorphic",
160
161 "cultivarName"
162 })
163 @XmlRootElement(name = "TaxonNameBase")
164 @Entity
165 @Audited
166 @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
167 @Table(appliesTo="TaxonNameBase", indexes = { @org.hibernate.annotations.Index(name = "taxonNameBaseTitleCacheIndex", columnNames = { "titleCache" }), @org.hibernate.annotations.Index(name = "taxonNameBaseNameCacheIndex", columnNames = { "nameCache" }) })
168 @NameMustFollowCode
169 public abstract class TaxonNameBase<T extends TaxonNameBase<?,?>, S extends INameCacheStrategy>
170 extends IdentifiableEntity<S>
171 implements ITaxonNameBase, INonViralName, IViralName, IBacterialName, IZoologicalName,
172 IBotanicalName, ICultivarPlantName,
173 IParsable, IRelated, IMatchable, IIntextReferenceTarget, Cloneable {
174
175 private static final long serialVersionUID = -791164269603409712L;
176 private static final Logger logger = Logger.getLogger(TaxonNameBase.class);
177
178
179 @XmlElement(name = "FullTitleCache")
180 @Column(length=800, name="fullTitleCache") //see #1592
181 @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
182 @CacheUpdate(noUpdate ="titleCache")
183 @NotEmpty(groups = Level2.class)
184 protected String fullTitleCache;
185
186 //if true titleCache will not be automatically generated/updated
187 @XmlElement(name = "ProtectedFullTitleCache")
188 @CacheUpdate(value ="fullTitleCache", noUpdate ="titleCache")
189 private boolean protectedFullTitleCache;
190
191 @XmlElementWrapper(name = "Descriptions")
192 @XmlElement(name = "Description")
193 @OneToMany(mappedBy="taxonName", fetch= FetchType.LAZY, orphanRemoval=true)
194 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
195 @NotNull
196 private Set<TaxonNameDescription> descriptions = new HashSet<>();
197
198 @XmlElement(name = "AppendedPhrase")
199 @Field
200 @CacheUpdate(value ="nameCache")
201 //TODO Val #3379
202 // @NullOrNotEmpty
203 @Column(length=255)
204 private String appendedPhrase;
205
206 @XmlElement(name = "NomenclaturalMicroReference")
207 @Field
208 @CacheUpdate(noUpdate ="titleCache")
209 //TODO Val #3379
210 // @NullOrNotEmpty
211 @Column(length=255)
212 private String nomenclaturalMicroReference;
213
214 @XmlAttribute
215 @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
216 private int parsingProblem = 0;
217
218 @XmlAttribute
219 @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
220 private int problemStarts = -1;
221
222 @XmlAttribute
223 @CacheUpdate(noUpdate ={"titleCache","fullTitleCache"})
224 private int problemEnds = -1;
225
226 @XmlElementWrapper(name = "TypeDesignations")
227 @XmlElement(name = "TypeDesignation")
228 @XmlIDREF
229 @XmlSchemaType(name = "IDREF")
230 @ManyToMany(fetch = FetchType.LAZY)
231 @JoinTable(
232 name="TaxonNameBase_TypeDesignationBase",
233 joinColumns=@javax.persistence.JoinColumn(name="TaxonNameBase_id"),
234 inverseJoinColumns=@javax.persistence.JoinColumn(name="typedesignations_id")
235 )
236 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
237 @NotNull
238 private Set<TypeDesignationBase> typeDesignations = new HashSet<>();
239
240 @XmlElement(name = "HomotypicalGroup")
241 @XmlIDREF
242 @XmlSchemaType(name = "IDREF")
243 @ManyToOne(fetch = FetchType.LAZY)
244 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
245 @Match(MatchMode.IGNORE)
246 @CacheUpdate(noUpdate ="titleCache")
247 //TODO Val #3379
248 // @NotNull
249 private HomotypicalGroup homotypicalGroup;
250
251 @XmlElementWrapper(name = "RelationsFromThisName")
252 @XmlElement(name = "RelationFromThisName")
253 @OneToMany(mappedBy="relatedFrom", fetch= FetchType.LAZY, orphanRemoval=true)
254 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
255 @Merge(MergeMode.RELATION)
256 @NotNull
257 @Valid
258 private Set<NameRelationship> relationsFromThisName = new HashSet<>();
259
260 @XmlElementWrapper(name = "RelationsToThisName")
261 @XmlElement(name = "RelationToThisName")
262 @XmlIDREF
263 @XmlSchemaType(name = "IDREF")
264 @OneToMany(mappedBy="relatedTo", fetch= FetchType.LAZY, orphanRemoval=true)
265 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
266 @Merge(MergeMode.RELATION)
267 @NotNull
268 @Valid
269 private Set<NameRelationship> relationsToThisName = new HashSet<>();
270
271 @XmlElementWrapper(name = "NomenclaturalStatuses")
272 @XmlElement(name = "NomenclaturalStatus")
273 @OneToMany(fetch= FetchType.LAZY, orphanRemoval=true)
274 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE,CascadeType.DELETE})
275 @NotNull
276 @IndexedEmbedded(depth=1)
277 private Set<NomenclaturalStatus> status = new HashSet<>();
278
279 @XmlElementWrapper(name = "TaxonBases")
280 @XmlElement(name = "TaxonBase")
281 @XmlIDREF
282 @XmlSchemaType(name = "IDREF")
283 @OneToMany(mappedBy="name", fetch= FetchType.LAZY)
284 @NotNull
285 @IndexedEmbedded(depth=1)
286 private Set<TaxonBase> taxonBases = new HashSet<>();
287
288 @XmlElement(name = "Rank")
289 @XmlIDREF
290 @XmlSchemaType(name = "IDREF")
291 @ManyToOne(fetch = FetchType.EAGER)
292 @CacheUpdate(value ="nameCache")
293 //TODO Val #3379, handle maybe as groups = Level2.class ??
294 // @NotNull
295 @IndexedEmbedded(depth=1)
296 private Rank rank;
297
298 @XmlElement(name = "NomenclaturalReference")
299 @XmlIDREF
300 @XmlSchemaType(name = "IDREF")
301 @ManyToOne(fetch = FetchType.LAZY)
302 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
303 @CacheUpdate(noUpdate ="titleCache")
304 @IndexedEmbedded
305 private Reference nomenclaturalReference;
306
307 //****** Non-ViralName attributes ***************************************/
308
309 @XmlElement(name = "NameCache")
310 @Fields({
311 @Field(name = "nameCache_tokenized"),
312 @Field(store = Store.YES, index = Index.YES, analyze = Analyze.YES)
313 })
314 @Analyzer(impl = org.apache.lucene.analysis.core.KeywordAnalyzer.class)
315 @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
316 cacheReplacedProperties={"genusOrUninomial", "infraGenericEpithet", "specificEpithet", "infraSpecificEpithet"} )
317 @NotEmpty(groups = Level2.class) // implicitly NotNull
318 @Column(length=255)
319 private String nameCache;
320
321 @XmlElement(name = "ProtectedNameCache")
322 @CacheUpdate(value="nameCache")
323 protected boolean protectedNameCache;
324
325 @XmlElement(name = "GenusOrUninomial")
326 @Field(analyze = Analyze.YES, indexNullAs=Field.DEFAULT_NULL_TOKEN)
327 @Match(MatchMode.EQUAL_REQUIRED)
328 @CacheUpdate("nameCache")
329 @Column(length=255)
330 @Pattern(regexp = "[A-Z][a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForUninomial.message}")
331 @NullOrNotEmpty
332 @NotNull(groups = Level2.class)
333 private String genusOrUninomial;
334
335 @XmlElement(name = "InfraGenericEpithet")
336 @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
337 @CacheUpdate("nameCache")
338 //TODO Val #3379
339 // @NullOrNotEmpty
340 @Column(length=255)
341 @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class,message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
342 private String infraGenericEpithet;
343
344 @XmlElement(name = "SpecificEpithet")
345 @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
346 @CacheUpdate("nameCache")
347 //TODO Val #3379
348 // @NullOrNotEmpty
349 @Column(length=255)
350 @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
351 private String specificEpithet;
352
353 @XmlElement(name = "InfraSpecificEpithet")
354 @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
355 @CacheUpdate("nameCache")
356 //TODO Val #3379
357 // @NullOrNotEmpty
358 @Column(length=255)
359 @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
360 private String infraSpecificEpithet;
361
362 @XmlElement(name = "CombinationAuthorship", type = TeamOrPersonBase.class)
363 @XmlIDREF
364 @XmlSchemaType(name = "IDREF")
365 @ManyToOne(fetch = FetchType.LAZY)
366 // @Target(TeamOrPersonBase.class)
367 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
368 @JoinColumn(name="combinationAuthorship_id")
369 @CacheUpdate("authorshipCache")
370 @IndexedEmbedded
371 private TeamOrPersonBase<?> combinationAuthorship;
372
373 @XmlElement(name = "ExCombinationAuthorship", type = TeamOrPersonBase.class)
374 @XmlIDREF
375 @XmlSchemaType(name = "IDREF")
376 @ManyToOne(fetch = FetchType.LAZY)
377 // @Target(TeamOrPersonBase.class)
378 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
379 @JoinColumn(name="exCombinationAuthorship_id")
380 @CacheUpdate("authorshipCache")
381 @IndexedEmbedded
382 private TeamOrPersonBase<?> exCombinationAuthorship;
383
384 @XmlElement(name = "BasionymAuthorship", type = TeamOrPersonBase.class)
385 @XmlIDREF
386 @XmlSchemaType(name = "IDREF")
387 @ManyToOne(fetch = FetchType.LAZY)
388 // @Target(TeamOrPersonBase.class)
389 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
390 @JoinColumn(name="basionymAuthorship_id")
391 @CacheUpdate("authorshipCache")
392 @IndexedEmbedded
393 private TeamOrPersonBase<?> basionymAuthorship;
394
395 @XmlElement(name = "ExBasionymAuthorship", type = TeamOrPersonBase.class)
396 @XmlIDREF
397 @XmlSchemaType(name = "IDREF")
398 @ManyToOne(fetch = FetchType.LAZY)
399 // @Target(TeamOrPersonBase.class)
400 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
401 @JoinColumn(name="exBasionymAuthorship_id")
402 @CacheUpdate("authorshipCache")
403 @IndexedEmbedded
404 private TeamOrPersonBase<?> exBasionymAuthorship;
405
406 @XmlElement(name = "AuthorshipCache")
407 @Fields({
408 @Field(name = "authorshipCache_tokenized"),
409 @Field(analyze = Analyze.NO)
410 })
411 @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
412 cacheReplacedProperties={"combinationAuthorship", "basionymAuthorship", "exCombinationAuthorship", "exBasionymAuthorship"} )
413 //TODO Val #3379
414 // @NotNull
415 @Column(length=255)
416 @Pattern(regexp = "^[A-Za-z0-9 \\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-\\&\\,\\(\\)\\.]+$", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForAuthority.message}")
417 private String authorshipCache;
418
419 @XmlElement(name = "ProtectedAuthorshipCache")
420 @CacheUpdate("authorshipCache")
421 protected boolean protectedAuthorshipCache;
422
423 @XmlElementWrapper(name = "HybridRelationsFromThisName")
424 @XmlElement(name = "HybridRelationsFromThisName")
425 @OneToMany(mappedBy="relatedFrom", fetch = FetchType.LAZY)
426 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
427 @Merge(MergeMode.RELATION)
428 @NotNull
429 private Set<HybridRelationship> hybridParentRelations = new HashSet<>();
430
431 @XmlElementWrapper(name = "HybridRelationsToThisName")
432 @XmlElement(name = "HybridRelationsToThisName")
433 @OneToMany(mappedBy="relatedTo", fetch = FetchType.LAZY, orphanRemoval=true) //a hybrid relation can be deleted automatically if the child is deleted.
434 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
435 @Merge(MergeMode.RELATION)
436 @NotNull
437 private Set<HybridRelationship> hybridChildRelations = new HashSet<>();
438
439
440 //if set: this name is a hybrid formula (a hybrid that does not have an own name) and no
441 //other hybrid flags may be set. A
442 //hybrid name may not have either an authorteam nor other name components.
443 @XmlElement(name ="IsHybridFormula")
444 @CacheUpdate("nameCache")
445 private boolean hybridFormula = false;
446
447 @XmlElement(name ="IsMonomHybrid")
448 @CacheUpdate("nameCache")
449 private boolean monomHybrid = false;
450
451 @XmlElement(name ="IsBinomHybrid")
452 @CacheUpdate("nameCache")
453 private boolean binomHybrid = false;
454
455 @XmlElement(name ="IsTrinomHybrid")
456 @CacheUpdate("nameCache")
457 private boolean trinomHybrid = false;
458
459 // ViralName attributes ************************* /
460
461 @XmlElement(name = "Acronym")
462 @Field
463 //TODO Val #3379
464 // @NullOrNotEmpty
465 @Column(length=255)
466 private String acronym;
467
468 // BacterialName attributes ***********************/
469
470 //Author team and year of the subgenus name
471 @XmlElement(name = "SubGenusAuthorship")
472 @Field
473 private String subGenusAuthorship;
474
475 //Approbation of name according to approved list, validation list, or validly published, paper in IJSB after 1980
476 @XmlElement(name = "NameApprobation")
477 @Field
478 private String nameApprobation;
479
480 //ZOOLOGICAL NAME
481
482 //Name of the breed of an animal
483 @XmlElement(name = "Breed")
484 @Field
485 @NullOrNotEmpty
486 @Column(length=255)
487 private String breed;
488
489 @XmlElement(name = "PublicationYear")
490 @Field(analyze = Analyze.NO)
491 @CacheUpdate(value ="authorshipCache")
492 @Min(0)
493 private Integer publicationYear;
494
495 @XmlElement(name = "OriginalPublicationYear")
496 @Field(analyze = Analyze.NO)
497 @CacheUpdate(value ="authorshipCache")
498 @Min(0)
499 private Integer originalPublicationYear;
500
501 //Cultivar attribute(s)
502
503 //the characteristical name of the cultivar
504 @XmlElement(name = "CultivarName")
505 //TODO Val #3379
506 //@NullOrNotEmpty
507 @Column(length=255)
508 private String cultivarName;
509
510
511 // *************** FACTORY METHODS ********************************/
512
513 //see TaxonNameFactory
514
515 // *********************** PARSER STATIC *******************************/
516
517
518
519 // ************* CONSTRUCTORS *************/
520 /**
521 * Class constructor: creates a new empty taxon name.
522 *
523 * @see #TaxonNameBase(Rank)
524 * @see #TaxonNameBase(HomotypicalGroup)
525 * @see #TaxonNameBase(Rank, HomotypicalGroup)
526 */
527 protected TaxonNameBase() {
528 super();
529 setNameCacheStrategy();
530 }
531 /**
532 * Class constructor: creates a new taxon name
533 * only containing its {@link Rank rank}.
534 *
535 * @param rank the rank to be assigned to <i>this</i> taxon name
536 * @see #TaxonNameBase()
537 * @see #TaxonNameBase(HomotypicalGroup)
538 * @see #TaxonNameBase(Rank, HomotypicalGroup)
539 */
540 protected TaxonNameBase(Rank rank) {
541 this(rank, null);
542 }
543 /**
544 * Class constructor: creates a new taxon name instance
545 * only containing its {@link HomotypicalGroup homotypical group}.
546 * The new taxon name will be also added to the set of taxon names
547 * belonging to this homotypical group.
548 *
549 * @param homotypicalGroup the homotypical group to which <i>this</i> taxon name belongs
550 * @see #TaxonNameBase()
551 * @see #TaxonNameBase(Rank)
552 * @see #TaxonNameBase(Rank, HomotypicalGroup)
553 */
554 protected TaxonNameBase(HomotypicalGroup homotypicalGroup) {
555 this(null, homotypicalGroup);
556 }
557
558 /**
559 * Class constructor: creates a new taxon name instance
560 * only containing its {@link Rank rank} and
561 * its {@link HomotypicalGroup homotypical group} and
562 * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
563 * The new taxon name will be also added to the set of taxon names
564 * belonging to this homotypical group.
565 *
566 * @param rank the rank to be assigned to <i>this</i> taxon name
567 * @param homotypicalGroup the homotypical group to which <i>this</i> taxon name belongs
568 * @see #TaxonNameBase()
569 * @see #TaxonNameBase(Rank)
570 * @see #TaxonNameBase(HomotypicalGroup)
571 */
572 protected TaxonNameBase(Rank rank, HomotypicalGroup homotypicalGroup) {
573 super();
574 this.setRank(rank);
575 if (homotypicalGroup == null){
576 homotypicalGroup = new HomotypicalGroup();
577 }
578 homotypicalGroup.addTypifiedName(this);
579 this.homotypicalGroup = homotypicalGroup;
580 setNameCacheStrategy();
581 }
582
583
584 /**
585 * Class constructor: creates a new non viral taxon name instance
586 * containing its {@link Rank rank},
587 * its {@link HomotypicalGroup homotypical group},
588 * its scientific name components, its {@link eu.etaxonomy.cdm.model.agent.TeamOrPersonBase author(team)},
589 * its {@link eu.etaxonomy.cdm.model.reference.Reference nomenclatural reference} and
590 * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
591 * The new non viral taxon name instance will be also added to the set of
592 * non viral taxon names belonging to this homotypical group.
593 *
594 * @param rank the rank to be assigned to <i>this</i> non viral taxon name
595 * @param genusOrUninomial the string for <i>this</i> non viral taxon name
596 * if its rank is genus or higher or for the genus part
597 * if its rank is lower than genus
598 * @param infraGenericEpithet the string for the first epithet of
599 * <i>this</i> non viral taxon name if its rank is lower than genus
600 * and higher than species aggregate
601 * @param specificEpithet the string for the first epithet of
602 * <i>this</i> non viral taxon name if its rank is species aggregate or lower
603 * @param infraSpecificEpithet the string for the second epithet of
604 * <i>this</i> non viral taxon name if its rank is lower than species
605 * @param combinationAuthorship the author or the team who published <i>this</i> non viral taxon name
606 * @param nomenclaturalReference the nomenclatural reference where <i>this</i> non viral taxon name was published
607 * @param nomenclMicroRef the string with the details for precise location within the nomenclatural reference
608 * @param homotypicalGroup the homotypical group to which <i>this</i> non viral taxon name belongs
609 * @see #NonViralName()
610 * @see #NonViralName(Rank, HomotypicalGroup)
611 * @see #NewInstance(Rank, HomotypicalGroup)
612 * @see eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
613 * @see eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
614 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
615 */
616 protected TaxonNameBase(Rank rank, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, TeamOrPersonBase combinationAuthorship, INomenclaturalReference nomenclaturalReference, String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
617 this(rank, homotypicalGroup);
618 setGenusOrUninomial(genusOrUninomial);
619 setInfraGenericEpithet (infraGenericEpithet);
620 setSpecificEpithet(specificEpithet);
621 setInfraSpecificEpithet(infraSpecificEpithet);
622 setCombinationAuthorship(combinationAuthorship);
623 setNomenclaturalReference(nomenclaturalReference);
624 this.setNomenclaturalMicroReference(nomenclMicroRef);
625 }
626
627
628 private void setNameCacheStrategy(){
629 if (getClass() == NonViralName.class){
630 this.cacheStrategy = (S)NonViralNameDefaultCacheStrategy.NewInstance();
631 }
632 }
633
634
635 @Override
636 public void initListener(){
637 PropertyChangeListener listener = new PropertyChangeListener() {
638 @Override
639 public void propertyChange(PropertyChangeEvent e) {
640 boolean protectedByLowerCache = false;
641 //authorship cache
642 if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
643 if (protectedAuthorshipCache){
644 protectedByLowerCache = true;
645 }else{
646 authorshipCache = null;
647 }
648 }
649
650 //nameCache
651 if (fieldHasCacheUpdateProperty(e.getPropertyName(), "nameCache")){
652 if (protectedNameCache){
653 protectedByLowerCache = true;
654 }else{
655 nameCache = null;
656 }
657 }
658 //title cache
659 if (! fieldHasNoUpdateProperty(e.getPropertyName(), "titleCache")){
660 if (isProtectedTitleCache()|| protectedByLowerCache == true ){
661 protectedByLowerCache = true;
662 }else{
663 titleCache = null;
664 }
665 }
666 //full title cache
667 if (! fieldHasNoUpdateProperty(e.getPropertyName(), "fullTitleCache")){
668 if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
669 protectedByLowerCache = true;
670 }else{
671 fullTitleCache = null;
672 }
673 }
674 }
675 };
676 addPropertyChangeListener(listener); //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
677 }
678
679 private static Map<String, java.lang.reflect.Field> allFields = null;
680 protected Map<String, java.lang.reflect.Field> getAllFields(){
681 if (allFields == null){
682 allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
683 }
684 return allFields;
685 }
686
687 /**
688 * @param propertyName
689 * @param string
690 * @return
691 */
692 private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
693 java.lang.reflect.Field field;
694 try {
695 field = getAllFields().get(propertyName);
696 if (field != null){
697 CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
698 if (updateAnnotation != null){
699 for (String value : updateAnnotation.value()){
700 if (cacheName.equals(value)){
701 return true;
702 }
703 }
704 }
705 }
706 return false;
707 } catch (SecurityException e1) {
708 throw e1;
709 }
710 }
711
712 private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
713 java.lang.reflect.Field field;
714 //do not update fields with the same name
715 if (cacheName.equals(propertyName)){
716 return true;
717 }
718 //evaluate annotation
719 try {
720 field = getAllFields().get(propertyName);
721 if (field != null){
722 CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
723 if (updateAnnotation != null){
724 for (String value : updateAnnotation.noUpdate()){
725 if (cacheName.equals(value)){
726 return true;
727 }
728 }
729 }
730 }
731 return false;
732 } catch (SecurityException e1) {
733 throw e1;
734 }
735 }
736
737 // ****************** GETTER / SETTER ****************************/
738
739 /**
740 * Returns the boolean value of the flag intended to protect (true)
741 * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
742 * string of <i>this</i> non viral taxon name.
743 *
744 * @return the boolean value of the protectedNameCache flag
745 * @see #getNameCache()
746 */
747 @Override
748 public boolean isProtectedNameCache() {
749 return protectedNameCache;
750 }
751
752 /**
753 * @see #isProtectedNameCache()
754 */
755 @Override
756 public void setProtectedNameCache(boolean protectedNameCache) {
757 this.protectedNameCache = protectedNameCache;
758 }
759
760 /**
761 * Returns either the scientific name string (without authorship) for <i>this</i>
762 * non viral taxon name if its rank is genus or higher (monomial) or the string for
763 * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
764 * Genus or uninomial strings begin with an upper case letter.
765 *
766 * @return the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
767 * @see #getNameCache()
768 */
769 @Override
770 public String getGenusOrUninomial() {
771 return genusOrUninomial;
772 }
773
774 /**
775 * @see #getGenusOrUninomial()
776 */
777 @Override
778 public void setGenusOrUninomial(String genusOrUninomial) {
779 this.genusOrUninomial = StringUtils.isBlank(genusOrUninomial) ? null : genusOrUninomial;
780 }
781
782 /**
783 * Returns the genus subdivision epithet string (infrageneric part) for
784 * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
785 * higher than species aggregate: binomial). Genus subdivision epithet
786 * strings begin with an upper case letter.
787 *
788 * @return the string containing the infrageneric part of <i>this</i> non viral taxon name
789 * @see #getNameCache()
790 */
791 @Override
792 public String getInfraGenericEpithet(){
793 return this.infraGenericEpithet;
794 }
795
796 /**
797 * @see #getInfraGenericEpithet()
798 */
799 @Override
800 public void setInfraGenericEpithet(String infraGenericEpithet){
801 this.infraGenericEpithet = StringUtils.isBlank(infraGenericEpithet)? null : infraGenericEpithet;
802 }
803
804 /**
805 * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
806 * species aggregate or lower (bi- or trinomial). Species epithet strings
807 * begin with a lower case letter.
808 *
809 * @return the string containing the species epithet of <i>this</i> non viral taxon name
810 * @see #getNameCache()
811 */
812 @Override
813 public String getSpecificEpithet(){
814 return this.specificEpithet;
815 }
816
817 /**
818 * @see #getSpecificEpithet()
819 */
820 @Override
821 public void setSpecificEpithet(String specificEpithet){
822 this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
823 }
824
825 /**
826 * Returns the species subdivision epithet string (infraspecific part) for
827 * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
828 * (lower than species: trinomial). Species subdivision epithet strings
829 * begin with a lower case letter.
830 *
831 * @return the string containing the infraspecific part of <i>this</i> non viral taxon name
832 * @see #getNameCache()
833 */
834 @Override
835 public String getInfraSpecificEpithet(){
836 return this.infraSpecificEpithet;
837 }
838
839 /**
840 * @see #getInfraSpecificEpithet()
841 */
842 @Override
843 public void setInfraSpecificEpithet(String infraSpecificEpithet){
844 this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
845 }
846
847 /**
848 * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
849 * taxon name.
850 *
851 * @return the nomenclatural author (team) of <i>this</i> non viral taxon name
852 * @see eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
853 * @see eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
854 */
855 @Override
856 public TeamOrPersonBase<?> getCombinationAuthorship(){
857 return this.combinationAuthorship;
858 }
859
860 /**
861 * @see #getCombinationAuthorship()
862 */
863 @Override
864 public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
865 this.combinationAuthorship = combinationAuthorship;
866 }
867
868 /**
869 * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
870 * the publication of <i>this</i> non viral taxon name as generally stated by
871 * the {@link #getCombinationAuthorship() combination author (team)} itself.<BR>
872 * An ex-author(-team) is an author(-team) to whom a taxon name was ascribed
873 * although it is not the author(-team) of a valid publication (for instance
874 * without the validating description or diagnosis in case of a name for a
875 * new taxon). The name of this ascribed authorship, followed by "ex", may
876 * be inserted before the name(s) of the publishing author(s) of the validly
877 * published name:<BR>
878 * <i>Lilium tianschanicum</i> was described by Grubov (1977) as a new species and
879 * its name was ascribed to Ivanova; since there is no indication that
880 * Ivanova provided the validating description, the name may be cited as
881 * <i>Lilium tianschanicum</i> N. A. Ivanova ex Grubov or <i>Lilium tianschanicum</i> Grubov.
882 * <P>
883 * The presence of an author (team) of <i>this</i> non viral taxon name is a
884 * condition for the existence of an ex author (team) for <i>this</i> same name.
885 *
886 * @return the nomenclatural ex author (team) of <i>this</i> non viral taxon name
887 * @see #getCombinationAuthorship()
888 * @see eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
889 * @see eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
890 */
891 @Override
892 public TeamOrPersonBase<?> getExCombinationAuthorship(){
893 return this.exCombinationAuthorship;
894 }
895
896 /**
897 * @see #getExCombinationAuthorship()
898 */
899 @Override
900 public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
901 this.exCombinationAuthorship = exCombinationAuthorship;
902 }
903
904 /**
905 * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
906 * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
907 * author (team) can only exist if <i>this</i> non viral taxon name is a new
908 * combination due to a taxonomical revision.
909 *
910 * @return the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
911 * @see #getCombinationAuthorship()
912 * @see eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
913 * @see eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
914 */
915 @Override
916 public TeamOrPersonBase<?> getBasionymAuthorship(){
917 return basionymAuthorship;
918 }
919
920 /**
921 * @see #getBasionymAuthorship()
922 */
923 @Override
924 public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
925 this.basionymAuthorship = basionymAuthorship;
926 }
927
928 /**
929 * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
930 * the publication of the original combination <i>this</i> non viral taxon name is
931 * based on. This should have been generally stated by
932 * the {@link #getBasionymAuthorship() basionym author (team)} itself.
933 * The presence of a basionym author (team) of <i>this</i> non viral taxon name is a
934 * condition for the existence of an ex basionym author (team)
935 * for <i>this</i> same name.
936 *
937 * @return the nomenclatural ex basionym author (team) of <i>this</i> non viral taxon name
938 * @see #getBasionymAuthorship()
939 * @see #getExCombinationAuthorship()
940 * @see #getCombinationAuthorship()
941 * @see eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
942 * @see eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
943 */
944 @Override
945 public TeamOrPersonBase<?> getExBasionymAuthorship(){
946 return exBasionymAuthorship;
947 }
948
949 /**
950 * @see #getExBasionymAuthorship()
951 */
952 @Override
953 public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
954 this.exBasionymAuthorship = exBasionymAuthorship;
955 }
956
957 /**
958 * Returns the boolean value of the flag intended to protect (true)
959 * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
960 * of <i>this</i> non viral taxon name.
961 *
962 * @return the boolean value of the protectedAuthorshipCache flag
963 * @see #getAuthorshipCache()
964 */
965 @Override
966 public boolean isProtectedAuthorshipCache() {
967 return protectedAuthorshipCache;
968 }
969
970 /**
971 * @see #isProtectedAuthorshipCache()
972 * @see #getAuthorshipCache()
973 */
974 @Override
975 public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
976 this.protectedAuthorshipCache = protectedAuthorshipCache;
977 }
978
979 /**
980 * Returns the set of all {@link HybridRelationship hybrid relationships}
981 * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
982 *
983 * @see #getHybridRelationships()
984 * @see #getChildRelationships()
985 * @see HybridRelationshipType
986 */
987 @Override
988 public Set<HybridRelationship> getHybridParentRelations() {
989 if(hybridParentRelations == null) {
990 this.hybridParentRelations = new HashSet<>();
991 }
992 return hybridParentRelations;
993 }
994
995 private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
996 this.hybridParentRelations = hybridParentRelations;
997 }
998
999
1000 /**
1001 * Returns the set of all {@link HybridRelationship hybrid relationships}
1002 * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
1003 *
1004 * @see #getHybridRelationships()
1005 * @see #getParentRelationships()
1006 * @see HybridRelationshipType
1007 */
1008 @Override
1009 public Set<HybridRelationship> getHybridChildRelations() {
1010 if(hybridChildRelations == null) {
1011 this.hybridChildRelations = new HashSet<>();
1012 }
1013 return hybridChildRelations;
1014 }
1015
1016 private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1017 this.hybridChildRelations = hybridChildRelations;
1018 }
1019
1020 @Override
1021 public boolean isProtectedFullTitleCache() {
1022 return protectedFullTitleCache;
1023 }
1024
1025 @Override
1026 public void setProtectedFullTitleCache(boolean protectedFullTitleCache) {
1027 this.protectedFullTitleCache = protectedFullTitleCache;
1028 }
1029
1030 /**
1031 * Returns the boolean value of the flag indicating whether the name of <i>this</i>
1032 * botanical taxon name is a hybrid formula (true) or not (false). A hybrid
1033 * named by a hybrid formula (composed with its parent names by placing the
1034 * multiplication sign between them) does not have an own published name
1035 * and therefore has neither an {@link NonViralName#getAuthorshipCache() autorship}
1036 * nor other name components. If this flag is set no other hybrid flags may
1037 * be set.
1038 *
1039 * @return the boolean value of the isHybridFormula flag
1040 * @see #isMonomHybrid()
1041 * @see #isBinomHybrid()
1042 * @see #isTrinomHybrid()
1043 */
1044 @Override
1045 @Transient
1046 @java.beans.Transient
1047 public boolean isHybridFormula(){
1048 return this.hybridFormula;
1049 }
1050
1051 /**
1052 * @see #isHybridFormula()
1053 */
1054 @Override
1055 public void setHybridFormula(boolean hybridFormula){
1056 this.hybridFormula = hybridFormula;
1057 }
1058
1059 /**
1060 * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1061 * taxon name is the name of an intergeneric hybrid (true) or not (false).
1062 * In this case the multiplication sign is placed before the scientific
1063 * name. If this flag is set no other hybrid flags may be set.
1064 *
1065 * @return the boolean value of the isMonomHybrid flag
1066 * @see #isHybridFormula()
1067 * @see #isBinomHybrid()
1068 * @see #isTrinomHybrid()
1069 */
1070 @Override
1071 public boolean isMonomHybrid(){
1072 return this.monomHybrid;
1073 }
1074
1075 /**
1076 * @see #isMonomHybrid()
1077 * @see #isBinomHybrid()
1078 * @see #isTrinomHybrid()
1079 */
1080 @Override
1081 public void setMonomHybrid(boolean monomHybrid){
1082 this.monomHybrid = monomHybrid;
1083 }
1084
1085 /**
1086 * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1087 * taxon name is the name of an interspecific hybrid (true) or not (false).
1088 * In this case the multiplication sign is placed before the species
1089 * epithet. If this flag is set no other hybrid flags may be set.
1090 *
1091 * @return the boolean value of the isBinomHybrid flag
1092 * @see #isHybridFormula()
1093 * @see #isMonomHybrid()
1094 * @see #isTrinomHybrid()
1095 */
1096 @Override
1097 public boolean isBinomHybrid(){
1098 return this.binomHybrid;
1099 }
1100
1101 /**
1102 * @see #isBinomHybrid()
1103 * @see #isMonomHybrid()
1104 * @see #isTrinomHybrid()
1105 */
1106 @Override
1107 public void setBinomHybrid(boolean binomHybrid){
1108 this.binomHybrid = binomHybrid;
1109 }
1110
1111 /**
1112 * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1113 * taxon name is the name of an infraspecific hybrid (true) or not (false).
1114 * In this case the term "notho-" (optionally abbreviated "n-") is used as
1115 * a prefix to the term denoting the infraspecific rank of <i>this</i> botanical
1116 * taxon name. If this flag is set no other hybrid flags may be set.
1117 *
1118 * @return the boolean value of the isTrinomHybrid flag
1119 * @see #isHybridFormula()
1120 * @see #isMonomHybrid()
1121 * @see #isBinomHybrid()
1122 */
1123 @Override
1124 public boolean isTrinomHybrid(){
1125 return this.trinomHybrid;
1126 }
1127
1128 /**
1129 * @see #isTrinomHybrid()
1130 * @see #isBinomHybrid()
1131 * @see #isMonomHybrid()
1132 */
1133 @Override
1134 public void setTrinomHybrid(boolean trinomHybrid){
1135 this.trinomHybrid = trinomHybrid;
1136 }
1137
1138 // ****************** VIRAL NAME ******************/
1139
1140 /**
1141 * Returns the accepted acronym (an assigned abbreviation) string for <i>this</i>
1142 * viral taxon name. For instance PCV stays for Peanut Clump Virus.
1143 *
1144 * @return the string containing the accepted acronym of <i>this</i> viral taxon name
1145 */
1146 @Override
1147 public String getAcronym(){
1148 return this.acronym;
1149 }
1150
1151 /**
1152 * @see #getAcronym()
1153 */
1154 @Override
1155 public void setAcronym(String acronym){
1156 this.acronym = StringUtils.isBlank(acronym)? null : acronym;
1157 }
1158
1159 // ****************** BACTERIAL NAME ******************/
1160
1161 /**
1162 * Returns the string containing the authorship with the year and details
1163 * of the reference in which the subgenus included in the scientific name
1164 * of <i>this</i> bacterial taxon name was published.
1165 * For instance if the bacterial taxon name is
1166 * 'Bacillus (subgen. Aerobacillus Donker 1926, 128) polymyxa' the subgenus
1167 * authorship string is 'Donker 1926, 128'.
1168 *
1169 * @return the string containing the complete subgenus' authorship
1170 * included in <i>this</i> bacterial taxon name
1171 */
1172 @Override
1173 public String getSubGenusAuthorship(){
1174 return this.subGenusAuthorship;
1175 }
1176
1177 /**
1178 * @see #getSubGenusAuthorship()
1179 */
1180 @Override
1181 public void setSubGenusAuthorship(String subGenusAuthorship){
1182 this.subGenusAuthorship = subGenusAuthorship;
1183 }
1184
1185 /**
1186 * Returns the string representing the reason for the approbation of <i>this</i>
1187 * bacterial taxon name. Bacterial taxon names are valid or approved
1188 * according to:
1189 * <ul>
1190 * <li>the approved list, c.f.r. IJSB 1980 (AL)
1191 * <li>the validation list, in IJSB after 1980 (VL)
1192 * </ul>
1193 * or
1194 * <ul>
1195 * <li>are validly published as paper in IJSB after 1980 (VP).
1196 * </ul>
1197 * IJSB is the acronym for International Journal of Systematic Bacteriology.
1198 *
1199 * @return the string with the source of the approbation for <i>this</i> bacterial taxon name
1200 */
1201 @Override
1202 public String getNameApprobation(){
1203 return this.nameApprobation;
1204 }
1205
1206 /**
1207 * @see #getNameApprobation()
1208 */
1209 @Override
1210 public void setNameApprobation(String nameApprobation){
1211 this.nameApprobation = nameApprobation;
1212 }
1213
1214 //************ Zoological Name
1215
1216 /**
1217 * Returns the breed name string for <i>this</i> animal (zoological taxon name).
1218 *
1219 * @return the string containing the breed name for <i>this</i> zoological taxon name
1220 */
1221 @Override
1222 public String getBreed(){
1223 return this.breed;
1224 }
1225 /**
1226 * @see #getBreed()
1227 */
1228 @Override
1229 public void setBreed(String breed){
1230 this.breed = StringUtils.isBlank(breed) ? null : breed;
1231 }
1232
1233 /**
1234 * Returns the publication year (as an integer) for <i>this</i> zoological taxon
1235 * name. If the publicationYear attribute is null and a nomenclatural
1236 * reference exists the year could be computed from the
1237 * {@link eu.etaxonomy.cdm.reference.INomenclaturalReference nomenclatural reference}.
1238 *
1239 * @return the integer representing the publication year for <i>this</i> zoological taxon name
1240 * @see #getOriginalPublicationYear()
1241 */
1242 @Override
1243 public Integer getPublicationYear() {
1244 return publicationYear;
1245 }
1246 /**
1247 * @see #getPublicationYear()
1248 */
1249 @Override
1250 public void setPublicationYear(Integer publicationYear) {
1251 this.publicationYear = publicationYear;
1252 }
1253
1254 /**
1255 * Returns the publication year (as an integer) of the original validly
1256 * published species epithet for <i>this</i> zoological taxon name. This only
1257 * applies for zoological taxon names that are no {@link TaxonNameBase#isOriginalCombination() original combinations}.
1258 * If the originalPublicationYear attribute is null the year could be taken
1259 * from the publication year of the corresponding original name (basionym)
1260 * or from the {@link eu.etaxonomy.cdm.reference.INomenclaturalReference nomenclatural reference} of the basionym
1261 * if it exists.
1262 *
1263 * @return the integer representing the publication year of the original
1264 * species epithet corresponding to <i>this</i> zoological taxon name
1265 * @see #getPublicationYear()
1266 */
1267 @Override
1268 public Integer getOriginalPublicationYear() {
1269 return originalPublicationYear;
1270 }
1271 /**
1272 * @see #getOriginalPublicationYear()
1273 */
1274 @Override
1275 public void setOriginalPublicationYear(Integer originalPublicationYear) {
1276 this.originalPublicationYear = originalPublicationYear;
1277 }
1278
1279 // **** Cultivar Name ************
1280
1281 /**
1282 * Returns the characteristical cultivar name part string assigned to <i>this</i>
1283 * cultivar taxon name. In the scientific name "Clematis alpina 'Ruby'" for
1284 * instance this characteristical string is "Ruby". This part of the name is
1285 * governed by the International Code for the Nomenclature of Cultivated
1286 * Plants and the string should include neither quotes nor + signs
1287 * (these elements of the name cache string will be generated by the
1288 * {@link eu.etaxonomy.cdm.strategy.cache.name.BotanicNameDefaultCacheStrategy default cache strategy}).
1289 */
1290 @Override
1291 public String getCultivarName(){
1292 return this.cultivarName;
1293 }
1294
1295 /**
1296 * @see #getCultivarName()
1297 */
1298 @Override
1299 public void setCultivarName(String cultivarName){
1300 this.cultivarName = StringUtils.isBlank(cultivarName) ? null : cultivarName;
1301 }
1302
1303
1304 // **************** ADDER / REMOVE *************************/
1305
1306 /**
1307 * Adds the given {@link HybridRelationship hybrid relationship} to the set
1308 * of {@link #getHybridRelationships() hybrid relationships} of both non-viral names
1309 * involved in this hybrid relationship. One of both non-viral names
1310 * must be <i>this</i> non-viral name otherwise no addition will be carried
1311 * out. The {@link eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedTo() child
1312 * non viral taxon name} must be a hybrid, which means that one of its four hybrid flags must be set.
1313 *
1314 * @param relationship the hybrid relationship to be added
1315 * @see #isHybridFormula()
1316 * @see #isMonomHybrid()
1317 * @see #isBinomHybrid()
1318 * @see #isTrinomHybrid()
1319 * @see #getHybridRelationships()
1320 * @see #getParentRelationships()
1321 * @see #getChildRelationships()
1322 * @see #addRelationship(RelationshipBase)
1323 * @throws IllegalArgumentException
1324 */
1325 protected void addHybridRelationship(HybridRelationship rel) {
1326 if (rel!=null && rel.getHybridName().equals(this)){
1327 this.hybridChildRelations.add(rel);
1328 }else if(rel!=null && rel.getParentName().equals(this)){
1329 this.hybridParentRelations.add(rel);
1330 }else{
1331 throw new IllegalArgumentException("Hybrid relationship is either null or the relationship does not reference this name");
1332 }
1333 }
1334
1335
1336 /**
1337 * Removes one {@link HybridRelationship hybrid relationship} from the set of
1338 * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1339 * is involved. The hybrid relationship will also be removed from the set
1340 * belonging to the second botanical taxon name involved.
1341 *
1342 * @param relationship the hybrid relationship which should be deleted from the corresponding sets
1343 * @see #getHybridRelationships()
1344 */
1345 @Override
1346 public void removeHybridRelationship(HybridRelationship hybridRelation) {
1347 if (hybridRelation == null) {
1348 return;
1349 }
1350
1351 TaxonNameBase<?,?> parent = hybridRelation.getParentName();
1352 TaxonNameBase<?,?> child = hybridRelation.getHybridName();
1353 if (this.equals(parent)){
1354 this.hybridParentRelations.remove(hybridRelation);
1355 child.hybridChildRelations.remove(hybridRelation);
1356 hybridRelation.setHybridName(null);
1357 hybridRelation.setParentName(null);
1358 }
1359 if (this.equals(child)){
1360 parent.hybridParentRelations.remove(hybridRelation);
1361 this.hybridChildRelations.remove(hybridRelation);
1362 hybridRelation.setHybridName(null);
1363 hybridRelation.setParentName(null);
1364 }
1365 }
1366
1367 //********* METHODS **************************************/
1368
1369 @Override
1370 public String generateFullTitle(){
1371 if (cacheStrategy == null){
1372 logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1373 return null;
1374 }else{
1375 return cacheStrategy.getFullTitleCache(this);
1376 }
1377 }
1378
1379
1380 @Override
1381 public void setFullTitleCache(String fullTitleCache){
1382 setFullTitleCache(fullTitleCache, PROTECTED);
1383 }
1384
1385 @Override
1386 public void setFullTitleCache(String fullTitleCache, boolean protectCache){
1387 fullTitleCache = getTruncatedCache(fullTitleCache);
1388 this.fullTitleCache = fullTitleCache;
1389 this.setProtectedFullTitleCache(protectCache);
1390 }
1391
1392 /**
1393 * Needs to be implemented by those classes that handle autonyms (e.g. botanical names).
1394 **/
1395 @Override
1396 @Transient
1397 public boolean isAutonym(){
1398 return false;
1399 }
1400
1401 @Override
1402 @Transient
1403 public List<TaggedText> getTaggedName(){
1404 return getCacheStrategy().getTaggedTitle(this);
1405 }
1406
1407 @Override
1408 @Transient
1409 public String getFullTitleCache(){
1410 if (protectedFullTitleCache){
1411 return this.fullTitleCache;
1412 }
1413 updateAuthorshipCache();
1414 if (fullTitleCache == null ){
1415 this.fullTitleCache = getTruncatedCache(generateFullTitle());
1416 }
1417 return fullTitleCache;
1418 }
1419
1420
1421 @Override
1422 public String getTitleCache(){
1423 if(!protectedTitleCache) {
1424 updateAuthorshipCache();
1425 }
1426 return super.getTitleCache();
1427 }
1428
1429 @Override
1430 public void setTitleCache(String titleCache, boolean protectCache){
1431 super.setTitleCache(titleCache, protectCache);
1432 }
1433
1434 /**
1435 * Returns the concatenated and formated authorteams string including
1436 * basionym and combination authors of <i>this</i> non viral taxon name.
1437 * If the protectedAuthorshipCache flag is set this method returns the
1438 * string stored in the the authorshipCache attribute, otherwise it
1439 * generates the complete authorship string, returns it and stores it in
1440 * the authorshipCache attribute.
1441 *
1442 * @return the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
1443 * @see #generateAuthorship()
1444 */
1445 @Override
1446 @Transient
1447 public String getAuthorshipCache() {
1448 if (protectedAuthorshipCache){
1449 return this.authorshipCache;
1450 }
1451 if (this.authorshipCache == null ){
1452 this.authorshipCache = generateAuthorship();
1453 }else{
1454 //TODO get isDirty of authors, make better if possible
1455 this.setAuthorshipCache(generateAuthorship(), protectedAuthorshipCache); //throw change event to inform higher caches
1456
1457 }
1458 return authorshipCache;
1459 }
1460
1461
1462
1463 /**
1464 * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
1465 * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
1466 * @return
1467 */
1468 private void updateAuthorshipCache() {
1469 //updates the authorship cache if necessary and via the listener updates all higher caches
1470 if (protectedAuthorshipCache == false){
1471 String oldCache = this.authorshipCache;
1472 String newCache = this.getAuthorshipCache();
1473 if ( (oldCache == null && newCache != null) || CdmUtils.nullSafeEqual(oldCache,newCache)){
1474 this.setAuthorshipCache(this.getAuthorshipCache(), false);
1475 }
1476 }
1477 }
1478
1479
1480 /**
1481 * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
1482 * flag to <code>true</code>.
1483 *
1484 * @param authorshipCache the string which identifies the complete authorship of <i>this</i> non viral taxon name
1485 * @see #getAuthorshipCache()
1486 */
1487 @Override
1488 public void setAuthorshipCache(String authorshipCache) {
1489 setAuthorshipCache(authorshipCache, true);
1490 }
1491
1492
1493 /**
1494 * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
1495 *
1496 * @param authorshipCache the string which identifies the complete authorship of <i>this</i> non viral taxon name
1497 * @param protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
1498 * the flag is set to <code>false</code>.
1499 * @see #getAuthorshipCache()
1500 */
1501 @Override
1502 public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
1503 this.authorshipCache = authorshipCache;
1504 this.setProtectedAuthorshipCache(protectedAuthorshipCache);
1505 }
1506
1507 /**
1508 * Generates and returns a concatenated and formated authorteams string
1509 * including basionym and combination authors of <i>this</i> non viral taxon name
1510 * according to the strategy defined in
1511 * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonNameBase) INonViralNameCacheStrategy}.
1512 *
1513 * @return the string with the concatenated and formatted author teams for <i>this</i> taxon name
1514 * @see eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(TaxonNameBase)
1515 */
1516 @Override
1517 public String generateAuthorship(){
1518 if (cacheStrategy == null){
1519 logger.warn("No CacheStrategy defined for taxon name: " + this.getUuid());
1520 return null;
1521 }else{
1522 return ((INonViralNameCacheStrategy)cacheStrategy).getAuthorshipCache(this);
1523 }
1524 }
1525
1526
1527
1528 /**
1529 * Tests if the given name has any authors.
1530 * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1531 */
1532 @Override
1533 public boolean hasAuthors() {
1534 return (this.getCombinationAuthorship() != null ||
1535 this.getExCombinationAuthorship() != null ||
1536 this.getBasionymAuthorship() != null ||
1537 this.getExBasionymAuthorship() != null);
1538 }
1539
1540 /**
1541 * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1542 * @return
1543 */
1544 @Override
1545 public String computeCombinationAuthorNomenclaturalTitle() {
1546 return computeNomenclaturalTitle(this.getCombinationAuthorship());
1547 }
1548
1549 /**
1550 * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1551 * @return
1552 */
1553 @Override
1554 public String computeBasionymAuthorNomenclaturalTitle() {
1555 return computeNomenclaturalTitle(this.getBasionymAuthorship());
1556 }
1557
1558
1559 /**
1560 * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1561 * @return
1562 */
1563 @Override
1564 public String computeExCombinationAuthorNomenclaturalTitle() {
1565 return computeNomenclaturalTitle(this.getExCombinationAuthorship());
1566 }
1567
1568 /**
1569 * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1570 * @return
1571 */
1572 @Override
1573 public String computeExBasionymAuthorNomenclaturalTitle() {
1574 return computeNomenclaturalTitle(this.getExBasionymAuthorship());
1575 }
1576
1577 private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1578 if (author == null){
1579 return null;
1580 }else{
1581 return author.getNomenclaturalTitle();
1582 }
1583 }
1584
1585 /**
1586 * Returns the set of all {@link NameRelationship name relationships}
1587 * in which <i>this</i> taxon name is involved. A taxon name can be both source
1588 * in some name relationships or target in some others.
1589 *
1590 * @see #getRelationsToThisName()
1591 * @see #getRelationsFromThisName()
1592 * @see #addNameRelationship(NameRelationship)
1593 * @see #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1594 * @see #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1595 */
1596 @Override
1597 @Transient
1598 public Set<NameRelationship> getNameRelations() {
1599 Set<NameRelationship> rels = new HashSet<NameRelationship>();
1600 rels.addAll(getRelationsFromThisName());
1601 rels.addAll(getRelationsToThisName());
1602 return rels;
1603 }
1604
1605 /**
1606 * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1607 * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1608 * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1609 *
1610 * @param toName the taxon name of the target for this new name relationship
1611 * @param type the type of this new name relationship
1612 * @param ruleConsidered the string which specifies the rule on which this name relationship is based
1613 * @see #getRelationsToThisName()
1614 * @see #getNameRelations()
1615 * @see #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1616 * @see #addNameRelationship(NameRelationship)
1617 */
1618 @Override
1619 public void addRelationshipToName(TaxonNameBase toName, NameRelationshipType type, String ruleConsidered){
1620 addRelationshipToName(toName, type, null, null, ruleConsidered);
1621 // NameRelationship rel = new NameRelationship(toName, this, type, ruleConsidered);
1622 }
1623
1624 /**
1625 * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from <i>this</i> taxon name to another taxon name
1626 * and adds it both to the set of {@link #getRelationsFromThisName() relations from <i>this</i> taxon name} and
1627 * to the set of {@link #getRelationsToThisName() relations to the other taxon name}.
1628 *
1629 * @param toName the taxon name of the target for this new name relationship
1630 * @param type the type of this new name relationship
1631 * @param ruleConsidered the string which specifies the rule on which this name relationship is based
1632 * @return
1633 * @see #getRelationsToThisName()
1634 * @see #getNameRelations()
1635 * @see #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1636 * @see #addNameRelationship(NameRelationship)
1637 */
1638 @Override
1639 public NameRelationship addRelationshipToName(TaxonNameBase toName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1640 if (toName == null){
1641 throw new NullPointerException("Null is not allowed as name for a name relationship");
1642 }
1643 NameRelationship rel = new NameRelationship(toName, this, type, citation, microCitation, ruleConsidered);
1644 return rel;
1645 }
1646
1647 /**
1648 * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1649 * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1650 * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1651 *
1652 * @param fromName the taxon name of the source for this new name relationship
1653 * @param type the type of this new name relationship
1654 * @param ruleConsidered the string which specifies the rule on which this name relationship is based
1655 * @param citation the reference in which this relation was described
1656 * @param microCitation the reference detail for this relation (e.g. page)
1657 * @see #getRelationsFromThisName()
1658 * @see #getNameRelations()
1659 * @see #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1660 * @see #addNameRelationship(NameRelationship)
1661 */
1662 @Override
1663 public NameRelationship addRelationshipFromName(TaxonNameBase fromName, NameRelationshipType type, String ruleConsidered){
1664 //fromName.addRelationshipToName(this, type, null, null, ruleConsidered);
1665 return this.addRelationshipFromName(fromName, type, null, null, ruleConsidered);
1666 }
1667 /**
1668 * Creates a new {@link NameRelationship#NameRelationship(TaxonNameBase, TaxonNameBase, NameRelationshipType, String) name relationship} from another taxon name to <i>this</i> taxon name
1669 * and adds it both to the set of {@link #getRelationsToThisName() relations to <i>this</i> taxon name} and
1670 * to the set of {@link #getRelationsFromThisName() relations from the other taxon name}.
1671 *
1672 * @param fromName the taxon name of the source for this new name relationship
1673 * @param type the type of this new name relationship
1674 * @param ruleConsidered the string which specifies the rule on which this name relationship is based
1675 * @param citation the reference in which this relation was described
1676 * @param microCitation the reference detail for this relation (e.g. page)
1677 * @see #getRelationsFromThisName()
1678 * @see #getNameRelations()
1679 * @see #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1680 * @see #addNameRelationship(NameRelationship)
1681 */
1682 @Override
1683 public NameRelationship addRelationshipFromName(TaxonNameBase fromName, NameRelationshipType type, Reference citation, String microCitation, String ruleConsidered){
1684 return fromName.addRelationshipToName(this, type, citation, microCitation, ruleConsidered);
1685 }
1686
1687 /**
1688 * Adds an existing {@link NameRelationship name relationship} either to the set of
1689 * {@link #getRelationsToThisName() relations to <i>this</i> taxon name} or to the set of
1690 * {@link #getRelationsFromThisName() relations from <i>this</i> taxon name}. If neither the
1691 * source nor the target of the name relationship match with <i>this</i> taxon name
1692 * no addition will be carried out.
1693 *
1694 * @param rel the name relationship to be added to one of <i>this</i> taxon name's name relationships sets
1695 * @see #getNameRelations()
1696 * @see #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1697 * @see #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1698 */
1699 protected void addNameRelationship(NameRelationship rel) {
1700 if (rel != null ){
1701 if (rel.getToName().equals(this)){
1702 this.relationsToThisName.add(rel);
1703 }else if(rel.getFromName().equals(this)){
1704 this.relationsFromThisName.add(rel);
1705 }
1706 NameRelationshipType type = rel.getType();
1707 if (type != null && ( type.isBasionymRelation() || type.isReplacedSynonymRelation() ) ){
1708 rel.getFromName().mergeHomotypicGroups(rel.getToName());
1709 }
1710 }else{
1711 throw new RuntimeException("NameRelationship is either null or the relationship does not reference this name");
1712 }
1713 }
1714 /**
1715 * Removes one {@link NameRelationship name relationship} from one of both sets of
1716 * {@link #getNameRelations() name relationships} in which <i>this</i> taxon name is involved.
1717 * The name relationship will also be removed from one of both sets belonging
1718 * to the second taxon name involved. Furthermore the fromName and toName
1719 * attributes of the name relationship object will be nullified.
1720 *
1721 * @param nameRelation the name relationship which should be deleted from one of both sets
1722 * @see #getNameRelations()
1723 */
1724 @Override
1725 public void removeNameRelationship(NameRelationship nameRelation) {
1726
1727 TaxonNameBase fromName = nameRelation.getFromName();
1728 TaxonNameBase toName = nameRelation.getToName();
1729
1730 if (nameRelation != null) {
1731 nameRelation.setToName(null);
1732 nameRelation.setFromName(null);
1733 }
1734
1735 if (fromName != null) {
1736 fromName.removeNameRelationship(nameRelation);
1737 }
1738
1739 if (toName != null) {
1740 toName.removeNameRelationship(nameRelation);
1741 }
1742
1743 this.relationsToThisName.remove(nameRelation);
1744 this.relationsFromThisName.remove(nameRelation);
1745 }
1746
1747 @Override
1748 public void removeRelationToTaxonName(TaxonNameBase toTaxonName) {
1749 Set<NameRelationship> nameRelationships = new HashSet<NameRelationship>();
1750 // nameRelationships.addAll(this.getNameRelations());
1751 nameRelationships.addAll(this.getRelationsFromThisName());
1752 nameRelationships.addAll(this.getRelationsToThisName());
1753 for(NameRelationship nameRelationship : nameRelationships) {
1754 // remove name relationship from this side
1755 if (nameRelationship.getFromName().equals(this) && nameRelationship.getToName().equals(toTaxonName)) {
1756 this.removeNameRelationship(nameRelationship);
1757 }
1758 }
1759 }
1760
1761
1762 /**
1763 * If relation is of type NameRelationship, addNameRelationship is called;
1764 * if relation is of type HybridRelationship addHybridRelationship is called,
1765 * otherwise an IllegalArgumentException is thrown.
1766 *
1767 * @param relation the relationship to be added to one of <i>this</i> taxon name's name relationships sets
1768 * @see #addNameRelationship(NameRelationship)
1769 * @see #getNameRelations()
1770 * @see NameRelationship
1771 * @see RelationshipBase
1772 * @see #addHybridRelationship(HybridRelationship)
1773
1774 * @deprecated to be used by RelationshipBase only
1775 */
1776 @Deprecated
1777 @Override
1778 public void addRelationship(RelationshipBase relation) {
1779 if (relation instanceof NameRelationship){
1780 addNameRelationship((NameRelationship)relation);
1781
1782 }else if (relation instanceof HybridRelationship){
1783 addHybridRelationship((HybridRelationship)relation);
1784 }else{
1785 logger.warn("Relationship not of type NameRelationship!");
1786 throw new IllegalArgumentException("Relationship not of type NameRelationship or HybridRelationship");
1787 }
1788 }
1789
1790 /**
1791 * Returns the set of all {@link NameRelationship name relationships}
1792 * in which <i>this</i> taxon name is involved as a source ("from"-side).
1793 *
1794 * @see #getNameRelations()
1795 * @see #getRelationsToThisName()
1796 * @see #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1797 */
1798 @Override
1799 public Set<NameRelationship> getRelationsFromThisName() {
1800 if(relationsFromThisName == null) {
1801 this.relationsFromThisName = new HashSet<>();
1802 }
1803 return relationsFromThisName;
1804 }
1805
1806 /**
1807 * Returns the set of all {@link NameRelationship name relationships}
1808 * in which <i>this</i> taxon name is involved as a target ("to"-side).
1809 *
1810 * @see #getNameRelations()
1811 * @see #getRelationsFromThisName()
1812 * @see #addRelationshipToName(TaxonNameBase, NameRelationshipType, String)
1813 */
1814 @Override
1815 public Set<NameRelationship> getRelationsToThisName() {
1816 if(relationsToThisName == null) {
1817 this.relationsToThisName = new HashSet<>();
1818 }
1819 return relationsToThisName;
1820 }
1821
1822 /**
1823 * Returns the set of {@link NomenclaturalStatus nomenclatural status} assigned
1824 * to <i>this</i> taxon name according to its corresponding nomenclature code.
1825 * This includes the {@link NomenclaturalStatusType type} of the nomenclatural status
1826 * and the nomenclatural code rule considered.
1827 *
1828 * @see NomenclaturalStatus
1829 * @see NomenclaturalStatusType
1830 */
1831 @Override
1832 public Set<NomenclaturalStatus> getStatus() {
1833 if(status == null) {
1834 this.status = new HashSet<>();
1835 }
1836 return status;
1837 }
1838
1839 /**
1840 * Adds a new {@link NomenclaturalStatus nomenclatural status}
1841 * to <i>this</i> taxon name's set of nomenclatural status.
1842 *
1843 * @param nomStatus the nomenclatural status to be added
1844 * @see #getStatus()
1845 */
1846 @Override
1847 public void addStatus(NomenclaturalStatus nomStatus) {
1848 this.status.add(nomStatus);
1849 }
1850 @Override
1851 public NomenclaturalStatus addStatus(NomenclaturalStatusType statusType, Reference citation, String microCitation) {
1852 NomenclaturalStatus newStatus = NomenclaturalStatus.NewInstance(statusType, citation, microCitation);
1853 this.status.add(newStatus);
1854 return newStatus;
1855 }
1856
1857 /**
1858 * Removes one element from the set of nomenclatural status of <i>this</i> taxon name.
1859 * Type and ruleConsidered attributes of the nomenclatural status object
1860 * will be nullified.
1861 *
1862 * @param nomStatus the nomenclatural status of <i>this</i> taxon name which should be deleted
1863 * @see #getStatus()
1864 */
1865 @Override
1866 public void removeStatus(NomenclaturalStatus nomStatus) {
1867 //TODO to be implemented?
1868 logger.warn("not yet fully implemented?");
1869 this.status.remove(nomStatus);
1870 }
1871
1872
1873 /**
1874 * Generates the composed name string of <i>this</i> non viral taxon name without author
1875 * strings or year according to the strategy defined in
1876 * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
1877 * The result might be stored in {@link #getNameCache() nameCache} if the
1878 * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
1879 *
1880 * @return the string with the composed name of <i>this</i> non viral taxon name without authors or year
1881 * @see #getNameCache()
1882 */
1883 protected String generateNameCache(){
1884 if (cacheStrategy == null){
1885 logger.warn("No CacheStrategy defined for taxon name: " + this.toString());
1886 return null;
1887 }else{
1888 return cacheStrategy.getNameCache(this);
1889 }
1890 }
1891
1892 /**
1893 * Returns or generates the nameCache (scientific name
1894 * without author strings and year) string for <i>this</i> non viral taxon name. If the
1895 * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
1896 * the string will be generated according to a defined strategy,
1897 * otherwise the value of the actual nameCache string will be returned.
1898 *
1899 * @return the string which identifies <i>this</i> non viral taxon name (without authors or year)
1900 * @see #generateNameCache()
1901 */
1902 @Override
1903 @Transient
1904 public String getNameCache() {
1905 if (protectedNameCache){
1906 return this.nameCache;
1907 }
1908 // is title dirty, i.e. equal NULL?
1909 if (nameCache == null){
1910 this.nameCache = generateNameCache();
1911 }
1912 return nameCache;
1913 }
1914
1915 /**
1916 * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1917 * Sets the protectedNameCache flag to <code>true</code>.
1918 *
1919 * @param nameCache the string which identifies <i>this</i> non viral taxon name (without authors or year)
1920 * @see #getNameCache()
1921 */
1922 @Override
1923 public void setNameCache(String nameCache){
1924 setNameCache(nameCache, true);
1925 }
1926
1927 /**
1928 * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
1929 * Sets the protectedNameCache flag to <code>true</code>.
1930 *
1931 * @param nameCache the string which identifies <i>this</i> non viral taxon name (without authors or year)
1932 * @param protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
1933 * <code>false</code>
1934 * @see #getNameCache()
1935 */
1936 @Override
1937 public void setNameCache(String nameCache, boolean protectedNameCache){
1938 this.nameCache = nameCache;
1939 this.setProtectedNameCache(protectedNameCache);
1940 }
1941
1942
1943 /**
1944 * Indicates whether <i>this</i> taxon name is a {@link NameRelationshipType#BASIONYM() basionym}
1945 * or a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1946 * of any other taxon name. Returns "true", if a basionym or a replaced
1947 * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1948 * false otherwise (also in case <i>this</i> taxon name is the only one in the
1949 * homotypical group).
1950 */
1951 @Override
1952 @Transient
1953 public boolean isOriginalCombination(){
1954 Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1955 for (NameRelationship relation : relationsFromThisName) {
1956 if (relation.getType().isBasionymRelation() ||
1957 relation.getType().isReplacedSynonymRelation()) {
1958 return true;
1959 }
1960 }
1961 return false;
1962 }
1963
1964 /**
1965 * Indicates <i>this</i> taxon name is a {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym}
1966 * of any other taxon name. Returns "true", if a replaced
1967 * synonym {@link NameRelationship relationship} from <i>this</i> taxon name to another taxon name exists,
1968 * false otherwise (also in case <i>this</i> taxon name is the only one in the
1969 * homotypical group).
1970 */
1971 @Override
1972 @Transient
1973 public boolean isReplacedSynonym(){
1974 Set<NameRelationship> relationsFromThisName = this.getRelationsFromThisName();
1975 for (NameRelationship relation : relationsFromThisName) {
1976 if (relation.getType().isReplacedSynonymRelation()) {
1977 return true;
1978 }
1979 }
1980 return false;
1981 }
1982
1983 /**
1984 * Returns the taxon name which is the {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
1985 * The basionym of a taxon name is its epithet-bringing synonym.
1986 * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
1987 * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
1988 * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
1989 *
1990 * If more than one basionym exists one is choosen at radom.
1991 *
1992 * If no basionym exists null is returned.
1993 */
1994 @Override
1995 @Transient
1996 public TaxonNameBase getBasionym(){
1997 Set<TaxonNameBase> basionyms = getBasionyms();
1998 if (basionyms.size() == 0){
1999 return null;
2000 }else{
2001 return basionyms.iterator().next();
2002 }
2003 }
2004
2005 /**
2006 * Returns the set of taxon names which are the {@link NameRelationshipType#BASIONYM() basionyms} of <i>this</i> taxon name.
2007 * The basionym of a taxon name is its epithet-bringing synonym.
2008 * For instance <i>Pinus abies</i> L. was published by Linnaeus and the botanist
2009 * Karsten transferred later <i>this</i> taxon to the genus Picea. Therefore,
2010 * <i>Pinus abies</i> L. is the basionym of the new combination <i>Picea abies</i> (L.) H. Karst.
2011 */
2012 @Override
2013 @Transient
2014 public Set<TaxonNameBase> getBasionyms(){
2015 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
2016 Set<NameRelationship> rels = this.getRelationsToThisName();
2017 for (NameRelationship rel : rels){
2018 if (rel.getType()!= null && rel.getType().isBasionymRelation()){
2019 TaxonNameBase<?,?> basionym = rel.getFromName();
2020 result.add(basionym);
2021 }
2022 }
2023 return result;
2024 }
2025
2026 /**
2027 * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name.
2028 * The basionym {@link NameRelationship relationship} will be added to <i>this</i> taxon name
2029 * and to the basionym. The basionym cannot have itself a basionym.
2030 * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2031 * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2032 *
2033 * @param basionym the taxon name to be set as the basionym of <i>this</i> taxon name
2034 * @see #getBasionym()
2035 * @see #addBasionym(TaxonNameBase, String)
2036 */
2037 @Override
2038 public void addBasionym(TaxonNameBase basionym){
2039 addBasionym(basionym, null, null, null);
2040 }
2041 /**
2042 * Assigns a taxon name as {@link NameRelationshipType#BASIONYM() basionym} of <i>this</i> taxon name
2043 * and keeps the nomenclatural rule considered for it. The basionym
2044 * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the basionym.
2045 * The basionym cannot have itself a basionym.
2046 * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the basionym
2047 * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2048 *
2049 * @param basionym the taxon name to be set as the basionym of <i>this</i> taxon name
2050 * @param ruleConsidered the string identifying the nomenclatural rule
2051 * @return
2052 * @see #getBasionym()
2053 * @see #addBasionym(TaxonNameBase)
2054 */
2055 @Override
2056 public NameRelationship addBasionym(TaxonNameBase basionym, Reference citation, String microcitation, String ruleConsidered){
2057 if (basionym != null){
2058 return basionym.addRelationshipToName(this, NameRelationshipType.BASIONYM(), citation, microcitation, ruleConsidered);
2059 }else{
2060 return null;
2061 }
2062 }
2063
2064 /**
2065 * Returns the set of taxon names which are the {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonyms} of <i>this</i> taxon name.
2066 *
2067 */
2068 @Override
2069 @Transient
2070 public Set<TaxonNameBase> getReplacedSynonyms(){
2071 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
2072 Set<NameRelationship> rels = this.getRelationsToThisName();
2073 for (NameRelationship rel : rels){
2074 if (rel.getType().isReplacedSynonymRelation()){
2075 TaxonNameBase replacedSynonym = rel.getFromName();
2076 result.add(replacedSynonym);
2077 }
2078 }
2079 return result;
2080 }
2081
2082 /**
2083 * Assigns a taxon name as {@link NameRelationshipType#REPLACED_SYNONYM() replaced synonym} of <i>this</i> taxon name
2084 * and keeps the nomenclatural rule considered for it. The replaced synonym
2085 * {@link NameRelationship relationship} will be added to <i>this</i> taxon name and to the replaced synonym.
2086 * The {@link HomotypicalGroup homotypical groups} of <i>this</i> taxon name and of the replaced synonym
2087 * will be {@link HomotypicalGroup#merge(HomotypicalGroup) merged}.
2088 *
2089 * @param basionym the taxon name to be set as the basionym of <i>this</i> taxon name
2090 * @param ruleConsidered the string identifying the nomenclatural rule
2091 * @see #getBasionym()
2092 * @see #addBasionym(TaxonNameBase)
2093 */
2094 //TODO: Check if true: The replaced synonym cannot have itself a replaced synonym (?).
2095 @Override
2096 public void addReplacedSynonym(TaxonNameBase replacedSynonym, Reference citation, String microcitation, String ruleConsidered){
2097 if (replacedSynonym != null){
2098 replacedSynonym.addRelationshipToName(this, NameRelationshipType.REPLACED_SYNONYM(), citation, microcitation, ruleConsidered);
2099 }
2100 }
2101
2102 /**
2103 * Removes the {@link NameRelationshipType#BASIONYM() basionym} {@link NameRelationship relationship} from the set of
2104 * {@link #getRelationsToThisName() name relationships to} <i>this</i> taxon name. The same relationhip will be
2105 * removed from the set of {@link #getRelationsFromThisName() name relationships from} the taxon name
2106 * previously used as basionym.
2107 *
2108 * @see #getBasionym()
2109 * @see #addBasionym(TaxonNameBase)
2110 */
2111 @Override
2112 public void removeBasionyms(){
2113 Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
2114 for (NameRelationship nameRelation : this.getRelationsToThisName()){
2115 if (nameRelation.getType().isBasionymRelation()){
2116 removeRelations.add(nameRelation);
2117 }
2118 }
2119 // Removing relations from a set through which we are iterating causes a
2120 // ConcurrentModificationException. Therefore, we delete the targeted
2121 // relations in a second step.
2122 for (NameRelationship relation : removeRelations){
2123 this.removeNameRelationship(relation);
2124 }
2125 }
2126
2127 /**
2128 * Returns the taxonomic {@link Rank rank} of <i>this</i> taxon name.
2129 *
2130 * @see Rank
2131 */
2132 @Override
2133 public Rank getRank(){
2134 return this.rank;
2135 }
2136
2137 /**
2138 * @see #getRank()
2139 */
2140 @Override
2141 public void setRank(Rank rank){
2142 this.rank = rank;
2143 }
2144
2145 /**
2146 * Returns the {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} of <i>this</i> taxon name.
2147 * The nomenclatural reference is here meant to be the one publication
2148 * <i>this</i> taxon name was originally published in while fulfilling the formal
2149 * requirements as specified by the corresponding {@link NomenclaturalCode nomenclatural code}.
2150 *
2151 * @see eu.etaxonomy.cdm.model.reference.INomenclaturalReference
2152 * @see eu.etaxonomy.cdm.model.reference.Reference
2153 */
2154 @Override
2155 public INomenclaturalReference getNomenclaturalReference(){
2156 return this.nomenclaturalReference;
2157 }
2158 /**
2159 * Assigns a {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference nomenclatural reference} to <i>this</i> taxon name.
2160 * The corresponding {@link eu.etaxonomy.cdm.model.reference.Reference.isNomenclaturallyRelevant nomenclaturally relevant flag} will be set to true
2161 * as it is obviously used for nomenclatural purposes.
2162 *
2163 * @throws IllegalArgumentException if parameter <code>nomenclaturalReference</code> is not assignable from {@link INomenclaturalReference}
2164 * @see #getNomenclaturalReference()
2165 */
2166 @Override
2167 public void setNomenclaturalReference(INomenclaturalReference nomenclaturalReference){
2168 if(nomenclaturalReference != null){
2169 if(!INomenclaturalReference.class.isAssignableFrom(nomenclaturalReference.getClass())){
2170 throw new IllegalArgumentException("Parameter nomenclaturalReference is not assignable from INomenclaturalReference");
2171 }
2172 this.nomenclaturalReference = (Reference)nomenclaturalReference;
2173 } else {
2174 this.nomenclaturalReference = null;
2175 }
2176 }
2177
2178 /**
2179 * Returns the appended phrase string assigned to <i>this</i> taxon name.
2180 * The appended phrase is a non-atomised addition to a name. It is
2181 * not ruled by a nomenclatural code.
2182 */
2183 @Override
2184 public String getAppendedPhrase(){
2185 return this.appendedPhrase;
2186 }
2187
2188 /**
2189 * @see #getAppendedPhrase()
2190 */
2191 @Override
2192 public void setAppendedPhrase(String appendedPhrase){
2193 this.appendedPhrase = StringUtils.isBlank(appendedPhrase)? null : appendedPhrase;
2194 }
2195
2196 /**
2197 * Returns the details string of the {@link #getNomenclaturalReference() nomenclatural reference} assigned
2198 * to <i>this</i> taxon name. The details describe the exact localisation within
2199 * the publication used as nomenclature reference. These are mostly
2200 * (implicitly) pages but can also be figures or tables or any other
2201 * element of a publication. A nomenclatural micro reference (details)
2202 * requires the existence of a nomenclatural reference.
2203 */
2204 //Details of the nomenclatural reference (protologue).
2205 @Override
2206 public String getNomenclaturalMicroReference(){
2207 return this.nomenclaturalMicroReference;
2208 }
2209 /**
2210 * @see #getNomenclaturalMicroReference()
2211 */
2212 @Override
2213 public void setNomenclaturalMicroReference(String nomenclaturalMicroReference){
2214 this.nomenclaturalMicroReference = StringUtils.isBlank(nomenclaturalMicroReference)? null : nomenclaturalMicroReference;
2215 }
2216
2217 @Override
2218 public int getParsingProblem(){
2219 return this.parsingProblem;
2220 }
2221
2222 @Override
2223 public void setParsingProblem(int parsingProblem){
2224 this.parsingProblem = parsingProblem;
2225 }
2226
2227 @Override
2228 public void addParsingProblem(ParserProblem problem){
2229 parsingProblem = ParserProblem.addProblem(parsingProblem, problem);
2230 }
2231
2232 @Override
2233 public void removeParsingProblem(ParserProblem problem) {
2234 parsingProblem = ParserProblem.removeProblem(parsingProblem, problem);
2235 }
2236
2237 /**
2238 * @param warnings
2239 */
2240 @Override
2241 public void addParsingProblems(int problems){
2242 parsingProblem = ParserProblem.addProblems(parsingProblem, problems);
2243 }
2244
2245 @Override
2246 public boolean hasProblem(){
2247 return parsingProblem != 0;
2248 }
2249
2250 @Override
2251 public boolean hasProblem(ParserProblem problem) {
2252 return getParsingProblems().contains(problem);
2253 }
2254
2255 @Override
2256 public int getProblemStarts(){
2257 return this.problemStarts;
2258 }
2259
2260 @Override
2261 public void setProblemStarts(int start) {
2262 this.problemStarts = start;
2263 }
2264
2265 @Override
2266 public int getProblemEnds(){
2267 return this.problemEnds;
2268 }
2269
2270 @Override
2271 public void setProblemEnds(int end) {
2272 this.problemEnds = end;
2273 }
2274
2275 //*********************** TYPE DESIGNATION *********************************************//
2276
2277 /**
2278 * Returns the set of {@link TypeDesignationBase type designations} assigned
2279 * to <i>this</i> taxon name.
2280 * @see NameTypeDesignation
2281 * @see SpecimenTypeDesignation
2282 */
2283 @Override
2284 public Set<TypeDesignationBase> getTypeDesignations() {
2285 if(typeDesignations == null) {
2286 this.typeDesignations = new HashSet<TypeDesignationBase>();
2287 }
2288 return typeDesignations;
2289 }
2290
2291 /**
2292 * Removes one element from the set of {@link TypeDesignationBase type designations} assigned to
2293 * <i>this</i> taxon name. The type designation itself will be nullified.
2294 *
2295 * @param typeDesignation the type designation which should be deleted
2296 */
2297 @Override
2298 @SuppressWarnings("deprecation")
2299 public void removeTypeDesignation(TypeDesignationBase typeDesignation) {
2300 this.typeDesignations.remove(typeDesignation);
2301 typeDesignation.removeTypifiedName(this);
2302 }
2303
2304 /**
2305 * Returns the set of {@link SpecimenTypeDesignation specimen type designations} assigned
2306 * to <i>this</i> taxon name. The {@link Rank rank} of <i>this</i> taxon name is generally
2307 * "species" or below. The specimen type designations include all the
2308 * specimens on which the typification of this name is based (which are
2309 * exclusively used to typify taxon names belonging to the same
2310 * {@link HomotypicalGroup homotypical group} to which <i>this</i> taxon name
2311 * belongs) and eventually the status of these designations.
2312 *
2313 * @see SpecimenTypeDesignation
2314 * @see NameTypeDesignation
2315 * @see HomotypicalGroup
2316 */
2317 @Override
2318 @Transient
2319 public Set<SpecimenTypeDesignation> getSpecimenTypeDesignationsOfHomotypicalGroup() {
2320 return this.getHomotypicalGroup().getSpecimenTypeDesignations();
2321 }
2322
2323 //*********************** NAME TYPE DESIGNATION *********************************************//
2324
2325 /**
2326 * Returns the set of {@link NameTypeDesignation name type designations} assigned
2327 * to <i>this</i> taxon name the rank of which must be above "species".
2328 * The name type designations include all the taxon names used to typify
2329 * <i>this</i> taxon name and eventually the rejected or conserved status
2330 * of these designations.
2331 *
2332 * @see NameTypeDesignation
2333 * @see SpecimenTypeDesignation
2334 */
2335 @Override
2336 @Transient
2337 public Set<NameTypeDesignation> getNameTypeDesignations() {
2338 Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
2339 for (TypeDesignationBase typeDesignation : this.typeDesignations){
2340 if (typeDesignation instanceof NameTypeDesignation){
2341 result.add((NameTypeDesignation)typeDesignation);
2342 }
2343 }
2344 return result;
2345 }
2346
2347 /**
2348 * Creates and adds a new {@link NameTypeDesignation name type designation}
2349 * to <i>this</i> taxon name's set of type designations.
2350 *
2351 * @param typeSpecies the taxon name to be used as type of <i>this</i> taxon name
2352 * @param citation the reference for this new designation
2353 * @param citationMicroReference the string with the details (generally pages) within the reference
2354 * @param originalNameString the taxon name string used in the reference to assert this designation
2355 * @param isRejectedType the boolean status for a rejected name type designation
2356 * @param isConservedType the boolean status for a conserved name type designation
2357 * @param isLectoType the boolean status for a lectotype name type designation
2358 * @param isNotDesignated the boolean status for a name type designation without name type
2359 * @param addToAllHomotypicNames the boolean indicating whether the name type designation should be
2360 * added to all taxon names of the homotypical group this taxon name belongs to
2361 * @return
2362 * @see #getNameTypeDesignations()
2363 * @see NameTypeDesignation
2364 * @see TypeDesignationBase#isNotDesignated()
2365 */
2366 @Override
2367 public NameTypeDesignation addNameTypeDesignation(TaxonNameBase typeSpecies,
2368 Reference citation,
2369 String citationMicroReference,
2370 String originalNameString,
2371 NameTypeDesignationStatus status,
2372 boolean isRejectedType,
2373 boolean isConservedType,
2374 /*boolean isLectoType, */
2375 boolean isNotDesignated,
2376 boolean addToAllHomotypicNames) {
2377 NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, citation, citationMicroReference, originalNameString, status, isRejectedType, isConservedType, isNotDesignated);
2378 //nameTypeDesignation.setLectoType(isLectoType);
2379 addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2380 return nameTypeDesignation;
2381 }
2382
2383 /**
2384 * Creates and adds a new {@link NameTypeDesignation name type designation}
2385 * to <i>this</i> taxon name's set of type designations.
2386 *
2387 * @param typeSpecies the taxon name to be used as type of <i>this</i> taxon name
2388 * @param citation the reference for this new designation
2389 * @param citationMicroReference the string with the details (generally pages) within the reference
2390 * @param originalNameString the taxon name string used in the reference to assert this designation
2391 * @param status the name type designation status
2392 * @param addToAllHomotypicNames the boolean indicating whether the name type designation should be
2393 * added to all taxon names of the homotypical group this taxon name belongs to
2394 * @return
2395 * @see #getNameTypeDesignations()
2396 * @see NameTypeDesignation
2397 * @see TypeDesignationBase#isNotDesignated()
2398 */
2399 @Override
2400 public NameTypeDesignation addNameTypeDesignation(TaxonNameBase typeSpecies,
2401 Reference citation,
2402 String citationMicroReference,
2403 String originalNameString,
2404 NameTypeDesignationStatus status,
2405 boolean addToAllHomotypicNames) {
2406 NameTypeDesignation nameTypeDesignation = new NameTypeDesignation(typeSpecies, status, citation, citationMicroReference, originalNameString);
2407 addTypeDesignation(nameTypeDesignation, addToAllHomotypicNames);
2408 return nameTypeDesignation;
2409 }
2410
2411 //*********************** SPECIMEN TYPE DESIGNATION *********************************************//
2412
2413 /**
2414 * Returns the set of {@link SpecimenTypeDesignation specimen type designations}
2415 * that typify <i>this</i> taxon name.
2416 */
2417 @Override
2418 @Transient
2419 public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
2420 Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
2421 for (TypeDesignationBase typeDesignation : this.typeDesignations){
2422 if (typeDesignation instanceof SpecimenTypeDesignation){
2423 result.add((SpecimenTypeDesignation)typeDesignation);
2424 }
2425 }
2426 return result;
2427 }
2428
2429
2430 /**
2431 * Creates and adds a new {@link SpecimenTypeDesignation specimen type designation}
2432 * to <i>this</i> taxon name's set of type designations.
2433 *
2434 * @param typeSpecimen the specimen to be used as a type for <i>this</i> taxon name
2435 * @param status the specimen type designation status
2436 * @param citation the reference for this new specimen type designation
2437 * @param citationMicroReference the string with the details (generally pages) within the reference
2438 * @param originalNameString the taxon name used in the reference to assert this designation
2439 * @param isNotDesignated the boolean status for a specimen type designation without specimen type
2440 * @param addToAllHomotypicNames the boolean indicating whether the specimen type designation should be
2441 * added to all taxon names of the homotypical group the typified
2442 * taxon name belongs to
2443 * @return
2444 * @see #getSpecimenTypeDesignations()
2445 * @see SpecimenTypeDesignationStatus
2446 * @see SpecimenTypeDesignation
2447 * @see TypeDesignationBase#isNotDesignated()
2448 */
2449 @Override
2450 public SpecimenTypeDesignation addSpecimenTypeDesignation(DerivedUnit typeSpecimen,
2451 SpecimenTypeDesignationStatus status,
2452 Reference citation,
2453 String citationMicroReference,
2454 String originalNameString,
2455 boolean isNotDesignated,
2456 boolean addToAllHomotypicNames) {
2457 SpecimenTypeDesignation specimenTypeDesignation = new SpecimenTypeDesignation(typeSpecimen, status, citation, citationMicroReference, originalNameString, isNotDesignated);
2458 addTypeDesignation(specimenTypeDesignation, addToAllHomotypicNames);
2459 return specimenTypeDesignation;
2460 }
2461
2462 //used by merge strategy
2463 private boolean addTypeDesignation(TypeDesignationBase typeDesignation){
2464 return addTypeDesignation(typeDesignation, true);
2465 }
2466
2467 /**
2468 * Adds a {@link TypeDesignationBase type designation} to <code>this</code> taxon name's set of type designations
2469 *
2470 * @param typeDesignation the typeDesignation to be added to <code>this</code> taxon name
2471 * @param addToAllNames the boolean indicating whether the type designation should be
2472 * added to all taxon names of the homotypical group the typified
2473 * taxon name belongs to
2474 * @return true if the operation was succesful
2475 *
2476 * @throws IllegalArgumentException if the type designation already has typified names, an {@link IllegalArgumentException exception}
2477 * is thrown. We do this to prevent a type designation to be used for multiple taxon names.
2478 *
2479 */
2480 @Override
2481 public boolean addTypeDesignation(TypeDesignationBase typeDesignation, boolean addToAllNames){
2482 //currently typeDesignations are not persisted with the homotypical group
2483 //so explicit adding to the homotypical group is not necessary.
2484 if (typeDesignation != null){
2485 checkHomotypicalGroup(typeDesignation);
2486 this.typeDesignations.add(typeDesignation);
2487 typeDesignation.addTypifiedName(this);
2488
2489 if (addToAllNames){
2490 for (TaxonNameBase taxonName : this.getHomotypicalGroup().getTypifiedNames()){
2491 if (taxonName != this){
2492 taxonName.addTypeDesignation(typeDesignation, false);
2493 }
2494 }
2495 }
2496 }
2497 return true;
2498 }
2499
2500 /**
2501 * Throws an Exception this type designation already has typified names from another homotypical group.
2502 * @param typeDesignation
2503 */
2504 private void checkHomotypicalGroup(TypeDesignationBase typeDesignation) {
2505 if(typeDesignation.getTypifiedNames().size() > 0){
2506 Set<HomotypicalGroup> groups = new HashSet<HomotypicalGroup>();
2507 Set<TaxonNameBase> names = typeDesignation.getTypifiedNames();
2508 for (TaxonNameBase taxonName: names){
2509 groups.add(taxonName.getHomotypicalGroup());
2510 }
2511 if (groups.size() > 1){
2512 throw new IllegalArgumentException("TypeDesignation already has typified names from another homotypical group.");
2513 }
2514 }
2515 }
2516
2517
2518
2519 //*********************** HOMOTYPICAL GROUP *********************************************//
2520
2521
2522 /**
2523 * Returns the {@link HomotypicalGroup homotypical group} to which
2524 * <i>this</i> taxon name belongs. A homotypical group represents all taxon names
2525 * that share the same types.
2526 *
2527 * @see HomotypicalGroup
2528 */
2529
2530 @Override
2531 public HomotypicalGroup getHomotypicalGroup() {
2532 if (homotypicalGroup == null){
2533 homotypicalGroup = new HomotypicalGroup();
2534 homotypicalGroup.typifiedNames.add(this);
2535 }
2536 return homotypicalGroup;
2537 }
2538
2539 /**
2540 * @see #getHomotypicalGroup()
2541 */
2542 @Override
2543 public void setHomotypicalGroup(HomotypicalGroup homotypicalGroup) {
2544 if (homotypicalGroup == null){
2545 throw new IllegalArgumentException("HomotypicalGroup of name should never be null but was set to 'null'");
2546 }
2547 /*if (this.homotypicalGroup != null){
2548 this.homotypicalGroup.removeTypifiedName(this, false);
2549 }*/
2550 this.homotypicalGroup = homotypicalGroup;
2551 if (!this.homotypicalGroup.typifiedNames.contains(this)){
2552 this.homotypicalGroup.addTypifiedName(this);
2553 }
2554 }
2555
2556
2557
2558 // *************************************************************************//
2559
2560 /**
2561 * Returns the complete string containing the
2562 * {@link eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation() nomenclatural reference citation}
2563 * and the {@link #getNomenclaturalMicroReference() details} assigned to <i>this</i> taxon name.
2564 *
2565 * @return the string containing the nomenclatural reference of <i>this</i> taxon name
2566 * @see eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getNomenclaturalCitation()
2567 * @see #getNomenclaturalReference()
2568 * @see #getNomenclaturalMicroReference()
2569 */
2570 @Override
2571 @Transient
2572 public String getCitationString(){
2573 return getNomenclaturalReference().getNomenclaturalCitation(getNomenclaturalMicroReference());
2574 }
2575
2576 /**
2577 * Returns the parsing problems
2578 * @return
2579 */
2580 @Override
2581 public List<ParserProblem> getParsingProblems(){
2582 return ParserProblem.warningList(this.parsingProblem);
2583 }
2584
2585 /**
2586 * Returns the string containing the publication date (generally only year)
2587 * of the {@link #getNomenclaturalReference() nomenclatural reference} for <i>this</i> taxon name, null if there is
2588 * no nomenclatural reference.
2589 *
2590 * @return the string containing the publication date of <i>this</i> taxon name
2591 * @see eu.etaxonomy.cdm.model.reference.INomenclaturalReference#getYear()
2592 */
2593 @Override
2594 @Transient
2595 @ValidTaxonomicYear(groups=Level3.class)
2596 public String getReferenceYear(){
2597 if (this.getNomenclaturalReference() != null ){
2598 return this.getNomenclaturalReference().getYear();
2599 }else{
2600 return null;
2601 }
2602 }
2603
2604 /**
2605 * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2606 * In this context a taxon base means the use of a taxon name by a reference
2607 * either as a {@link eu.etaxonomy.cdm.model.taxon.Taxon taxon} ("accepted/correct" name) or
2608 * as a (junior) {@link eu.etaxonomy.cdm.model.taxon.Synonym synonym}.
2609 * A taxon name can be used by several distinct {@link eu.etaxonomy.cdm.model.reference.Reference references} but only once
2610 * within a taxonomic treatment (identified by one reference).
2611 *
2612 * @see #getTaxa()
2613 * @see #getSynonyms()
2614 */
2615 @Override
2616 public Set<TaxonBase> getTaxonBases() {
2617 if(taxonBases == null) {
2618 this.taxonBases = new HashSet<TaxonBase>();
2619 }
2620 return this.taxonBases;
2621 }
2622
2623 /**
2624 * Adds a new {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon base}
2625 * to the set of taxon bases using <i>this</i> taxon name.
2626 *
2627 * @param taxonBase the taxon base to be added
2628 * @see #getTaxonBases()
2629 * @see #removeTaxonBase(TaxonBase)
2630 */
2631 //TODO protected
2632 @Override
2633 public void addTaxonBase(TaxonBase taxonBase){
2634 Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonNameBase.class});
2635 ReflectionUtils.makeAccessible(method);
2636 ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {this});
2637 taxonBases.add(taxonBase);
2638 }
2639 /**
2640 * Removes one element from the set of {@link eu.etaxonomy.cdm.model.taxon.TaxonBase taxon bases} that refer to <i>this</i> taxon name.
2641 *
2642 * @param taxonBase the taxon base which should be removed from the corresponding set
2643 * @see #getTaxonBases()
2644 * @see #addTaxonBase(TaxonBase)
2645 */
2646 @Override
2647 public void removeTaxonBase(TaxonBase taxonBase){
2648 Method method = ReflectionUtils.findMethod(TaxonBase.class, "setName", new Class[] {TaxonNameBase.class});
2649 ReflectionUtils.makeAccessible(method);
2650 ReflectionUtils.invokeMethod(method, taxonBase, new Object[] {null});
2651
2652
2653 }
2654
2655 /**
2656 * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Taxon taxa} ("accepted/correct" names according to any
2657 * reference) that are based on <i>this</i> taxon name. This set is a subset of
2658 * the set returned by getTaxonBases().
2659 *
2660 * @see eu.etaxonomy.cdm.model.taxon.Taxon
2661 * @see #getTaxonBases()
2662 * @see #getSynonyms()
2663 */
2664 @Override
2665 @Transient
2666 public Set<Taxon> getTaxa(){
2667 Set<Taxon> result = new HashSet<Taxon>();
2668 for (TaxonBase taxonBase : this.taxonBases){
2669 if (taxonBase instanceof Taxon){
2670 result.add((Taxon)taxonBase);
2671 }
2672 }
2673 return result;
2674 }
2675
2676 /**
2677 * Returns the set of {@link eu.etaxonomy.cdm.model.taxon.Synonym (junior) synonyms} (according to any
2678 * reference) that are based on <i>this</i> taxon name. This set is a subset of
2679 * the set returned by getTaxonBases().
2680 *
2681 * @see eu.etaxonomy.cdm.model.taxon.Synonym
2682 * @see #getTaxonBases()
2683 * @see #getTaxa()
2684 */
2685 @Override
2686 @Transient
2687 public Set<Synonym> getSynonyms() {
2688 Set<Synonym> result = new HashSet<Synonym>();
2689 for (TaxonBase taxonBase : this.taxonBases){
2690 if (taxonBase instanceof Synonym){
2691 result.add((Synonym)taxonBase);
2692 }
2693 }
2694 return result;
2695 }
2696
2697 // ************* RELATIONSHIPS *****************************/
2698
2699
2700 /**
2701 * Returns the hybrid child relationships ordered by relationship type, or if equal
2702 * by title cache of the related names.
2703 * @see #getHybridParentRelations()
2704 */
2705 @Override
2706 @Transient
2707 public List<HybridRelationship> getOrderedChildRelationships(){
2708 List<HybridRelationship> result = new ArrayList<HybridRelationship>();
2709 result.addAll(this.hybridChildRelations);
2710 Collections.sort(result);
2711 Collections.reverse(result);
2712 return result;
2713
2714 }
2715
2716
2717 /**
2718 * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2719 * to <i>this</i> botanical name. A HybridRelationship may be of type
2720 * "is first/second parent" or "is male/female parent". By invoking this
2721 * method <i>this</i> botanical name becomes a hybrid child of the parent
2722 * botanical name.
2723 *
2724 * @param parentName the botanical name of the parent for this new hybrid name relationship
2725 * @param type the type of this new name relationship
2726 * @param ruleConsidered the string which specifies the rule on which this name relationship is based
2727 * @return
2728 * @see #addHybridChild(BotanicalName, HybridRelationshipType,String )
2729 * @see #getRelationsToThisName()
2730 * @see #getNameRelations()
2731 * @see #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
2732 * @see #addNameRelationship(NameRelationship)
2733 */
2734 @Override
2735 public HybridRelationship addHybridParent(INonViralName parentName, HybridRelationshipType type, String ruleConsidered){
2736 return new HybridRelationship(this, parentName, type, ruleConsidered);
2737 }
2738
2739 /**
2740 * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
2741 * to <i>this</i> botanical name. A HybridRelationship may be of type
2742 * "is first/second parent" or "is male/female parent". By invoking this
2743 * method <i>this</i> botanical name becomes a parent of the hybrid child
2744 * botanical name.
2745 *
2746 * @param childName the botanical name of the child for this new hybrid name relationship
2747 * @param type the type of this new name relationship
2748 * @param ruleConsidered the string which specifies the rule on which this name relationship is based
2749 * @return
2750 * @see #addHybridParent(BotanicalName, HybridRelationshipType,String )
2751 * @see #getRelationsToThisName()
2752 * @see #getNameRelations()
2753 * @see #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
2754 * @see #addNameRelationship(NameRelationship)
2755 */
2756 @Override
2757 public HybridRelationship addHybridChild(INonViralName childName, HybridRelationshipType type, String ruleConsidered){
2758 return new HybridRelationship(childName, this, type, ruleConsidered);
2759 }
2760
2761 @Override
2762 public void removeHybridChild(INonViralName child) {
2763 Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2764 hybridRelationships.addAll(this.getHybridChildRelations());
2765 hybridRelationships.addAll(this.getHybridParentRelations());
2766 for(HybridRelationship hybridRelationship : hybridRelationships) {
2767 // remove name relationship from this side
2768 if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
2769 this.removeHybridRelationship(hybridRelationship);
2770 }
2771 }
2772 }
2773
2774 @Override
2775 public void removeHybridParent(INonViralName parent) {
2776 Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
2777 hybridRelationships.addAll(this.getHybridChildRelations());
2778 hybridRelationships.addAll(this.getHybridParentRelations());
2779 for(HybridRelationship hybridRelationship : hybridRelationships) {
2780 // remove name relationship from this side
2781 if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
2782 this.removeHybridRelationship(hybridRelationship);
2783 }
2784 }
2785 }
2786
2787
2788
2789 // *********** DESCRIPTIONS *************************************
2790
2791 /**
2792 * Returns the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2793 * to <i>this</i> taxon name. A taxon name description is a piece of information
2794 * concerning the taxon name like for instance the content of its first
2795 * publication (protolog) or a picture of this publication.
2796 *
2797 * @see #addDescription(TaxonNameDescription)
2798 * @see #removeDescription(TaxonNameDescription)
2799 * @see eu.etaxonomy.cdm.model.description.TaxonNameDescription
2800 */
2801 @Override
2802 public Set<TaxonNameDescription> getDescriptions() {
2803 return descriptions;
2804 }
2805
2806 /**
2807 * Adds a new {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name description}
2808 * to the set of taxon name descriptions assigned to <i>this</i> taxon name. The
2809 * content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute} of the
2810 * taxon name description itself will be replaced with <i>this</i> taxon name.
2811 *
2812 * @param description the taxon name description to be added
2813 * @see #getDescriptions()
2814 * @see #removeDescription(TaxonNameDescription)
2815 */
2816 @Override
2817 public void addDescription(TaxonNameDescription description) {
2818 java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonNameBase.class);
2819 ReflectionUtils.makeAccessible(field);
2820 ReflectionUtils.setField(field, description, this);
2821 descriptions.add(description);
2822 }
2823 /**
2824 * Removes one element from the set of {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription taxon name descriptions} assigned
2825 * to <i>this</i> taxon name. The content of the {@link eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName() taxonName attribute}
2826 * of the description itself will be set to "null".
2827 *
2828 * @param description the taxon name description which should be removed
2829 * @see #getDescriptions()
2830 * @see #addDescription(TaxonNameDescription)
2831 * @see eu.etaxonomy.cdm.model.description.TaxonNameDescription#getTaxonName()
2832 */
2833 @Override
2834 public void removeDescription(TaxonNameDescription description) {
2835 java.lang.reflect.Field field = ReflectionUtils.findField(TaxonNameDescription.class, "taxonName", TaxonNameBase.class);
2836 ReflectionUtils.makeAccessible(field);
2837 ReflectionUtils.setField(field, description, null);
2838 descriptions.remove(description);
2839 }
2840
2841 // *********** HOMOTYPIC GROUP METHODS **************************************************
2842
2843 @Override
2844 @Transient
2845 public void mergeHomotypicGroups(TaxonNameBase name){
2846 this.getHomotypicalGroup().merge(name.getHomotypicalGroup());
2847 //HomotypicalGroup thatGroup = name.homotypicalGroup;
2848 name.setHomotypicalGroup(this.homotypicalGroup);
2849 }
2850
2851 /**
2852 * Returns the boolean value indicating whether a given taxon name belongs
2853 * to the same {@link HomotypicalGroup homotypical group} as <i>this</i> taxon name (true)
2854 * or not (false). Returns "true" only if the homotypical groups of both
2855 * taxon names exist and if they are identical.
2856 *
2857 * @param homoTypicName the taxon name the homotypical group of which is to be checked
2858 * @return the boolean value of the check
2859 * @see HomotypicalGroup
2860 */
2861 @Override
2862 @Transient
2863 public boolean isHomotypic(TaxonNameBase homoTypicName) {
2864 if (homoTypicName == null) {
2865 return false;
2866 }
2867 HomotypicalGroup homotypicGroup = homoTypicName.getHomotypicalGroup();
2868 if (homotypicGroup == null || this.getHomotypicalGroup() == null) {
2869 return false;
2870 }
2871 if (homotypicGroup.equals(this.getHomotypicalGroup())) {
2872 return true;
2873 }
2874 return false;
2875 }
2876
2877
2878 /**
2879 * Checks whether name is a basionym for ALL names
2880 * in its homotypical group.
2881 * Returns <code>false</code> if there are no other names in the group
2882 * @param name
2883 * @return
2884 */
2885 @Override
2886 @Transient
2887 public boolean isGroupsBasionym() {
2888 if (homotypicalGroup == null){
2889 homotypicalGroup = HomotypicalGroup.NewInstance();
2890 homotypicalGroup.addTypifiedName(this);
2891 }
2892 Set<TaxonNameBase> typifiedNames = homotypicalGroup.getTypifiedNames();
2893
2894 // Check whether there are any other names in the group
2895 if (typifiedNames.size() == 1) {
2896 return false;
2897 }
2898
2899 for (TaxonNameBase<?,?> taxonName : typifiedNames) {
2900 if (!taxonName.equals(this)) {
2901 if (! isBasionymFor(taxonName)) {
2902 return false;
2903 }
2904 }
2905 }
2906 return true;
2907 }
2908
2909 /**
2910 * Checks whether a basionym relationship exists between fromName and toName.
2911 *
2912 * @param fromName
2913 * @param toName
2914 * @return
2915 */
2916 @Override
2917 @Transient
2918 public boolean isBasionymFor(TaxonNameBase newCombinationName) {
2919 Set<NameRelationship> relations = newCombinationName.getRelationsToThisName();
2920 for (NameRelationship relation : relations) {
2921 if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
2922 relation.getFromName().equals(this)) {
2923 return true;
2924 }
2925 }
2926 return false;
2927 }
2928
2929 /**
2930 * Creates a basionym relationship to all other names in this names homotypical
2931 * group.
2932 *
2933 * @see HomotypicalGroup.setGroupBasionym(TaxonNameBase basionymName)
2934 */
2935 @Override
2936 @Transient
2937 public void makeGroupsBasionym() {
2938 this.homotypicalGroup.setGroupBasionym(this);
2939 }
2940
2941
2942 //********* Rank comparison shortcuts ********************//
2943 /**
2944 * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2945 * taxon name is higher than the genus rank (true) or not (false).
2946 * Suprageneric non viral names are monomials.
2947 * Returns false if rank is null.
2948 *
2949 * @see #isGenus()
2950 * @see #isInfraGeneric()
2951 * @see #isSpecies()
2952 * @see #isInfraSpecific()
2953 */
2954 @Override
2955 @Transient
2956 public boolean isSupraGeneric() {
2957 if (rank == null){
2958 return false;
2959 }
2960 return getRank().isSupraGeneric();
2961 }
2962 /**
2963 * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2964 * taxon name is the genus rank (true) or not (false). Non viral names with
2965 * genus rank are monomials. Returns false if rank is null.
2966 *
2967 * @see #isSupraGeneric()
2968 * @see #isInfraGeneric()
2969 * @see #isSpecies()
2970 * @see #isInfraSpecific()
2971 */
2972 @Override
2973 @Transient
2974 public boolean isGenus() {
2975 if (rank == null){
2976 return false;
2977 }
2978 return getRank().isGenus();
2979 }
2980 /**
2981 * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2982 * taxon name is higher than the species rank and lower than the
2983 * genus rank (true) or not (false). Infrageneric non viral names are
2984 * binomials. Returns false if rank is null.
2985 *
2986 * @see #isSupraGeneric()
2987 * @see #isGenus()
2988 * @see #isSpecies()
2989 * @see #isInfraSpecific()
2990 */
2991 @Override
2992 @Transient
2993 public boolean isInfraGeneric() {
2994 if (rank == null){
2995 return false;
2996 }
2997 return getRank().isInfraGeneric();
2998 }
2999
3000 /**
3001 * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3002 * taxon name is higher than the species rank (true) or not (false).
3003 * Returns false if rank is null.
3004 *
3005 * @see #isGenus()
3006 * @see #isInfraGeneric()
3007 * @see #isSpecies()
3008 * @see #isInfraSpecific()
3009 */
3010 @Override
3011 @Transient
3012 public boolean isSupraSpecific(){
3013 if (rank == null) {
3014 return false;
3015 }
3016 return getRank().isHigher(Rank.SPECIES());
3017 }
3018
3019 /**
3020 * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3021 * taxon name is the species rank (true) or not (false). Non viral names
3022 * with species rank are binomials.
3023 * Returns false if rank is null.
3024 *
3025 * @see #isSupraGeneric()
3026 * @see #isGenus()
3027 * @see #isInfraGeneric()
3028 * @see #isInfraSpecific()
3029 */
3030 @Override
3031 @Transient
3032 public boolean isSpecies() {
3033 if (rank == null){
3034 return false;
3035 }
3036 return getRank().isSpecies();
3037 }
3038 /**
3039 * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
3040 * taxon name is lower than the species rank (true) or not (false).
3041 * Infraspecific non viral names are trinomials.
3042 * Returns false if rank is null.
3043 *
3044 * @see #isSupraGeneric()
3045 * @see #isGenus()
3046 * @see #isInfraGeneric()
3047 * @see #isSpecies()
3048 */
3049 @Override
3050 @Transient
3051 public boolean isInfraSpecific() {
3052 if (rank == null){
3053 return false;
3054 }
3055 return getRank().isInfraSpecific();
3056 }
3057
3058 /**
3059 * Returns true if this name's rank indicates a rank that aggregates species like species
3060 * aggregates or species groups, false otherwise. This methods currently returns false
3061 * for all user defined ranks.
3062 *
3063 *@see Rank#isSpeciesAggregate()
3064 *
3065 * @return
3066 */
3067 @Override
3068 @Transient
3069 public boolean isSpeciesAggregate() {
3070 if (rank == null){
3071 return false;
3072 }
3073 return getRank().isSpeciesAggregate();
3074 }
3075
3076
3077 /**
3078 * Returns null as the {@link NomenclaturalCode nomenclatural code} that governs
3079 * the construction of <i>this</i> taxon name since there is no specific
3080 * nomenclatural code defined. The real implementention takes place in the
3081 * subclasses {@link IBacterialName BacterialName},
3082 * {@link IBotanicalName BotanicalName}, {@link ICultivarPlantName CultivarPlantName} and
3083 * {@link IZoologicalName ZoologicalName}. Each taxon name is governed by one
3084 * and only one nomenclatural code.
3085 *
3086 * @return null
3087 * @see #isCodeCompliant()
3088 * @see #getHasProblem()
3089 */
3090 @Override
3091 public NomenclaturalCode getNomenclaturalCode() {
3092 logger.warn("TaxonNameBase has no specific Code defined. Use subclasses");
3093 return null;
3094 }
3095
3096
3097 /**
3098 * Generates and returns the string with the scientific name of <i>this</i>
3099 * taxon name (only non viral taxon names can be generated from their
3100 * components). This string may be stored in the inherited
3101 * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
3102 * This method overrides the generic and inherited
3103 * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle() method} from
3104 * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity IdentifiableEntity}.
3105 *
3106 * @return the string with the composed name of this non viral taxon name with authorship (and maybe year)
3107 * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
3108 * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
3109 */
3110 // @Override
3111 // public abstract String generateTitle();
3112
3113 /**
3114 * Creates a basionym relationship between this name and
3115 * each name in its homotypic group.
3116 *
3117 * @param basionymName
3118 */
3119 @Override
3120 @Transient
3121 public void setAsGroupsBasionym() {
3122
3123 HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3124 if (homotypicalGroup == null) {
3125 return;
3126 }
3127
3128 Set<NameRelationship> relations = new HashSet<NameRelationship>();
3129 Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3130
3131 for(TaxonNameBase<?, ?> typifiedName : homotypicalGroup.getTypifiedNames()){
3132
3133 Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3134
3135 for(NameRelationship nameRelation : nameRelations){
3136 relations.add(nameRelation);
3137 }
3138 }
3139
3140 for (NameRelationship relation : relations) {
3141
3142 // If this is a basionym relation, and toName is in the homotypical group,
3143 // remove the relationship.
3144 if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3145 relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3146 removeRelations.add(relation);
3147 }
3148 }
3149
3150 // Removing relations from a set through which we are iterating causes a
3151 // ConcurrentModificationException. Therefore, we delete the targeted
3152 // relations in a second step.
3153 for (NameRelationship relation : removeRelations) {
3154 this.removeNameRelationship(relation);
3155 }
3156
3157 for (TaxonNameBase<?, ?> name : homotypicalGroup.getTypifiedNames()) {
3158 if (!name.equals(this)) {
3159
3160 // First check whether the relationship already exists
3161 if (!this.isBasionymFor(name)) {
3162
3163 // Then create it
3164 name.addRelationshipFromName(this,
3165 NameRelationshipType.BASIONYM(), null);
3166 }
3167 }
3168 }
3169 }
3170
3171 /**
3172 * Removes basionym relationship between this name and
3173 * each name in its homotypic group.
3174 *
3175 * @param basionymName
3176 */
3177 @Override
3178 @Transient
3179 public void removeAsGroupsBasionym() {
3180
3181 HomotypicalGroup homotypicalGroup = this.getHomotypicalGroup();
3182
3183 if (homotypicalGroup == null) {
3184 return;
3185 }
3186
3187 Set<NameRelationship> relations = new HashSet<NameRelationship>();
3188 Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
3189
3190 for(TaxonNameBase<?, ?> typifiedName : homotypicalGroup.getTypifiedNames()){
3191
3192 Set<NameRelationship> nameRelations = typifiedName.getRelationsFromThisName();
3193
3194 for(NameRelationship nameRelation : nameRelations){
3195 relations.add(nameRelation);
3196 }
3197 }
3198
3199 for (NameRelationship relation : relations) {
3200
3201 // If this is a basionym relation, and toName is in the homotypical group,
3202 // and fromName is basionymName, remove the relationship.
3203 if (relation.getType().equals(NameRelationshipType.BASIONYM()) &&
3204 relation.getFromName().equals(this) &&
3205 relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
3206 removeRelations.add(relation);
3207 }
3208 }
3209
3210 // Removing relations from a set through which we are iterating causes a
3211 // ConcurrentModificationException. Therefore, we delete the targeted
3212 // relations in a second step.
3213 for (NameRelationship relation : removeRelations) {
3214 this.removeNameRelationship(relation);
3215 }
3216 }
3217
3218
3219 /**
3220 * Defines the last part of the name.
3221 * This is for infraspecific taxa, the infraspecific epithet,
3222 * for species the specific epithet, for infageneric taxa the infrageneric epithet
3223 * else the genusOrUninomial.
3224 * However, the result does not depend on the rank (which may be not correctly set
3225 * in case of dirty data) but returns the first name part which is not blank
3226 * considering the above order.
3227 * @return the first not blank name part in reverse order
3228 */
3229 @Override
3230 public String getLastNamePart() {
3231 String result =
3232 StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
3233 this.getInfraSpecificEpithet() :
3234 StringUtils.isNotBlank(this.getSpecificEpithet()) ?
3235 this.getSpecificEpithet():
3236 StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
3237 this.getInfraGenericEpithet():
3238 this.getGenusOrUninomial();
3239 return result;
3240 }
3241
3242 /**
3243 * {@inheritDoc}
3244 */
3245 @Override
3246 public boolean isHybridName() {
3247 return this.isMonomHybrid() || this.isBinomHybrid() || this.isTrinomHybrid();
3248 }
3249
3250 /**
3251 * {@inheritDoc}
3252 */
3253 @Override
3254 public boolean isHybrid() {
3255 return this.isHybridName() || this.isHybridFormula();
3256 }
3257
3258 // ***************** COMPARE ********************************/
3259
3260 @Override
3261 public int compareToName(TaxonNameBase<?,?> otherName){
3262
3263 int result = 0;
3264
3265 if (otherName == null) {
3266 throw new NullPointerException("Cannot compare to null.");
3267 }
3268
3269 //other
3270 otherName = deproxy(otherName);
3271 String otherNameCache = otherName.getNameCache();
3272 String otherTitleCache = otherName.getTitleCache();
3273 //TODO is this really necessary, is it not the normal way how name cache is filled for autonyms?
3274 if (otherName.isAutonym()){
3275 boolean isProtected = otherName.isProtectedNameCache();
3276 String oldNameCache = otherName.getNameCache();
3277 otherName.setProtectedNameCache(false);
3278 otherName.setNameCache(null, false);
3279 otherNameCache = otherName.getNameCache();
3280 otherName.setNameCache(oldNameCache, isProtected);
3281 }
3282
3283 //this
3284 String thisNameCache = this.getNameCache();
3285 String thisTitleCache = this.getTitleCache();
3286
3287 if (this.isAutonym()){
3288 boolean isProtected = this.isProtectedNameCache();
3289 String oldNameCache = this.getNameCache();
3290 this.setProtectedNameCache(false);
3291 this.setNameCache(null, false);
3292 thisNameCache = this.getNameCache();
3293 this.setNameCache(oldNameCache, isProtected);
3294 }
3295
3296
3297 // Compare name cache of taxon names
3298 if (CdmUtils.isNotBlank(otherNameCache) && CdmUtils.isNotBlank(thisNameCache)) {
3299 thisNameCache = normalizeName(thisNameCache);
3300 otherNameCache = normalizeName(otherNameCache);
3301 result = thisNameCache.compareTo(otherNameCache);
3302 }
3303
3304 // Compare title cache of taxon names
3305 if (result == 0){
3306 if ( (CdmUtils.isNotBlank(otherTitleCache) || CdmUtils.isNotBlank(thisTitleCache))) {
3307 thisTitleCache = normalizeName(thisTitleCache);
3308 otherTitleCache = normalizeName(otherTitleCache);
3309 result = CdmUtils.nullSafeCompareTo(thisTitleCache, otherTitleCache);
3310 }
3311 }
3312
3313 return result;
3314 }
3315
3316 static final String HYBRID_SIGN = UTF8.HYBRID.toString();
3317 static final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
3318
3319 /**
3320 * @param thisNameCache
3321 * @param HYBRID_SIGN
3322 * @param QUOT_SIGN
3323 * @return
3324 */
3325 private String normalizeName(String thisNameCache) {
3326 thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
3327 thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
3328 return thisNameCache;
3329 }
3330
3331 // ********************** INTERFACES ********************************************/
3332
3333 /**
3334 * Method to cast a interfaced name to a concrete name.
3335 * The method includes a deproxy to guarantee that no
3336 * class cast exception is thrown.
3337 *
3338 * @see #castAndDeproxy(Set)
3339 * @param interfacedName
3340 * @return
3341 */
3342 public static TaxonNameBase castAndDeproxy(ITaxonNameBase interfacedName){
3343 return deproxy(interfacedName, TaxonNameBase.class);
3344 }
3345
3346 /**
3347 * Method to cast a set of interfaced names to concrete namex.
3348 * The method includes a deproxy to guarantee that no
3349 * class cast exception is thrown.
3350 *
3351 * @see #castAndDeproxy(ITaxonNameBase)
3352 * @param naminterfacedNames
3353 * @return
3354 */
3355 public static Set<TaxonNameBase> castAndDeproxy(Set<ITaxonNameBase> naminterfacedNames) {
3356 Set<TaxonNameBase> result = new HashSet<>();
3357 for (ITaxonNameBase naminterfacedName : naminterfacedNames){
3358 result.add(castAndDeproxy(naminterfacedName));
3359 }
3360 return result;
3361 }
3362
3363
3364 //*********************** CLONE ********************************************************/
3365
3366 /**
3367 * Clones <i>this</i> taxon name. This is a shortcut that enables to create
3368 * a new instance that differs only slightly from <i>this</i> taxon name by
3369 * modifying only some of the attributes.<BR><BR>
3370 * Usages of this name in a taxon concept are <b>not</b> cloned.<BR>
3371 * <b>The name gets a newly created homotypical group</b><BR>
3372 * (CAUTION: this behaviour needs to be discussed and may change in future).<BR><BR>
3373 * {@link TaxonNameDescription Name descriptions} are cloned and not reused.<BR>
3374 * {@link TypeDesignationBase Type designations} are cloned and not reused.<BR>
3375 *
3376 * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
3377 * @see java.lang.Object#clone()
3378 */
3379 @Override
3380 public Object clone() {
3381 TaxonNameBase<?,?> result;
3382 try {
3383 result = (TaxonNameBase)super.clone();
3384
3385 //taxonBases -> empty
3386 result.taxonBases = new HashSet<>();
3387
3388 //empty caches
3389 if (! protectedFullTitleCache){
3390 result.fullTitleCache = null;
3391 }
3392
3393 //descriptions
3394 result.descriptions = new HashSet<>();
3395 for (TaxonNameDescription taxonNameDescription : getDescriptions()){
3396 TaxonNameDescription newDescription = (TaxonNameDescription)taxonNameDescription.clone();
3397 result.descriptions.add(newDescription);
3398 }
3399
3400 //status
3401 result.status = new HashSet<>();
3402 for (NomenclaturalStatus nomenclaturalStatus : getStatus()){
3403 NomenclaturalStatus newStatus = (NomenclaturalStatus)nomenclaturalStatus.clone();
3404 result.status.add(newStatus);
3405 }
3406
3407
3408 //To Relations
3409 result.relationsToThisName = new HashSet<>();
3410 for (NameRelationship toRelationship : getRelationsToThisName()){
3411 NameRelationship newRelationship = (NameRelationship)toRelationship.clone();
3412 newRelationship.setRelatedTo(result);
3413 result.relationsToThisName.add(newRelationship);
3414 }
3415
3416 //From Relations
3417 result.relationsFromThisName = new HashSet<>();
3418 for (NameRelationship fromRelationship : getRelationsFromThisName()){
3419 NameRelationship newRelationship = (NameRelationship)fromRelationship.clone();
3420 newRelationship.setRelatedFrom(result);
3421 result.relationsFromThisName.add(newRelationship);
3422 }
3423
3424 //type designations
3425 result.typeDesignations = new HashSet<>();
3426 for (TypeDesignationBase<?> typeDesignation : getTypeDesignations()){
3427 TypeDesignationBase<?> newDesignation = (TypeDesignationBase<?>)typeDesignation.clone();
3428 result.typeDesignations.add(newDesignation);
3429 newDesignation.addTypifiedName(result);
3430 }
3431
3432 //homotypicalGroup
3433 //TODO still needs to be discussed
3434 result.homotypicalGroup = HomotypicalGroup.NewInstance();
3435 result.homotypicalGroup.addTypifiedName(this);
3436
3437
3438 //HybridChildRelations
3439 result.hybridChildRelations = new HashSet<HybridRelationship>();
3440 for (HybridRelationship hybridRelationship : getHybridChildRelations()){
3441 HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
3442 newChildRelationship.setRelatedTo(result);
3443 result.hybridChildRelations.add(newChildRelationship);
3444 }
3445
3446 //HybridParentRelations
3447 result.hybridParentRelations = new HashSet<HybridRelationship>();
3448 for (HybridRelationship hybridRelationship : getHybridParentRelations()){
3449 HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
3450 newParentRelationship.setRelatedFrom(result);
3451 result.hybridParentRelations.add(newParentRelationship);
3452 }
3453
3454 //empty caches
3455 if (! protectedNameCache){
3456 result.nameCache = null;
3457 }
3458
3459 //empty caches
3460 if (! protectedAuthorshipCache){
3461 result.authorshipCache = null;
3462 }
3463
3464 //no changes to: appendedPharse, nomenclaturalReference,
3465 //nomenclaturalMicroReference, parsingProblem, problemEnds, problemStarts
3466 //protectedFullTitleCache, rank
3467 //basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
3468 //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
3469 //protectedAuthorshipCache, protectedNameCache,
3470 //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
3471 //acronym
3472 //subGenusAuthorship, nameApprobation
3473 //anamorphic
3474 //cultivarName
3475 return result;
3476 } catch (CloneNotSupportedException e) {
3477 logger.warn("Object does not implement cloneable");
3478 e.printStackTrace();
3479 return null;
3480 }
3481
3482 }
3483
3484 }