Project

General

Profile

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

    
11
package eu.etaxonomy.cdm.model.taxon;
12

    
13
import java.util.ArrayList;
14
import java.util.HashSet;
15
import java.util.List;
16
import java.util.Set;
17

    
18
import javax.persistence.Entity;
19
import javax.persistence.FetchType;
20
import javax.persistence.ManyToOne;
21
import javax.persistence.OneToMany;
22
import javax.persistence.OrderBy;
23
import javax.persistence.OrderColumn;
24
import javax.persistence.Transient;
25
import javax.validation.constraints.Size;
26
import javax.xml.bind.annotation.XmlAccessType;
27
import javax.xml.bind.annotation.XmlAccessorType;
28
import javax.xml.bind.annotation.XmlElement;
29
import javax.xml.bind.annotation.XmlElementWrapper;
30
import javax.xml.bind.annotation.XmlIDREF;
31
import javax.xml.bind.annotation.XmlRootElement;
32
import javax.xml.bind.annotation.XmlSchemaType;
33
import javax.xml.bind.annotation.XmlType;
34

    
35
import org.apache.log4j.Logger;
36
import org.hibernate.annotations.Cascade;
37
import org.hibernate.annotations.CascadeType;
38
import org.hibernate.annotations.Index;
39
import org.hibernate.annotations.Table;
40
import org.hibernate.envers.Audited;
41
import org.hibernate.search.annotations.ContainedIn;
42
import org.hibernate.search.annotations.Indexed;
43
import org.hibernate.search.annotations.IndexedEmbedded;
44

    
45
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
46
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
47
import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
48
import eu.etaxonomy.cdm.model.common.CdmBase;
49
import eu.etaxonomy.cdm.model.common.DefinedTerm;
50
import eu.etaxonomy.cdm.model.common.ITreeNode;
51
import eu.etaxonomy.cdm.model.name.Rank;
52
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
53
import eu.etaxonomy.cdm.model.reference.Reference;
54
import eu.etaxonomy.cdm.validation.Level3;
55
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustBeLowerRankThanParent;
56
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustDeriveNameFromParent;
57
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustNotSkipRanks;
58

    
59
/**
60
 * @author a.mueller
61
 * @created 31.03.2009
62
 */
