Project

General

Profile

Download (29.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.Column;
19
import javax.persistence.Entity;
20
import javax.persistence.FetchType;
21
import javax.persistence.ManyToOne;
22
import javax.persistence.OneToMany;
23
import javax.persistence.OrderBy;
24
import javax.persistence.OrderColumn;
25
import javax.persistence.Transient;
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.IndexedEmbedded;
43

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

    
58
/**
59
 * @author a.mueller
60
 * @created 31.03.2009
61
 */
62
@XmlAccessorType(XmlAccessType.FIELD)
63
@XmlType(name = "TaxonNode", propOrder = {
64
    "classification",
65
    "taxon",
66
    "parent",
67
    "treeIndex",
68
    "sortIndex",
69
    "childNodes",
70
    "referenceForParentChildRelation",
71
    "microReferenceForParentChildRelation",
72
    "countChildren",
73
    "agentRelations",
74
    "synonymToBeUsed"
75
})
76
@XmlRootElement(name = "TaxonNode")
77
@Entity
78
//@Indexed disabled to reduce clutter in indexes, since this type is not used by any search
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
    @Column(length=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,CascadeType.MERGE})
116
//	TODO @NotNull // avoids creating a UNIQUE key for this field
117
    @IndexedEmbedded(includeEmbeddedObjectId=true)
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,CascadeType.MERGE})
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 = "agentRelations")
149
    @XmlElement(name = "agentRelation")
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> agentRelations = 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,CascadeType.MERGE})
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
// ****************** Agent Relations ****************************/
300

    
301

    
302
    /**
303
     * @return
304
     */
305
    public Set<TaxonNodeAgentRelation> getAgentRelations() {
306
        return this.agentRelations;
307
    }
308

    
309
    public TaxonNodeAgentRelation addAgentRelation(DefinedTerm type, TeamOrPersonBase<?> agent){
310
        TaxonNodeAgentRelation result = TaxonNodeAgentRelation.NewInstance(this, agent, type);
311
        return result;
312
    }
313
    /**
314
     * @param nodeAgentRelation
315
     */
316
    protected void addAgentRelation(TaxonNodeAgentRelation agentRelation) {
317
        agentRelation.setTaxonNode(this);
318
        this.agentRelations.add(agentRelation);
319
    }
320

    
321
    /**
322
     * @param nodeAgentRelation
323
     */
324
    public void removeNodeAgent(TaxonNodeAgentRelation agentRelation) {
325
        agentRelation.setTaxonNode(this);
326
        agentRelations.remove(agentRelation);
327
    }
328

    
329
//********************
330

    
331
    //synonymToBeused
332
    public Synonym getSynonymToBeUsed() {
333
        return synonymToBeUsed;
334
    }
335
    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
336
        this.synonymToBeUsed = synonymToBeUsed;
337
    }
338

    
339

    
340
    //treeindex
341
    @Override
342
    public String treeIndex() {
343
        return treeIndex;
344
    }
345
    @Override
346
    @Deprecated //for CDM lib internal use only, may be removed in future versions
347
    public void setTreeIndex(String treeIndex) {
348
        this.treeIndex = treeIndex;
349
    }
350

    
351

    
352

    
353
//************************ METHODS **************************/
354

    
355
   @Override
356
    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
357
        return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
358
    }
359

    
360

    
361
    @Override
362
    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
363
        Classification classification = HibernateProxyHelper.deproxy(this.getClassification(), Classification.class);
364

    
365
        if (this.getClassification().isTaxonInTree(taxon)){
366
            throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));
367
       }
368
       return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
369
    }
370

    
371
    /**
372
     * Moves a taxon node to a new parent. Descendents of the node are moved as well
373
     *
374
     * @param childNode the taxon node to be moved to the new parent
375
     * @return the child node in the state of having a new parent
376
     */
377
    @Override
378
    public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference){
379
        addChildNode(childNode, childNodes.size(), reference, microReference);
380
        return childNode;
381
    }
