Project

General

Profile

Download (25.6 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.taxon;
11

    
12
import java.util.ArrayList;
13
import java.util.HashMap;
14
import java.util.HashSet;
15
import java.util.Iterator;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.Set;
19

    
20
import javax.persistence.Entity;
21
import javax.persistence.FetchType;
22
import javax.persistence.JoinColumn;
23
import javax.persistence.JoinTable;
24
import javax.persistence.ManyToMany;
25
import javax.persistence.ManyToOne;
26
import javax.persistence.MapKeyJoinColumn;
27
import javax.persistence.OneToMany;
28
import javax.persistence.OneToOne;
29
import javax.persistence.Transient;
30
import javax.xml.bind.annotation.XmlAccessType;
31
import javax.xml.bind.annotation.XmlAccessorType;
32
import javax.xml.bind.annotation.XmlElement;
33
import javax.xml.bind.annotation.XmlElementWrapper;
34
import javax.xml.bind.annotation.XmlIDREF;
35
import javax.xml.bind.annotation.XmlRootElement;
36
import javax.xml.bind.annotation.XmlSchemaType;
37
import javax.xml.bind.annotation.XmlType;
38
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
39

    
40
import org.apache.log4j.Logger;
41
import org.hibernate.annotations.Cascade;
42
import org.hibernate.annotations.CascadeType;
43
import org.hibernate.envers.Audited;
44
import org.hibernate.search.annotations.Indexed;
45
import org.hibernate.search.annotations.IndexedEmbedded;
46

    
47
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
48
import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
49
import eu.etaxonomy.cdm.model.common.IReferencedEntity;
50
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
51
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
52
import eu.etaxonomy.cdm.model.common.Language;
53
import eu.etaxonomy.cdm.model.common.LanguageString;
54
import eu.etaxonomy.cdm.model.common.MultilanguageText;
55
import eu.etaxonomy.cdm.model.common.TimePeriod;
56
import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
57
import eu.etaxonomy.cdm.model.location.NamedArea;
58
import eu.etaxonomy.cdm.model.reference.Reference;
59
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
60

    
61
/**
62
 * @author a.mueller
63
 * @since 31.03.2009
64
 */
65
@XmlAccessorType(XmlAccessType.FIELD)
66
@XmlType(name = "Classification", propOrder = {
67
    "name",
68
    "description",
69
    "rootNode",
70
    "reference",
71
    "microReference",
72
    "source",
73
    "timeperiod",
74
    "geoScopes"
75
})
76
@XmlRootElement(name = "Classification")
77
@Entity
78
@Audited
79
@Indexed(index = "eu.etaxonomy.cdm.model.taxon.Classification")
80
public class Classification
81
            extends IdentifiableEntity<IIdentifiableEntityCacheStrategy<Classification>>
82
            implements IReferencedEntity, ITaxonTreeNode{
83

    
84
    private static final long serialVersionUID = -753804821474209635L;
85
    private static final Logger logger = Logger.getLogger(Classification.class);
86

    
87
    @XmlElement(name = "Name")
88
    @OneToOne(fetch = FetchType.LAZY)
89
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
90
    @JoinColumn(name = "name_id", referencedColumnName = "id")
91
    @IndexedEmbedded
92
    private LanguageString name;
93

    
94

    
95
    @XmlElement(name = "rootNode")
96
    @XmlIDREF
97
    @XmlSchemaType(name = "IDREF")
98
    @OneToOne(fetch=FetchType.LAZY)
99
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
100
    private TaxonNode rootNode;
101

    
102
    @XmlElement(name = "reference")
103
    @XmlIDREF
104
    @XmlSchemaType(name = "IDREF")
105
    @ManyToOne(fetch = FetchType.LAZY)
106
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
107
    private Reference reference;
108

    
109
    @XmlElement(name = "microReference")
110
    private String microReference;
111

    
112
	@XmlElement(name = "TimePeriod")
113
    private TimePeriod timeperiod = TimePeriod.NewInstance();
114

    
115
    @XmlElementWrapper( name = "GeoScopes")
116
    @XmlElement( name = "GeoScope")
117
    @XmlIDREF
118
    @XmlSchemaType(name="IDREF")
119
    @ManyToMany(fetch = FetchType.LAZY)
120
    @JoinTable(name="Classification_GeoScope")
121
//    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})  remove cascade #5755
122
    private Set<NamedArea> geoScopes = new HashSet<>();
123

    
124
	@XmlElement(name = "Description")
125
	@XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
126
	@OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
127
	@MapKeyJoinColumn(name="description_mapkey_id")
128
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE })
129
	@JoinTable(name = "Classification_Description")