63
@XmlAccessorType(XmlAccessType.FIELD)
64
@XmlType(name = "TaxonNode", propOrder = {
65
    "classification",
66
    "taxon",
67
    "parent",
68
    "treeIndex",
69
    "sortIndex",
70
    "childNodes",
71
    "referenceForParentChildRelation",
72
    "microReferenceForParentChildRelation",
73
    "countChildren",
74
    "agents",
75
    "synonymToBeUsed"
76
})
77
@XmlRootElement(name = "TaxonNode")
78
@Entity
79
@Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonNode")
80
@Audited
81
@Table(appliesTo="TaxonNode", indexes = { @Index(name = "taxonNodeTreeIndex", columnNames = { "treeIndex" }) })
82
@ChildTaxaMustBeLowerRankThanParent(groups = Level3.class)
83
@ChildTaxaMustNotSkipRanks(groups = Level3.class)
84
@ChildTaxaMustDeriveNameFromParent(groups = Level3.class)
85
public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITreeNode<TaxonNode>, Cloneable{
86
    private static final long serialVersionUID = -4743289894926587693L;
87
    private static final Logger logger = Logger.getLogger(TaxonNode.class);
88

    
89
    @XmlElement(name = "taxon")
90
    @XmlIDREF
91
    @XmlSchemaType(name = "IDREF")
92
    @ManyToOne(fetch = FetchType.LAZY)
93
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
94
    @ContainedIn
95
    private Taxon taxon;
96

    
97

    
98
    @XmlElement(name = "parent")
99
    @XmlIDREF
100
    @XmlSchemaType(name = "IDREF")
101
    @ManyToOne(fetch = FetchType.LAZY)
102
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
103
    private TaxonNode parent;
104

    
105

    
106
    @XmlElement(name = "treeIndex")
107
    @Size(max=255)
108
    private String treeIndex;
109

    
110

    
111
    @XmlElement(name = "classification")
112
    @XmlIDREF
113
    @XmlSchemaType(name = "IDREF")
114
    @ManyToOne(fetch = FetchType.LAZY)
115
    @Cascade({CascadeType.SAVE_UPDATE})
116
//	TODO @NotNull // avoids creating a UNIQUE key for this field
117
    @IndexedEmbedded
118
    private Classification classification;
119

    
120
    @XmlElementWrapper(name = "childNodes")
121
    @XmlElement(name = "childNode")
122
    @XmlIDREF
123
    @XmlSchemaType(name = "IDREF")
124
    //see https://dev.e-taxonomy.eu/trac/ticket/3722
125
    @OrderColumn(name="sortIndex")
126
    @OrderBy("sortIndex")
127
    @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
128
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
129
    private List<TaxonNode> childNodes = new ArrayList<TaxonNode>();
130

    
131
    //see https://dev.e-taxonomy.eu/trac/ticket/3722
132
    //see https://dev.e-taxonomy.eu/trac/ticket/4200
133
    private Integer sortIndex = -1;
134

    
135
	@XmlElement(name = "reference")
136
    @XmlIDREF
137
    @XmlSchemaType(name = "IDREF")
138
    @ManyToOne(fetch = FetchType.LAZY)
139
    @Cascade({CascadeType.SAVE_UPDATE})
140
    private Reference<?> referenceForParentChildRelation;
141

    
142
    @XmlElement(name = "microReference")
143
    private String microReferenceForParentChildRelation;
144

    
145
    @XmlElement(name = "countChildren")
146
    private int countChildren;
147

    
148
    @XmlElementWrapper(name = "nodeAgents")
149
    @XmlElement(name = "nodeAgent")
150
    @XmlIDREF
151
    @XmlSchemaType(name = "IDREF")
152
    @OneToMany(mappedBy="taxonNode", fetch=FetchType.LAZY)
153
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
154
    private Set<TaxonNodeAgentRelation> agents = new HashSet<TaxonNodeAgentRelation>();
155

    
156

    
157
//	private Taxon originalConcept;
158
//	//or
159
    @XmlElement(name = "synonymToBeUsed")
160
    @XmlIDREF
161
    @XmlSchemaType(name = "IDREF")
162
    @ManyToOne(fetch = FetchType.LAZY)
163
    @Cascade({CascadeType.SAVE_UPDATE})
164
    private Synonym synonymToBeUsed;
165

    
166
// ******************** CONSTRUCTOR **********************************************/
167

    
168
    protected TaxonNode(){super();}
169

    
170
    /**
171
     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
172
     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
173
     * @param taxon
174
     * @param classification
175
     * @deprecated setting of classification is handled in the addTaxonNode() method,
176
     * use TaxonNode(taxon) instead
177
     */
178
    @Deprecated
179
    protected TaxonNode (Taxon taxon, Classification classification){
180
        this(taxon);
181
        setClassification(classification);
182
    }
183

    
184
    /**
185
     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
186
     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
187
     *
188
     * @param taxon
189
     */
190
    protected TaxonNode(Taxon taxon){
191
        setTaxon(taxon);
192
    }
193

    
194
// ************************* GETTER / SETTER *******************************/
195

    
196
    public Integer getSortIndex() {
197
		return sortIndex;
198
	}
199
    /**
200
     * SortIndex shall be handled only internally, therefore not public.
201
     * However, as javaassist only supports protected methods it needs to be protected, not private.
202
     * Alternatively we could use deproxy on every call of this method (see commented code)
203
     * @param i
204
     * @return
205
     * @deprecated for internal use only
206
     */
207
     @Deprecated
208
    protected void setSortIndex(Integer i) {
209
//    	 CdmBase.deproxy(this, TaxonNode.class).sortIndex = i;  //alternative solution for private, DON'T remove
210
    	 sortIndex = i;
211
	}
212

    
213

    
214
    public Taxon getTaxon() {
215
        return taxon;
216
    }
217
    protected void setTaxon(Taxon taxon) {
218
        this.taxon = taxon;
219
        if (taxon != null){
220
            taxon.addTaxonNode(this);
221
        }
222
    }
223

    
224

    
225
    @Override
226
    public List<TaxonNode> getChildNodes() {
227
        return childNodes;
228
    }
229
	protected void setChildNodes(List<TaxonNode> childNodes) {
230
		this.childNodes = childNodes;
231
	}
232

    
233

    
234
    public Classification getClassification() {
235
        return classification;
236
    }
237
    /**
238
     * THIS METHOD SHOULD NOT BE CALLED!
239
     * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
240
     * @param classification
241
     * @deprecated for internal use only
242
     */
243
    @Deprecated
244
    protected void setClassification(Classification classification) {
245
        this.classification = classification;
246
    }
247

    
248

    
249
    @Override
250
    public String getMicroReference() {
251
        return microReferenceForParentChildRelation;
252
    }
253
    public void setMicroReference(String microReference) {
254
        this.microReferenceForParentChildRelation = microReference;
255
    }
256

    
257

    
258
    @Override
259
    public Reference getReference() {
260
        return referenceForParentChildRelation;
261
    }
262
    public void setReference(Reference reference) {
263
        this.referenceForParentChildRelation = reference;
264
    }
265

    
266
    //countChildren
267
    public int getCountChildren() {
268
        return countChildren;
269
    }
270
    /**
271
     * @deprecated for internal use only
272
     * @param countChildren
273
     */
274
    @Deprecated
275
    protected void setCountChildren(int countChildren) {
276
        this.countChildren = countChildren;
277
    }
278

    
279

    
280
    //parent
281
    @Override
282
    public TaxonNode getParent(){
283
        return parent;
284
    }
285
    /**
286
     * Sets the parent of this taxon node.<BR>
287
     *
288
     * In most cases you would want to call setParentTreeNode(ITreeNode) which
289
     * handles updating of the bidirectional relationship
290
     *
291
     * @see setParentTreeNode(ITreeNode)
292
     * @param parent
293
     *
294
     */
295
    protected void setParent(TaxonNode parent) {
296
        this.parent = parent;
297
    }
298

    
299
    public TaxonNodeAgentRelation addNodeAgent(TaxonNode taxonNode, TeamOrPersonBase<?> agent, DefinedTerm type){
300
        TaxonNodeAgentRelation result = TaxonNodeAgentRelation.NewInstance(taxonNode, agent, type);
301
        return result;
302
    }
303
    /**
304
     * @param nodeAgentRelation
305
     */
306
    protected void addAgent(TaxonNodeAgentRelation agentRelation) {
307
        agentRelation.setTaxonNode(this);
308
        this.agents.add(agentRelation);
309
    }
310

    
311
    /**
312
     * @param nodeAgentRelation
313
     */
314
    public void removeNodeAgent(TaxonNodeAgentRelation agentRelation) {
315
        agentRelation.setTaxonNode(this);
316
        agents.remove(agentRelation);
317
    }
318

    
319

    
320

    
321
    //synonymToBeused
322
    public Synonym getSynonymToBeUsed() {
323
        return synonymToBeUsed;
324
    }
325
    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
326
        this.synonymToBeUsed = synonymToBeUsed;
327
    }
