Project

General

Profile

Download (26.2 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.common.AnnotatableEntity;
47
import eu.etaxonomy.cdm.model.common.CdmBase;
48
import eu.etaxonomy.cdm.model.common.ITreeNode;
49
import eu.etaxonomy.cdm.model.reference.Reference;
50

    
51
/**
52
 * @author a.mueller
53
 * @created 31.03.2009
54
 */
55
@XmlAccessorType(XmlAccessType.FIELD)
56
@XmlType(name = "TaxonNode", propOrder = {
57
    "classification",
58
    "taxon",
59
    "parent",
60
    "treeIndex",
61
    "sortIndex",
62
    "childNodes",
63
    "referenceForParentChildRelation",
64
    "microReferenceForParentChildRelation",
65
    "countChildren",
66
    "synonymToBeUsed"
67
})
68
@XmlRootElement(name = "TaxonNode")
69
@Entity
70
@Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonNode")
71
@Audited
72
@Table(appliesTo="TaxonNode", indexes = { @Index(name = "taxonNodeTreeIndex", columnNames = { "treeIndex" }) })
73
public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITreeNode<TaxonNode>, Cloneable{
74
    private static final long serialVersionUID = -4743289894926587693L;
75
    private static final Logger logger = Logger.getLogger(TaxonNode.class);
76

    
77
    @XmlElement(name = "taxon")
78
    @XmlIDREF
79
    @XmlSchemaType(name = "IDREF")
80
    @ManyToOne(fetch = FetchType.LAZY)
81
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
82
    @ContainedIn
83
    private Taxon taxon;
84

    
85

    
86
    @XmlElement(name = "parent")
87
    @XmlIDREF
88
    @XmlSchemaType(name = "IDREF")
89
    @ManyToOne(fetch = FetchType.LAZY)
90
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
91
    private TaxonNode parent;
92

    
93

    
94
    @XmlElement(name = "treeIndex")
95
    @Size(max=255)
96
    private String treeIndex;
97

    
98

    
99
    @XmlElement(name = "classification")
100
    @XmlIDREF
101
    @XmlSchemaType(name = "IDREF")
102
    @ManyToOne(fetch = FetchType.LAZY)
103
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
104
//	TODO @NotNull // avoids creating a UNIQUE key for this field
105
    @IndexedEmbedded
106
    private Classification classification;
107

    
108
    @XmlElementWrapper(name = "childNodes")
109
    @XmlElement(name = "childNode")
110
    @XmlIDREF
111
    @XmlSchemaType(name = "IDREF")
112
    //see https://dev.e-taxonomy.eu/trac/ticket/3722
113
    @OrderColumn(name="sortIndex")
114
    @OrderBy("sortIndex")
115
    @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
116
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
117
    private List<TaxonNode> childNodes = new ArrayList<TaxonNode>();
118

    
119
    //see https://dev.e-taxonomy.eu/trac/ticket/3722
120
    //see https://dev.e-taxonomy.eu/trac/ticket/4200
121
    private Integer sortIndex = -1;
122

    
123
	@XmlElement(name = "reference")
124
    @XmlIDREF
125
    @XmlSchemaType(name = "IDREF")
126
    @ManyToOne(fetch = FetchType.LAZY)
127
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
128
    private Reference<?> referenceForParentChildRelation;
129

    
130
    @XmlElement(name = "microReference")
131
    private String microReferenceForParentChildRelation;
132

    
133
    @XmlElement(name = "countChildren")
134
    private int countChildren;
135

    
136
//	private Taxon originalConcept;
137
//	//or
138
    @XmlElement(name = "synonymToBeUsed")
139
    @XmlIDREF
140
    @XmlSchemaType(name = "IDREF")
141
    @ManyToOne(fetch = FetchType.LAZY)
142
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
143
    private Synonym synonymToBeUsed;
144

    
145
// ******************** CONSTRUCTOR **********************************************/
146

    
147
    protected TaxonNode(){super();}
148

    
149
    /**
150
     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
151
     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
152
     * @param taxon
153
     * @param classification
154
     * @deprecated setting of classification is handled in the addTaxonNode() method,
155
     * use TaxonNode(taxon) instead
156
     */
157
    @Deprecated
158
    protected TaxonNode (Taxon taxon, Classification classification){
159
        this(taxon);
160
        setClassification(classification);
161
    }
162

    
163
    /**
164
     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
165
     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
166
     *
167
     * @param taxon
168
     */
169
    protected TaxonNode(Taxon taxon){
170
        setTaxon(taxon);
171
    }
172

    
173
// ************************* GETTER / SETTER *******************************/
174
    
175
    public Integer getSortIndex() {
176
		return sortIndex;
177
	}
178
    /**
179
     * SortIndex shall be handled only internally, therefore not public.
180
     * However, as javaassist only supports protected methods it needs to be protected, not private.
181
     * Alternatively we could use deproxy on every call of this method (see commented code)
182
     * @param i
183
     * @return
184
     * @deprecated for internal use only
185
     */
186
     protected void setSortIndex(Integer i) { 
187
//    	 CdmBase.deproxy(this, TaxonNode.class).sortIndex = i;  //alternative solution for private, DON'T remove
188
    	 sortIndex = i;
189
	}
190
    
191

    
192
    public Taxon getTaxon() {
193
        return taxon;
194
    }
195
    protected void setTaxon(Taxon taxon) {
196
        this.taxon = taxon;
197
        if (taxon != null){
198
            taxon.addTaxonNode(this);
199
        }
200
    }
201
    
202

    
203
    @Override
204
    public List<TaxonNode> getChildNodes() {
205
        return childNodes;
206
    }
207
	protected void setChildNodes(List<TaxonNode> childNodes) {
208
		this.childNodes = childNodes;	
209
	}
210

    
211
    
212
    public Classification getClassification() {
213
        return classification;
214
    }
215
    /**
216
     * THIS METHOD SHOULD NOT BE CALLED!
217
     * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
218
     * @param classification
219
     * @deprecated for internal use only
220
     */
221
    protected void setClassification(Classification classification) {
222
        this.classification = classification;
223
    }
224
    
225

    
226
    @Override
227
    public String getMicroReference() {
228
        return microReferenceForParentChildRelation;
229
    }
230
    public void setMicroReference(String microReference) {
231
        this.microReferenceForParentChildRelation = microReference;
232
    }
233
    
234

    
235
    @Override
236
    public Reference getReference() {
237
        return referenceForParentChildRelation;
238
    }
239
    public void setReference(Reference reference) {
240
        this.referenceForParentChildRelation = reference;
241
    }
242

    
243
    //countChildren
244
    public int getCountChildren() {
245
        return countChildren;
246
    }
247
    /**
248
     * @deprecated for internal use only
249
     * @param countChildren
250
     */
251
    protected void setCountChildren(int countChildren) {
252
        this.countChildren = countChildren;
253
    }
254
    
255

    
256
    //parent
257
    @Override
258
    public TaxonNode getParent(){
259
        return parent;
260
    }
261
    /**
262
     * Sets the parent of this taxon node.<BR>
263
     *
264
     * In most cases you would want to call setParentTreeNode(ITreeNode) which
265
     * handles updating of the bidirectional relationship
266
     *
267
     * @see setParentTreeNode(ITreeNode)
268
     * @param parent
269
     *
270
     */
271
    protected void setParent(TaxonNode parent) {
272
        this.parent = parent;
273
    }
274
    
275

    
276
    //synonymToBeused
277
    public Synonym getSynonymToBeUsed() {
278
        return synonymToBeUsed;
279
    }
280
    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
281
        this.synonymToBeUsed = synonymToBeUsed;
282
    }
283
    
284

    
285
    //treeindex
286
    @Override
287
    public String treeIndex() {
288
        return treeIndex;
289
    }
290
    @Override
291
    @Deprecated //for CDM lib internal use only, may be removed in future versions
292
    public void setTreeIndex(String treeIndex) {
293
        this.treeIndex = treeIndex;
294
    }
295
    
296

    
297

    
298
//************************ METHODS **************************/
299

    
300
   @Override
301
    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
302
        return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
303
    }