382

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

    
408
        child.setParentTreeNode(this, index);
409

    
410
        child.setReference(reference);
411
        child.setMicroReference(microReference);
412

    
413
        return child;
414
    }
415

    
416
    /**
417
     * Sets this nodes classification. Updates classification of child nodes recursively.
418
     *
419
     * If the former and the actual tree are equal() this method does nothing.
420
     *
421
     * @throws IllegalArgumentException if newClassifciation is null
422
     *
423
     * @param newClassification
424
     */
425
    @Transient
426
    private void setClassificationRecursively(Classification newClassification) {
427
        if (newClassification == null){
428
        	throw new IllegalArgumentException("New Classification must not be 'null' when setting new classification.");
429
        }
430
    	if(! newClassification.equals(this.getClassification())){
431
            this.setClassification(newClassification);
432
            for(TaxonNode childNode : this.getChildNodes()){
433
                childNode.setClassificationRecursively(newClassification);
434
            }
435
        }
436
    }
437

    
438
    @Override
439
    public boolean deleteChildNode(TaxonNode node) {
440
        boolean result = removeChildNode(node);
441
        Taxon taxon = node.getTaxon();
442
        node.setTaxon(null);
443
        taxon.removeTaxonNode(node);
444

    
445
        ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
446
        for(TaxonNode childNode : childNodes){
447
            node.deleteChildNode(childNode);
448
        }
449

    
450
        return result;
451
    }
452

    
453
    /**
454
     * Deletes the child node and also removes children of childnode
455
     * recursively if delete children is <code>true</code>
456
     * @param node
457
     * @param deleteChildren
458
     * @return
459
     */
460
    public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
461
        boolean result = removeChildNode(node);
462
        Taxon taxon = node.getTaxon();
463
        node.setTaxon(null);
464
        taxon.removeTaxonNode(node);
465
        if (deleteChildren){
466
            ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
467
            for(TaxonNode childNode : childNodes){
468
                node.deleteChildNode(childNode, deleteChildren);
469
            }
470
        } else{
471
        	ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
472
            for(TaxonNode childNode : childNodes){
473
             this.addChildNode(childNode, null, null);
474
            }
475
        }
476

    
477
        return result;
478
    }
479

    
480
    /**
481
     * Removes the child node from this node. Sets the parent and the classification of the child
482
     * node to null
483
     *
484
     * @param childNode
485
     * @return
486
     */
487
    protected boolean removeChildNode(TaxonNode childNode){
488
        boolean result = true;
489
        while (childNodes.contains(null)){
490
            childNodes.remove(null);
491
        }
492
        if(childNode == null){
493
            throw new IllegalArgumentException("TaxonNode may not be null");
494
        }
495
        int index = childNodes.indexOf(childNode);
496
        if (index >= 0){
497
            removeChild(index);
498
        } else {
499
            result = false;
500
        }
501
        return result;
502
    }
503

    
504
    /**
505
     * Removes the child node placed at the given (index + 1) position
506
     * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
507
     * Sets the parent and the classification of the child
508
     * node to null.
509
     * If the given index is out of bounds no child will be removed.
510
     *
511
     * @param  index	the integer indicating the position of the taxon node to
512
     * 					be removed
513
     * @see     		#getChildNodes()
514
     * @see				#addChildNode(TaxonNode, Reference, String)
515
     * @see				#addChildNode(TaxonNode, int, Reference, String)
516
     * @see				#deleteChildNode(TaxonNode)
517
     */
518
    public void removeChild(int index){
519
        //TODO: Only as a workaround. We have to find out why merge creates null entries.
520

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

    
524
        if (child != null){
525

    
526
            TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent(), TaxonNode.class);
527
            TaxonNode thisNode = HibernateProxyHelper.deproxy(this, TaxonNode.class);
528
            if(parent != null && parent != thisNode){
529
                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());
530
            }else if (parent == null){
531
                throw new IllegalStateException("Parent of child is null in TaxonNode.removeChild(int). This should not happen.");
532
            }
