Project

General

Profile

Download (26.2 KB) Statistics
| Branch: | Tag: | Revision:
1 ef846006 Andreas Müller
// $Id$
2
/**
3
* Copyright (C) 2007 EDIT
4 3a7ec38f Andreas Kohlbecker
* European Distributed Institute of Taxonomy
5 ef846006 Andreas Müller
* http://www.e-taxonomy.eu
6 3a7ec38f Andreas Kohlbecker
*
7 ef846006 Andreas Müller
* 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 3c6d0f75 n.hoffmann
import java.util.ArrayList;
14 f4cfc931 n.hoffmann
import java.util.HashSet;
15 efdb6847 Andreas Müller
import java.util.List;
16 f4cfc931 n.hoffmann
import java.util.Set;
17 ef846006 Andreas Müller
18
import javax.persistence.Entity;
19
import javax.persistence.FetchType;
20
import javax.persistence.ManyToOne;
21
import javax.persistence.OneToMany;
22 efdb6847 Andreas Müller
import javax.persistence.OrderBy;
23 d0fbf21c Andreas Müller
import javax.persistence.OrderColumn;
24 f480cc13 Andreas Kohlbecker
import javax.persistence.Transient;
25 897c420b Andreas Müller
import javax.validation.constraints.Size;
26 ef846006 Andreas Müller
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 c14fc0ed Andreas Müller
import org.hibernate.annotations.Index;
39
import org.hibernate.annotations.Table;
40 ef846006 Andreas Müller
import org.hibernate.envers.Audited;
41 ad190552 Andreas Kohlbecker
import org.hibernate.search.annotations.ContainedIn;
42
import org.hibernate.search.annotations.Indexed;
43
import org.hibernate.search.annotations.IndexedEmbedded;
44 ef846006 Andreas Müller
45 bdb534d1 n.hoffmann
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
46 ef846006 Andreas Müller
import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
47 57157341 Andreas Müller
import eu.etaxonomy.cdm.model.common.CdmBase;
48 f6c2e10f Andreas Müller
import eu.etaxonomy.cdm.model.common.ITreeNode;
49 1d36aa54 Andreas Müller
import eu.etaxonomy.cdm.model.reference.Reference;
50 ef846006 Andreas Müller
51
/**
52
 * @author a.mueller
53
 * @created 31.03.2009
54
 */
