Project

General

Profile

Download (25 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.common;
11

    
12

    
13
import java.beans.PropertyChangeEvent;
14
import java.beans.PropertyChangeListener;
15
import java.util.ArrayList;
16
import java.util.HashSet;
17
import java.util.List;
18
import java.util.Set;
19
import java.util.UUID;
20

    
21
import javax.persistence.Column;
22
import javax.persistence.Embedded;
23
import javax.persistence.FetchType;
24
import javax.persistence.MappedSuperclass;
25
import javax.persistence.OneToMany;
26
import javax.persistence.OrderColumn;
27
import javax.persistence.Transient;
28
import javax.validation.constraints.NotNull;
29
import javax.xml.bind.annotation.XmlAccessType;
30
import javax.xml.bind.annotation.XmlAccessorType;
31
import javax.xml.bind.annotation.XmlElement;
32
import javax.xml.bind.annotation.XmlElementWrapper;
33
import javax.xml.bind.annotation.XmlTransient;
34
import javax.xml.bind.annotation.XmlType;
35
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
36

    
37
import org.apache.commons.lang.StringUtils;
38
import org.apache.log4j.Logger;
39
import org.hibernate.annotations.Cascade;
40
import org.hibernate.annotations.CascadeType;
41
import org.hibernate.envers.Audited;
42
import org.hibernate.search.annotations.Analyze;
43
import org.hibernate.search.annotations.Field;
44
import org.hibernate.search.annotations.FieldBridge;
45
import org.hibernate.search.annotations.Fields;
46
import org.hibernate.search.annotations.Index;
47
import org.hibernate.search.annotations.SortableField;
48
import org.hibernate.search.annotations.Store;
49
import org.hibernate.validator.constraints.NotEmpty;
50

    
51
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
52
import eu.etaxonomy.cdm.hibernate.search.StripHtmlBridge;
53
import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
54
import eu.etaxonomy.cdm.jaxb.LSIDAdapter;
55
import eu.etaxonomy.cdm.model.media.Rights;
56
import eu.etaxonomy.cdm.model.name.BotanicalName;
57
import eu.etaxonomy.cdm.model.name.NonViralName;
58
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
59
import eu.etaxonomy.cdm.model.reference.Reference;
60
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
61
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
62
import eu.etaxonomy.cdm.strategy.match.Match;
63
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
64
import eu.etaxonomy.cdm.strategy.match.MatchMode;
65
import eu.etaxonomy.cdm.strategy.merge.Merge;
66
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
67
import eu.etaxonomy.cdm.validation.Level2;
68

    
69
/**
70
 * Superclass for the primary CDM classes that can be referenced from outside via LSIDs and contain a simple generated title string as a label for human reading.
71
 * All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
72
 * Any number of right statements can be attached as well as multiple {@link OriginalSourceBase} objects.
73
 * Original sources carry a reference to the source, an ID within that source and the original title/label of this object as it was used in that source (originalNameString).
74
 * A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
75
 * The originalSource representing that taxon as it was found in IPNI would contain IPNI as the reference, the IPNI id of the taxon and the name of the taxon exactly as it was used in IPNI.
76
 *
77
 * @author m.doering
78
 * @created 08-Nov-2007 13:06:27
79
 */
80
@XmlAccessorType(XmlAccessType.FIELD)
81
@XmlType(name = "IdentifiableEntity", propOrder = {
82
    "lsid",
83
    "titleCache",
84
    "protectedTitleCache",
85
    "credits",
86
    "extensions",
87
    "identifiers",
88
    "rights",
89
    "sources"
90
})
91
@Audited
92
@MappedSuperclass
93
public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrategy> extends AnnotatableEntity
94
        implements IIdentifiableEntity /*, ISourceable<IdentifiableSource> */ {
95
    private static final long serialVersionUID = 7912083412108359559L;
96

    
97
    private static final Logger logger = Logger.getLogger(IdentifiableEntity.class);
98

    
99
    @XmlTransient
100
    public static final boolean PROTECTED = true;
101
    @XmlTransient
102
    public static final boolean NOT_PROTECTED = false;
103

    
104
    @XmlElement(name = "LSID", type = String.class)
105
    @XmlJavaTypeAdapter(LSIDAdapter.class)
106
    @Embedded
107
    private LSID lsid;
108

    
109
    @XmlElement(name = "TitleCache", required = true)
110
    @XmlJavaTypeAdapter(FormattedTextAdapter.class)
111
    @Column(name="titleCache", length=800) //see #1592
112
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
113
    @NotEmpty(groups = Level2.class) // implictly NotNull
114
    @Fields({
115
        @Field(store=Store.YES),
116
        //  If the field is only needed for sorting and nothing else, you may configure it as
117
        //  un-indexed and un-stored, thus avoid unnecessary index growth.
118
        @Field(name = "titleCache__sort", analyze = Analyze.NO, store=Store.NO, index = Index.NO)
119
    })
120
    @SortableField(forField = "titleCache__sort")
121
    @FieldBridge(impl=StripHtmlBridge.class)
122
    protected String titleCache;
123

    
124
    //if true titleCache will not be automatically generated/updated
125
    @XmlElement(name = "ProtectedTitleCache")
126
    protected boolean protectedTitleCache;
127

    
128
    @XmlElementWrapper(name = "Rights", nillable = true)
129
    @XmlElement(name = "Rights")
130
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
131
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
132
    //TODO
133
    @Merge(MergeMode.ADD_CLONE)
134
    @NotNull
135
    private Set<Rights> rights = new HashSet<Rights>();
136

    
137
    @XmlElementWrapper(name = "Credits", nillable = true)
138
    @XmlElement(name = "Credit")
139
    @OrderColumn(name="sortIndex")
140
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
141
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
142
    //TODO
143
    @Merge(MergeMode.ADD_CLONE)
144
    @NotNull
145
    private List<Credit> credits = new ArrayList<Credit>();
146

    
147
    @XmlElementWrapper(name = "Extensions", nillable = true)
148
    @XmlElement(name = "Extension")
149
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
150
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
151
    @Merge(MergeMode.ADD_CLONE)
152
    @NotNull
153
    private Set<Extension> extensions = new HashSet<Extension>();
154

    
155
    @XmlElementWrapper(name = "Identifiers", nillable = true)
156
    @XmlElement(name = "Identifier")
157
    @OrderColumn(name="sortIndex")
158
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
159
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
160
    @Merge(MergeMode.ADD_CLONE)
161
    @NotNull
162
    private List<Identifier> identifiers = new ArrayList<Identifier>();
163

    
164
    @XmlElementWrapper(name = "Sources", nillable = true)
165
    @XmlElement(name = "IdentifiableSource")
166
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
167
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
168
    @Merge(MergeMode.ADD_CLONE)
169
    @NotNull
170
    private Set<IdentifiableSource> sources = new HashSet<IdentifiableSource>();
171

    
172
    @XmlTransient
173
    @Transient
174
    protected S cacheStrategy;
175

    
176
    protected IdentifiableEntity(){
177
        initListener();
178
    }
179

    
180
    @Override
181
    public void initListener(){
182
        PropertyChangeListener listener = new PropertyChangeListener() {
183
            @Override
184
            public void propertyChange(PropertyChangeEvent ev) {
185
                if (! "titleCache".equals(ev.getPropertyName()) && !"cacheStrategy".equals(ev.getPropertyName()) && ! isProtectedTitleCache()){
186
                    titleCache = null;
187
                }
188
            }
189
        };
190
        addPropertyChangeListener(listener);
191
    }
192

    
193
    /**
194
     * By default, we expect most cdm objects to be abstract things
195
     * i.e. unable to return a data representation.
196
     *
197
     * Specific subclasses (e.g. Sequence) can override if necessary.
198
     */
199
    @Override
200
    public byte[] getData() {
201
        return null;
202
    }
203

    
204
//******************************** CACHE *****************************************************/
205

    
206
    // @Transient  - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
207
    @Override
208
    public String getTitleCache(){
209
        if (protectedTitleCache){
210
            return this.titleCache;
211
        }
212
        // is title dirty, i.e. equal NULL?
213
        if (titleCache == null){
214
            this.titleCache = generateTitle();
215
            this.titleCache = getTruncatedCache(this.titleCache) ;
216
        }
217
        //removed due to #5849
218
//        if(StringUtils.isBlank(titleCache)){
219
//            titleCache = this.toString();
220
//        }
221
        return titleCache;
222
    }
223

    
224
    @Deprecated
225
    @Override
226
    public void setTitleCache(String titleCache){
227
    	//TODO shouldn't we call setTitleCache(String, boolean),but is this conformant with Java Bean Specification?
228
    	this.titleCache = getTruncatedCache(titleCache);
229
    }
230

    
231
    @Override
232
    public void setTitleCache(String titleCache, boolean protectCache){
233
        titleCache = getTruncatedCache(titleCache);
234
        this.titleCache = titleCache;
235
        this.protectedTitleCache = protectCache;
236
    }
237

    
238
    /**
239
     * @param cache
240
     * @return
241
     */
242
    @Transient
243
    protected String getTruncatedCache(String cache) {
244
        int maxLength = 800;
245
    	if (cache != null && cache.length() > maxLength){
246
            logger.warn("Truncation of cache: " + this.toString() + "/" + cache);
247
            cache = cache.substring(0, maxLength - 4) + "...";   //TODO do we need -4 or is -3 enough
248
        }
249
        return cache;
250
    }
251

    
252

    
253
    @Override
254
    public boolean isProtectedTitleCache() {
255
        return protectedTitleCache;
256
    }
257

    
258
    @Override
259
    public void setProtectedTitleCache(boolean protectedTitleCache) {
260
        this.protectedTitleCache = protectedTitleCache;
261
    }
262

    
263
    /**
264
     *
265
     * @return true, if the current state of the titleCache (without generating it new)
266
     * is <code>null</code> or the empty string. This is primarily meant for internal use.
267
     */
268
    public boolean hasEmptyTitleCache(){
269
        return this.titleCache == null || "".equals(this.titleCache);
270
    }
271

    
272
//**************************************************************************************
273

    
274
    @Override
275
    public LSID getLsid(){
276
        return this.lsid;
277
    }
278
    @Override
279
    public void setLsid(LSID lsid){
280
        this.lsid = lsid;
281
    }
282
    @Override
283
    public Set<Rights> getRights() {
284
        if(rights == null) {
285
            this.rights = new HashSet<Rights>();
286
        }
287
        return this.rights;
288
    }
289

    
290
    @Override
291
    public void addRights(Rights right){
292
        getRights().add(right);
293
    }
294
    @Override
295
    public void removeRights(Rights right){
296
        getRights().remove(right);
297
    }
298

    
299

    
300
    @Override
301
    public List<Credit> getCredits() {
302
        if(credits == null) {
303
            this.credits = new ArrayList<Credit>();
304
        }
305
        return this.credits;
306
    }
307

    
308
    @Override
309
    public Credit getCredits(Integer index){
310
        return getCredits().get(index);
311
    }
312

    
313
    @Override
314
    public void addCredit(Credit credit){
315
        getCredits().add(credit);
316
    }
317

    
318

    
319
    @Override
320
    public void addCredit(Credit credit, int index){
321
        getCredits().add(index, credit);
322
    }
323

    
324
    @Override
325
    public void removeCredit(Credit credit){
326
        getCredits().remove(credit);
327
    }
328

    
329
    @Override
330
    public void removeCredit(int index){
331
        getCredits().remove(index);
332
    }
333

    
334
    @Override
335
    public boolean replaceCredit(Credit newObject, Credit oldObject){
336
        return replaceInList(this.credits, newObject, oldObject);
337
    }
338

    
339

    
340
    @Override
341
    public List<Identifier> getIdentifiers(){
342
        if(this.identifiers == null) {
343
            this.identifiers = new ArrayList<Identifier>();
344
        }
345
        return this.identifiers;
346
    }
347
    /**
348
     * @param type
349
     * @return a set of identifier value strings
350
     */
351
    public Set<String> getIdentifiers(DefinedTerm type){
352
       return getIdentifiers(type.getUuid());
353
    }
354
    /**
355
     * @param identifierTypeUuid
356
     * @return a set of identifier value strings
357
     */
358
    public Set<String> getIdentifiers(UUID identifierTypeUuid){
359
        Set<String> result = new HashSet<String>();
360
        for (Identifier<?> identifier : getIdentifiers()){
361
            if (identifier.getType().getUuid().equals(identifierTypeUuid)){
362
                result.add(identifier.getIdentifier());
363
            }
364
        }
365
        return result;
366
    }
367

    
368
    @Override
369
    public Identifier addIdentifier(String identifier, DefinedTerm identifierType){
370
    	Identifier<?> result = Identifier.NewInstance(identifier, identifierType);
371
    	addIdentifier(result);
372
    	return result;
373
    }
374

    
375
     @Override
376
    public void addIdentifier(int index, Identifier identifier){
377
        if (identifier != null){
378
        	//deduplication
379
        	int oldIndex = getIdentifiers().indexOf(identifier);
380
        	if(oldIndex > -1){
381
        		getIdentifiers().remove(identifier);
382
        		if (oldIndex < index){
383
        			index--;
384
        		}
385
        	}
386
        	getIdentifiers().add(index, identifier);
387
        }
388
    }
389

    
390
    @Override
391
    public void addIdentifier(Identifier identifier){
392
        addIdentifier(getIdentifiers().size(), identifier);
393
    }
394

    
395
    @Override
396
    public void removeIdentifier(Identifier identifier){
397
        if (identifier != null){
398
            getIdentifiers().remove(identifier);
399
        }
400
    }
401
    @Override
402
    public void removeIdentifier(int index){
403
    	getIdentifiers().remove(index);
404
    }
405

    
406
    @Override
407
    public boolean replaceIdentifier(Identifier newObject, Identifier oldObject){
408
        return replaceInList(this.identifiers, newObject, oldObject);
409
    }
410

    
411

    
412
    @Override
413
    public Set<Extension> getExtensions(){
414
        if(extensions == null) {
415
            this.extensions = new HashSet<Extension>();
416
        }
417
        return this.extensions;
418
    }
419
    /**
420
     * @param type
421
     * @return a Set of extension value strings
422
     */
423
    public Set<String> getExtensions(ExtensionType type){
424
       return getExtensions(type.getUuid());
425
    }
426
    /**
427
     * @param extensionTypeUuid
428
     * @return a Set of extension value strings
429
     */
430
    public Set<String> getExtensions(UUID extensionTypeUuid){
431
        Set<String> result = new HashSet<String>();
432
        for (Extension extension : getExtensions()){
433
            if (extension.getType().getUuid().equals(extensionTypeUuid)){
434
                result.add(extension.getValue());
435
            }
436
        }
437
        return result;
438
    }
439

    
440
    public void addExtension(String value, ExtensionType extensionType){
441
        Extension.NewInstance(this, value, extensionType);
442
    }
443

    
444
    @Override
445
    public void addExtension(Extension extension){
446
        if (extension != null){
447
            getExtensions().add(extension);
448
        }
449
    }
450
    @Override
451
    public void removeExtension(Extension extension){
452
        if (extension != null){
453
            getExtensions().remove(extension);
454
        }
455
    }
456

    
457

    
458
    @Override
459
    public Set<IdentifiableSource> getSources() {
460
        if(sources == null) {
461
            this.sources = new HashSet<IdentifiableSource>();
462
        }
463
        return this.sources;
464
    }
465

    
466
    @Override
467
    public void addSource(IdentifiableSource source) {
468
        if (source != null){
469
            getSources().add(source);
470
        }
471
    }
472

    
473
    @Override
474
    public void addSources(Set<IdentifiableSource> sources) {
475
        if (sources != null){
476
        	for (IdentifiableSource source: sources){
477
	            getSources().add(source);
478
        	}
479
        }
480
    }
481

    
482
    @Override
483
    public void removeSources() {
484
       this.sources.clear();
485
    }
486

    
487
    @Override
488
    public IdentifiableSource addSource(OriginalSourceType type, String id, String idNamespace, Reference citation, String microCitation) {
489
        if (id == null && idNamespace == null && citation == null && microCitation == null){
490
            return null;
491
        }
492
        IdentifiableSource source = IdentifiableSource.NewInstance(type, id, idNamespace, citation, microCitation);
493
        addSource(source);
494
        return source;
495
    }
496

    
497

    
498
    @Override
499
    public IdentifiableSource addImportSource(String id, String idNamespace, Reference citation, String microCitation) {
500
        if (id == null && idNamespace == null && citation == null && microCitation == null){
501
            return null;
502
        }
503
        IdentifiableSource source = IdentifiableSource.NewInstance(OriginalSourceType.Import, id, idNamespace, citation, microCitation);
504
        addSource(source);
505
        return source;
506
    }
507

    
508

    
509
    @Override
510
    public void removeSource(IdentifiableSource source) {
511
        getSources().remove(source);
512
    }
513

    
514
//******************************** TO STRING *****************************************************/
515

    
516
    @Override
517
    public String toString() {
518
        String result;
519
        if (StringUtils.isBlank(titleCache)){
520
            result = super.toString();
521
        }else{
522
            result = this.titleCache;
523
        }
524
        return result;
525
    }
526

    
527

    
528
    public int compareTo(IdentifiableEntity identifiableEntity) {
529

    
530
         int result = 0;
531

    
532
         if (identifiableEntity == null) {
533
             throw new NullPointerException("Cannot compare to null.");
534
         }
535

    
536
         // First, compare the name cache.
537
         // TODO: Avoid using instanceof operator
538
         // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
539

    
540
         String specifiedNameCache = "";
541
         String thisNameCache = "";
542
         String specifiedTitleCache = "";
543
         String thisTitleCache = "";
544
         String specifiedReferenceTitleCache = "";
545
         String thisReferenceTitleCache = "";
546
         String thisGenusString = "";
547
         String specifiedGenusString = "";
548
         int thisrank_order = 0;
549
         final String HYBRID_SIGN = "\u00D7";
550
         final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
551
         //TODO we can remove all the deproxies here except for the first one
552
         identifiableEntity = HibernateProxyHelper.deproxy(identifiableEntity, IdentifiableEntity.class);
553
         if(identifiableEntity instanceof NonViralName) {
554
             specifiedNameCache = HibernateProxyHelper.deproxy(identifiableEntity, NonViralName.class).getNameCache();
555
             specifiedTitleCache = identifiableEntity.getTitleCache();
556
            if (identifiableEntity instanceof BotanicalName){
557
            	 if (((BotanicalName)identifiableEntity).isAutonym()){
558
            		 boolean isProtected = false;
559
            		 String oldNameCache = ((BotanicalName) identifiableEntity).getNameCache();
560
            		 if ( ((BotanicalName)identifiableEntity).isProtectedNameCache()){
561
            			 isProtected = true;
562
            		 }
563
            		 ((BotanicalName)identifiableEntity).setProtectedNameCache(false);
564
            		 ((BotanicalName)identifiableEntity).setNameCache(null, false);
565
            		 specifiedNameCache = ((BotanicalName) identifiableEntity).getNameCache();
566
            		 ((BotanicalName)identifiableEntity).setNameCache(oldNameCache, isProtected);
567

    
568
            	 }
569
             }
570

    
571
         } else if(identifiableEntity instanceof TaxonBase) {
572
             TaxonBase taxonBase = HibernateProxyHelper.deproxy(identifiableEntity, TaxonBase.class);
573

    
574
             TaxonNameBase<?,?> taxonNameBase = taxonBase.getName();
575

    
576

    
577
             NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
578
             specifiedNameCache = nonViralName.getNameCache();
579
             specifiedTitleCache = taxonNameBase.getTitleCache();
580

    
581
             specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
582
             Reference reference = taxonBase.getSec();
583
             if (reference != null) {
584
                 reference = HibernateProxyHelper.deproxy(reference, Reference.class);
585
                 specifiedReferenceTitleCache = reference.getTitleCache();
586
             }
587
         }
588

    
589
         if(this.isInstanceOf(NonViralName.class)) {
590
             thisNameCache = HibernateProxyHelper.deproxy(this, NonViralName.class).getNameCache();
591
             thisTitleCache = getTitleCache();
592

    
593
             if (this instanceof BotanicalName){
594
            	 if (((BotanicalName)this).isAutonym()){
595
            		 boolean isProtected = false;
596
            		 String oldNameCache = ((BotanicalName) this).getNameCache();
597
            		 if ( ((BotanicalName)this).isProtectedNameCache()){
598
            			 isProtected = true;
599
            		 }
600
            		 ((BotanicalName)this).setProtectedNameCache(false);
601
            		 ((BotanicalName)this).setNameCache(null, false);
602
            		 thisNameCache = ((BotanicalName) this).getNameCache();
603
            		 ((BotanicalName)this).setNameCache(oldNameCache, isProtected);
604
            	 }
605
             }
606
         } else if(this.isInstanceOf(TaxonBase.class)) {
607
             TaxonNameBase<?,?> taxonNameBase= HibernateProxyHelper.deproxy(this, TaxonBase.class).getName();
608
             NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
609
             thisNameCache = nonViralName.getNameCache();
610
             thisTitleCache = taxonNameBase.getTitleCache();
611
             thisReferenceTitleCache = ((TaxonBase)this).getSec().getTitleCache();
612
             thisGenusString = nonViralName.getGenusOrUninomial();
613
         }
614

    
615
         // Compare name cache of taxon names
616

    
617

    
618

    
619
         if (!specifiedNameCache.equals("") && !thisNameCache.equals("")) {
620

    
621
        	 thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
622
        	 thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
623

    
624

    
625
        	 specifiedNameCache = specifiedNameCache.replaceAll(HYBRID_SIGN, "");
626
        	 specifiedNameCache = specifiedNameCache.replaceAll(QUOT_SIGN, "");
627

    
628

    
629
             result = thisNameCache.compareTo(specifiedNameCache);
630
         }
631

    
632
         // Compare title cache of taxon names
633

    
634
         if ((result == 0) && (!specifiedTitleCache.equals("") || !thisTitleCache.equals(""))) {
635
        	 thisTitleCache = thisTitleCache.replaceAll(HYBRID_SIGN, "");
636
        	 thisTitleCache = thisTitleCache.replaceAll(QUOT_SIGN, "");
637

    
638
        	 specifiedTitleCache = specifiedTitleCache.replaceAll(HYBRID_SIGN, "");
639
        	 specifiedTitleCache = specifiedTitleCache.replaceAll(QUOT_SIGN, "");
640
             result = thisTitleCache.compareTo(specifiedTitleCache);
641
         }
642

    
643
         // Compare title cache of taxon references
644

    
645
         if ((result == 0) && (!specifiedReferenceTitleCache.equals("") || !thisReferenceTitleCache.equals(""))) {
646
             result = thisReferenceTitleCache.compareTo(specifiedReferenceTitleCache);
647
         }
648

    
649
         return result;
650
     }
651

    
652
    /**
653
     * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
654
     * several strings corresponding to <i>this</i> identifiable entity
655
     * (in particular taxon name caches and author strings).
656
     *
657
     * @return  the cache strategy used for <i>this</i> identifiable entity
658
     * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
659
     */
660
    public S getCacheStrategy() {
661
        return this.cacheStrategy;
662
    }
663
    /**
664
     * @see 	#getCacheStrategy()
665
     */
666

    
667
    public void setCacheStrategy(S cacheStrategy) {
668
        this.cacheStrategy = cacheStrategy;
669
    }
670

    
671
    @Override
672
    public String generateTitle() {
673
        if (getCacheStrategy() == null){
674
            //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
675
            return this.getClass() + ": " + this.getUuid();
676
        }else{
677
            return getCacheStrategy().getTitleCache(this);
678
        }
679
    }
680

    
681
//****************** CLONE ************************************************/
682

    
683
    @Override
684
    public Object clone() throws CloneNotSupportedException{
685
        IdentifiableEntity<?> result = (IdentifiableEntity<?>)super.clone();
686

    
687
        //Extensions
688
        result.extensions = new HashSet<Extension>();
689
        for (Extension extension : getExtensions() ){
690
            Extension newExtension = (Extension)extension.clone();
691
            result.addExtension(newExtension);
692
        }
693

    
694
        //Identifier
695
        result.identifiers = new ArrayList<Identifier>();
696
        for (Identifier<?> identifier : getIdentifiers() ){
697
        	Identifier<?> newIdentifier = (Identifier<?>)identifier.clone();
698
            result.addIdentifier(newIdentifier);
699
        }
700

    
701
        //OriginalSources
702
        result.sources = new HashSet<IdentifiableSource>();
703
        for (IdentifiableSource source : getSources()){
704
            IdentifiableSource newSource = (IdentifiableSource)source.clone();
705
            result.addSource(newSource);
706
        }
707

    
708
        //Rights
709
        result.rights = new HashSet<Rights>();
710
        for(Rights rights : getRights()) {
711
            Rights newRights = (Rights)rights.clone();
712
            result.addRights(newRights);
713
        }
714

    
715

    
716
        //Credits
717
        result.credits = new ArrayList<Credit>();
718
        for(Credit credit : getCredits()) {
719
            Credit newCredit = (Credit)credit.clone();
720
            result.addCredit(newCredit);
721
        }
722

    
723
        //no changes to: lsid, titleCache, protectedTitleCache
724

    
725
        //empty titleCache
726
        if (! protectedTitleCache){
727
            result.titleCache = null;
728
        }
729

    
730
        result.initListener();
731
        return result;
732
    }
733

    
734

    
735
}
(36-36/73)