533
            childNodes.remove(index);
534
            child.setClassification(null);
535

    
536
            //update sortindex
537
            //TODO workaround (see sortIndex doc)
538
            this.countChildren = childNodes.size();
539
            child.setParent(null);
540
            child.setTreeIndex(null);
541
            updateSortIndex(index);
542
            child.setSortIndex(null);
543
        }
544
    }
545

    
546

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

    
560
    /**
561
     * Remove this taxonNode From its taxonomic parent
562
     *
563
     * @return true on success
564
     */
565
    public boolean delete(boolean deleteChildren){
566
        if(isTopmostNode()){
567
            return classification.deleteChildNode(this, deleteChildren);
568
        }else{
569
            return getParent().deleteChildNode(this, deleteChildren);
570
        }
571
    }
572

    
573
    @Override
574
    @Deprecated //for CDM lib internal use only, may be removed in future versions
575
    public int treeId() {
576
        if (this.classification == null){
577
        	logger.warn("TaxonNode has no classification. This should not happen.");  //#3840
578
        	return -1;
579
        }else{
580
        	return this.classification.getId();
581
        }
582
    }
583

    
584

    
585
    /**
586
     * Sets the parent of this taxon node to the given parent. Cleans up references to
587
     * old parents and sets the classification to the new parents classification
588
     *
589
     * @param parent
590
     */
591
    @Transient
592
    protected void setParentTreeNode(TaxonNode parent, int index){
593
        // remove ourselves from the old parent
594
        TaxonNode formerParent = this.getParent();
595
        formerParent = HibernateProxyHelper.deproxy(formerParent, TaxonNode.class);
596
        if (formerParent != null){
597
        	//special case, child already exists for same parent
598
            //FIXME document / check for correctness
599
            if (formerParent.equals(parent)){
600
                int currentIndex = formerParent.getChildNodes().indexOf(this);
601
                if (currentIndex != -1 && currentIndex < index){
602
                    index--;
603
                }
604
        	}
605

    
606
        	//remove from old parent
607
            formerParent.removeChildNode(this);
608
        }
609

    
610
        // set the new parent
611
        setParent(parent);
612

    
613
        // set the classification to the parents classification
614

    
615
        Classification classification = parent.getClassification();
616
        //FIXME also set the tree index here for performance reasons
617
        classification = HibernateProxyHelper.deproxy(classification, Classification.class);
618
        setClassificationRecursively(classification);
619

    
620
        // add this node to the parent's child nodes
621
        parent = HibernateProxyHelper.deproxy(parent, TaxonNode.class);
622
        List<TaxonNode> parentChildren = parent.getChildNodes();
623
       //TODO: Only as a workaround. We have to find out why merge creates null entries.
624
        while (parentChildren.contains(null)){
625
            parentChildren.remove(null);
626
        }
627
        parent.updateSortIndex(0);
628
        if (index > parent.getChildNodes().size()){
629
            index = parent.getChildNodes().size();
630
        }
631
        if (parentChildren.contains(this)){
632
            //avoid duplicates
633
            if (parentChildren.indexOf(this) < index){
634
                index = index -1;
635
            }
636
            parentChildren.remove(this);
637
            parentChildren.add(index, this);
638
        }else{
639
            parentChildren.add(index, this);
640
        }
641

    
642

    
643
        //sortIndex
644
        //TODO workaround (see sortIndex doc)
645
        this.getParent().updateSortIndex(index);
646
        //only for debugging
647
        if (! this.getSortIndex().equals(index)){
648
        	logger.warn("index and sortindex are not equal");
649
        }
650

    
651
        // update the children count
652
        parent.setCountChildren(parent.getChildNodes().size());
653
    }
654

    
655
	/**
656
	 * As long as the sort index is not correctly handled through hibernate this is a workaround method
657
	 * to update the sort index manually
658
	 * @param parentChildren
659
	 * @param index
660
	 */