55
@XmlAccessorType(XmlAccessType.FIELD)
56
@XmlType(name = "TaxonNode", propOrder = {
57 4cc79481 Andreas Kohlbecker
    "classification",
58
    "taxon",
59 ef846006 Andreas Müller
    "parent",
60 0365fb40 Andreas Müller
    "treeIndex",
61 c1b9474e Andreas Müller
    "sortIndex",
62 ef846006 Andreas Müller
    "childNodes",
63
    "referenceForParentChildRelation",
64
    "microReferenceForParentChildRelation",
65
    "countChildren",
66
    "synonymToBeUsed"
67
})
68
@XmlRootElement(name = "TaxonNode")
69
@Entity
70 ad190552 Andreas Kohlbecker
@Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonNode")
71 ef846006 Andreas Müller
@Audited
72 c14fc0ed Andreas Müller
@Table(appliesTo="TaxonNode", indexes = { @Index(name = "taxonNodeTreeIndex", columnNames = { "treeIndex" }) })
73 f6c2e10f Andreas Müller
public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITreeNode<TaxonNode>, Cloneable{
74 3a7ec38f Andreas Kohlbecker
    private static final long serialVersionUID = -4743289894926587693L;
75
    private static final Logger logger = Logger.getLogger(TaxonNode.class);
76
77
    @XmlElement(name = "taxon")
78
    @XmlIDREF
79
    @XmlSchemaType(name = "IDREF")
80
    @ManyToOne(fetch = FetchType.LAZY)
81
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
82 ad190552 Andreas Kohlbecker
    @ContainedIn
83 3a7ec38f Andreas Kohlbecker
    private Taxon taxon;
84
85
86
    @XmlElement(name = "parent")
87
    @XmlIDREF
88
    @XmlSchemaType(name = "IDREF")
89
    @ManyToOne(fetch = FetchType.LAZY)
90
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
91
    private TaxonNode parent;
92 4cc79481 Andreas Kohlbecker
93
94 0365fb40 Andreas Müller
    @XmlElement(name = "treeIndex")
95 897c420b Andreas Müller
    @Size(max=255)
96 4cc79481 Andreas Kohlbecker
    private String treeIndex;
97
98 3a7ec38f Andreas Kohlbecker
99
    @XmlElement(name = "classification")
100
    @XmlIDREF
101
    @XmlSchemaType(name = "IDREF")
102
    @ManyToOne(fetch = FetchType.LAZY)
103 903cecc9 Cherian Mathew
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
104 5d1ae6b5 Andreas Müller
//	TODO @NotNull // avoids creating a UNIQUE key for this field
105 08fcc2cd Andreas Kohlbecker
    @IndexedEmbedded
106 3a7ec38f Andreas Kohlbecker
    private Classification classification;
107
108
    @XmlElementWrapper(name = "childNodes")
109
    @XmlElement(name = "childNode")
110 ef846006 Andreas Müller
    @XmlIDREF
111
    @XmlSchemaType(name = "IDREF")
112 a3326dd6 Andreas Müller
    //see https://dev.e-taxonomy.eu/trac/ticket/3722
113 d0fbf21c Andreas Müller
    @OrderColumn(name="sortIndex")
114 efdb6847 Andreas Müller
    @OrderBy("sortIndex")
115 ef846006 Andreas Müller
    @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
116 08fcc2cd Andreas Kohlbecker
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
117 efdb6847 Andreas Müller
    private List<TaxonNode> childNodes = new ArrayList<TaxonNode>();
118 4cc79481 Andreas Kohlbecker
119 a3326dd6 Andreas Müller
    //see https://dev.e-taxonomy.eu/trac/ticket/3722
120 c40252b2 Andreas Müller
    //see https://dev.e-taxonomy.eu/trac/ticket/4200
121 d0fbf21c Andreas Müller
    private Integer sortIndex = -1;
122 8640593e Katja Luther
123
	@XmlElement(name = "reference")
124 3a7ec38f Andreas Kohlbecker
    @XmlIDREF
125
    @XmlSchemaType(name = "IDREF")
126
    @ManyToOne(fetch = FetchType.LAZY)
127 903cecc9 Cherian Mathew
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
128 95d79c4d Andreas Müller
    private Reference<?> referenceForParentChildRelation;
129 3a7ec38f Andreas Kohlbecker
130
    @XmlElement(name = "microReference")
131
    private String microReferenceForParentChildRelation;
132
133
    @XmlElement(name = "countChildren")
134
    private int countChildren;
135
136 ef846006 Andreas Müller
//	private Taxon originalConcept;
137
//	//or
138 3a7ec38f Andreas Kohlbecker
    @XmlElement(name = "synonymToBeUsed")
139
    @XmlIDREF
140
    @XmlSchemaType(name = "IDREF")
141
    @ManyToOne(fetch = FetchType.LAZY)
142 903cecc9 Cherian Mathew
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
143 3a7ec38f Andreas Kohlbecker
    private Synonym synonymToBeUsed;
144
145 61380896 Andreas Müller
// ******************** CONSTRUCTOR **********************************************/
146 3a7ec38f Andreas Kohlbecker
147 61380896 Andreas Müller
    protected TaxonNode(){super();}
148 3a7ec38f Andreas Kohlbecker
149
    /**
150
     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
151
     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
152
     * @param taxon
153
     * @param classification
154
     * @deprecated setting of classification is handled in the addTaxonNode() method,
155
     * use TaxonNode(taxon) instead
156
     */
157 4cc79481 Andreas Kohlbecker
    @Deprecated
158 3a7ec38f Andreas Kohlbecker
    protected TaxonNode (Taxon taxon, Classification classification){
159
        this(taxon);
160
        setClassification(classification);
161
    }
162
163
    /**
164
     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
165
     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
166
     *
167
     * @param taxon
168
     */
169
    protected TaxonNode(Taxon taxon){
170
        setTaxon(taxon);
171
    }
172
173 c40252b2 Andreas Müller
// ************************* GETTER / SETTER *******************************/
174
    
175
    public Integer getSortIndex() {
176
		return sortIndex;
177
	}
178
    /**
179
     * SortIndex shall be handled only internally, therefore not public.
180 df879c1f Andreas Müller
     * However, as javaassist only supports protected methods it needs to be protected, not private.
181
     * Alternatively we could use deproxy on every call of this method (see commented code)
182 c40252b2 Andreas Müller
     * @param i
183
     * @return
184
     * @deprecated for internal use only
185
     */
186 df879c1f Andreas Müller
     protected void setSortIndex(Integer i) { 
187
//    	 CdmBase.deproxy(this, TaxonNode.class).sortIndex = i;  //alternative solution for private, DON'T remove
188
    	 sortIndex = i;
189 c40252b2 Andreas Müller
	}
190
    
191 3a7ec38f Andreas Kohlbecker
192 c40252b2 Andreas Müller
    public Taxon getTaxon() {
193
        return taxon;
194
    }
195
    protected void setTaxon(Taxon taxon) {
196
        this.taxon = taxon;
197
        if (taxon != null){
198
            taxon.addTaxonNode(this);
199
        }
200
    }
201
    
202 3a7ec38f Andreas Kohlbecker
203 c40252b2 Andreas Müller
    @Override
204
    public List<TaxonNode> getChildNodes() {
205
        return childNodes;
206
    }
207
	protected void setChildNodes(List<TaxonNode> childNodes) {
208
		this.childNodes = childNodes;	
209
	}
210
211
    
212
    public Classification getClassification() {
213
        return classification;
214
    }
215
    /**
216
     * THIS METHOD SHOULD NOT BE CALLED!
217
     * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
218
     * @param classification
219
     * @deprecated for internal use only
220
     */
221
    protected void setClassification(Classification classification) {
222
        this.classification = classification;
223
    }
224
    
225 3a7ec38f Andreas Kohlbecker
226 efdb6847 Andreas Müller
    @Override
227 c40252b2 Andreas Müller
    public String getMicroReference() {
228
        return microReferenceForParentChildRelation;
229
    }
230
    public void setMicroReference(String microReference) {
231
        this.microReferenceForParentChildRelation = microReference;
232
    }
233
    
234
235
    @Override
236
    public Reference getReference() {
237
        return referenceForParentChildRelation;
238
    }
239
    public void setReference(Reference reference) {
240
        this.referenceForParentChildRelation = reference;
241
    }
242
243
    //countChildren
244
    public int getCountChildren() {
245
        return countChildren;
246
    }
247
    /**
248
     * @deprecated for internal use only
249
     * @param countChildren
250
     */
251
    protected void setCountChildren(int countChildren) {
252
        this.countChildren = countChildren;
253
    }
254
    
255
256
    //parent
257
    @Override
258
    public TaxonNode getParent(){
259
        return parent;
260
    }
261
    /**
262
     * Sets the parent of this taxon node.<BR>
263
     *
264
     * In most cases you would want to call setParentTreeNode(ITreeNode) which
265
     * handles updating of the bidirectional relationship
266
     *
267
     * @see setParentTreeNode(ITreeNode)
268
     * @param parent
269
     *
270
     */
271
    protected void setParent(TaxonNode parent) {
272
        this.parent = parent;
273
    }
274
    
275
276
    //synonymToBeused
277
    public Synonym getSynonymToBeUsed() {
278
        return synonymToBeUsed;
279
    }
280
    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
281
        this.synonymToBeUsed = synonymToBeUsed;
282
    }
