added not blank handling for potential notEmpty strings #4653
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / name / NonViralName.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
13 import java.beans.PropertyChangeEvent;
14 import java.beans.PropertyChangeListener;
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.Entity;
23 import javax.persistence.FetchType;
24 import javax.persistence.ManyToOne;
25 import javax.persistence.OneToMany;
26 import javax.persistence.Transient;
27 import javax.validation.constraints.NotNull;
28 import javax.validation.constraints.Pattern;
29 import javax.validation.constraints.Size;
30 import javax.xml.bind.annotation.XmlAccessType;
31 import javax.xml.bind.annotation.XmlAccessorType;
32 import javax.xml.bind.annotation.XmlElement;
33 import javax.xml.bind.annotation.XmlElementWrapper;
34 import javax.xml.bind.annotation.XmlIDREF;
35 import javax.xml.bind.annotation.XmlRootElement;
36 import javax.xml.bind.annotation.XmlSchemaType;
37 import javax.xml.bind.annotation.XmlType;
38
39 import org.apache.commons.lang.StringUtils;
40 import org.apache.log4j.Logger;
41 import org.hibernate.annotations.Cascade;
42 import org.hibernate.annotations.CascadeType;
43 import org.hibernate.envers.Audited;
44 import org.hibernate.search.annotations.Analyze;
45 import org.hibernate.search.annotations.Analyzer;
46 import org.hibernate.search.annotations.Field;
47 import org.hibernate.search.annotations.Fields;
48 import org.hibernate.search.annotations.Index;
49 import org.hibernate.search.annotations.Indexed;
50 import org.hibernate.search.annotations.IndexedEmbedded;
51 import org.hibernate.search.annotations.Store;
52 import org.hibernate.validator.constraints.NotEmpty;
53 import org.springframework.beans.factory.annotation.Configurable;
54
55 import eu.etaxonomy.cdm.common.CdmUtils;
56 import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
57 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
58 import eu.etaxonomy.cdm.model.common.CdmBase;
59 import eu.etaxonomy.cdm.model.common.RelationshipBase;
60 import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
61 import eu.etaxonomy.cdm.model.reference.Reference;
62 import eu.etaxonomy.cdm.strategy.cache.name.CacheUpdate;
63 import eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy;
64 import eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy;
65 import eu.etaxonomy.cdm.strategy.match.Match;
66 import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
67 import eu.etaxonomy.cdm.strategy.match.MatchMode;
68 import eu.etaxonomy.cdm.strategy.merge.Merge;
69 import eu.etaxonomy.cdm.strategy.merge.MergeMode;
70 import eu.etaxonomy.cdm.validation.Level2;
71 import eu.etaxonomy.cdm.validation.Level3;
72 import eu.etaxonomy.cdm.validation.annotation.CorrectEpithetsForRank;
73 import eu.etaxonomy.cdm.validation.annotation.NameMustHaveAuthority;
74 import eu.etaxonomy.cdm.validation.annotation.NoDuplicateNames;
75 import eu.etaxonomy.cdm.validation.annotation.NullOrNotEmpty;
76
77 /**
78 * The taxon name class for all non viral taxa. Parenthetical authorship is derived
79 * from basionym relationship. The scientific name including author strings and
80 * maybe year can be stored as a string in the inherited {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
81 * The year itself is an information obtained from the {@link eu.etaxonomy.cdm.model.reference.Reference#getYear() nomenclatural reference}.
82 * The scientific name string without author strings and year can be stored in the {@link #getNameCache() nameCache} attribute.
83 * <P>
84 * This class corresponds partially to: <ul>
85 * <li> TaxonName according to the TDWG ontology
86 * <li> ScientificName and CanonicalName according to the TCS
87 * <li> ScientificName according to the ABCD schema
88 * </ul>
89 *
90 * @author m.doering
91 * @version 1.0
92 * @created 08-Nov-2007 13:06:39
93 */
94 @XmlAccessorType(XmlAccessType.FIELD)
95 @XmlType(name = "NonViralName", propOrder = {
96 "nameCache",
97 "genusOrUninomial",
98 "infraGenericEpithet",
99 "specificEpithet",
100 "infraSpecificEpithet",
101 "combinationAuthorTeam",
102 "exCombinationAuthorTeam",
103 "basionymAuthorTeam",
104 "exBasionymAuthorTeam",
105 "authorshipCache",
106 "protectedAuthorshipCache",
107 "protectedNameCache",
108 "hybridParentRelations",
109 "hybridChildRelations",
110 "hybridFormula",
111 "monomHybrid",
112 "binomHybrid",
113 "trinomHybrid"
114 })
115 @XmlRootElement(name = "NonViralName")
116 @Entity
117 @Indexed(index = "eu.etaxonomy.cdm.model.name.TaxonNameBase")
118 @Audited
119 @Configurable
120 @CorrectEpithetsForRank(groups = Level2.class)
121 @NameMustHaveAuthority(groups = Level2.class)
122 @NoDuplicateNames(groups = Level3.class)
123 public class NonViralName<T extends NonViralName> extends TaxonNameBase<T, INonViralNameCacheStrategy> implements Cloneable{
124 private static final long serialVersionUID = 4441110073881088033L;
125 private static final Logger logger = Logger.getLogger(NonViralName.class);
126
127 @XmlElement(name = "NameCache")
128 @Fields({
129 @Field(name = "nameCache_tokenized"),
130 @Field(store = Store.YES, index = Index.YES, analyze = Analyze.YES)
131 })
132 @Analyzer (impl = org.apache.lucene.analysis.KeywordAnalyzer.class)
133 @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
134 cacheReplacedProperties={"genusOrUninomial", "infraGenericEpithet", "specificEpithet", "infraSpecificEpithet"} )
135 @NotEmpty(groups = Level2.class) // implicitly NotNull
136 @Size(max = 255)
137 private String nameCache;
138
139 @XmlElement(name = "ProtectedNameCache")
140 @CacheUpdate(value="nameCache")
141 protected boolean protectedNameCache;
142
143 @XmlElement(name = "GenusOrUninomial")
144 @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
145 @Match(MatchMode.EQUAL_REQUIRED)
146 @CacheUpdate("nameCache")
147 @Size(max = 255)
148 @Pattern(regexp = "[A-Z][a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForUninomial.message}")
149 @NullOrNotEmpty
150 @NotNull(groups = Level2.class)
151 private String genusOrUninomial;
152
153 @XmlElement(name = "InfraGenericEpithet")
154 @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
155 @CacheUpdate("nameCache")
156 //TODO Val #3379
157 // @NullOrNotEmpty
158 @Size(max = 255)
159 @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class,message="{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
160 private String infraGenericEpithet;
161
162 @XmlElement(name = "SpecificEpithet")
163 @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
164 @CacheUpdate("nameCache")
165 //TODO Val #3379
166 // @NullOrNotEmpty
167 @Size(max = 255)
168 @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
169 private String specificEpithet;
170
171 @XmlElement(name = "InfraSpecificEpithet")
172 @Field(analyze = Analyze.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
173 @CacheUpdate("nameCache")
174 //TODO Val #3379
175 // @NullOrNotEmpty
176 @Size(max = 255)
177 @Pattern(regexp = "[a-z\\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-]+", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForEpithet.message}")
178 private String infraSpecificEpithet;
179
180 @XmlElement(name = "CombinationAuthorTeam", type = TeamOrPersonBase.class)
181 @XmlIDREF
182 @XmlSchemaType(name = "IDREF")
183 @ManyToOne(fetch = FetchType.LAZY)
184 // @Target(TeamOrPersonBase.class)
185 @Cascade(CascadeType.SAVE_UPDATE)
186 @CacheUpdate("authorshipCache")
187 @IndexedEmbedded
188 private TeamOrPersonBase<?> combinationAuthorTeam;
189
190 @XmlElement(name = "ExCombinationAuthorTeam", type = TeamOrPersonBase.class)
191 @XmlIDREF
192 @XmlSchemaType(name = "IDREF")
193 @ManyToOne(fetch = FetchType.LAZY)
194 // @Target(TeamOrPersonBase.class)
195 @Cascade(CascadeType.SAVE_UPDATE)
196 @CacheUpdate("authorshipCache")
197 @IndexedEmbedded
198 private TeamOrPersonBase<?> exCombinationAuthorTeam;
199
200 @XmlElement(name = "BasionymAuthorTeam", type = TeamOrPersonBase.class)
201 @XmlIDREF
202 @XmlSchemaType(name = "IDREF")
203 @ManyToOne(fetch = FetchType.LAZY)
204 // @Target(TeamOrPersonBase.class)
205 @Cascade(CascadeType.SAVE_UPDATE)
206 @CacheUpdate("authorshipCache")
207 @IndexedEmbedded
208 private TeamOrPersonBase<?> basionymAuthorTeam;
209
210 @XmlElement(name = "ExBasionymAuthorTeam", type = TeamOrPersonBase.class)
211 @XmlIDREF
212 @XmlSchemaType(name = "IDREF")
213 @ManyToOne(fetch = FetchType.LAZY)
214 // @Target(TeamOrPersonBase.class)
215 @Cascade(CascadeType.SAVE_UPDATE)
216 @CacheUpdate("authorshipCache")
217 @IndexedEmbedded
218 private TeamOrPersonBase<?> exBasionymAuthorTeam;
219
220 @XmlElement(name = "AuthorshipCache")
221 @Fields({
222 @Field(name = "authorshipCache_tokenized"),
223 @Field(analyze = Analyze.NO)
224 })
225 @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
226 cacheReplacedProperties={"combinationAuthorTeam", "basionymAuthorTeam", "exCombinationAuthorTeam", "exBasionymAuthorTeam"} )
227 //TODO Val #3379
228 // @NotNull
229 @Size(max = 255)
230 @Pattern(regexp = "^[A-Za-z0-9 \\u00E4\\u00EB\\u00EF\\u00F6\\u00FC\\-\\&\\,\\(\\)\\.]+$", groups=Level2.class, message = "{eu.etaxonomy.cdm.model.name.NonViralName.allowedCharactersForAuthority.message}")
231 private String authorshipCache;
232
233 @XmlElement(name = "ProtectedAuthorshipCache")
234 @CacheUpdate("authorshipCache")
235 protected boolean protectedAuthorshipCache;
236
237 @XmlElementWrapper(name = "HybridRelationsFromThisName")
238 @XmlElement(name = "HybridRelationsFromThisName")
239 @OneToMany(mappedBy="relatedFrom", fetch = FetchType.LAZY, orphanRemoval=true)
240 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
241 @Merge(MergeMode.RELATION)
242 @NotNull
243 private Set<HybridRelationship> hybridParentRelations = new HashSet<HybridRelationship>();
244
245 @XmlElementWrapper(name = "HybridRelationsToThisName")
246 @XmlElement(name = "HybridRelationsToThisName")
247 @OneToMany(mappedBy="relatedTo", fetch = FetchType.LAZY, orphanRemoval=true) //a hybrid relation can be deleted automatically if the child is deleted.
248 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
249 @Merge(MergeMode.RELATION)
250 @NotNull
251 private Set<HybridRelationship> hybridChildRelations = new HashSet<HybridRelationship>();
252
253 //if set: this name is a hybrid formula (a hybrid that does not have an own name) and no other hybrid flags may be set. A
254 //hybrid name may not have either an authorteam nor other name components.
255 @XmlElement(name ="IsHybridFormula")
256 @CacheUpdate("nameCache")
257 private boolean hybridFormula = false;
258
259 @XmlElement(name ="IsMonomHybrid")
260 @CacheUpdate("nameCache")
261 private boolean monomHybrid = false;
262
263 @XmlElement(name ="IsBinomHybrid")
264 @CacheUpdate("nameCache")
265 private boolean binomHybrid = false;
266
267 @XmlElement(name ="IsTrinomHybrid")
268 @CacheUpdate("nameCache")
269 private boolean trinomHybrid = false;
270
271 /**
272 * Creates a new non viral taxon name instance
273 * only containing its {@link common.Rank rank} and
274 * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
275 *
276 * @param rank the rank to be assigned to <i>this</i> non viral taxon name
277 * @see #NewInstance(Rank, HomotypicalGroup)
278 * @see #NonViralName(Rank, HomotypicalGroup)
279 * @see #NonViralName()
280 * @see #NonViralName(Rank, String, String, String, String, TeamOrPersonBase, Reference, String, HomotypicalGroup)
281 * @see eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
282 * @see eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
283 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
284 */
285 public static NonViralName NewInstance(Rank rank){
286 return new NonViralName(rank, null);
287 }
288
289 /**
290 * Creates a new non viral taxon name instance
291 * only containing its {@link common.Rank rank},
292 * its {@link HomotypicalGroup homotypical group} and
293 * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
294 * The new non viral taxon name instance will be also added to the set of
295 * non viral taxon names belonging to this homotypical group.
296 *
297 * @param rank the rank to be assigned to <i>this</i> non viral taxon name
298 * @param homotypicalGroup the homotypical group to which <i>this</i> non viral taxon name belongs
299 * @see #NewInstance(Rank)
300 * @see #NonViralName(Rank, HomotypicalGroup)
301 * @see #NonViralName()
302 * @see #NonViralName(Rank, String, String, String, String, TeamOrPersonBase, Reference, String, HomotypicalGroup)
303 * @see eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
304 * @see eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
305 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
306 */
307 public static NonViralName NewInstance(Rank rank, HomotypicalGroup homotypicalGroup){
308 return new NonViralName(rank, homotypicalGroup);
309 }
310
311 // ************************** CONSTRUCTORS *************/
312
313 //needed by hibernate
314 /**
315 * Class constructor: creates a new non viral taxon name instance
316 * only containing the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
317 *
318 * @see #NonViralName(Rank, HomotypicalGroup)
319 * @see #NonViralName(Rank, String, String, String, String, TeamOrPersonBase, Reference, String, HomotypicalGroup)
320 * @see eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
321 * @see eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
322 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
323 */
324 protected NonViralName(){
325 super();
326 setNameCacheStrategy();
327 }
328
329 /**
330 * Class constructor: creates a new non viral taxon name instance
331 * only containing its {@link Rank rank},
332 * its {@link HomotypicalGroup homotypical group} and
333 * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
334 * The new non viral taxon name instance will be also added to the set of
335 * non viral taxon names belonging to this homotypical group.
336 *
337 * @param rank the rank to be assigned to <i>this</i> non viral taxon name
338 * @param homotypicalGroup the homotypical group to which <i>this</i> non viral taxon name belongs
339 * @see #NonViralName()
340 * @see #NonViralName(Rank, String, String, String, String, TeamOrPersonBase, Reference, String, HomotypicalGroup)
341 * @see #NewInstance(Rank, HomotypicalGroup)
342 * @see eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
343 * @see eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
344 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
345 */
346 protected NonViralName(Rank rank, HomotypicalGroup homotypicalGroup) {
347 super(rank, homotypicalGroup);
348 setNameCacheStrategy();
349 }
350 /**
351 * Class constructor: creates a new non viral taxon name instance
352 * containing its {@link Rank rank},
353 * its {@link HomotypicalGroup homotypical group},
354 * its scientific name components, its {@link eu.etaxonomy.cdm.model.agent.TeamOrPersonBase author(team)},
355 * its {@link eu.etaxonomy.cdm.model.reference.Reference nomenclatural reference} and
356 * the {@link eu.etaxonomy.cdm.strategy.cache.name.NonViralNameDefaultCacheStrategy default cache strategy}.
357 * The new non viral taxon name instance will be also added to the set of
358 * non viral taxon names belonging to this homotypical group.
359 *
360 * @param rank the rank to be assigned to <i>this</i> non viral taxon name
361 * @param genusOrUninomial the string for <i>this</i> non viral taxon name
362 * if its rank is genus or higher or for the genus part
363 * if its rank is lower than genus
364 * @param infraGenericEpithet the string for the first epithet of
365 * <i>this</i> non viral taxon name if its rank is lower than genus
366 * and higher than species aggregate
367 * @param specificEpithet the string for the first epithet of
368 * <i>this</i> non viral taxon name if its rank is species aggregate or lower
369 * @param infraSpecificEpithet the string for the second epithet of
370 * <i>this</i> non viral taxon name if its rank is lower than species
371 * @param combinationAuthorTeam the author or the team who published <i>this</i> non viral taxon name
372 * @param nomenclaturalReference the nomenclatural reference where <i>this</i> non viral taxon name was published
373 * @param nomenclMicroRef the string with the details for precise location within the nomenclatural reference
374 * @param homotypicalGroup the homotypical group to which <i>this</i> non viral taxon name belongs
375 * @see #NonViralName()
376 * @see #NonViralName(Rank, HomotypicalGroup)
377 * @see #NewInstance(Rank, HomotypicalGroup)
378 * @see eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy
379 * @see eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy
380 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
381 */
382 protected NonViralName(Rank rank, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, TeamOrPersonBase combinationAuthorTeam, INomenclaturalReference nomenclaturalReference, String nomenclMicroRef, HomotypicalGroup homotypicalGroup) {
383 super(rank, homotypicalGroup);
384 setNameCacheStrategy();
385 setGenusOrUninomial(genusOrUninomial);
386 setInfraGenericEpithet (infraGenericEpithet);
387 setSpecificEpithet(specificEpithet);
388 setInfraSpecificEpithet(infraSpecificEpithet);
389 setCombinationAuthorTeam(combinationAuthorTeam);
390 setNomenclaturalReference(nomenclaturalReference);
391 this.setNomenclaturalMicroReference(nomenclMicroRef);
392 }
393
394
395
396 //**************************** METHODS **************************************/
397
398
399 private void setNameCacheStrategy(){
400 if (getClass() == NonViralName.class){
401 this.cacheStrategy = NonViralNameDefaultCacheStrategy.NewInstance();
402 }
403 }
404
405 @Override
406 protected void initListener(){
407 PropertyChangeListener listener = new PropertyChangeListener() {
408 @Override
409 public void propertyChange(PropertyChangeEvent e) {
410 boolean protectedByLowerCache = false;
411 //authorship cache
412 if (fieldHasCacheUpdateProperty(e.getPropertyName(), "authorshipCache")){
413 if (protectedAuthorshipCache){
414 protectedByLowerCache = true;
415 }else{
416 authorshipCache = null;
417 }
418 }
419
420 //nameCache
421 if (fieldHasCacheUpdateProperty(e.getPropertyName(), "nameCache")){
422 if (protectedNameCache){
423 protectedByLowerCache = true;
424 }else{
425 nameCache = null;
426 }
427 }
428 //title cache
429 if (! fieldHasNoUpdateProperty(e.getPropertyName(), "titleCache")){
430 if (isProtectedTitleCache()|| protectedByLowerCache == true ){
431 protectedByLowerCache = true;
432 }else{
433 titleCache = null;
434 }
435 }
436 //full title cache
437 if (! fieldHasNoUpdateProperty(e.getPropertyName(), "fullTitleCache")){
438 if (isProtectedFullTitleCache()|| protectedByLowerCache == true ){
439 protectedByLowerCache = true;
440 }else{
441 fullTitleCache = null;
442 }
443 }
444 }
445 };
446 addPropertyChangeListener(listener); //didn't use this.addXXX to make lsid.AssemblerTest run in cdmlib-remote
447 }
448
449 private static Map<String, java.lang.reflect.Field> allFields = null;
450 @Override
451 protected Map<String, java.lang.reflect.Field> getAllFields(){
452 if (allFields == null){
453 allFields = CdmUtils.getAllFields(this.getClass(), CdmBase.class, false, false, false, true);
454 }
455 return allFields;
456 }
457
458 /**
459 * @param propertyName
460 * @param string
461 * @return
462 */
463 private boolean fieldHasCacheUpdateProperty(String propertyName, String cacheName) {
464 java.lang.reflect.Field field;
465 try {
466 field = getAllFields().get(propertyName);
467 if (field != null){
468 CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
469 if (updateAnnotation != null){
470 for (String value : updateAnnotation.value()){
471 if (cacheName.equals(value)){
472 return true;
473 }
474 }
475 }
476 }
477 return false;
478 } catch (SecurityException e1) {
479 throw e1;
480 }
481 }
482
483 private boolean fieldHasNoUpdateProperty(String propertyName, String cacheName) {
484 java.lang.reflect.Field field;
485 //do not update fields with the same name
486 if (cacheName.equals(propertyName)){
487 return true;
488 }
489 //evaluate annotation
490 try {
491 field = getAllFields().get(propertyName);
492 if (field != null){
493 CacheUpdate updateAnnotation = field.getAnnotation(CacheUpdate.class);
494 if (updateAnnotation != null){
495 for (String value : updateAnnotation.noUpdate()){
496 if (cacheName.equals(value)){
497 return true;
498 }
499 }
500 }
501 }
502 return false;
503 } catch (SecurityException e1) {
504 throw e1;
505 }
506 }
507
508
509 /**
510 * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published <i>this</i> non viral
511 * taxon name.
512 *
513 * @return the nomenclatural author (team) of <i>this</i> non viral taxon name
514 * @see eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
515 * @see eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
516 */
517 public TeamOrPersonBase<?> getCombinationAuthorTeam(){
518 return this.combinationAuthorTeam;
519 }
520
521 /**
522 * @see #getCombinationAuthorTeam()
523 */
524 public void setCombinationAuthorTeam(TeamOrPersonBase<?> combinationAuthorTeam){
525 this.combinationAuthorTeam = combinationAuthorTeam;
526 }
527
528 /**
529 * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
530 * the publication of <i>this</i> non viral taxon name as generally stated by
531 * the {@link #getCombinationAuthorTeam() combination author (team)} itself.<BR>
532 * An ex-author(-team) is an author(-team) to whom a taxon name was ascribed
533 * although it is not the author(-team) of a valid publication (for instance
534 * without the validating description or diagnosis in case of a name for a
535 * new taxon). The name of this ascribed authorship, followed by "ex", may
536 * be inserted before the name(s) of the publishing author(s) of the validly
537 * published name:<BR>
538 * <i>Lilium tianschanicum</i> was described by Grubov (1977) as a new species and
539 * its name was ascribed to Ivanova; since there is no indication that
540 * Ivanova provided the validating description, the name may be cited as
541 * <i>Lilium tianschanicum</i> N. A. Ivanova ex Grubov or <i>Lilium tianschanicum</i> Grubov.
542 * <P>
543 * The presence of an author (team) of <i>this</i> non viral taxon name is a
544 * condition for the existence of an ex author (team) for <i>this</i> same name.
545 *
546 * @return the nomenclatural ex author (team) of <i>this</i> non viral taxon name
547 * @see #getCombinationAuthorTeam()
548 * @see eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
549 * @see eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
550 */
551 public TeamOrPersonBase<?> getExCombinationAuthorTeam(){
552 return this.exCombinationAuthorTeam;
553 }
554
555 /**
556 * @see #getExCombinationAuthorTeam()
557 */
558 public void setExCombinationAuthorTeam(TeamOrPersonBase<?> exCombinationAuthorTeam){
559 this.exCombinationAuthorTeam = exCombinationAuthorTeam;
560 }
561
562 /**
563 * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that published the original combination
564 * on which <i>this</i> non viral taxon name is nomenclaturally based. Such an
565 * author (team) can only exist if <i>this</i> non viral taxon name is a new
566 * combination due to a taxonomical revision.
567 *
568 * @return the nomenclatural basionym author (team) of <i>this</i> non viral taxon name
569 * @see #getCombinationAuthorTeam()
570 * @see eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
571 * @see eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
572 */
573 public TeamOrPersonBase<?> getBasionymAuthorTeam(){
574 return basionymAuthorTeam;
575 }
576
577 /**
578 * @see #getBasionymAuthorTeam()
579 */
580 public void setBasionymAuthorTeam(TeamOrPersonBase<?> basionymAuthorTeam) {
581 this.basionymAuthorTeam = basionymAuthorTeam;
582 }
583
584 /**
585 * Returns the {@link eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor author (team)} that contributed to
586 * the publication of the original combination <i>this</i> non viral taxon name is
587 * based on. This should have been generally stated by
588 * the {@link #getBasionymAuthorTeam() basionym author (team)} itself.
589 * The presence of a basionym author (team) of <i>this</i> non viral taxon name is a
590 * condition for the existence of an ex basionym author (team)
591 * for <i>this</i> same name.
592 *
593 * @return the nomenclatural ex basionym author (team) of <i>this</i> non viral taxon name
594 * @see #getBasionymAuthorTeam()
595 * @see #getExCombinationAuthorTeam()
596 * @see #getCombinationAuthorTeam()
597 * @see eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
598 * @see eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
599 */
600 public TeamOrPersonBase<?> getExBasionymAuthorTeam(){
601 return exBasionymAuthorTeam;
602 }
603
604 /**
605 * @see #getExBasionymAuthorTeam()
606 */
607 public void setExBasionymAuthorTeam(TeamOrPersonBase<?> exBasionymAuthorTeam) {
608 this.exBasionymAuthorTeam = exBasionymAuthorTeam;
609 }
610 /**
611 * Returns either the scientific name string (without authorship) for <i>this</i>
612 * non viral taxon name if its rank is genus or higher (monomial) or the string for
613 * the genus part of it if its {@link Rank rank} is lower than genus (bi- or trinomial).
614 * Genus or uninomial strings begin with an upper case letter.
615 *
616 * @return the string containing the suprageneric name, the genus name or the genus part of <i>this</i> non viral taxon name
617 * @see #getNameCache()
618 */
619 public String getGenusOrUninomial() {
620 return genusOrUninomial;
621 }
622
623 /**
624 * @see #getGenusOrUninomial()
625 */
626 public void setGenusOrUninomial(String genusOrUninomial) {
627 this.genusOrUninomial = StringUtils.isBlank(genusOrUninomial) ? null : genusOrUninomial;
628 }
629
630 /**
631 * Returns the genus subdivision epithet string (infrageneric part) for
632 * <i>this</i> non viral taxon name if its {@link Rank rank} is infrageneric (lower than genus and
633 * higher than species aggregate: binomial). Genus subdivision epithet
634 * strings begin with an upper case letter.
635 *
636 * @return the string containing the infrageneric part of <i>this</i> non viral taxon name
637 * @see #getNameCache()
638 */
639 public String getInfraGenericEpithet(){
640 return this.infraGenericEpithet;
641 }
642
643 /**
644 * @see #getInfraGenericEpithet()
645 */
646 public void setInfraGenericEpithet(String infraGenericEpithet){
647 this.infraGenericEpithet = StringUtils.isBlank(infraGenericEpithet)? null : infraGenericEpithet;
648 }
649
650 /**
651 * Returns the species epithet string for <i>this</i> non viral taxon name if its {@link Rank rank} is
652 * species aggregate or lower (bi- or trinomial). Species epithet strings
653 * begin with a lower case letter.
654 *
655 * @return the string containing the species epithet of <i>this</i> non viral taxon name
656 * @see #getNameCache()
657 */
658 public String getSpecificEpithet(){
659 return this.specificEpithet;
660 }
661
662 /**
663 * @see #getSpecificEpithet()
664 */
665 public void setSpecificEpithet(String specificEpithet){
666 this.specificEpithet = StringUtils.isBlank(specificEpithet) ? null : specificEpithet;
667 }
668
669 /**
670 * Returns the species subdivision epithet string (infraspecific part) for
671 * <i>this</i> non viral taxon name if its {@link Rank rank} is infraspecific
672 * (lower than species: trinomial). Species subdivision epithet strings
673 * begin with a lower case letter.
674 *
675 * @return the string containing the infraspecific part of <i>this</i> non viral taxon name
676 * @see #getNameCache()
677 */
678 public String getInfraSpecificEpithet(){
679 return this.infraSpecificEpithet;
680 }
681
682 /**
683 * @see #getInfraSpecificEpithet()
684 */
685 public void setInfraSpecificEpithet(String infraSpecificEpithet){
686 this.infraSpecificEpithet = StringUtils.isBlank(infraSpecificEpithet)?null : infraSpecificEpithet;
687 }
688
689 /**
690 * Generates and returns the string with the scientific name of <i>this</i>
691 * non viral taxon name including author strings and maybe year according to
692 * the strategy defined in
693 * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
694 * This string may be stored in the inherited
695 * {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} attribute.
696 * This method overrides the generic and inherited
697 * TaxonNameBase#generateTitle() method.
698 *
699 * @return the string with the composed name of <i>this</i> non viral taxon name with authorship (and maybe year)
700 * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
701 * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache()
702 * @see TaxonNameBase#generateTitle()
703 */
704 // @Override
705 // public String generateTitle(){
706 // if (cacheStrategy == null){
707 // logger.warn("No CacheStrategy defined for nonViralName: " + this.getUuid());
708 // return null;
709 // }else{
710 // return cacheStrategy.getTitleCache(this);
711 // }
712 // }
713
714 @Override
715 public String generateFullTitle(){
716 if (cacheStrategy == null){
717 logger.warn("No CacheStrategy defined for nonViralName: " + this.getUuid());
718 return null;
719 }else{
720 return cacheStrategy.getFullTitleCache(this);
721 }
722 }
723
724 /**
725 * Generates the composed name string of <i>this</i> non viral taxon name without author
726 * strings or year according to the strategy defined in
727 * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy INonViralNameCacheStrategy}.
728 * The result might be stored in {@link #getNameCache() nameCache} if the
729 * flag {@link #isProtectedNameCache() protectedNameCache} is not set.
730 *
731 * @return the string with the composed name of <i>this</i> non viral taxon name without authors or year
732 * @see #getNameCache()
733 */
734 protected String generateNameCache(){
735 if (cacheStrategy == null){
736 logger.warn("No CacheStrategy defined for taxonName: " + this.toString());
737 return null;
738 }else{
739 return cacheStrategy.getNameCache(this);
740 }
741 }
742
743 /**
744 * Returns or generates the nameCache (scientific name
745 * without author strings and year) string for <i>this</i> non viral taxon name. If the
746 * {@link #isProtectedNameCache() protectedNameCache} flag is not set (False)
747 * the string will be generated according to a defined strategy,
748 * otherwise the value of the actual nameCache string will be returned.
749 *
750 * @return the string which identifies <i>this</i> non viral taxon name (without authors or year)
751 * @see #generateNameCache()
752 */
753 @Transient
754 public String getNameCache() {
755 if (protectedNameCache){
756 return this.nameCache;
757 }
758 // is title dirty, i.e. equal NULL?
759 if (nameCache == null){
760 this.nameCache = generateNameCache();
761 }
762 return nameCache;
763 }
764
765 /**
766 * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
767 * Sets the protectedNameCache flag to <code>true</code>.
768 *
769 * @param nameCache the string which identifies <i>this</i> non viral taxon name (without authors or year)
770 * @see #getNameCache()
771 */
772 public void setNameCache(String nameCache){
773 setNameCache(nameCache, true);
774 }
775
776 /**
777 * Assigns a nameCache string to <i>this</i> non viral taxon name and protects it from being overwritten.
778 * Sets the protectedNameCache flag to <code>true</code>.
779 *
780 * @param nameCache the string which identifies <i>this</i> non viral taxon name (without authors or year)
781 * @param protectedNameCache if true teh protectedNameCache is set to <code>true</code> or otherwise set to
782 * <code>false</code>
783 * @see #getNameCache()
784 */
785 public void setNameCache(String nameCache, boolean protectedNameCache){
786 this.nameCache = nameCache;
787 this.setProtectedNameCache(protectedNameCache);
788 }
789
790 /**
791 * Returns the boolean value of the flag intended to protect (true)
792 * or not (false) the {@link #getNameCache() nameCache} (scientific name without author strings and year)
793 * string of <i>this</i> non viral taxon name.
794 *
795 * @return the boolean value of the protectedNameCache flag
796 * @see #getNameCache()
797 */
798 public boolean isProtectedNameCache() {
799 return protectedNameCache;
800 }
801
802 /**
803 * @see #isProtectedNameCache()
804 */
805 public void setProtectedNameCache(boolean protectedNameCache) {
806 this.protectedNameCache = protectedNameCache;
807 }
808
809
810 /**
811 * Generates and returns a concatenated and formated authorteams string
812 * including basionym and combination authors of <i>this</i> non viral taxon name
813 * according to the strategy defined in
814 * {@link eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(NonViralName) INonViralNameCacheStrategy}.
815 *
816 * @return the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
817 * @see eu.etaxonomy.cdm.strategy.cache.name.INonViralNameCacheStrategy#getAuthorshipCache(NonViralName)
818 */
819 public String generateAuthorship(){
820 if (cacheStrategy == null){
821 logger.warn("No CacheStrategy defined for nonViralName: " + this.getUuid());
822 return null;
823 }else{
824 return ((INonViralNameCacheStrategy<T>)cacheStrategy).getAuthorshipCache((T)this);
825 }
826 }
827
828 /**
829 * Returns the concatenated and formated authorteams string including
830 * basionym and combination authors of <i>this</i> non viral taxon name.
831 * If the protectedAuthorshipCache flag is set this method returns the
832 * string stored in the the authorshipCache attribute, otherwise it
833 * generates the complete authorship string, returns it and stores it in
834 * the authorshipCache attribute.
835 *
836 * @return the string with the concatenated and formated authorteams for <i>this</i> non viral taxon name
837 * @see #generateAuthorship()
838 */
839 @Transient
840 public String getAuthorshipCache() {
841 if (protectedAuthorshipCache){
842 return this.authorshipCache;
843 }
844 if (this.authorshipCache == null ){
845 this.authorshipCache = generateAuthorship();
846 }else{
847 //TODO get is Dirty of authors, make better if possible
848 this.setAuthorshipCache(generateAuthorship(), protectedAuthorshipCache); //throw change event to inform higher caches
849
850 }
851 return authorshipCache;
852 }
853
854
855 /**
856 * Updates the authorship cache if any changes appeared in the authors nomenclatural caches.
857 * Deletes the titleCache and the fullTitleCache if not protected and if any change has happened
858 * @return
859 */
860 private void updateAuthorshipCache() {
861 //updates the authorship cache if necessary and via the listener updates all higher caches
862 if (protectedAuthorshipCache == false){
863 String oldCache = this.authorshipCache;
864 String newCache = this.getAuthorshipCache();
865 if ( (oldCache == null && newCache != null) || CdmUtils.nullSafeEqual(oldCache,newCache)){
866 this.setAuthorshipCache(this.getAuthorshipCache(), false);
867 }
868 }
869 }
870
871 /**
872 * Assigns an authorshipCache string to <i>this</i> non viral taxon name. Sets the isProtectedAuthorshipCache
873 * flag to <code>true</code>.
874 *
875 * @param authorshipCache the string which identifies the complete authorship of <i>this</i> non viral taxon name
876 * @see #getAuthorshipCache()
877 */
878 public void setAuthorshipCache(String authorshipCache) {
879 setAuthorshipCache(authorshipCache, true);
880 }
881
882 @Override
883 @Transient
884 public String getFullTitleCache(){
885 updateAuthorshipCache();
886 return super.getFullTitleCache();
887 }
888
889 @Override
890 public String getTitleCache(){
891 if(!protectedTitleCache) {
892 updateAuthorshipCache();
893 }
894
895 return super.getTitleCache();
896 }
897
898
899 /**
900 * Assigns an authorshipCache string to <i>this</i> non viral taxon name.
901 *
902 * @param authorshipCache the string which identifies the complete authorship of <i>this</i> non viral taxon name
903 * @param protectedAuthorshipCache if true the isProtectedAuthorshipCache flag is set to <code>true</code>, otherwise
904 * the flag is set to <code>false</code>.
905 * @see #getAuthorshipCache()
906 */
907 public void setAuthorshipCache(String authorshipCache, boolean protectedAuthorshipCache) {
908 this.authorshipCache = authorshipCache;
909 this.setProtectedAuthorshipCache(protectedAuthorshipCache);
910 }
911
912 @Override
913 public void setTitleCache(String titleCache, boolean protectCache){
914 super.setTitleCache(titleCache, protectCache);
915 }
916
917 /**
918 * Returns the boolean value "false" since the components of <i>this</i> taxon name
919 * cannot follow the rules of a corresponding {@link NomenclaturalCode nomenclatural code}
920 * which is not defined for this class. The nomenclature code depends on
921 * the concrete name subclass ({@link BacterialName BacterialName},
922 * {@link BotanicalName BotanicalName}, {@link CultivarPlantName CultivarPlantName} or
923 * {@link ZoologicalName ZoologicalName} to which <i>this</i> non viral taxon name belongs.
924 * This method overrides the isCodeCompliant method from the abstract
925 * {@link TaxonNameBase#isCodeCompliant() TaxonNameBase} class.
926 *
927 * @return false
928 * @see TaxonNameBase#isCodeCompliant()
929 */
930 @Override
931 @Transient
932 public boolean isCodeCompliant() {
933 //FIXME
934 logger.warn("is CodeCompliant not yet implemented");
935 return false;
936 }
937
938
939 /**
940 * Returns null as {@link NomenclaturalCode nomenclatural code} that governs
941 * the construction of <i>this</i> non viral taxon name since there is no specific
942 * nomenclatural code defined. The real implementention takes place in the
943 * subclasses {@link BacterialName BacterialName},
944 * {@link BotanicalName BotanicalName}, {@link CultivarPlantName CultivarPlantName} and
945 * {@link ZoologicalName ZoologicalName}.
946 * This method overrides the {@link TaxonNameBase#getNomenclaturalCode()} method from {@link TaxonNameBase TaxonNameBase}.
947 *
948 * @return null
949 * @see #isCodeCompliant()
950 * @see TaxonNameBase#getHasProblem()
951 */
952 @Override
953 @Transient
954 public NomenclaturalCode getNomenclaturalCode() {
955 logger.warn("Non Viral Name has no specific Code defined. Use subclasses");
956 return null;
957 }
958
959 /**
960 * Returns the boolean value of the flag intended to protect (true)
961 * or not (false) the {@link #getAuthorshipCache() authorshipCache} (complete authorship string)
962 * of <i>this</i> non viral taxon name.
963 *
964 * @return the boolean value of the protectedAuthorshipCache flag
965 * @see #getAuthorshipCache()
966 */
967 public boolean isProtectedAuthorshipCache() {
968 return protectedAuthorshipCache;
969 }
970
971 /**
972 * @see #isProtectedAuthorshipCache()
973 * @see #getAuthorshipCache()
974 */
975 public void setProtectedAuthorshipCache(boolean protectedAuthorshipCache) {
976 this.protectedAuthorshipCache = protectedAuthorshipCache;
977 }
978
979
980 /**
981 * Returns the boolean value of the flag indicating whether the name of <i>this</i>
982 * botanical taxon name is a hybrid formula (true) or not (false). A hybrid
983 * named by a hybrid formula (composed with its parent names by placing the
984 * multiplication sign between them) does not have an own published name
985 * and therefore has neither an {@link NonViralName#getAuthorshipCache() autorship}
986 * nor other name components. If this flag is set no other hybrid flags may
987 * be set.
988 *
989 * @return the boolean value of the isHybridFormula flag
990 * @see #isMonomHybrid()
991 * @see #isBinomHybrid()
992 * @see #isTrinomHybrid()
993 */
994 public boolean isHybridFormula(){
995 return this.hybridFormula;
996 }
997
998 /**
999 * @see #isHybridFormula()
1000 */
1001 public void setHybridFormula(boolean hybridFormula){
1002 this.hybridFormula = hybridFormula;
1003 }
1004
1005 /**
1006 * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1007 * taxon name is the name of an intergeneric hybrid (true) or not (false).
1008 * In this case the multiplication sign is placed before the scientific
1009 * name. If this flag is set no other hybrid flags may be set.
1010 *
1011 * @return the boolean value of the isMonomHybrid flag
1012 * @see #isHybridFormula()
1013 * @see #isBinomHybrid()
1014 * @see #isTrinomHybrid()
1015 */
1016 public boolean isMonomHybrid(){
1017 return this.monomHybrid;
1018 }
1019
1020 /**
1021 * @see #isMonomHybrid()
1022 * @see #isBinomHybrid()
1023 * @see #isTrinomHybrid()
1024 */
1025 public void setMonomHybrid(boolean monomHybrid){
1026 this.monomHybrid = monomHybrid;
1027 }
1028
1029 /**
1030 * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1031 * taxon name is the name of an interspecific hybrid (true) or not (false).
1032 * In this case the multiplication sign is placed before the species
1033 * epithet. If this flag is set no other hybrid flags may be set.
1034 *
1035 * @return the boolean value of the isBinomHybrid flag
1036 * @see #isHybridFormula()
1037 * @see #isMonomHybrid()
1038 * @see #isTrinomHybrid()
1039 */
1040 public boolean isBinomHybrid(){
1041 return this.binomHybrid;
1042 }
1043
1044 /**
1045 * @see #isBinomHybrid()
1046 * @see #isMonomHybrid()
1047 * @see #isTrinomHybrid()
1048 */
1049 public void setBinomHybrid(boolean binomHybrid){
1050 this.binomHybrid = binomHybrid;
1051 }
1052
1053 /**
1054 * Returns the boolean value of the flag indicating whether <i>this</i> botanical
1055 * taxon name is the name of an infraspecific hybrid (true) or not (false).
1056 * In this case the term "notho-" (optionally abbreviated "n-") is used as
1057 * a prefix to the term denoting the infraspecific rank of <i>this</i> botanical
1058 * taxon name. If this flag is set no other hybrid flags may be set.
1059 *
1060 * @return the boolean value of the isTrinomHybrid flag
1061 * @see #isHybridFormula()
1062 * @see #isMonomHybrid()
1063 * @see #isBinomHybrid()
1064 */
1065 public boolean isTrinomHybrid(){
1066 return this.trinomHybrid;
1067 }
1068
1069 /**
1070 * @see #isTrinomHybrid()
1071 * @see #isBinomHybrid()
1072 * @see #isMonomHybrid()
1073 */
1074 public void setTrinomHybrid(boolean trinomHybrid){
1075 this.trinomHybrid = trinomHybrid;
1076 }
1077
1078
1079 /**
1080 * Returns the set of all {@link HybridRelationship hybrid relationships}
1081 * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedFrom() parent}.
1082 *
1083 * @see #getHybridRelationships()
1084 * @see #getChildRelationships()
1085 * @see HybridRelationshipType
1086 */
1087 public Set<HybridRelationship> getHybridParentRelations() {
1088 if(hybridParentRelations == null) {
1089 this.hybridParentRelations = new HashSet<HybridRelationship>();
1090 }
1091 return hybridParentRelations;
1092 }
1093
1094 private void setHybridParentRelations(Set<HybridRelationship> hybridParentRelations) {
1095 this.hybridParentRelations = hybridParentRelations;
1096 }
1097
1098
1099 /**
1100 * Returns the set of all {@link HybridRelationship hybrid relationships}
1101 * in which <i>this</i> taxon name is involved as a {@link common.RelationshipBase#getRelatedTo() child}.
1102 *
1103 * @see #getHybridRelationships()
1104 * @see #getParentRelationships()
1105 * @see HybridRelationshipType
1106 */
1107 public Set<HybridRelationship> getHybridChildRelations() {
1108 if(hybridChildRelations == null) {
1109 this.hybridChildRelations = new HashSet<HybridRelationship>();
1110 }
1111 return hybridChildRelations;
1112 }
1113
1114 private void setHybridChildRelations(Set<HybridRelationship> hybridChildRelations) {
1115 this.hybridChildRelations = hybridChildRelations;
1116 }
1117
1118 /**
1119 * Returns the hybrid child relationships ordered by relationship type, or if equal
1120 * by title cache of the related names.
1121 * @see #getHybridParentRelations()
1122 */
1123 @Transient
1124 public List<HybridRelationship> getOrderedChildRelationships(){
1125 List<HybridRelationship> result = new ArrayList<HybridRelationship>();
1126 result.addAll(this.hybridChildRelations);
1127 Collections.sort(result);
1128 Collections.reverse(result);
1129 return result;
1130
1131 }
1132
1133
1134 /**
1135 * Adds the given {@link HybridRelationship hybrid relationship} to the set
1136 * of {@link #getHybridRelationships() hybrid relationships} of both non-viral names
1137 * involved in this hybrid relationship. One of both non-viral names
1138 * must be <i>this</i> non-viral name otherwise no addition will be carried
1139 * out. The {@link eu.etaxonomy.cdm.model.common.RelationshipBase#getRelatedTo() child
1140 * non viral taxon name} must be a hybrid, which means that one of its four hybrid flags must be set.
1141 *
1142 * @param relationship the hybrid relationship to be added
1143 * @see #isHybridFormula()
1144 * @see #isMonomHybrid()
1145 * @see #isBinomHybrid()
1146 * @see #isTrinomHybrid()
1147 * @see #getHybridRelationships()
1148 * @see #getParentRelationships()
1149 * @see #getChildRelationships()
1150 * @see #addRelationship(RelationshipBase)
1151 * @throws IllegalArgumentException
1152 */
1153 protected void addHybridRelationship(HybridRelationship rel) {
1154 if (rel!=null && rel.getHybridName().equals(this)){
1155 this.hybridChildRelations.add(rel);
1156 }else if(rel!=null && rel.getParentName().equals(this)){
1157 this.hybridParentRelations.add(rel);
1158 }else{
1159 throw new IllegalArgumentException("Hybrid relationship is either null or the relationship does not reference this name");
1160 }
1161 }
1162
1163
1164
1165 /**
1166 * Does the same as the addHybridRelationship method if the given
1167 * {@link common.RelationshipBase relation} is also a {@link HybridRelationship hybrid relationship}.
1168 * Otherwise this method does the same as the overwritten {@link TaxonNameBase#addRelationship(RelationshipBase) addRelationship}
1169 * method from TaxonNameBase.
1170 *
1171 * @param relation the relationship to be added to some of <i>this</i> taxon name's relationships sets
1172 * @see #addHybridRelationship(HybridRelationship)
1173 * @see TaxonNameBase#addRelationship(RelationshipBase)
1174 * @see TaxonNameBase#addNameRelationship(NameRelationship)
1175 * @deprecated to be used by RelationshipBase only
1176 */
1177 @Override
1178 @Deprecated //to be used by RelationshipBase only
1179 public void addRelationship(RelationshipBase relation) {
1180 if (relation instanceof HybridRelationship){
1181 addHybridRelationship((HybridRelationship)relation);
1182 }else {
1183 super.addRelationship(relation);
1184 }
1185 }
1186
1187 /**
1188 * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
1189 * to <i>this</i> botanical name. A HybridRelationship may be of type
1190 * "is first/second parent" or "is male/female parent". By invoking this
1191 * method <i>this</i> botanical name becomes a hybrid child of the parent
1192 * botanical name.
1193 *
1194 * @param parentName the botanical name of the parent for this new hybrid name relationship
1195 * @param type the type of this new name relationship
1196 * @param ruleConsidered the string which specifies the rule on which this name relationship is based
1197 * @return
1198 * @see #addHybridChild(BotanicalName, HybridRelationshipType,String )
1199 * @see #getRelationsToThisName()
1200 * @see #getNameRelations()
1201 * @see #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1202 * @see #addNameRelationship(NameRelationship)
1203 */
1204 public HybridRelationship addHybridParent(NonViralName parentName, HybridRelationshipType type, String ruleConsidered){
1205 return new HybridRelationship(this, parentName, type, ruleConsidered);
1206 }
1207
1208 /**
1209 * Creates a new {@link HybridRelationship#HybridRelationship(BotanicalName, BotanicalName, HybridRelationshipType, String) hybrid relationship}
1210 * to <i>this</i> botanical name. A HybridRelationship may be of type
1211 * "is first/second parent" or "is male/female parent". By invoking this
1212 * method <i>this</i> botanical name becomes a parent of the hybrid child
1213 * botanical name.
1214 *
1215 * @param childName the botanical name of the child for this new hybrid name relationship
1216 * @param type the type of this new name relationship
1217 * @param ruleConsidered the string which specifies the rule on which this name relationship is based
1218 * @return
1219 * @see #addHybridParent(BotanicalName, HybridRelationshipType,String )
1220 * @see #getRelationsToThisName()
1221 * @see #getNameRelations()
1222 * @see #addRelationshipFromName(TaxonNameBase, NameRelationshipType, String)
1223 * @see #addNameRelationship(NameRelationship)
1224 */
1225 public HybridRelationship addHybridChild(NonViralName childName, HybridRelationshipType type, String ruleConsidered){
1226 return new HybridRelationship(childName, this, type, ruleConsidered);
1227 }
1228
1229
1230 /**
1231 * Removes one {@link HybridRelationship hybrid relationship} from the set of
1232 * {@link #getHybridRelationships() hybrid relationships} in which <i>this</i> botanical taxon name
1233 * is involved. The hybrid relationship will also be removed from the set
1234 * belonging to the second botanical taxon name involved.
1235 *
1236 * @param relationship the hybrid relationship which should be deleted from the corresponding sets
1237 * @see #getHybridRelationships()
1238 */
1239 public void removeHybridRelationship(HybridRelationship hybridRelation) {
1240 if (hybridRelation == null) {
1241 return;
1242 }
1243
1244 NonViralName<?> parent = hybridRelation.getParentName();
1245 NonViralName<?> child = hybridRelation.getHybridName();
1246
1247 hybridRelation.setHybridName(null);
1248 hybridRelation.setParentName(null);
1249
1250 if (parent != null) {
1251 parent.removeHybridRelationship(hybridRelation);
1252 }
1253
1254 if (child != null) {
1255 child.removeHybridRelationship(hybridRelation);
1256 }
1257
1258 this.hybridChildRelations.remove(hybridRelation);
1259 this.hybridParentRelations.remove(hybridRelation);
1260 }
1261
1262
1263 public void removeHybridChild(NonViralName child) {
1264 Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
1265 hybridRelationships.addAll(this.getHybridChildRelations());
1266 hybridRelationships.addAll(this.getHybridParentRelations());
1267 for(HybridRelationship hybridRelationship : hybridRelationships) {
1268 // remove name relationship from this side
1269 if (hybridRelationship.getParentName().equals(this) && hybridRelationship.getHybridName().equals(child)) {
1270 this.removeHybridRelationship(hybridRelationship);
1271 }
1272 }
1273 }
1274
1275 public void removeHybridParent(NonViralName parent) {
1276 Set<HybridRelationship> hybridRelationships = new HashSet<HybridRelationship>();
1277 hybridRelationships.addAll(this.getHybridChildRelations());
1278 hybridRelationships.addAll(this.getHybridParentRelations());
1279 for(HybridRelationship hybridRelationship : hybridRelationships) {
1280 // remove name relationship from this side
1281 if (hybridRelationship.getParentName().equals(parent) && hybridRelationship.getHybridName().equals(this)) {
1282 this.removeHybridRelationship(hybridRelationship);
1283 }
1284 }
1285 }
1286
1287 /**
1288 * Needs to be implemented by those classes that handle autonyms (e.g. botanical names).
1289 **/
1290 @Transient
1291 public boolean isAutonym(){
1292 return false;
1293 }
1294
1295
1296 // /**
1297 // * Returns the boolean value indicating whether <i>this</i> names rank is Rank "unranked"
1298 // * (uuid = 'a965befb-70a9-4747-a18f-624456c65223') but most likely it is an infrageneric rank
1299 // * due to existing atomized data for the genus epithet and the infrageneric epithet but missing
1300 // * specific epithet.
1301 // * Returns false if <i>this</i> names rank is null.
1302 // *
1303 // * @see #isSupraGeneric()
1304 // * @see #isGenus()
1305 // * @see #isSpeciesAggregate()
1306 // * @see #isSpecies()
1307 // * @see #isInfraSpecific()
1308 // */
1309 // @Transient
1310 // public boolean isInfragenericUnranked() {
1311 // Rank rank = this.getRank();
1312 // if (rank == null || ! rank.equals(Rank.UNRANKED())){
1313 // return false;
1314 // }
1315 // if (StringUtils.isBlank(this.getSpecificEpithet()) && StringUtils.isBlank(this.getInfraSpecificEpithet()) ){
1316 // return true;
1317 // }else{
1318 // return false;
1319 // }
1320 // }
1321
1322
1323 /**
1324 * Tests if the given name has any authors.
1325 * @return false if no author ((ex)combination or (ex)basionym) exists, true otherwise
1326 */
1327 public boolean hasAuthors() {
1328 return (this.getCombinationAuthorTeam() != null ||
1329 this.getExCombinationAuthorTeam() != null ||
1330 this.getBasionymAuthorTeam() != null ||
1331 this.getExBasionymAuthorTeam() != null);
1332 }
1333
1334 /**
1335 * Shortcut. Returns the combination authors title cache. Returns null if no combination author exists.
1336 * @return
1337 */
1338 public String computeCombinationAuthorNomenclaturalTitle() {
1339 return computeNomenclaturalTitle(this.getCombinationAuthorTeam());
1340 }
1341
1342 /**
1343 * Shortcut. Returns the basionym authors title cache. Returns null if no basionym author exists.
1344 * @return
1345 */
1346 public String computeBasionymAuthorNomenclaturalTitle() {
1347 return computeNomenclaturalTitle(this.getBasionymAuthorTeam());
1348 }
1349
1350
1351 /**
1352 * Shortcut. Returns the ex-combination authors title cache. Returns null if no ex-combination author exists.
1353 * @return
1354 */
1355 public String computeExCombinationAuthorNomenclaturalTitle() {
1356 return computeNomenclaturalTitle(this.getExCombinationAuthorTeam());
1357 }
1358
1359 /**
1360 * Shortcut. Returns the ex-basionym authors title cache. Returns null if no exbasionym author exists.
1361 * @return
1362 */
1363 public String computeExBasionymAuthorNomenclaturalTitle() {
1364 return computeNomenclaturalTitle(this.getExBasionymAuthorTeam());
1365 }
1366
1367 private String computeNomenclaturalTitle(INomenclaturalAuthor author){
1368 if (author == null){
1369 return null;
1370 }else{
1371 return author.getNomenclaturalTitle();
1372 }
1373 }
1374
1375 //*********************** CLONE ********************************************************/
1376
1377 /**
1378 * Clones <i>this</i> non-viral name. This is a shortcut that enables to create
1379 * a new instance that differs only slightly from <i>this</i> non-viral name by
1380 * modifying only some of the attributes.
1381 *
1382 * @see eu.etaxonomy.cdm.model.name.TaxonNameBase#clone()
1383 * @see java.lang.Object#clone()
1384 */
1385 @Override
1386 public Object clone() {
1387 NonViralName<?> result = (NonViralName<?>)super.clone();
1388
1389 //HybridChildRelations
1390 result.hybridChildRelations = new HashSet<HybridRelationship>();
1391 for (HybridRelationship hybridRelationship : getHybridChildRelations()){
1392 HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
1393 newChildRelationship.setRelatedTo(result);
1394 result.hybridChildRelations.add(newChildRelationship);
1395 }
1396
1397 //HybridParentRelations
1398 result.hybridParentRelations = new HashSet<HybridRelationship>();
1399 for (HybridRelationship hybridRelationship : getHybridParentRelations()){
1400 HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
1401 newParentRelationship.setRelatedFrom(result);
1402 result.hybridParentRelations.add(newParentRelationship);
1403 }
1404
1405 //empty caches
1406 if (! protectedNameCache){
1407 result.nameCache = null;
1408 }
1409
1410 //empty caches
1411 if (! protectedAuthorshipCache){
1412 result.authorshipCache = null;
1413 }
1414
1415 //no changes to: basionamyAuthorTeam, combinationAuthorTeam, exBasionymAuthorTeam, exCombinationAuthorTeam
1416 //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
1417 //protectedAuthorshipCache, protectedNameCache
1418 //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
1419 return result;
1420 }
1421 }