661
	private void updateSortIndex(int index) {
662
	    List<TaxonNode> children = this.getChildNodes();
663

    
664

    
665
	    for(int i = index; i < children.size(); i++){
666
        	TaxonNode child = children.get(i);
667
        	if (child != null){
668
//        		child = CdmBase.deproxy(child, TaxonNode.class);  //deproxy not needed as long as setSortIndex is protected or public #4200
669
        		child.setSortIndex(i);
670
        	}else{
671
        		String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";
672
        		throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));
673
        	}
674
        }
675
	}
676

    
677

    
678

    
679

    
680
    /**
681
     * Returns a set containing this node and all nodes that are descendants of this node
682
     *
683
     * @return
684
     */
685

    
686
    protected Set<TaxonNode> getDescendants(){
687
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
688

    
689
        nodeSet.add(this);
690

    
691
        for(TaxonNode childNode : getChildNodes()){
692
            nodeSet.addAll(childNode.getDescendants());
693
        }
694

    
695
        return nodeSet;
696
    }
697

    
698
    /**
699
     * Returns a set containing a clone of this node and of all nodes that are descendants of this node
700
     *
701
     * @return
702
     */
703
    protected TaxonNode cloneDescendants(){
704

    
705
        TaxonNode clone = (TaxonNode)this.clone();
706
        TaxonNode childClone;
707

    
708
        for(TaxonNode childNode : getChildNodes()){
709
            childClone = (TaxonNode) childNode.clone();
710
            for (TaxonNode childChild:childNode.getChildNodes()){
711
                childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());
712
            }
713
            clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference());
714
            //childClone.addChildNode(childNode.cloneDescendants());
715
        }
716
        return clone;
717
    }
718

    
719
    /**
720
     * Returns a
721
     *
722
     * @return
723
     */
724
    protected Set<TaxonNode> getAncestors(){
725
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
726
       // nodeSet.add(this);
727
        if(this.getParent() != null){
728
        	TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);
729
            nodeSet.addAll(parent.getAncestors());
730
        }
731
        return nodeSet;
732
    }
733
    public TaxonNode getAncestorOfRank(Rank rank){
734
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
735
       // nodeSet.add(this);
736
        TaxonBase taxon = CdmBase.deproxy(this.getTaxon(), Taxon.class);
737
        TaxonNameBase name = CdmBase.deproxy(taxon.getName(), TaxonNameBase.class);
738
        if (name.getRank().isHigher(rank)){
739
        	return null;
740
        }
741
        if (name.getRank().equals(rank)){
742
        	return this;
743
        }
744
        if(this.getParent() != null ){
745
        	TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);
746
            return parent.getAncestorOfRank(rank);
747
        }
748
		return null;
749

    
750
    }
751

    
752
    /**
753
     * Whether this TaxonNode is a direct child of the classification TreeNode
754
     * @return
755
     */
756
    @Transient
757
    public boolean isTopmostNode(){
758
    	boolean parentCheck = false;
759
    	boolean classificationCheck = false;
760

    
761
    	if(getParent() != null) {
762
    		if(getParent().getTaxon() == null) {
763
    			parentCheck = true;
764
    		}
765
    	}
766

    
767
    	//TODO remove
768
    	// FIXME This should work but doesn't, due to missing sort indexes, can be removed after fixing #4200, #4098
769
    	if (classification != null){
770
    		classificationCheck = classification.getRootNode().getChildNodes().contains(this);
771
    	}else{
772
    		classificationCheck = false;
773
    	}
774

    
775
    	// The following is just for logging purposes for the missing sort indexes problem
776
    	// ticket #4098
777
    	if(parentCheck != classificationCheck) {
778
    		logger.warn("isTopmost node check " + parentCheck + " not same as classificationCheck : " + classificationCheck + " for taxon node ");
779
    		if(this.getParent() != null) {
780
    			logger.warn("-- with parent uuid " + this.getParent().getUuid().toString());
781
    			logger.warn("-- with parent id " + this.getParent().getId());
782
    			for(TaxonNode node : this.getParent().getChildNodes()) {
783
    				if(node == null) {
784
    					logger.warn("-- child node is null");
785
    				} else if (node.getTaxon() == null) {
786
    					logger.warn("-- child node taxon is null");
787
    				}
788
    			}
789
    			logger.warn("-- parent child count" + this.getParent().getChildNodes().size());
790
    		}
791
    	}
792

    
793
    	return parentCheck;
794
    }
