Project

General

Profile

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

    
216

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

    
227

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

    
236

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

    
251

    
252
    @Override
253
    public String getMicroReference() {
254
        return microReferenceForParentChildRelation;
255
    }
256
    public void setMicroReference(String microReference) {
257
        this.microReferenceForParentChildRelation = microReference;
258
    }
259

    
260

    
261
    @Override
262
    public Reference getReference() {
263
        return referenceForParentChildRelation;
264
    }
265
    public void setReference(Reference reference) {
266
        this.referenceForParentChildRelation = reference;
267
    }
268

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

    
282

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

    
302
// ****************** Agent Relations ****************************/
303

    
304

    
305
    /**
306
     * @return
307
     */
308
    public Set<TaxonNodeAgentRelation> getAgentRelations() {
309
        return this.agentRelations;
310
    }
311

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

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

    
332
//********************
333

    
334
    //synonymToBeused
335
    public Synonym getSynonymToBeUsed() {
336
        return synonymToBeUsed;
337
    }
338
    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
339
        this.synonymToBeUsed = synonymToBeUsed;
340
    }
341

    
342

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

    
354

    
355

    
356
//************************ METHODS **************************/
357

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

    
363

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

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

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

    
411
        child.setParentTreeNode(this, index);
412

    
413
        child.setReference(reference);
414
        child.setMicroReference(microReference);
415

    
416
        return child;
417
    }
418

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

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

    
448
        ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
449
        for(TaxonNode childNode : childNodes){
450
            node.deleteChildNode(childNode);
451
        }
452

    
453
        return result;
454
    }
455

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

    
480
        return result;
481
    }
482

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

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

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

    
525
        if (child != null){
526

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

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

    
547

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

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

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

    
585

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

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

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

    
614
        // set the classification to the parents classification
615

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

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

    
626
        HHH_9751_Util.removeAllNull(parentChildren);
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
	    if (this.hasChildNodes()){
663
    	    List<TaxonNode> children = this.getChildNodes();
664
    	    for(int i = index; i < children.size(); i++){
665
                	TaxonNode child = children.get(i);
666
                	if (child != null){
667
        //        		child = CdmBase.deproxy(child, TaxonNode.class);  //deproxy not needed as long as setSortIndex is protected or public #4200
668
                		child.setSortIndex(i);
669
                	}else{
670
                		String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";
671
                		throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));
672
                	}
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 all ancestor nodes of this node
721
     *
722
     * @return a set of all parent nodes
723
     */
724
    protected Set<TaxonNode> getAncestors(){
725
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
726
        if(this.getParent() != null){
727
        	TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);
728
        	nodeSet.add(parent);
729
            nodeSet.addAll(parent.getAncestors());
730
        }
731
        return nodeSet;
732
    }
733

    
734
    /**
735
     * Retrieves the first ancestor of the given rank
736
     * @param rank the rank the ancestor should have
737
     * @return the first found instance of a parent taxon node with the given rank
738
     */
739
    public TaxonNode getAncestorOfRank(Rank rank){
740
        TaxonBase taxon = HibernateProxyHelper.deproxy(this.getTaxon(), Taxon.class);
741
        if (taxon == null){
742
            return null;
743
        }
744
        TaxonNameBase name = HibernateProxyHelper.deproxy(taxon.getName(), TaxonNameBase.class);
745
        if (name.getRank().isHigher(rank)){
746
        	return null;
747
        }
748
        if (name.getRank().equals(rank)){
749
        	return this;
750
        }
751

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

    
758
    }
759

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

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

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

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

    
801
    	return parentCheck;
802
    }
803

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

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

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

    
852

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

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

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

    
874

    
875

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

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

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

    
909
}
(14-14/21)