283
    
284
285
    //treeindex
286
    @Override
287
    public String treeIndex() {
288
        return treeIndex;
289
    }
290
    @Override
291
    @Deprecated //for CDM lib internal use only, may be removed in future versions
292
    public void setTreeIndex(String treeIndex) {
293
        this.treeIndex = treeIndex;
294
    }
295
    
296
297
298
//************************ METHODS **************************/
299
300
   @Override
301 efdb6847 Andreas Müller
    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
302 4cc79481 Andreas Kohlbecker
        return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
303 d0fbf21c Andreas Müller
    }
304
305
306
    @Override
307
    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
308 3a7ec38f Andreas Kohlbecker
        if (this.getClassification().isTaxonInTree(taxon)){
309 67faa037 Andreas Müller
            throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));
310 d0fbf21c Andreas Müller
       }
311 4cc79481 Andreas Kohlbecker
       return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
312 3a7ec38f Andreas Kohlbecker
    }
313
314
    /**
315
     * Moves a taxon node to a new parent. Descendents of the node are moved as well
316
     *
317
     * @param childNode the taxon node to be moved to the new parent
318
     * @return the child node in the state of having a new parent
319
     */
320 efdb6847 Andreas Müller
    @Override
321
    public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference){
322
        addChildNode(childNode, childNodes.size(), reference, microReference);
323
        return childNode;
324
    }