795

    
796
    /**
797
     * Whether this TaxonNode is a descendant of the given TaxonNode
798
     *
799
     * Caution: use this method with care on big branches. -> performance and memory hungry
800
     *
801
     * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
802
     * other direction (up). It will always result in a rather small set of consecutive parents beeing
803
     * generated.
804
     *
805
     * TODO implement more efficiently without generating the set of descendants first
806
     *
807
     * @param possibleParent
808
     * @return true if this is a descendant
809
     */
810
    @Transient
811
    public boolean isDescendant(TaxonNode possibleParent){
812
    	if (this.treeIndex() == null || possibleParent.treeIndex() == null) {
813
    		return false;
814
    	}
815
    	return possibleParent == null ? false : this.treeIndex().startsWith(possibleParent.treeIndex() );
816
    }
817

    
818
    /**
819
     * Whether this TaxonNode is an ascendant of the given TaxonNode
820
     *
821
     * @param possibleChild
822
     * @return true if there are ascendants
823
     */
824
    @Transient
825
    public boolean isAncestor(TaxonNode possibleChild){
826
    	if (this.treeIndex() == null || possibleChild.treeIndex() == null) {
827
    		return false;
828
    	}
829
       // return possibleChild == null ? false : possibleChild.getAncestors().contains(this);
830
        return possibleChild == null ? false : possibleChild.treeIndex().startsWith(this.treeIndex());
831
    }
832

    
833
    /**
834
     * Whether this taxon has child nodes
835
     *
836
     * @return true if the taxonNode has childNodes
837
     */
838
    @Transient
839
    @Override
840
    public boolean hasChildNodes(){
841
        return childNodes.size() > 0;
842
    }
843

    
844

    
845
    public boolean hasTaxon() {
846
        return (taxon!= null);
847
    }
848

    
849
    /**
850
     * @return
851
     */
852
    @Transient
853
    public Rank getNullSafeRank() {
854
        return hasTaxon() ? getTaxon().getNullSafeRank() : null;
855
    }
856

    
857

    
858

    
859
//*********************** CLONE ********************************************************/
860
    /**
861
     * Clones <i>this</i> taxon node. This is a shortcut that enables to create
862
     * a new instance that differs only slightly from <i>this</i> taxon node by
863
     * modifying only some of the attributes.<BR><BR>
864
     * The child nodes are not copied.<BR>
865
     * The taxon and parent are the same as for the original taxon node. <BR>
866
     *
867
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
868
     * @see java.lang.Object#clone()
869
     */
870
    @Override
871
    public Object clone()  {
872
        try{
873
            TaxonNode result = (TaxonNode)super.clone();
874
            result.getTaxon().addTaxonNode(result);
875
            result.childNodes = new ArrayList<TaxonNode>();
876
            result.countChildren = 0;
877

    
878
            //agents
879
            result.agentRelations = new HashSet<TaxonNodeAgentRelation>();
880
            for (TaxonNodeAgentRelation rel : this.agentRelations){
881
                result.addAgentRelation((TaxonNodeAgentRelation)rel.clone());
882
            }
883

    
884
            return result;
885
        }catch (CloneNotSupportedException e) {
886
            logger.warn("Object does not implement cloneable");
887
            e.printStackTrace();
888
            return null;
889
        }
890
    }
891

    
892
}
(14-14/21)