328

    
329

    
330
    //treeindex
331
    @Override
332
    public String treeIndex() {
333
        return treeIndex;
334
    }
335
    @Override
336
    @Deprecated //for CDM lib internal use only, may be removed in future versions
337
    public void setTreeIndex(String treeIndex) {
338
        this.treeIndex = treeIndex;
339
    }
340

    
341

    
342

    
343
//************************ METHODS **************************/
344

    
345
   @Override
346
    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
347
        return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
348
    }
349

    
350

    
351
    @Override
352
    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
353
        if (this.getClassification().isTaxonInTree(taxon)){
354
            throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));
355
       }
356
       return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
357
    }
358

    
359
    /**
360
     * Moves a taxon node to a new parent. Descendents of the node are moved as well
361
     *
362
     * @param childNode the taxon node to be moved to the new parent
363
     * @return the child node in the state of having a new parent
364
     */
365
    @Override
366
    public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference){
367
        addChildNode(childNode, childNodes.size(), reference, microReference);
368
        return childNode;
369
    }
370

    
371
    /**
372
     * Inserts the given taxon node in the list of children of <i>this</i> taxon node
373
     * at the given (index + 1) position. If the given index is out of bounds
374
     * an exception will arise.<BR>
375
     * Due to bidirectionality this method must also assign <i>this</i> taxon node
376
     * as the parent of the given child.
377
     *
378
     * @param	child	the taxon node to be added
379
     * @param	index	the integer indicating the position at which the child
380
     * 					should be added
381
     * @see				#getChildNodes()
382
     * @see				#addChildNode(TaxonNode, Reference, String, Synonym)
383
     * @see				#deleteChildNode(TaxonNode)
384
     * @see				#deleteChildNode(int)
385
     */