325 4cc79481 Andreas Kohlbecker
326
    /**
327
     * Inserts the given taxon node in the list of children of <i>this</i> taxon node
328
     * at the given (index + 1) position. If the given index is out of bounds
329
     * an exception will arise.<BR>
330
     * Due to bidirectionality this method must also assign <i>this</i> taxon node
331
     * as the parent of the given child.
332
     *
333
     * @param	child	the taxon node to be added
334
     * @param	index	the integer indicating the position at which the child
335
     * 					should be added
336
     * @see				#getChildNodes()
337
     * @see				#addChildNode(TaxonNode, Reference, String, Synonym)
338
     * @see				#deleteChildNode(TaxonNode)
339
     * @see				#deleteChildNode(int)
340
     */
341 d0fbf21c Andreas Müller
    @Override
342
    public TaxonNode addChildNode(TaxonNode child, int index, Reference reference, String microReference){
343 4cc79481 Andreas Kohlbecker
        if (index < 0 || index > childNodes.size() + 1){
344
            throw new IndexOutOfBoundsException("Wrong index: " + index);
345
        }
346
           // check if this node is a descendant of the childNode
347 5d00156f Andreas Müller
        if(child.getParent() != this && child.isAncestor(this)){
348 3a7ec38f Andreas Kohlbecker
            throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
349
        }
350 4cc79481 Andreas Kohlbecker
351 c40252b2 Andreas Müller
        child.setParentTreeNode(this, index); 
352
        
353 efdb6847 Andreas Müller
        child.setReference(reference);
354
        child.setMicroReference(microReference);
355 3a7ec38f Andreas Kohlbecker
356 d0fbf21c Andreas Müller
        return child;
357 4cc79481 Andreas Kohlbecker
    }
358 3a7ec38f Andreas Kohlbecker
359
    /**
360 61380896 Andreas Müller
     * Sets this nodes classification. Updates classification of child nodes recursively.
361 3a7ec38f Andreas Kohlbecker
     *
362 1d875b90 Andreas Müller
     * If the former and the actual tree are equal() this method does nothing.
363
     * 
364
     * @throws IllegalArgumentException if newClassifciation is null
365 3a7ec38f Andreas Kohlbecker
     *
366 813b3df9 Andreas Müller
     * @param newClassification
367 3a7ec38f Andreas Kohlbecker
     */
368
    @Transient
369 813b3df9 Andreas Müller
    private void setClassificationRecursively(Classification newClassification) {
370
        if (newClassification == null){
371
        	throw new IllegalArgumentException("New Classification must not be 'null' when setting new classification.");
372
        }
373
    	if(! newClassification.equals(this.getClassification())){
374
            this.setClassification(newClassification);
375 3a7ec38f Andreas Kohlbecker
            for(TaxonNode childNode : this.getChildNodes()){
376 813b3df9 Andreas Müller
                childNode.setClassificationRecursively(newClassification);
377 3a7ec38f Andreas Kohlbecker
            }
378
        }
379
    }
380 4cc79481 Andreas Kohlbecker
381
    @Override
382 3a7ec38f Andreas Kohlbecker
    public boolean deleteChildNode(TaxonNode node) {
383
        boolean result = removeChildNode(node);
384 00e1ad00 Katja Luther
        Taxon taxon = node.getTaxon();
385 3a7ec38f Andreas Kohlbecker
        node.setTaxon(null);
386 679d8c93 Katja Luther
        taxon.removeTaxonNode(node);
387 4cc79481 Andreas Kohlbecker
388 3a7ec38f Andreas Kohlbecker
        ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
389
        for(TaxonNode childNode : childNodes){
390
            node.deleteChildNode(childNode);
391
        }
392
393 00e1ad00 Katja Luther
        return result;
394
    }