304

    
305

    
306
    @Override
307
    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
308
        if (this.getClassification().isTaxonInTree(taxon)){
309
            throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));
310
       }
311
       return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
312
    }
313

    
314
    /**
315
     * Moves a taxon node to a new parent. Descendents of the node are moved as well
316
     *
317
     * @param childNode the taxon node to be moved to the new parent
318
     * @return the child node in the state of having a new parent
319
     */
320
    @Override
321
    public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference){
322
        addChildNode(childNode, childNodes.size(), reference, microReference);
323
        return childNode;
324
    }
325

    
326
    /**
327
     * Inserts the given taxon node in the list of children of <i>this</i> taxon node
328
     * at the given (index + 1) position. If the given index is out of bounds
329
     * an exception will arise.<BR>
330
     * Due to bidirectionality this method must also assign <i>this</i> taxon node
331
     * as the parent of the given child.
332
     *
333
     * @param	child	the taxon node to be added
334
     * @param	index	the integer indicating the position at which the child
335
     * 					should be added
336
     * @see				#getChildNodes()
337
     * @see				#addChildNode(TaxonNode, Reference, String, Synonym)
338
     * @see				#deleteChildNode(TaxonNode)
339
     * @see				#deleteChildNode(int)
340
     */
341
    @Override