386
    @Override
387
    public TaxonNode addChildNode(TaxonNode child, int index, Reference reference, String microReference){
388
        if (index < 0 || index > childNodes.size() + 1){
389
            throw new IndexOutOfBoundsException("Wrong index: " + index);
390
        }
391
           // check if this node is a descendant of the childNode
392
        if(child.getParent() != this && child.isAncestor(this)){
393
            throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
394
        }
395

    
396
        child.setParentTreeNode(this, index);
397

    
398
        child.setReference(reference);
399
        child.setMicroReference(microReference);
400

    
401
        return child;
402
    }
403

    
404
    /**
405
     * Sets this nodes classification. Updates classification of child nodes recursively.
406
     *
407
     * If the former and the actual tree are equal() this method does nothing.
408
     *
409
     * @throws IllegalArgumentException if newClassifciation is null
410
     *
411
     * @param newClassification
412
     */
413
    @Transient
414
    private void setClassificationRecursively(Classification newClassification) {
415
        if (newClassification == null){
416
        	throw new IllegalArgumentException("New Classification must not be 'null' when setting new classification.");
417
        }
418
    	if(! newClassification.equals(this.getClassification())){
419
            this.setClassification(newClassification);
420
            for(TaxonNode childNode : this.getChildNodes()){
421
                childNode.setClassificationRecursively(newClassification);
422
            }
423
        }
424
    }
425

    
426
    @Override
427
    public boolean deleteChildNode(TaxonNode node) {
428
        boolean result = removeChildNode(node);
429
        Taxon taxon = node.getTaxon();
430
        node.setTaxon(null);
431
        taxon.removeTaxonNode(node);
432

    
433
        ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
434
        for(TaxonNode childNode : childNodes){
435
            node.deleteChildNode(childNode);
436
        }
437

    
438
        return result;
439
    }
440

    
441
    /**
442
     * Deletes the child node and also removes children of childnode
443
     * recursively if delete children is <code>true</code>
444
     * @param node
445
     * @param deleteChildren
446
     * @return
447
     */
448
    public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
449
        boolean result = removeChildNode(node);
450
        Taxon taxon = node.getTaxon();
451
        node.setTaxon(null);
452
        taxon.removeTaxonNode(node);
453
        if (deleteChildren){
454
            ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
455
            for(TaxonNode childNode : childNodes){
456
                node.deleteChildNode(childNode, deleteChildren);
457
            }
458
        } else{
459
        	ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
460
            for(TaxonNode childNode : childNodes){
461
             this.addChildNode(childNode, null, null);
462
            }
463
        }
464

    
465
        return result;
466
    }
467

    
468
    /**
469
     * Removes the child node from this node. Sets the parent and the classification of the child
470
     * node to null
471
     *
472
     * @param childNode
473
     * @return
474
     */
475
    protected boolean removeChildNode(TaxonNode childNode){
476
        boolean result = true;
477

    
478
        if(childNode == null){
479
            throw new IllegalArgumentException("TaxonNode may not be null");
480
        }
481
        int index = childNodes.indexOf(childNode);
482
        if (index >= 0){
483
            removeChild(index);
484
        } else {
485
            result = false;
486
        }
487
        return result;
488
    }