395 4cc79481 Andreas Kohlbecker
396 c40252b2 Andreas Müller
    /**
397
     * Deletes the child node and also removes children of childnode 
398
     * recursively if delete children is <code>true</code>
399
     * @param node
400
     * @param deleteChildren
401
     * @return
402
     */
403 00e1ad00 Katja Luther
    public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
404
        boolean result = removeChildNode(node);
405 679d8c93 Katja Luther
        Taxon taxon = node.getTaxon();
406
        node.setTaxon(null);
407
        taxon.removeTaxonNode(node);
408 00e1ad00 Katja Luther
        if (deleteChildren){
409 4cc79481 Andreas Kohlbecker
            ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
410
            for(TaxonNode childNode : childNodes){
411
                node.deleteChildNode(childNode, deleteChildren);
412
            }
413 8640593e Katja Luther
        } else{
414
        	ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
415
            for(TaxonNode childNode : childNodes){
416
             this.addChildNode(childNode, null, null);
417
            }
418 00e1ad00 Katja Luther
        }
419
420 3a7ec38f Andreas Kohlbecker
        return result;
421
    }
422
423
    /**
424
     * Removes the child node from this node. Sets the parent and the classification of the child
425
     * node to null
426
     *
427
     * @param childNode
428
     * @return
429
     */
430
    protected boolean removeChildNode(TaxonNode childNode){
431 85338079 Katja Luther
        boolean result = true;
432 4cc79481 Andreas Kohlbecker
433
        if(childNode == null){
434
            throw new IllegalArgumentException("TaxonNode may not be null");
435
        }
436 efdb6847 Andreas Müller
        int index = childNodes.indexOf(childNode);
437 4cc79481 Andreas Kohlbecker
        if (index >= 0){
438
            removeChild(index);
439
        } else {
440
            result = false;
441
        }
442 3a7ec38f Andreas Kohlbecker
        return result;
443
    }
444 4cc79481 Andreas Kohlbecker
445
    /**
446
     * Removes the child node placed at the given (index + 1) position
447
     * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
448
     * Sets the parent and the classification of the child
449 efdb6847 Andreas Müller
     * node to null.
450 4cc79481 Andreas Kohlbecker
     * If the given index is out of bounds no child will be removed.
451
     *
452
     * @param  index	the integer indicating the position of the taxon node to
453
     * 					be removed
454
     * @see     		#getChildNodes()
455
     * @see				#addChildNode(TaxonNode, Reference, String)
456
     * @see				#addChildNode(TaxonNode, int, Reference, String)
457
     * @see				#deleteChildNode(TaxonNode)
458
     */
459
    public void removeChild(int index){
460
461
        TaxonNode child = childNodes.get(index);
462
        child = HibernateProxyHelper.deproxy(child, TaxonNode.class); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
463
464
        if (child != null){
465
466
            TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent(), TaxonNode.class);
467
            TaxonNode thisNode = HibernateProxyHelper.deproxy(this, TaxonNode.class);
468
            if(parent != null && parent != thisNode){
469
                throw new IllegalArgumentException("Child TaxonNode (id:" + child.getId() +") must be a child of this (id:" + thisNode.getId() + " node. Sortindex is: " + index + ", parent-id:" + parent.getId());
470
            }else if (parent == null){
471
                throw new IllegalStateException("Parent of child is null in TaxonNode.removeChild(int). This should not happen.");
472
            }
473
            childNodes.remove(index);
474 c40252b2 Andreas Müller
            child.setClassification(null);
475
            
476
            //update sortindex
477
            //TODO workaround (see sortIndex doc)
478 4cc79481 Andreas Kohlbecker
            this.countChildren = childNodes.size();
479
            child.setParent(null);
480 23dc9c55 Katja Luther
            child.setTreeIndex(null);
481 c40252b2 Andreas Müller
            updateSortIndex(childNodes, index);
482 61380896 Andreas Müller
            child.setSortIndex(null);
483 4cc79481 Andreas Kohlbecker
        }
