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