489

    
490
    /**
491
     * Removes the child node placed at the given (index + 1) position
492
     * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
493
     * Sets the parent and the classification of the child
494
     * node to null.
495
     * If the given index is out of bounds no child will be removed.
496
     *
497
     * @param  index	the integer indicating the position of the taxon node to
498
     * 					be removed
499
     * @see     		#getChildNodes()
500
     * @see				#addChildNode(TaxonNode, Reference, String)
501
     * @see				#addChildNode(TaxonNode, int, Reference, String)
502
     * @see				#deleteChildNode(TaxonNode)
503
     */
504
    public void removeChild(int index){
505

    
506
        TaxonNode child = childNodes.get(index);
507
        child = HibernateProxyHelper.deproxy(child, TaxonNode.class); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
508

    
509
        if (child != null){
510

    
511
            TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent(), TaxonNode.class);
512
            TaxonNode thisNode = HibernateProxyHelper.deproxy(this, TaxonNode.class);
513
            if(parent != null && parent != thisNode){
514
                throw new IllegalArgumentException("Child TaxonNode (id:" + child.getId() +") must be a child of this (id:" + thisNode.getId() + " node. Sortindex is: " + index + ", parent-id:" + parent.getId());
515
            }else if (parent == null){
516
                throw new IllegalStateException("Parent of child is null in TaxonNode.removeChild(int). This should not happen.");
517
            }
518
            childNodes.remove(index);
519
            child.setClassification(null);
520

    
521
            //update sortindex
522
            //TODO workaround (see sortIndex doc)
523
            this.countChildren = childNodes.size();
524
            child.setParent(null);
525
            child.setTreeIndex(null);
526
            updateSortIndex(childNodes, index);
527
            child.setSortIndex(null);
528
        }
529
    }
530

    
531

    
532
    /**
533
     * Remove this taxonNode From its taxonomic parent
534
     *
535
     * @return true on success
536
     */
537
    public boolean delete(){
538
        if(isTopmostNode()){
539
            return classification.deleteChildNode(this);
540
        }else{
541
            return getParent().deleteChildNode(this);
542
        }
543
    }
544

    
545
    /**
546
     * Remove this taxonNode From its taxonomic parent
547
     *
548
     * @return true on success
549
     */
550
    public boolean delete(boolean deleteChildren){
551
        if(isTopmostNode()){
552
            return classification.deleteChildNode(this, deleteChildren);
553
        }else{
554
            return getParent().deleteChildNode(this, deleteChildren);
555
        }
556
    }
557

    
558
    @Override
559
    @Deprecated //for CDM lib internal use only, may be removed in future versions
560
    public int treeId() {
561
        if (this.classification == null){
562
        	logger.warn("TaxonNode has no classification. This should not happen.");  //#3840
563
        	return -1;
564
        }else{
565
        	return this.classification.getId();
566
        }
567
    }
568

    
569

    
570
    /**
571
     * Sets the parent of this taxon node to the given parent. Cleans up references to
572
     * old parents and sets the classification to the new parents classification
573
     *
574
     * @param parent
575
     */
576
    @Transient
577
    protected void setParentTreeNode(TaxonNode parent, int index){
578
        // remove ourselves from the old parent
579
        TaxonNode formerParent = this.getParent();
580

    
581
        if (formerParent != null){
582
        	//special case, child already exists for same parent
583
            //FIXME document / check for correctness
584
            if (formerParent.equals(parent)){
585
                int currentIndex = formerParent.getChildNodes().indexOf(this);
586
                if (currentIndex != -1 && currentIndex < index){
587
                    index--;
588
                }
589
        	}
590

    
591
        	//remove from old parent
592
            formerParent.removeChildNode(this);
593
        }
594

    
595
        // set the new parent
596
        setParent(parent);
597

    
598
        // set the classification to the parents classification
599

    
600
	        Classification classification = parent.getClassification();
601
	        //FIXME also set the tree index here for performance reasons
602
	        setClassificationRecursively(classification);
603

    
604
        // add this node to the parent child nodes
605
        List<TaxonNode> parentChildren = parent.getChildNodes();
606
        if (parentChildren.contains(this)){
607
            //avoid duplicates
608
            if (parentChildren.indexOf(this) < index){
609
                index = index -1;
610
            }
611
            parentChildren.remove(this);
612
            parentChildren.add(index, this);
613
        }else{
614
            parentChildren.add(index, this);
615
        }
616

    
617

    
618
        //sortIndex
619
        //TODO workaround (see sortIndex doc)
620
        updateSortIndex(parentChildren, index);
621
        //only for debugging
622
        if (! this.getSortIndex().equals(index)){
623
        	logger.warn("index and sortindex are not equal");
624
        }
625

    
626
        // update the children count
627
        parent.setCountChildren(parent.getChildNodes().size());
628
    }