342
    public TaxonNode addChildNode(TaxonNode child, int index, Reference reference, String microReference){
343
        if (index < 0 || index > childNodes.size() + 1){
344
            throw new IndexOutOfBoundsException("Wrong index: " + index);
345
        }
346
           // check if this node is a descendant of the childNode
347
        if(child.getParent() != this && child.isAncestor(this)){
348
            throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
349
        }
350

    
351
        child.setParentTreeNode(this, index); 
352
        
353
        child.setReference(reference);
354
        child.setMicroReference(microReference);
355

    
356
        return child;
357
    }
358

    
359
    /**
360
     * Sets this nodes classification. Updates classification of child nodes recursively.
361
     *
362
     * If the former and the actual tree are equal() this method does nothing.
363
     * 
364
     * @throws IllegalArgumentException if newClassifciation is null
365
     *
366
     * @param newClassification
367
     */
368
    @Transient
369
    private void setClassificationRecursively(Classification newClassification) {
370
        if (newClassification == null){
371
        	throw new IllegalArgumentException("New Classification must not be 'null' when setting new classification.");
372
        }
373
    	if(! newClassification.equals(this.getClassification())){
374
            this.setClassification(newClassification);
375
            for(TaxonNode childNode : this.getChildNodes()){
376
                childNode.setClassificationRecursively(newClassification);
377
            }
378
        }
379
    }
380

    
381
    @Override
382
    public boolean deleteChildNode(TaxonNode node) {
383
        boolean result = removeChildNode(node);
384
        Taxon taxon = node.getTaxon();
385
        node.setTaxon(null);
386
        taxon.removeTaxonNode(node);
387

    
388
        ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
389
        for(TaxonNode childNode : childNodes){
390
            node.deleteChildNode(childNode);
391
        }
392

    
393
        return result;
394
    }
395

    
396
    /**
397
     * Deletes the child node and also removes children of childnode 
398
     * recursively if delete children is <code>true</code>
399
     * @param node
400
     * @param deleteChildren
401
     * @return
402
     */
403
    public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
404
        boolean result = removeChildNode(node);
405
        Taxon taxon = node.getTaxon();
406
        node.setTaxon(null);
407
        taxon.removeTaxonNode(node);
408
        if (deleteChildren){
409
            ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
410
            for(TaxonNode childNode : childNodes){
411
                node.deleteChildNode(childNode, deleteChildren);
412
            }
413
        } else{
414
        	ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
415
            for(TaxonNode childNode : childNodes){
416
             this.addChildNode(childNode, null, null);
417
            }
418
        }