130
//	@Field(name="text", store=Store.YES)
131
//    @FieldBridge(impl=MultilanguageTextFieldBridge.class)
132
    private Map<Language,LanguageString> description = new HashMap<>();
133

    
134
    //the source for this single classification
135
    @XmlElement(name = "source")
136
    @XmlIDREF
137
    @XmlSchemaType(name = "IDREF")
138
    @OneToOne(fetch = FetchType.LAZY, orphanRemoval=true)
139
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
140
    private IdentifiableSource source;
141

    
142

    
143
//	/**
144
//	 * If this classification is an alternative classification for a subclassification in
145
//	 * an other classification(parent view),
146
//	 * the alternativeViewRoot is the connection node from this classification to the parent classification.
147
//	 * It replaces another node in the parent view.
148
//	 */
149
//	private AlternativeViewRoot alternativeViewRoot;
150

    
151
// ********************** FACTORY METHODS *********************************************/
152

    
153
    public static Classification NewInstance(String name){
154
        return NewInstance(name, null, Language.DEFAULT());
155
    }
156

    
157
    public static Classification NewInstance(String name, Language language){
158
        return NewInstance(name, null, language);
159
    }
160

    
161
    public static Classification NewInstance(String name, Reference reference){
162
        return NewInstance(name, reference, Language.DEFAULT());
163
    }
164

    
165
    public static Classification NewInstance(String name, Reference reference, Language language){
166
        return new Classification(name, reference, language);
167
    }
168

    
169
// **************************** CONSTRUCTOR *********************************/
170

    
171
    //for hibernate use only, protected required by Javassist
172
    protected Classification(){super();}
173

    
174
    protected Classification(String name, Reference reference, Language language){
175
        this();
176
        LanguageString langName = LanguageString.NewInstance(name, language);
177
        setName(langName);
178
        setReference(reference);
179
        this.rootNode = new TaxonNode();
180
        rootNode.setClassification(this);
181
    }
182

    
183
//********************** xxxxxxxxxxxxx ******************************************/
184
    /**
185
     * Returns the topmost {@link TaxonNode taxon node} (root node) of <i>this</i>
186
     * classification. The root node does not have any parent and no taxon. Since taxon nodes
187
     * recursively point to their child nodes the complete classification is
188
     * defined by its root node.
189
     */
190
    public TaxonNode getRootNode(){
191
        return rootNode;
192
    }
193

    
194
    public void setRootNode(TaxonNode root){
195
        this.rootNode = root;
196
    }
197

    
198
    @Override
199
    public TaxonNode addChildNode(TaxonNode childNode, Reference citation, String microCitation) {
200
        return addChildNode(childNode, rootNode.getCountChildren(), citation, microCitation);
201
    }
202

    
203
    @Override
204
    public TaxonNode addChildNode(TaxonNode childNode, int index, Reference citation, String microCitation) {
205

    
206
        childNode.setParentTreeNode(this.rootNode, index);
207

    
208
        childNode.setReference(citation);
209
        childNode.setMicroReference(microCitation);
210
//		childNode.setSynonymToBeUsed(synonymToBeUsed);
211

    
212
        return childNode;
213
    }
214

    
215
    @Override
216
    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
217
        return addChildTaxon(taxon, rootNode.getCountChildren(), citation, microCitation);
218
    }
219

    
220
    @Override
221
    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
222
        return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
223
    }
224

    
225
    @Override
226
    public TaxonNode addChildTaxon(Taxon taxon, DescriptionElementSource source) {
227
        return addChildTaxon(taxon, rootNode.getCountChildren(), source);
228
    }
229

    
230
    @Override
231
    public TaxonNode addChildTaxon(Taxon taxon, int index, DescriptionElementSource source) {
232
        return addChildNode(new TaxonNode(taxon), index, source);
233
    }
234

    
235
    @Override
236
    public TaxonNode addChildNode(TaxonNode childNode, int index, DescriptionElementSource source) {
237
        childNode.setParentTreeNode(this.rootNode, index);
238
        childNode.setSource(source);
239

    
240
        return childNode;
241
    }