629

    
630
	/**
631
	 * As long as the sort index is not correctly handled through hibernate this is a workaround method
632
	 * to update the sort index manually
633
	 * @param parentChildren
634
	 * @param index
635
	 */
636
	private void updateSortIndex(List<TaxonNode> children, int index) {
637
		for(int i = index; i < children.size(); i++){
638
        	TaxonNode child = children.get(i);
639
        	if (child != null){
640
//        		child = CdmBase.deproxy(child, TaxonNode.class);  //deproxy not needed as long as setSortIndex is protected or public #4200
641
        		child.setSortIndex(i);
642
        	}else{
643
        		String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";
644
        		throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));
645
        	}
646
        }
647
	}
648

    
649

    
650

    
651

    
652
    /**
653
     * Returns a set containing this node and all nodes that are descendants of this node
654
     *
655
     * @return
656
     */
657

    
658
    protected Set<TaxonNode> getDescendants(){
659
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
660

    
661
        nodeSet.add(this);
662

    
663
        for(TaxonNode childNode : getChildNodes()){
664
            nodeSet.addAll(childNode.getDescendants());
665
        }
666

    
667
        return nodeSet;
668
    }
669

    
670
    /**
671
     * Returns a set containing a clone of this node and of all nodes that are descendants of this node
672
     *
673
     * @return
674
     */
675
    protected TaxonNode cloneDescendants(){
676

    
677
        TaxonNode clone = (TaxonNode)this.clone();
678
        TaxonNode childClone;
679

    
680
        for(TaxonNode childNode : getChildNodes()){
681
            childClone = (TaxonNode) childNode.clone();
682
            for (TaxonNode childChild:childNode.getChildNodes()){
683
                childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());
684
            }
685
            clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference());
686
            //childClone.addChildNode(childNode.cloneDescendants());
687
        }
688
        return clone;
689
    }
690

    
691
    /**
692
     * Returns a
693
     *
694
     * @return
695
     */
696
    protected Set<TaxonNode> getAncestors(){
697
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
698
       // nodeSet.add(this);
699
        if(this.getParent() != null){
700
        	TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);
701
            nodeSet.addAll(parent.getAncestors());
702
        }
703
        return nodeSet;
704
    }
705
    public TaxonNode getAncestorOfRank(Rank rank){
706
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
707
       // nodeSet.add(this);
708
        TaxonBase taxon = CdmBase.deproxy(this.getTaxon(), Taxon.class);
709
        TaxonNameBase name = CdmBase.deproxy(taxon.getName(), TaxonNameBase.class);
710
        if (name.getRank().isHigher(rank)){
711
        	return null;
712
        }
713
        if (name.getRank().equals(rank)){
714
        	return this;
715
        }
716
        if(this.getParent() != null ){
717
        	TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);
718
            return parent.getAncestorOfRank(rank);
719
        }
720
		return null;
721

    
722
    }
723

    
724
    /**
725
     * Whether this TaxonNode is a direct child of the classification TreeNode
726
     * @return
727
     */
728
    @Transient
