Project

General

Profile

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

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

    
12

    
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.Column;
23
import javax.persistence.Entity;
24
import javax.persistence.FetchType;
25
import javax.persistence.JoinColumn;
26
import javax.persistence.ManyToOne;
27
import javax.persistence.OneToMany;
28
import javax.persistence.Transient;
29
import javax.validation.constraints.NotNull;
30
import javax.validation.constraints.Pattern;
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
    "combinationAuthorship",
103
    "exCombinationAuthorship",
104
    "basionymAuthorship",
105
    "exBasionymAuthorship",
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.core.KeywordAnalyzer.class)
134
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.DEFINED,
135
            cacheReplacedProperties={"genusOrUninomial", "infraGenericEpithet", "specificEpithet", "infraSpecificEpithet"} )
136
    @NotEmpty(groups = Level2.class) // implicitly NotNull
137
    @Column(length=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
    @Column(length=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
    @Column(length=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
    @Column(length=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
    @Column(length=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 = "CombinationAuthorship", 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<?> combinationAuthorship;
191

    
192
    @XmlElement(name = "ExCombinationAuthorship", 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<?> exCombinationAuthorship;
202

    
203
    @XmlElement(name = "BasionymAuthorship", 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<?> basionymAuthorship;
213

    
214
    @XmlElement(name = "ExBasionymAuthorship", 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<?> exBasionymAuthorship;
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={"combinationAuthorship", "basionymAuthorship", "exCombinationAuthorship", "exBasionymAuthorship"} )
232
    //TODO Val #3379
233
//    @NotNull
234
    @Column(length=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	combinationAuthorship  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 combinationAuthorship, 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
        setCombinationAuthorship(combinationAuthorship);
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
    public 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<?> getCombinationAuthorship(){
523
        return this.combinationAuthorship;
524
    }
525

    
526
    /**
527
     * @see  #getCombinationAuthorship()
528
     */
529
    public void setCombinationAuthorship(TeamOrPersonBase<?> combinationAuthorship){
530
        this.combinationAuthorship = combinationAuthorship;
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 #getCombinationAuthorship() 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 	#getCombinationAuthorship()
553
     * @see 	eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
554
     * @see 	eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
555
     */
556
    public TeamOrPersonBase<?> getExCombinationAuthorship(){
557
        return this.exCombinationAuthorship;
558
    }
559

    
560
    /**
561
     * @see  #getExCombinationAuthorship()
562
     */
563
    public void setExCombinationAuthorship(TeamOrPersonBase<?> exCombinationAuthorship){
564
        this.exCombinationAuthorship = exCombinationAuthorship;
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 	#getCombinationAuthorship()
575
     * @see 	eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
576
     * @see 	eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
577
     */
578
    public TeamOrPersonBase<?> getBasionymAuthorship(){
579
        return basionymAuthorship;
580
    }
581

    
582
    /**
583
     * @see  #getBasionymAuthorship()
584
     */
585
    public void setBasionymAuthorship(TeamOrPersonBase<?> basionymAuthorship) {
586
        this.basionymAuthorship = basionymAuthorship;
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 #getBasionymAuthorship() 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 	#getBasionymAuthorship()
600
     * @see 	#getExCombinationAuthorship()
601
     * @see 	#getCombinationAuthorship()
602
     * @see 	eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor
603
     * @see 	eu.etaxonomy.cdm.model.agent.TeamOrPersonBase#getNomenclaturalTitle()
604
     */
605
    public TeamOrPersonBase<?> getExBasionymAuthorship(){
606
        return exBasionymAuthorship;
607
    }
608

    
609
    /**
610
     * @see  #getExBasionymAuthorship()
611
     */
612
    public void setExBasionymAuthorship(TeamOrPersonBase<?> exBasionymAuthorship) {
613
        this.exBasionymAuthorship = exBasionymAuthorship;
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.getCombinationAuthorship() != null ||
1336
                this.getExCombinationAuthorship() != null ||
1337
                this.getBasionymAuthorship() != null ||
1338
                this.getExBasionymAuthorship() != 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.getCombinationAuthorship());
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.getBasionymAuthorship());
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.getExCombinationAuthorship());
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.getExBasionymAuthorship());
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

    
1383
    /**
1384
     * Defines the last part of the name.
1385
     * This is for infraspecific taxa, the infraspecific epithet,
1386
     * for species the specific epithet, for infageneric taxa the infrageneric epithet
1387
     * else the genusOrUninomial.
1388
     * However, the result does not depend on the rank (which may be not correctly set
1389
     * in case of dirty data) but returns the first name part which is not blank
1390
     * considering the above order.
1391
     * @return the first not blank name part in reverse order
1392
     */
1393
    public String getLastNamePart() {
1394
        String result =
1395
                StringUtils.isNotBlank(this.getInfraSpecificEpithet())?
1396
                    this.getInfraSpecificEpithet() :
1397
                StringUtils.isNotBlank(this.getSpecificEpithet()) ?
1398
                    this.getSpecificEpithet():
1399
                StringUtils.isNotBlank(this.getInfraGenericEpithet()) ?
1400
                    this.getInfraGenericEpithet():
1401
                this.getGenusOrUninomial();
1402
        return result;
1403
    }
1404

    
1405
//*********************** CLONE ********************************************************/
1406

    
1407
    /**
1408
     * Clones <i>this</i> non-viral name. This is a shortcut that enables to create
1409
     * a new instance that differs only slightly from <i>this</i> non-viral name by
1410
     * modifying only some of the attributes.
1411
     *
1412
     * @see eu.etaxonomy.cdm.model.name.TaxonNameBase#clone()
1413
     * @see java.lang.Object#clone()
1414
     */
1415
    @Override
1416
    public Object clone() {
1417
        NonViralName<?> result = (NonViralName<?>)super.clone();
1418

    
1419
        //HybridChildRelations
1420
        result.hybridChildRelations = new HashSet<HybridRelationship>();
1421
        for (HybridRelationship hybridRelationship : getHybridChildRelations()){
1422
            HybridRelationship newChildRelationship = (HybridRelationship)hybridRelationship.clone();
1423
            newChildRelationship.setRelatedTo(result);
1424
            result.hybridChildRelations.add(newChildRelationship);
1425
        }
1426

    
1427
        //HybridParentRelations
1428
        result.hybridParentRelations = new HashSet<HybridRelationship>();
1429
        for (HybridRelationship hybridRelationship : getHybridParentRelations()){
1430
            HybridRelationship newParentRelationship = (HybridRelationship)hybridRelationship.clone();
1431
            newParentRelationship.setRelatedFrom(result);
1432
            result.hybridParentRelations.add(newParentRelationship);
1433
        }
1434

    
1435
        //empty caches
1436
        if (! protectedNameCache){
1437
            result.nameCache = null;
1438
        }
1439

    
1440
        //empty caches
1441
        if (! protectedAuthorshipCache){
1442
            result.authorshipCache = null;
1443
        }
1444

    
1445
        //no changes to: basionamyAuthorship, combinationAuthorship, exBasionymAuthorship, exCombinationAuthorship
1446
        //genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
1447
        //protectedAuthorshipCache, protectedNameCache
1448
        //binomHybrid, monomHybrid, trinomHybrid, hybridFormula,
1449
        return result;
1450
    }
1451
}
(16-16/28)