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