729
    public boolean isTopmostNode(){
730
    	boolean parentCheck = false;
731
    	boolean classificationCheck = false;
732

    
733
    	if(getParent() != null) {
734
    		if(getParent().getTaxon() == null) {
735
    			parentCheck = true;
736
    		}
737
    	}
738

    
739
    	//TODO remove
740
    	// FIXME This should work but doesn't, due to missing sort indexes, can be removed after fixing #4200, #4098
741
    	if (classification != null){
742
    		classificationCheck = classification.getRootNode().getChildNodes().contains(this);
743
    	}else{
744
    		classificationCheck = false;
745
    	}
746

    
747
    	// The following is just for logging purposes for the missing sort indexes problem
748
    	// ticket #4098
749
    	if(parentCheck != classificationCheck) {
750
    		logger.warn("isTopmost node check " + parentCheck + " not same as classificationCheck : " + classificationCheck + " for taxon node ");
751
    		if(this.getParent() != null) {
752
    			logger.warn("-- with parent uuid " + this.getParent().getUuid().toString());
753
    			logger.warn("-- with parent id " + this.getParent().getId());
754
    			for(TaxonNode node : this.getParent().getChildNodes()) {
755
    				if(node == null) {
756
    					logger.warn("-- child node is null");
757
    				} else if (node.getTaxon() == null) {
758
    					logger.warn("-- child node taxon is null");
759
    				}
760
    			}
761
    			logger.warn("-- parent child count" + this.getParent().getChildNodes().size());
762
    		}
763
    	}
764

    
765
    	return parentCheck;
766
    }
767

    
768
    /**
769
     * Whether this TaxonNode is a descendant of the given TaxonNode
770
     *
771
     * Caution: use this method with care on big branches. -> performance and memory hungry
772
     *
773
     * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
774
     * other direction (up). It will always result in a rather small set of consecutive parents beeing
775
     * generated.
776
     *
777
     * TODO implement more efficiently without generating the set of descendants first
778
     *
779
     * @param possibleParent
780
     * @return true if this is a descendant
781
     */
782
    @Transient
783
    public boolean isDescendant(TaxonNode possibleParent){
784
    	if (this.treeIndex() == null || possibleParent.treeIndex() == null) {
785
    		return false;
786
    	}
787
    	return possibleParent == null ? false : this.treeIndex().startsWith(possibleParent.treeIndex() );
788
    }
789

    
790
    /**
791
     * Whether this TaxonNode is an ascendant of the given TaxonNode
792
     *
793
     * @param possibleChild
794
     * @return true if there are ascendants
795
     */
796
    @Transient
797
    public boolean isAncestor(TaxonNode possibleChild){
798
    	if (this.treeIndex() == null || possibleChild.treeIndex() == null) {
799
    		return false;
800
    	}
801
       // return possibleChild == null ? false : possibleChild.getAncestors().contains(this);
802
        return possibleChild == null ? false : possibleChild.treeIndex().startsWith(this.treeIndex());
803
    }
804

    
805
    /**
806
     * Whether this taxon has child nodes
807
     *
808
     * @return true if the taxonNode has childNodes
809
     */
810
    @Transient
811
    @Override
812
    public boolean hasChildNodes(){
813
        return childNodes.size() > 0;
814
    }
815

    
816

    
817
    public boolean hasTaxon() {
818
        return (taxon!= null);
819
    }
820

    
821
    /**
822
     * @return
823
     */
824
    @Transient
825
    public Rank getNullSafeRank() {
826
        return hasTaxon() ? getTaxon().getNullSafeRank() : null;
827
    }
828

    
829

    
830

    
831
//*********************** CLONE ********************************************************/
832
    /**
833
     * Clones <i>this</i> taxon node. This is a shortcut that enables to create
834
     * a new instance that differs only slightly from <i>this</i> taxon node by
835
     * modifying only some of the attributes.<BR><BR>
836
     * The child nodes are not copied.<BR>
837
     * The taxon and parent are the same as for the original taxon node. <BR>
838
     *
839
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
840
     * @see java.lang.Object#clone()
841
     */
842
    @Override
843
    public Object clone()  {
844
        try{
845
            TaxonNode result = (TaxonNode)super.clone();
846
            result.getTaxon().addTaxonNode(result);
847
            result.childNodes = new ArrayList<TaxonNode>();
848
            result.countChildren = 0;
849

    
850
            //agents
851
            result.agents = new HashSet<TaxonNodeAgentRelation>();
852
            for (TaxonNodeAgentRelation rel : this.agents){
853
                result.addAgent((TaxonNodeAgentRelation)rel.clone());
854
            }
855

    
856
            return result;
857
        }catch (CloneNotSupportedException e) {
858
            logger.warn("Object does not implement cloneable");
859
            e.printStackTrace();
860
            return null;
861
        }
862
    }
863
}
(14-14/20)