Project

General

Profile

Download (29.9 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.LazyInitializationException;
37
import org.hibernate.annotations.Cascade;
38
import org.hibernate.annotations.CascadeType;
39
import org.hibernate.annotations.Index;
40
import org.hibernate.annotations.Table;
41
import org.hibernate.envers.Audited;
42
import org.hibernate.search.annotations.ContainedIn;
43
import org.hibernate.search.annotations.IndexedEmbedded;
44

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

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

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

    
99

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

    
107

    
108
    @XmlElement(name = "treeIndex")
109
    @Column(length=255)
110
    private String treeIndex;
111

    
112

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

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

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

    
137
	@XmlElement(name = "reference")
138
    @XmlIDREF
139
    @XmlSchemaType(name = "IDREF")
140
    @ManyToOne(fetch = FetchType.LAZY)
141
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
142
    private Reference referenceForParentChildRelation;
143

    
144
    @XmlElement(name = "microReference")
145
    private String microReferenceForParentChildRelation;
146

    
147
    @XmlElement(name = "countChildren")
148
    private int countChildren;
149

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

    
158

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

    
168
// ******************** CONSTRUCTOR **********************************************/
169

    
170
    protected TaxonNode(){super();}
171

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

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

    
196
// ************************* GETTER / SETTER *******************************/
197

    
198
    @Transient
199
    public Integer getSortIndex() {
200
        TaxonNode parent = HibernateProxyHelper.deproxy(this.parent, TaxonNode.class);
201
        parent.removeNullValueFromChildren();
202

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

    
219

    
220
    public Taxon getTaxon() {
221
        return taxon;
222
    }
223
    protected void setTaxon(Taxon taxon) {
224
        this.taxon = taxon;
225
        if (taxon != null){
226
            taxon.addTaxonNode(this);
227
        }
228
    }
229

    
230

    
231
    @Override
232
    public List<TaxonNode> getChildNodes() {
233
        return childNodes;
234
    }
235
	protected void setChildNodes(List<TaxonNode> childNodes) {
236
		this.childNodes = childNodes;
237
	}
238

    
239

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

    
254

    
255
    @Override
256
    public String getMicroReference() {
257
        return microReferenceForParentChildRelation;
258
    }
259
    public void setMicroReference(String microReference) {
260
        this.microReferenceForParentChildRelation = microReference;
261
    }
262

    
263

    
264
    @Override
265
    public Reference getReference() {
266
        return referenceForParentChildRelation;
267
    }
268
    public void setReference(Reference reference) {
269
        this.referenceForParentChildRelation = reference;
270
    }
271

    
272
    //countChildren
273
    public int getCountChildren() {
274
        return countChildren;
275
    }
276
    /**
277
     * @deprecated for internal use only
278
     * @param countChildren
279
     */
280
    @Deprecated
281
    protected void setCountChildren(int countChildren) {
282
        this.countChildren = countChildren;
283
    }
284

    
285

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

    
305
// ****************** Agent Relations ****************************/
306

    
307

    
308
    /**
309
     * @return
310
     */
311
    public Set<TaxonNodeAgentRelation> getAgentRelations() {
312
        return this.agentRelations;
313
    }
314

    
315
    public TaxonNodeAgentRelation addAgentRelation(DefinedTerm type, TeamOrPersonBase<?> agent){
316
        TaxonNodeAgentRelation result = TaxonNodeAgentRelation.NewInstance(this, agent, type);
317
        return result;
318
    }
319
    /**
320
     * @param nodeAgentRelation
321
     */
322
    protected void addAgentRelation(TaxonNodeAgentRelation agentRelation) {
323
        agentRelation.setTaxonNode(this);
324
        this.agentRelations.add(agentRelation);
325
    }
326

    
327
    /**
328
     * @param nodeAgentRelation
329
     */
330
    public void removeNodeAgent(TaxonNodeAgentRelation agentRelation) {
331
        agentRelation.setTaxonNode(this);
332
        agentRelations.remove(agentRelation);
333
    }
334

    
335
//********************
336

    
337
    //synonymToBeused
338
    public Synonym getSynonymToBeUsed() {
339
        return synonymToBeUsed;
340
    }
341
    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
342
        this.synonymToBeUsed = synonymToBeUsed;
343
    }
344

    
345

    
346
    //treeindex
347
    @Override
348
    public String treeIndex() {
349
        return treeIndex;
350
    }
351
    @Override
352
    @Deprecated //for CDM lib internal use only, may be removed in future versions
353
    public void setTreeIndex(String treeIndex) {
354
        this.treeIndex = treeIndex;
355
    }
356

    
357

    
358

    
359
//************************ METHODS **************************/
360

    
361
   @Override
362
    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
363
        return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
364
    }