419

    
420
        return result;
421
    }
422

    
423
    /**
424
     * Removes the child node from this node. Sets the parent and the classification of the child
425
     * node to null
426
     *
427
     * @param childNode
428
     * @return
429
     */
430
    protected boolean removeChildNode(TaxonNode childNode){
431
        boolean result = true;
432

    
433
        if(childNode == null){
434
            throw new IllegalArgumentException("TaxonNode may not be null");
435
        }
436
        int index = childNodes.indexOf(childNode);
437
        if (index >= 0){
438
            removeChild(index);
439
        } else {
440
            result = false;
441
        }
442
        return result;
443
    }
444

    
445
    /**
446
     * Removes the child node placed at the given (index + 1) position
447
     * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
448
     * Sets the parent and the classification of the child
449
     * node to null.
450
     * If the given index is out of bounds no child will be removed.
451
     *
452
     * @param  index	the integer indicating the position of the taxon node to
453
     * 					be removed
454
     * @see     		#getChildNodes()
455
     * @see				#addChildNode(TaxonNode, Reference, String)
456
     * @see				#addChildNode(TaxonNode, int, Reference, String)
457
     * @see				#deleteChildNode(TaxonNode)
458
     */
459
    public void removeChild(int index){
460

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

    
464
        if (child != null){
465

    
466
            TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent(), TaxonNode.class);
467
            TaxonNode thisNode = HibernateProxyHelper.deproxy(this, TaxonNode.class);
468
            if(parent != null && parent != thisNode){
469
                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());
470
            }else if (parent == null){
471
                throw new IllegalStateException("Parent of child is null in TaxonNode.removeChild(int). This should not happen.");
472
            }
473
            childNodes.remove(index);
474
            child.setClassification(null);
475
            
476
            //update sortindex
477
            //TODO workaround (see sortIndex doc)
478
            this.countChildren = childNodes.size();
479
            child.setParent(null);
480
            child.setTreeIndex(null);
481
            updateSortIndex(childNodes, index);
482
            child.setSortIndex(null);
483
        }
484
    }
485

    
486

    
487
    /**
488
     * Remove this taxonNode From its taxonomic parent
489
     *
490
     * @return true on success
491
     */
492
    public boolean delete(){
493
        if(isTopmostNode()){
494
            return classification.deleteChildNode(this);
495
        }else{
496
            return getParent().deleteChildNode(this);
497
        }
498
    }
499

    
500
    /**
501
     * Remove this taxonNode From its taxonomic parent
502
     *
503
     * @return true on success
504
     */
505
    public boolean delete(boolean deleteChildren){
506
        if(isTopmostNode()){
507
            return classification.deleteChildNode(this, deleteChildren);
508
        }else{
509
            return getParent().deleteChildNode(this, deleteChildren);
510
        }
511
    }
512

    
513
    @Override
514
    @Deprecated //for CDM lib internal use only, may be removed in future versions
515
    public int treeId() {
516
        if (this.classification == null){
517
        	logger.warn("TaxonNode has no classification. This should not happen.");  //#3840
518
        	return -1;
519
        }else{
520
        	return this.classification.getId();
521
        }
522
    }
523

    
524

    
525
    /**
526
     * Sets the parent of this taxon node to the given parent. Cleans up references to
527
     * old parents and sets the classification to the new parents classification
528
     *
529
     * @param parent
530
     */
531
    @Transient