242

    
243

    
244
    @Override
245
    public boolean deleteChildNode(TaxonNode node) {
246
        boolean result = removeChildNode(node);
247

    
248
        if (node.hasTaxon()){
249
            node.getTaxon().removeTaxonNode(node);
250
            node.setTaxon(null);
251
        }
252

    
253
        ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
254
        for (TaxonNode childNode : childNodes){
255
            if (childNode != null){
256
                node.deleteChildNode(childNode);
257
            }
258
        }
259
        return result;
260
    }
261

    
262
    public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
263
        boolean result = removeChildNode(node);
264
        if (node.hasTaxon()){
265
            node.getTaxon().removeTaxonNode(node);
266
            node.setTaxon(null);
267
        }
268

    
269
        if (deleteChildren){
270
            ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
271
            for (TaxonNode childNode : childNodes){
272
                node.deleteChildNode(childNode);
273
            }
274
        }
275
        return result;
276
    }
277

    
278
    protected boolean removeChildNode(TaxonNode node){
279
        boolean result = false;
280
        rootNode = HibernateProxyHelper.deproxy(rootNode, TaxonNode.class);
281
        if(!rootNode.getChildNodes().contains(node)){
282
            throw new IllegalArgumentException("TaxonNode is a not a root node of this classification");
283
        }
284
//        rootNode = HibernateProxyHelper.deproxy(rootNode, TaxonNode.class);
285
        result = rootNode.removeChildNode(node);
286

    
287
        node.setParent(null);
288
        node.setClassification(null);
289

    
290
        return result;
291
    }
292

    
293
    public boolean removeRootNode(){
294
        boolean result = false;
295

    
296
        if (rootNode != null){
297
            this.rootNode.setChildNodes(new ArrayList<TaxonNode>());
298
            this.rootNode.setParent(null);
299
            rootNode = null;
300
            result = true;
301
        }
302
        return result;
303
    }
304

    
305
    /**
306
     * Appends an existing topmost node to another node of this tree. The existing topmost node becomes
307
     * an ordinary node.
308
     * @param topmostNode
309
     * @param otherNode
310
     * @param ref
311
     * @param microReference
312
     * @throws IllegalArgumentException
313
     */
314
    public void makeTopmostNodeChildOfOtherNode(TaxonNode topmostNode, TaxonNode otherNode, Reference ref, String microReference)
315
                throws IllegalArgumentException{
316
        if (otherNode == null){
317
            throw new NullPointerException("other node must not be null");
318
        }
319
        if (! getChildNodes().contains(topmostNode)){
320
            throw new IllegalArgumentException("root node to be added as child must already be root node within this tree");
321
        }
322
        if (otherNode.getClassification() == null || ! otherNode.getClassification().equals(this)){
323
            throw new IllegalArgumentException("other node must already be node within this tree");
324
        }
325
        if (otherNode.equals(topmostNode)){
326
            throw new IllegalArgumentException("root node and other node must not be the same");
327
        }
328
        otherNode.addChildNode(topmostNode, ref, microReference);
329
    }
330

    
331
    /**
332
     * Checks if the given taxon is part of <b>this</b> tree.
333
     * @param taxon
334
     * @return
335
     */
336
    public boolean isTaxonInTree(Taxon taxon){
337
        taxon = HibernateProxyHelper.deproxy(taxon, Taxon.class);
338
        return (getNode(taxon) != null);
339
    }
340

    
341
    /**
342
     * Checks if the given taxon is part of <b>this</b> tree. If so the according TaxonNode is returned.
343
     * Otherwise <code>null</code> is returned.
344
     * @param taxon
345
     * @return
346
     */
347
    public TaxonNode getNode(Taxon taxon){
348
        if (taxon == null){
349
            return null;
350
        }
351

    
352
        for (TaxonNode taxonNode: taxon.getTaxonNodes()){
353
        	Classification classification = deproxy(taxonNode.getClassification(), Classification.class);
354
            if (classification.equals(this)){
355
                return taxonNode;
356
            }
357
        }
358
        return null;
359
    }
360

    
361
    /**
362
     * Checks if the given taxon is one of the topmost taxa in <b>this</b> tree.
363
     * @param taxon
364
     * @return
365
     */
366
    public boolean isTopmostInTree(Taxon taxon){
367
        return (getTopmostNode(taxon) != null);
368
    }