365

    
366

    
367
    @Override
368
    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
369
        Classification classification = HibernateProxyHelper.deproxy(this.getClassification(), Classification.class);
370
        taxon = HibernateProxyHelper.deproxy(taxon, Taxon.class);
371
        if (this.getClassification().isTaxonInTree(taxon)){
372
            throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));
373
       }
374
       return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
375
    }
376

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

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

    
414
        child.setParentTreeNode(this, index);
415

    
416
        child.setReference(reference);
417
        child.setMicroReference(microReference);
418

    
419
        return child;
420
    }
421

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

    
444
    @Override
445
    public boolean deleteChildNode(TaxonNode node) {
446
        boolean result = removeChildNode(node);
447
        Taxon taxon = node.getTaxon();
448
        node.setTaxon(null);
449
        taxon.removeTaxonNode(node);
450

    
451
        ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
452
        for(TaxonNode childNode : childNodes){
453
            node.deleteChildNode(childNode);
454
        }
455

    
456
        return result;
457
    }
458

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

    
483
        return result;
484
    }
485

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

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

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

    
528
        if (child != null){
529

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

    
540
            //update sortindex
541
            //TODO workaround (see sortIndex doc)
542
            this.countChildren = childNodes.size();
543
            child.setParent(null);
544
            child.setTreeIndex(null);
545
            updateSortIndex(index);
546
            child.setSortIndex(null);
547
        }
548
    }
549

    
550

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

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

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

    
588

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

    
610
        	//remove from old parent
611
            formerParent.removeChildNode(this);
612
        }
613

    
614
        // set the new parent
615
        setParent(parent);
616

    
617
        // set the classification to the parents classification
618

    
619
        Classification classification = parent.getClassification();
620
        //FIXME also set the tree index here for performance reasons
621
        classification = HibernateProxyHelper.deproxy(classification, Classification.class);
622
        setClassificationRecursively(classification);
623

    
624
        // add this node to the parent's child nodes
625
        parent = HibernateProxyHelper.deproxy(parent, TaxonNode.class);
626
        List<TaxonNode> parentChildren = parent.getChildNodes();
627
       //TODO: Only as a workaround. We have to find out why merge creates null entries.
628

    
629
        HHH_9751_Util.removeAllNull(parentChildren);
630
        parent.updateSortIndex(0);
631
        if (index > parent.getChildNodes().size()){
632
            index = parent.getChildNodes().size();
633
        }
634
        if (parentChildren.contains(this)){
635
            //avoid duplicates
636
            if (parentChildren.indexOf(this) < index){
637
                index = index -1;
638
            }
639
            parentChildren.remove(this);
640
            parentChildren.add(index, this);
641
        }else{
642
            parentChildren.add(index, this);
643
        }
644

    
645

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

    
654
        // update the children count
655
        parent.setCountChildren(parent.getChildNodes().size());
656
    }
657

    
658
	/**
659
	 * As long as the sort index is not correctly handled through hibernate this is a workaround method
660
	 * to update the sort index manually
661
	 * @param parentChildren
662
	 * @param index
663
	 */
664
	private void updateSortIndex(int index) {
665
	    if (this.hasChildNodes()){
666
    	    List<TaxonNode> children = this.getChildNodes();
667
    	    for(int i = index; i < children.size(); i++){
668
                	TaxonNode child = children.get(i);
669
                	if (child != null){
670
        //        		child = CdmBase.deproxy(child, TaxonNode.class);  //deproxy not needed as long as setSortIndex is protected or public #4200
671
                		child.setSortIndex(i);
672
                	}else{
673
                		String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";
674
                		throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));
675
                	}
676
            }
677
	    }
678
	}
679

    
680

    
681

    
682

    
683
    /**
684
     * Returns a set containing this node and all nodes that are descendants of this node
685
     *
686
     * @return
687
     */