532
    protected void setParentTreeNode(TaxonNode parent, int index){
533
        // remove ourselves from the old parent
534
        TaxonNode formerParent = this.getParent();
535

    
536
        if (formerParent != null){
537
        	//special case, child already exists for same parent
538
            //FIXME document / check for correctness
539
            if (formerParent.equals(parent)){
540
                int currentIndex = formerParent.getChildNodes().indexOf(this);
541
                if (currentIndex != -1 && currentIndex < index){
542
                    index--;
543
                }
544
        	}
545
        	
546
        	//remove from old parent
547
            formerParent.removeChildNode(this);
548
        }
549
        
550
        // set the new parent
551
        setParent(parent);
552

    
553
        // set the classification to the parents classification
554
        Classification classification = parent.getClassification();
555
        //FIXME also set the tree index here for performance reasons
556
        setClassificationRecursively(classification);
557

    
558
        // add this node to the parent child nodes
559
        List<TaxonNode> parentChildren = parent.getChildNodes();
560
        if (parentChildren.contains(this)){
561
            //avoid duplicates
562
            if (parentChildren.indexOf(this) < index){
563
                index = index -1;
564
            }
565
            parentChildren.remove(this);
566
            parentChildren.add(index, this);
567
        }else{
568
            parentChildren.add(index, this);
569
        }
570

    
571
        
572
        //sortIndex
573
        //TODO workaround (see sortIndex doc)
574
        updateSortIndex(parentChildren, index);
575
        //only for debugging
576
        if (! this.getSortIndex().equals(index)){
577
        	logger.warn("index and sortindex are not equal");
578
        }
579
        
580
        // update the children count
581
        parent.setCountChildren(parent.getChildNodes().size());
582
    }
583
    
584
	/**
585
	 * As long as the sort index is not correctly handled through hibernate this is a workaround method
586
	 * to update the sort index manually
587
	 * @param parentChildren
588
	 * @param index
589
	 */
590
	private void updateSortIndex(List<TaxonNode> children, int index) {
591
		for(int i = index; i < children.size(); i++){
592
        	TaxonNode child = children.get(i);
593
        	if (child != null){
594
//        		child = CdmBase.deproxy(child, TaxonNode.class);  //deproxy not needed as long as setSortIndex is protected or public #4200
595
        		child.setSortIndex(i);
596
        	}else{
597
        		String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";
598
        		throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));
599
        	}
600
        }
601
	}
602

    
603

    
604

    
605

    
606
    /**
607
     * Returns a set containing this node and all nodes that are descendants of this node
608
     *
609
     * @return
610
     */
611
    
612
    protected Set<TaxonNode> getDescendants(){
613
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
614

    
615
        nodeSet.add(this);
616

    
617
        for(TaxonNode childNode : getChildNodes()){
618
            nodeSet.addAll(childNode.getDescendants());
619
        }
620

    
621
        return nodeSet;
622
    }
623

    
624
    /**
625
     * Returns a set containing a clone of this node and of all nodes that are descendants of this node
626
     *
627
     * @return
628
     */
629
    protected TaxonNode cloneDescendants(){
630

    
631
        TaxonNode clone = (TaxonNode)this.clone();
632
        TaxonNode childClone;
633

    
634
        for(TaxonNode childNode : getChildNodes()){
635
            childClone = (TaxonNode) childNode.clone();
636
            for (TaxonNode childChild:childNode.getChildNodes()){
637
                childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());
638
            }
639
            clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference());
640
            //childClone.addChildNode(childNode.cloneDescendants());
641
        }
642
        return clone;
643
    }
644

    
645
    /**
646
     * Returns a
647
     *
648
     * @return
649
     */
650
    protected Set<TaxonNode> getAncestors(){
651
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
652
       // nodeSet.add(this);
653
        if(this.getParent() != null){
654
        	TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);
655
            nodeSet.addAll(parent.getAncestors());
656
        }
657
        return nodeSet;
658
    }
659

    
660

    
661
    /**
662
     * Whether this TaxonNode is a direct child of the classification TreeNode
663
     * @return
664
     */
665
    @Transient