369

    
370
    /**
371
     * Checks if the taxon is a direct child of the root of <b>this</b> tree and returns the according node if true.
372
     * Returns <code>null</code> otherwise.
373
     * @param taxon
374
     * @return
375
     */
376
    public TaxonNode getTopmostNode(Taxon taxon){
377
        if (taxon == null){
378
            return null;
379
        }
380
        for (TaxonNode taxonNode: taxon.getTaxonNodes()){
381
            if (taxonNode.getClassification().equals(this)){
382
                if (this.getChildNodes().contains(taxonNode)){
383
                    if (taxonNode.getParent() == null){
384
                        logger.warn("A topmost node should always have the root node as parent but actually has no parent");
385
                    }else if (taxonNode.getParent().getParent() != null){
386
                        logger.warn("The root node should have not parent but actually has one");
387
                    }else if (taxonNode.getParent().getTaxon() != null){
388
                        logger.warn("The root node should have not taxon but actually has one");
389
                    }
390
                    return taxonNode;
391
                }
392
            }
393
        }
394
        return null;
395
    }
396

    
397
    private boolean handleCitationOverwrite(TaxonNode childNode, Reference citation, String microCitation){
398
        if (citation != null){
399
            if (childNode.getReference() != null && ! childNode.getReference().equals(citation)){
400
                logger.warn("ReferenceForParentChildRelation will be overwritten");
401
            }
402
            childNode.setReference(citation);
403
        }
404
        if (microCitation != null){
405
            if (childNode.getMicroReference() != null && ! childNode.getMicroReference().equals(microCitation)){
406
                logger.warn("MicroReferenceForParentChildRelation will be overwritten");
407
            }
408
            childNode.setMicroReference(microCitation);
409
        }
410
        return true;
411
    }
412

    
413
    /**
414
     * Relates two taxa as parent-child nodes within a classification. <BR>
415
     * If the taxa are not yet part of the tree they are added to it.<Br>
416
     * If the child taxon is a topmost node still it is added as child and deleted from the rootNode set.<Br>
417
     * If the child is a child of another parent already an IllegalStateException is thrown because a child can have only
418
     * one parent. <Br>
419
     * If the parent-child relationship between these two taxa already exists nothing is changed. Only
420
     * citation and microcitation are overwritten by the new values if these values are not null.
421
     *
422
     * @param parent
423
     * @param child
424
     * @param citation
425
     * @param microCitation
426
     * @return the childNode
427
     * @throws IllegalStateException If the child is a child of another parent already
428
     */
429
    public TaxonNode addParentChild (Taxon parent, Taxon child, Reference citation, String microCitation)
430
            throws IllegalStateException{
431
        try {
432
            if (parent == null || child == null){
433
                logger.warn("Child or parent taxon is null.");
434
                return null;
435
            }
436
            if (parent == child){
437
                logger.warn("A taxon should never be its own child. Child not added");
438
                return null;
439
            }
440
            TaxonNode parentNode = this.getNode(parent);
441
            TaxonNode childNode = this.getNode(child);
442

    
443
            //if child exists in tree and has a parent
444
            //no multiple parents are allowed in the tree
445
            if (childNode != null && ! childNode.isTopmostNode()){
446
                //...different to the parent taxon  throw exception
447
                if ( !(childNode.getParent().getTaxon().equals(parent) )){
448
                	if (childNode.getParent().getTaxon().getId() != 0 && childNode.getParent().getTaxon().getName().equals(parent.getName())){
449
                		throw new IllegalStateException("The child taxon is already part of the tree but has an other parent taxon than the parent to be added. Child: " + child.toString() + ", new parent:" + parent.toString() + ", old parent: " + childNode.getParent().getTaxon().toString()) ;
450
                	}
451
                    //... same as the parent taxon do nothing but overwriting citation and microCitation
452
                }else{
453
                    handleCitationOverwrite(childNode, citation, microCitation);
454
                    return childNode;
455
                }
456
            }
457

    
458
            //add parent node if not exist
459
            if (parentNode == null){
460
                parentNode = this.addChildTaxon(parent, null, null);
461
            }
462

    
463
            //add child if not exists
464
            if (childNode == null){
465
                childNode = parentNode.addChildTaxon(child, citation, microCitation);
466
            }else{
467
                //child is still topmost node
468
                //TODO test if child is topmostNode otherwise throw IllegalStateException
469
                if (! this.isTopmostInTree(child)){
470
                    //throw new IllegalStateException("Child is not a topmost node but must be");
471
                    if (childNode.getClassification() != null && childNode.getParent() == null){
472
                        logger.warn("Child has no parent and is not a topmost node, child: " + child.getId() + " classification: " + childNode.getClassification().getId());
473
                    }else{
474
                        logger.warn("ChildNode has no classification: " + childNode.getId());
475
                    }
476
                    parentNode.addChildNode(childNode, citation, microCitation);
477
                    if (!parentNode.isTopmostNode()){
478
                        this.addChildNode(parentNode, citation, microCitation);
479
                        logger.warn("parent is added as a topmost node");
480
                    }else{
481
                        logger.warn("parent is already a topmost node");
482
                    }
483
                }else{
484
                    this.makeTopmostNodeChildOfOtherNode(childNode, parentNode, citation, microCitation);
485
                }
486
            }
487
            return childNode;
488
        } catch (IllegalStateException e) {
489
            throw e;
490
        } catch (RuntimeException e){
491
            throw e;
492
        }
493
    }