484
    }
485 3a7ec38f Andreas Kohlbecker
486
487
    /**
488
     * Remove this taxonNode From its taxonomic parent
489
     *
490
     * @return true on success
491
     */
492
    public boolean delete(){
493
        if(isTopmostNode()){
494
            return classification.deleteChildNode(this);
495
        }else{
496
            return getParent().deleteChildNode(this);
497
        }
498
    }
499 4cc79481 Andreas Kohlbecker
500 00e1ad00 Katja Luther
    /**
501
     * Remove this taxonNode From its taxonomic parent
502
     *
503
     * @return true on success
504
     */
505
    public boolean delete(boolean deleteChildren){
506
        if(isTopmostNode()){
507
            return classification.deleteChildNode(this, deleteChildren);
508
        }else{
509
            return getParent().deleteChildNode(this, deleteChildren);
510
        }
511
    }
512 3a7ec38f Andreas Kohlbecker
513 4cc79481 Andreas Kohlbecker
    @Override
514
    @Deprecated //for CDM lib internal use only, may be removed in future versions
515
    public int treeId() {
516 7a31789c Andreas Müller
        if (this.classification == null){
517 cbd7bb87 Andreas Müller
        	logger.warn("TaxonNode has no classification. This should not happen.");  //#3840
518 7a31789c Andreas Müller
        	return -1;
519
        }else{
520
        	return this.classification.getId();
521
        }
522 4cc79481 Andreas Kohlbecker
    }
523
524 3a7ec38f Andreas Kohlbecker
525
    /**
526
     * Sets the parent of this taxon node to the given parent. Cleans up references to
527
     * old parents and sets the classification to the new parents classification
528
     *
529
     * @param parent
530
     */
531
    @Transient
532 5d00156f Andreas Müller
    protected void setParentTreeNode(TaxonNode parent, int index){
533 3a7ec38f Andreas Kohlbecker
        // remove ourselves from the old parent
534 5d00156f Andreas Müller
        TaxonNode formerParent = this.getParent();
535 4cc79481 Andreas Kohlbecker
536 5d00156f Andreas Müller
        if (formerParent != null){
537 61380896 Andreas Müller
        	//special case, child already exists for same parent
538
            //FIXME document / check for correctness
539
            if (formerParent.equals(parent)){
540 5d00156f Andreas Müller
                int currentIndex = formerParent.getChildNodes().indexOf(this);
541
                if (currentIndex != -1 && currentIndex < index){
542
                    index--;
543
                }
544
        	}
545
        	
546 61380896 Andreas Müller
        	//remove from old parent
547 5d00156f Andreas Müller
            formerParent.removeChildNode(this);
548 3a7ec38f Andreas Kohlbecker
        }
549 5d00156f Andreas Müller
        
550 3a7ec38f Andreas Kohlbecker
        // set the new parent
551
        setParent(parent);
552
553
        // set the classification to the parents classification
554 5d00156f Andreas Müller
        Classification classification = parent.getClassification();
555 cc57dbe5 Andreas Müller
        //FIXME also set the tree index here for performance reasons
556 3a7ec38f Andreas Kohlbecker
        setClassificationRecursively(classification);
557
558
        // add this node to the parent child nodes
559 64377e70 Andreas Müller
        List<TaxonNode> parentChildren = parent.getChildNodes();
560
        if (parentChildren.contains(this)){
561 325bc7e9 Andreas Kohlbecker
            //avoid duplicates
562
            if (parentChildren.indexOf(this) < index){
563
                index = index -1;
564
            }
565
            parentChildren.remove(this);
566
            parentChildren.add(index, this);
567 955f9843 Andreas Müller
        }else{
568 325bc7e9 Andreas Kohlbecker
            parentChildren.add(index, this);
569 955f9843 Andreas Müller
        }
570 4cc79481 Andreas Kohlbecker
571 c40252b2 Andreas Müller
        
572
        //sortIndex
573 955f9843 Andreas Müller
        //TODO workaround (see sortIndex doc)
574 c40252b2 Andreas Müller
        updateSortIndex(parentChildren, index);
575 61380896 Andreas Müller
        //only for debugging
576
        if (! this.getSortIndex().equals(index)){
577
        	logger.warn("index and sortindex are not equal");
578
        }
579
        
580 3a7ec38f Andreas Kohlbecker
        // update the children count
581 5d00156f Andreas Müller
        parent.setCountChildren(parent.getChildNodes().size());
582 3a7ec38f Andreas Kohlbecker
    }