666
    public boolean isTopmostNode(){
667
    	boolean parentCheck = false;
668
    	boolean classificationCheck = false;
669

    
670
    	if(getParent() != null) {
671
    		if(getParent().getTaxon() == null) {
672
    			parentCheck = true;
673
    		}
674
    	}
675

    
676
    	//TODO remove
677
    	// FIXME This should work but doesn't, due to missing sort indexes, can be removed after fixing #4200, #4098
678
    	if (classification != null){          	
679
    		classificationCheck = classification.getRootNode().getChildNodes().contains(this);
680
    	}else{
681
    		classificationCheck = false;
682
    	}
683

    
684
    	// The following is just for logging purposes for the missing sort indexes problem
685
    	// ticket #4098
686
    	if(parentCheck != classificationCheck) {        	
687
    		logger.warn("isTopmost node check " + parentCheck + " not same as classificationCheck : " + classificationCheck + " for taxon node ");        	
688
    		if(this.getParent() != null) {
689
    			logger.warn("-- with parent uuid " + this.getParent().getUuid().toString());
690
    			logger.warn("-- with parent id " + this.getParent().getId());
691
    			for(TaxonNode node : this.getParent().getChildNodes()) {
692
    				if(node == null) {
693
    					logger.warn("-- child node is null");
694
    				} else if (node.getTaxon() == null) {
695
    					logger.warn("-- child node taxon is null");
696
    				} 
697
    			}
698
    			logger.warn("-- parent child count" + this.getParent().getChildNodes().size());        		
699
    		}       	
700
    	}
701

    
702
    	return parentCheck;
703
    }
704

    
705
    /**
706
     * Whether this TaxonNode is a descendant of the given TaxonNode
707
     *
708
     * Caution: use this method with care on big branches. -> performance and memory hungry
709
     *
710
     * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
711
     * other direction (up). It will always result in a rather small set of consecutive parents beeing
712
     * generated.
713
     *
714
     * TODO implement more efficiently without generating the set of descendants first
715
     *
716
     * @param possibleParent
717
     * @return true if this is a descendant
718
     */
719
    @Transient
720
    public boolean isDescendant(TaxonNode possibleParent){
721
    	if (this.treeIndex() == null || possibleParent.treeIndex() == null) {
722
    		return false;
723
    	}
724
    	return possibleParent == null ? false : this.treeIndex().startsWith(possibleParent.treeIndex() );
725
    }
726

    
727
    /**
728
     * Whether this TaxonNode is an ascendant of the given TaxonNode
729
     *
730
     * @param possibleChild
731
     * @return true if there are ascendants
732
     */
733
    @Transient
734
    public boolean isAncestor(TaxonNode possibleChild){
735
    	if (this.treeIndex() == null || possibleChild.treeIndex() == null) {
736
    		return false;
737
    	}
738
       // return possibleChild == null ? false : possibleChild.getAncestors().contains(this);
739
        return possibleChild == null ? false : possibleChild.treeIndex().startsWith(this.treeIndex());
740
    }
741

    
742
    /**
743
     * Whether this taxon has child nodes
744
     *
745
     * @return true if the taxonNode has childNodes
746
     */
747
    @Transient
748
    @Override
749
    public boolean hasChildNodes(){
750
        return childNodes.size() > 0;
751
    }
752

    
753
//*********************** CLONE ********************************************************/
754
    /**
755
     * Clones <i>this</i> taxon node. This is a shortcut that enables to create
756
     * a new instance that differs only slightly from <i>this</i> taxon node by
757
     * modifying only some of the attributes.<BR><BR>
758
     * The child nodes are not copied.<BR>
759
     * The taxon and parent are the same as for the original taxon node. <BR>
760
     *
761
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
762
     * @see java.lang.Object#clone()
763
     */
764
    @Override
765
    public Object clone()  {
766
        TaxonNode result;
767
        try{
768
        result = (TaxonNode)super.clone();
769
        result.getTaxon().addTaxonNode(result);
770
        result.childNodes = new ArrayList<TaxonNode>();
771
        result.countChildren = 0;
772

    
773
        return result;
774
        }catch (CloneNotSupportedException e) {
775
            logger.warn("Object does not implement cloneable");
776
            e.printStackTrace();
777
            return null;
778
        }
779
    }
780

    
781
	public boolean hasTaxon() {
782
		return (taxon!= null);
783
	}
784

    
785

    
786

    
787
}
(13-13/18)