494

    
495
    @Override
496
    @Transient
497
    public Reference getCitation() {
498
        return reference;
499
    }
500

    
501
    public LanguageString getName() {
502
        return name;
503
    }
504
    public void setName(LanguageString name) {
505
        this.name = name;
506
    }
507

    
508
    /**
509
     * Returns a set containing all nodes in this classification.
510
     *
511
     * Caution: Use this method with care. It can be very time and resource consuming and might
512
     * run into OutOfMemoryExceptions for big trees.
513
     *
514
     * @return
515
     */
516
    @Transient
517
    public Set<TaxonNode> getAllNodes() {
518
        Set<TaxonNode> allNodes = new HashSet<>();
519

    
520
        for(TaxonNode rootNode : getChildNodes()){
521
            allNodes.addAll(rootNode.getDescendants());
522
        }
523

    
524
        return allNodes;
525
    }
526

    
527
    @Override
528
    @Transient
529
    public List<TaxonNode> getChildNodes() {
530
        return rootNode.getChildNodes();
531
    }
532

    
533
    @Override
534
    public Reference getReference() {
535
        return reference;
536
    }
537
    public void setReference(Reference reference) {
538
        this.reference = reference;
539
    }
540

    
541

    
542
    @Override
543
    public String getMicroReference() {
544
        return microReference;
545
    }
546
    public void setMicroReference(String microReference) {
547
        this.microReference = microReference;
548
    }
549

    
550

    
551
    /**
552
	 * The point in time, the time period or the season for which this description element
553
	 * is valid. A season may be expressed by not filling the year part(s) of the time period.
554
	 */
555
	public TimePeriod getTimeperiod() {
556
		return timeperiod;
557
	}
558

    
559
	/**
560
	 * @see #getTimeperiod()
561
	 */
562
	public void setTimeperiod(TimePeriod timeperiod) {
563
		if (timeperiod == null){
564
			timeperiod = TimePeriod.NewInstance();
565
		}
566
		this.timeperiod = timeperiod;
567
	}
568

    
569

    
570
    /**
571
     * Returns the set of {@link NamedArea named areas} indicating the geospatial
572
     * data where <i>this</i> {@link Classification} is valid.
573
     */
574
    public Set<NamedArea> getGeoScopes(){
575
        return this.geoScopes;
576
    }
577

    
578
    /**
579
     * Adds a {@link NamedArea named area} to the set of {@link #getGeoScopes() named areas}
580
     * delimiting the geospatial area where <i>this</i> {@link Classification} is valid.
581
     *
582
     * @param geoScope	the named area to be additionally assigned to <i>this</i> taxon description
583
     * @see    	   		#getGeoScopes()
584
     */
585
    public void addGeoScope(NamedArea geoScope){
586
        this.geoScopes.add(geoScope);
587
    }
588

    
589
    /**
590
     * Removes one element from the set of {@link #getGeoScopes() named areas} delimiting
591
     * the geospatial area where <i>this</i> {@link Classification} is valid.
592
     *
593
     * @param  geoScope   the named area which should be removed
594
     * @see     		  #getGeoScopes()
595
     * @see     		  #addGeoScope(NamedArea)
596
     */
597
    public void removeGeoScope(NamedArea geoScope){
598
        this.geoScopes.remove(geoScope);
599
    }