688

    
689
    protected Set<TaxonNode> getDescendants(){
690
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
691

    
692
        nodeSet.add(this);
693

    
694
        for(TaxonNode childNode : getChildNodes()){
695
            nodeSet.addAll(childNode.getDescendants());
696
        }
697

    
698
        return nodeSet;
699
    }
700

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

    
708
        TaxonNode clone = (TaxonNode)this.clone();
709
        TaxonNode childClone;
710

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

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

    
751
        if(this.getParent() != null){
752
        	TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);
753
            return parent.getAncestorOfRank(rank);
754
        }
755
		return null;
756

    
757
    }
758

    
759
    /**
760
     * Whether this TaxonNode is a direct child of the classification TreeNode
761
     * @return
762
     */
763
    @Transient
764
    public boolean isTopmostNode(){
765
    	boolean parentCheck = false;
766
    	boolean classificationCheck = false;
767

    
768
    	if(getParent() != null) {
769
    		if(getParent().getTaxon() == null) {
770
    			parentCheck = true;
771
    		}
772
    	}
773

    
774
    	//TODO remove
775
    	// FIXME This should work but doesn't, due to missing sort indexes, can be removed after fixing #4200, #4098
776
    	if (classification != null){
777
    		classificationCheck = classification.getRootNode().getChildNodes().contains(this);
778
    	}else{
779
    		classificationCheck = false;
780
    	}
781

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

    
800
    	return parentCheck;
801
    }
802

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

    
825
    /**
826
     * Whether this TaxonNode is an ascendant of the given TaxonNode
827
     *
828
     * @param possibleChild
829
     * @return true if there are ascendants
830
     */
831
    @Transient
832
    public boolean isAncestor(TaxonNode possibleChild){
833
    	if (this.treeIndex() == null || possibleChild.treeIndex() == null) {
834
    		return false;
835
    	}
836
       // return possibleChild == null ? false : possibleChild.getAncestors().contains(this);
837
        return possibleChild == null ? false : possibleChild.treeIndex().startsWith(this.treeIndex());
838
    }
839

    
840
    /**
841
     * Whether this taxon has child nodes
842
     *
843
     * @return true if the taxonNode has childNodes
844
     */
845
    @Transient
846
    @Override
847
    public boolean hasChildNodes(){
848
        return childNodes.size() > 0;
849
    }
850

    
851

    
852
    public boolean hasTaxon() {
853
        return (taxon!= null);
854
    }
855

    
856
    /**
857
     * @return
858
     */
859
    @Transient
860
    public Rank getNullSafeRank() {
861
        return hasTaxon() ? getTaxon().getNullSafeRank() : null;
862
    }
863

    
864
    public void removeNullValueFromChildren(){
865
        try {
866
            HHH_9751_Util.removeAllNull(childNodes);
867
            this.updateSortIndex(0);
868
        } catch (LazyInitializationException e) {
869
            logger.info("Cannot clean up uninitialized children without a session, skipping.");
870
        }
871
    }
872

    
873

    
874

    
875
//*********************** CLONE ********************************************************/
876
    /**
877
     * Clones <i>this</i> taxon node. This is a shortcut that enables to create
878
     * a new instance that differs only slightly from <i>this</i> taxon node by
879
     * modifying only some of the attributes.<BR><BR>
880
     * The child nodes are not copied.<BR>
881
     * The taxon and parent are the same as for the original taxon node. <BR>
882
     *
883
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
884
     * @see java.lang.Object#clone()
885
     */
886
    @Override
887
    public Object clone()  {
888
        try{
889
            TaxonNode result = (TaxonNode)super.clone();
890
            result.getTaxon().addTaxonNode(result);
891
            result.childNodes = new ArrayList<TaxonNode>();
892
            result.countChildren = 0;
893

    
894
            //agents
895
            result.agentRelations = new HashSet<TaxonNodeAgentRelation>();
896
            for (TaxonNodeAgentRelation rel : this.agentRelations){
897
                result.addAgentRelation((TaxonNodeAgentRelation)rel.clone());
898
            }
899

    
900
            return result;
901
        }catch (CloneNotSupportedException e) {
902
            logger.warn("Object does not implement cloneable");
903
            e.printStackTrace();
904
            return null;
905
        }
906
    }
907

    
908
}
(14-14/21)