583 c40252b2 Andreas Müller
    
584
	/**
585
	 * As long as the sort index is not correctly handled through hibernate this is a workaround method
586
	 * to update the sort index manually
587
	 * @param parentChildren
588
	 * @param index
589
	 */
590
	private void updateSortIndex(List<TaxonNode> children, int index) {
591
		for(int i = index; i < children.size(); i++){
592
        	TaxonNode child = children.get(i);
593
        	if (child != null){
594
//        		child = CdmBase.deproxy(child, TaxonNode.class);  //deproxy not needed as long as setSortIndex is protected or public #4200
595
        		child.setSortIndex(i);
596
        	}else{
597
        		String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";
598
        		throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));
599
        	}
600
        }
601
	}
602
603 3a7ec38f Andreas Kohlbecker
604
605
606
    /**
607
     * Returns a set containing this node and all nodes that are descendants of this node
608
     *
609
     * @return
610
     */
611 072bdb69 Katja Luther
    
612
    protected Set<TaxonNode> getDescendants(){
613 3a7ec38f Andreas Kohlbecker
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
614
615
        nodeSet.add(this);
616
617
        for(TaxonNode childNode : getChildNodes()){
618
            nodeSet.addAll(childNode.getDescendants());
619
        }
620
621
        return nodeSet;
622
    }
623
624
    /**
625
     * Returns a set containing a clone of this node and of all nodes that are descendants of this node
626
     *
627
     * @return
628
     */
629
    protected TaxonNode cloneDescendants(){
630
631
        TaxonNode clone = (TaxonNode)this.clone();
632
        TaxonNode childClone;
633
634
        for(TaxonNode childNode : getChildNodes()){
635
            childClone = (TaxonNode) childNode.clone();
636
            for (TaxonNode childChild:childNode.getChildNodes()){
637 efdb6847 Andreas Müller
                childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());
638 3a7ec38f Andreas Kohlbecker
            }
639 efdb6847 Andreas Müller
            clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference());
640 3a7ec38f Andreas Kohlbecker
            //childClone.addChildNode(childNode.cloneDescendants());
641
        }
642
        return clone;
643
    }
644
645
    /**
646
     * Returns a
647
     *
648
     * @return
649
     */
650
    protected Set<TaxonNode> getAncestors(){
651
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
652 23dc9c55 Katja Luther
       // nodeSet.add(this);
653 3a7ec38f Andreas Kohlbecker
        if(this.getParent() != null){
654 57157341 Andreas Müller
        	TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);
655
            nodeSet.addAll(parent.getAncestors());
656 3a7ec38f Andreas Kohlbecker
        }
657
        return nodeSet;
658
    }
659
660 efdb6847 Andreas Müller
661 3a7ec38f Andreas Kohlbecker
    /**
662
     * Whether this TaxonNode is a direct child of the classification TreeNode
663
     * @return
664
     */
665
    @Transient
666
    public boolean isTopmostNode(){
667 a80ab4d7 Cherian Mathew
    	boolean parentCheck = false;
668
    	boolean classificationCheck = false;
669
670
    	if(getParent() != null) {
671
    		if(getParent().getTaxon() == null) {
672
    			parentCheck = true;
673
    		}
674
    	}
675
676 c40252b2 Andreas Müller
    	//TODO remove
677
    	// FIXME This should work but doesn't, due to missing sort indexes, can be removed after fixing #4200, #4098
678 a80ab4d7 Cherian Mathew
    	if (classification != null){          	
679
    		classificationCheck = classification.getRootNode().getChildNodes().contains(this);
680
    	}else{
681
    		classificationCheck = false;
682
    	}
683
684
    	// The following is just for logging purposes for the missing sort indexes problem
685
    	// ticket #4098
686
    	if(parentCheck != classificationCheck) {        	
687
    		logger.warn("isTopmost node check " + parentCheck + " not same as classificationCheck : " + classificationCheck + " for taxon node ");        	
688
    		if(this.getParent() != null) {
689
    			logger.warn("-- with parent uuid " + this.getParent().getUuid().toString());
690
    			logger.warn("-- with parent id " + this.getParent().getId());
691
    			for(TaxonNode node : this.getParent().getChildNodes()) {
692
    				if(node == null) {
693
    					logger.warn("-- child node is null");
694
    				} else if (node.getTaxon() == null) {
695
    					logger.warn("-- child node taxon is null");
696
    				} 
697
    			}
698
    			logger.warn("-- parent child count" + this.getParent().getChildNodes().size());        		
699
    		}       	
700
    	}
701
702
    	return parentCheck;
703 3a7ec38f Andreas Kohlbecker
    }