600

    
601

    
602
	/**
603
	 * Returns the i18n description used to describe
604
	 * <i>this</i> {@link Classification}. The different {@link LanguageString language strings}
605
	 * contained in the multilanguage text should all have the same meaning.
606
	 */
607
	public Map<Language,LanguageString> getDescription(){
608
		return this.description;
609
	}
610

    
611
	/**
612
	 * Adds a translated {@link LanguageString text in a particular language}
613
	 * to the {@link MultilanguageText description} used to describe
614
	 * <i>this</i> {@link Classification}.
615
	 *
616
	 * @param description	the language string describing the individuals association
617
	 * 						in a particular language
618
	 * @see    	   			#getDescription()
619
	 * @see    	   			#putDescription(Language, String)
620
	 *
621
	 */
622
	public void putDescription(LanguageString description){
623
		this.description.put(description.getLanguage(),description);
624
	}
625
	/**
626
	 * Creates a {@link LanguageString language string} based on the given text string
627
	 * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
628
	 * used to describe <i>this</i> {@link Classification}.
629
	 *
630
	 * @param text		the string describing the individuals association
631
	 * 					in a particular language
632
	 * @param language	the language in which the text string is formulated
633
	 * @see    	   		#getDescription()
634
	 * @see    	   		#putDescription(LanguageString)
635
	 */
636
	public void putDescription(Language language, String text){
637
		this.description.put(language, LanguageString.NewInstance(text, language));
638
	}
639
	/**
640
	 * Removes from the {@link MultilanguageText description} used to describe
641
	 * <i>this</i>  {@link Classification} the one {@link LanguageString language string}
642
	 * with the given {@link Language language}.
643
	 *
644
	 * @param  language	the language in which the language string to be removed
645
	 * 					has been formulated
646
	 * @see     		#getDescription()
647
	 */
648
	public void removeDescription(Language language){
649
		this.description.remove(language);
650
	}
651

    
652

    
653
    @Override
654
    public String generateTitle() {
655
        //TODO implement as cache strategy
656
        if (protectedTitleCache){
657
            return this.titleCache;
658
        }else if (name != null){
659
            return name.getText();
660
        }else if (reference != null){
661
            return this.reference.getTitleCache();
662
        }else{
663
            return this.toString();
664
        }
665
    }
666

    
667
    public int compareTo(Object o) {
668
        //TODO needs to be implemented
669
        return 0;
670
    }
671

    
672

    
673
    @Override
674
    public boolean hasChildNodes() {
675
        return getChildNodes().size() > 0;
676
    }
677

    
678
    //*********************** CLONE ********************************************************/
679
    /**
680
     * Clones <i>this</i> classification. This is a shortcut that enables to create
681
     * a new instance that differs only slightly from <i>this</i> classification by
682
     * modifying only some of the attributes.<BR><BR>
683

    
684
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
685
     * @see java.lang.Object#clone()
686
     */
687
    @Override
688
    public Object clone() {
689
        Classification result;
690
        try{
691
            result = (Classification)super.clone();
692
            //result.rootNode.childNodes = new ArrayList<TaxonNode>();
693
            List<TaxonNode> rootNodes = new ArrayList<>();
694
            TaxonNode rootNodeClone;
695

    
696

    
697
            rootNodes.addAll(rootNode.getChildNodes());
698
            TaxonNode rootNode;
699
            Iterator<TaxonNode> iterator = rootNodes.iterator();
700

    
701
            result.description = cloneLanguageString(this.description);
702

    
703
            while (iterator.hasNext()){
704
                rootNode = iterator.next();
705
                rootNodeClone = rootNode.cloneDescendants();
706
                rootNodeClone.setClassification(result);
707
                result.addChildNode(rootNodeClone, rootNode.getReference(), rootNode.getMicroReference());
708
                rootNodeClone.setSynonymToBeUsed(rootNode.getSynonymToBeUsed());
709
            }
710

    
711
            //geo-scopes
712
            result.geoScopes = new HashSet<>();
713
            for (NamedArea namedArea : getGeoScopes()){
714
                result.geoScopes.add(namedArea);
715
            }
716

    
717
            return result;
718

    
719
        }catch (CloneNotSupportedException e) {
720
            logger.warn("Object does not implement cloneable");
721
            e.printStackTrace();
722
            return null;
723
        }
724

    
725
    }
726

    
727

    
728
}
(2-2/21)