704
705
    /**
706
     * Whether this TaxonNode is a descendant of the given TaxonNode
707
     *
708
     * Caution: use this method with care on big branches. -> performance and memory hungry
709
     *
710
     * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
711
     * other direction (up). It will always result in a rather small set of consecutive parents beeing
712
     * generated.
713
     *
714
     * TODO implement more efficiently without generating the set of descendants first
715
     *
716
     * @param possibleParent
717
     * @return true if this is a descendant
718
     */
719
    @Transient
720
    public boolean isDescendant(TaxonNode possibleParent){
721 23dc9c55 Katja Luther
    	if (this.treeIndex() == null || possibleParent.treeIndex() == null) {
722
    		return false;
723
    	}
724
    	return possibleParent == null ? false : this.treeIndex().startsWith(possibleParent.treeIndex() );
725 3a7ec38f Andreas Kohlbecker
    }
726
727
    /**
728
     * Whether this TaxonNode is an ascendant of the given TaxonNode
729
     *
730
     * @param possibleChild
731
     * @return true if there are ascendants
732
     */
733
    @Transient
734
    public boolean isAncestor(TaxonNode possibleChild){
735 23dc9c55 Katja Luther
    	if (this.treeIndex() == null || possibleChild.treeIndex() == null) {
736
    		return false;
737
    	}
738
       // return possibleChild == null ? false : possibleChild.getAncestors().contains(this);
739
        return possibleChild == null ? false : possibleChild.treeIndex().startsWith(this.treeIndex());
740 3a7ec38f Andreas Kohlbecker
    }
741
742
    /**
743
     * Whether this taxon has child nodes
744
     *
745
     * @return true if the taxonNode has childNodes
746
     */
747
    @Transient
748 efdb6847 Andreas Müller
    @Override
749 3a7ec38f Andreas Kohlbecker
    public boolean hasChildNodes(){
750
        return childNodes.size() > 0;
751
    }
752
753 8eb6145e Katja Luther
//*********************** CLONE ********************************************************/
754 3a7ec38f Andreas Kohlbecker
    /**
755
     * Clones <i>this</i> taxon node. This is a shortcut that enables to create
756
     * a new instance that differs only slightly from <i>this</i> taxon node by
757
     * modifying only some of the attributes.<BR><BR>
758
     * The child nodes are not copied.<BR>
759
     * The taxon and parent are the same as for the original taxon node. <BR>
760
     *
761
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
762
     * @see java.lang.Object#clone()
763
     */
764
    @Override
765
    public Object clone()  {
766
        TaxonNode result;
767
        try{
768
        result = (TaxonNode)super.clone();
769
        result.getTaxon().addTaxonNode(result);
770 efdb6847 Andreas Müller
        result.childNodes = new ArrayList<TaxonNode>();
771 3a7ec38f Andreas Kohlbecker
        result.countChildren = 0;
772
773
        return result;
774
        }catch (CloneNotSupportedException e) {
775
            logger.warn("Object does not implement cloneable");
776
            e.printStackTrace();
777
            return null;
778
        }
779
    }
780 0365fb40 Andreas Müller
781 fc5ccc45 Katja Luther
	public boolean hasTaxon() {
782
		return (taxon!= null);
783
	}
784
785 0365fb40 Andreas Müller
786 f6c2e10f Andreas Müller
787 ef846006 Andreas Müller
}