root/trunk/cdmlib/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/taxon/TaxonNode.java

Revision 13338, 17.4 kB (checked in by a.mueller, 6 months ago)

minor

Line 
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
11package eu.etaxonomy.cdm.model.taxon;
12
13import java.util.ArrayList;
14import java.util.HashSet;
15import java.util.Set;
16
17import javax.persistence.Entity;
18import javax.persistence.FetchType;
19import javax.persistence.ManyToOne;
20import javax.persistence.OneToMany;
21import javax.persistence.Transient;
22import javax.xml.bind.annotation.XmlAccessType;
23import javax.xml.bind.annotation.XmlAccessorType;
24import javax.xml.bind.annotation.XmlElement;
25import javax.xml.bind.annotation.XmlElementWrapper;
26import javax.xml.bind.annotation.XmlIDREF;
27import javax.xml.bind.annotation.XmlRootElement;
28import javax.xml.bind.annotation.XmlSchemaType;
29import javax.xml.bind.annotation.XmlType;
30
31import org.apache.log4j.Logger;
32import org.hibernate.annotations.Cascade;
33import org.hibernate.annotations.CascadeType;
34import org.hibernate.envers.Audited;
35
36import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
37import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
38import eu.etaxonomy.cdm.model.reference.Reference;
39
40/**
41 * @author a.mueller
42 * @created 31.03.2009
43 * @version 1.0
44 */
45@XmlAccessorType(XmlAccessType.FIELD)
46@XmlType(name = "TaxonNode", propOrder = {
47    "taxon",
48    "parent",
49    "classification",
50    "childNodes",
51    "referenceForParentChildRelation",
52    "microReferenceForParentChildRelation",
53    "countChildren",
54    "synonymToBeUsed"
55})
56@XmlRootElement(name = "TaxonNode")
57@Entity
58@Audited
59public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable{
60    private static final long serialVersionUID = -4743289894926587693L;
61    private static final Logger logger = Logger.getLogger(TaxonNode.class);
62
63    @XmlElement(name = "taxon")
64    @XmlIDREF
65    @XmlSchemaType(name = "IDREF")
66    @ManyToOne(fetch = FetchType.LAZY)
67    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
68    private Taxon taxon;
69
70
71    @XmlElement(name = "parent")
72    @XmlIDREF
73    @XmlSchemaType(name = "IDREF")
74    @ManyToOne(fetch = FetchType.LAZY)
75    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
76    private TaxonNode parent;
77
78
79    @XmlElement(name = "classification")
80    @XmlIDREF
81    @XmlSchemaType(name = "IDREF")
82    @ManyToOne(fetch = FetchType.LAZY)
83    @Cascade({CascadeType.SAVE_UPDATE})
84
85//      TODO @NotNull // avoids creating a UNIQUE key for this field
86    private Classification classification;
87
88    @XmlElementWrapper(name = "childNodes")
89    @XmlElement(name = "childNode")
90    @XmlIDREF
91    @XmlSchemaType(name = "IDREF")
92    @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
93   @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
94    private Set<TaxonNode> childNodes = new HashSet<TaxonNode>();
95
96    @XmlElement(name = "reference")
97    @XmlIDREF
98    @XmlSchemaType(name = "IDREF")
99    @ManyToOne(fetch = FetchType.LAZY)
100    @Cascade({CascadeType.SAVE_UPDATE})
101    private Reference referenceForParentChildRelation;
102
103    @XmlElement(name = "microReference")
104    private String microReferenceForParentChildRelation;
105
106    @XmlElement(name = "countChildren")
107    private int countChildren;
108
109//      private Taxon originalConcept;
110//      //or
111    @XmlElement(name = "synonymToBeUsed")
112    @XmlIDREF
113    @XmlSchemaType(name = "IDREF")
114    @ManyToOne(fetch = FetchType.LAZY)
115    @Cascade({CascadeType.SAVE_UPDATE})
116    private Synonym synonymToBeUsed;
117
118
119    protected TaxonNode(){
120        super();
121    }
122
123    /**
124     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
125     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
126     * @param taxon
127     * @param classification
128     * @deprecated setting of classification is handled in the addTaxonNode() method,
129     * use TaxonNode(taxon) instead
130     */
131    protected TaxonNode (Taxon taxon, Classification classification){
132        this(taxon);
133        setClassification(classification);
134    }
135
136    /**
137     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
138     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
139     *
140     * @param taxon
141     */
142    protected TaxonNode(Taxon taxon){
143        setTaxon(taxon);
144    }
145
146
147
148//************************ METHODS **************************/
149
150    /* (non-Javadoc)
151     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#addChildTaxon(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.model.reference.Reference, java.lang.String, eu.etaxonomy.cdm.model.taxon.Synonym)
152     */
153    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation, Synonym synonymToBeUsed) {
154        if (this.getClassification().isTaxonInTree(taxon)){
155             throw new IllegalArgumentException(String.format("Taxon may not be in a taxonomic view twice: %s", taxon.getTitleCache()));
156        }
157
158        return addChildNode(new TaxonNode(taxon), citation, microCitation, synonymToBeUsed);
159    }
160
161    /**
162     * Moves a taxon node to a new parent. Descendents of the node are moved as well
163     *
164     * @param childNode the taxon node to be moved to the new parent
165     * @return the child node in the state of having a new parent
166     */
167    public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference, Synonym synonymToBeUsed){
168
169        // check if this node is a descendant of the childNode
170        if(childNode.getParentTreeNode() != this && childNode.isAncestor(this)){
171            throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
172        }
173
174        childNode.setParentTreeNode(this);
175
176        childNode.setReference(reference);
177        childNode.setMicroReference(microReference);
178        childNode.setSynonymToBeUsed(synonymToBeUsed);
179
180        return childNode;
181    }
182
183    /**
184     * Sets this nodes classification. Updates classification of child nodes recursively
185     *
186     * If the former and the actual tree are equal() this method does nothing
187     *
188     * @param newTree
189     */
190    @Transient
191    private void setClassificationRecursively(Classification newTree) {
192        if(! newTree.equals(this.getClassification())){
193            this.setClassification(newTree);
194            for(TaxonNode childNode : this.getChildNodes()){
195                childNode.setClassificationRecursively(newTree);
196            }
197        }
198    }
199
200
201    /* (non-Javadoc)
202     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#removeChildNode(eu.etaxonomy.cdm.model.taxon.TaxonNode)
203     */
204    public boolean deleteChildNode(TaxonNode node) {
205        boolean result = removeChildNode(node);
206
207        node.getTaxon().removeTaxonNode(node);
208        node.setTaxon(null);
209
210        ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
211        for(TaxonNode childNode : childNodes){
212            node.deleteChildNode(childNode);
213        }
214
215//              // two iterations because of ConcurrentModificationErrors
216//        Set<TaxonNode> removeNodes = new HashSet<TaxonNode>();
217//        for (TaxonNode grandChildNode : node.getChildNodes()) {
218//                removeNodes.add(grandChildNode);
219//        }
220//        for (TaxonNode childNode : removeNodes) {
221//                childNode.deleteChildNode(node);
222//        }
223
224        return result;
225    }
226
227    /**
228     * Removes the child node from this node. Sets the parent and the classification of the child
229     * node to null
230     *
231     * @param childNode
232     * @return
233     */
234    protected boolean removeChildNode(TaxonNode childNode){
235        boolean result = false;
236
237        if(childNode == null){
238            throw new IllegalArgumentException("TaxonNode may not be null");
239        }
240        if(HibernateProxyHelper.deproxy(childNode.getParent(), TaxonNode.class) != this){
241            throw new IllegalArgumentException("TaxonNode must be a child of this node");
242        }
243
244        result = childNodes.remove(childNode);
245        this.countChildren--;
246        if (this.countChildren < 0){
247            throw new IllegalStateException("children count must not be negative ");
248        }
249        childNode.setParent(null);
250        childNode.setClassification(null);
251
252        return result;
253    }
254
255
256    /**
257     * Remove this taxonNode From its taxonomic parent
258     *
259     * @return true on success
260     */
261    public boolean delete(){
262        if(isTopmostNode()){
263            return classification.deleteChildNode(this);
264        }else{
265            return getParent().deleteChildNode(this);
266        }
267    }
268
269//*********** GETTER / SETTER ***********************************/
270
271    public Taxon getTaxon() {
272        return taxon;
273    }
274    protected void setTaxon(Taxon taxon) {
275        this.taxon = taxon;
276        if (taxon != null){
277            taxon.addTaxonNode(this);
278        }
279    }
280    @Transient
281    public ITreeNode getParentTreeNode() {
282        if(isTopmostNode())
283            return getClassification();
284        return parent;
285    }
286
287    public TaxonNode getParent(){
288        return parent;
289    }
290
291    /**
292     * Sets the parent of this taxon node.
293     *
294     * In most cases you would want to call setParentTreeNode(ITreeNode) which
295     * handles updating of the bidirectional relationship
296     *
297     * @param parent
298     *
299     * @see setParentTreeNode(ITreeNode)
300     */
301    protected void setParent(ITreeNode parent) {
302        if(parent instanceof Classification){
303            this.parent = null;
304            return;
305        }
306        this.parent = (TaxonNode) parent;
307    }
308
309    /**
310     * Sets the parent of this taxon node to the given parent. Cleans up references to
311     * old parents and sets the classification to the new parents classification
312     *
313     * @param parent
314     */
315    @Transient
316    protected void setParentTreeNode(ITreeNode parent){
317        // remove ourselves from the old parent
318        ITreeNode formerParent = this.getParentTreeNode();
319        if(formerParent instanceof TaxonNode){  //child was a child itself
320            ((TaxonNode) formerParent).removeChildNode(this);
321        }
322        else if((formerParent instanceof Classification) && ! formerParent.equals(parent)){ //child was root in old tree
323            ((Classification) formerParent).removeChildNode(this);
324        }
325
326        // set the new parent
327        setParent(parent);
328
329        // set the classification to the parents classification
330        Classification classification = (parent instanceof Classification) ? (Classification) parent : ((TaxonNode) parent).getClassification();
331        setClassificationRecursively(classification);
332
333        // add this node to the parent child nodes
334        parent.getChildNodes().add(this);
335
336        // update the children count
337        if(parent instanceof TaxonNode){
338            TaxonNode parentTaxonNode = (TaxonNode) parent;
339            parentTaxonNode.setCountChildren(parentTaxonNode.getCountChildren() + 1);
340        }
341    }
342
343    public Classification getClassification() {
344        return classification;
345    }
346    /**
347     * THIS METHOD SHOULD NOT BE CALLED!
348     * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
349     * @param classification
350     */
351    protected void setClassification(Classification classification) {
352        this.classification = classification;
353    }
354
355    public Set<TaxonNode> getChildNodes() {
356        return childNodes;
357    }
358
359    /**
360     * Returns a set containing this node and all nodes that are descendants of this node
361     *
362     * @return
363     */
364    protected Set<TaxonNode> getDescendants(){
365        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
366
367        nodeSet.add(this);
368
369        for(TaxonNode childNode : getChildNodes()){
370            nodeSet.addAll(childNode.getDescendants());
371        }
372
373        return nodeSet;
374    }
375
376    /**
377     * Returns a set containing a clone of this node and of all nodes that are descendants of this node
378     *
379     * @return
380     */
381    protected TaxonNode cloneDescendants(){
382
383        TaxonNode clone = (TaxonNode)this.clone();
384        TaxonNode childClone;
385
386        for(TaxonNode childNode : getChildNodes()){
387            childClone = (TaxonNode) childNode.clone();
388            for (TaxonNode childChild:childNode.getChildNodes()){
389                childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference(), childChild.getSynonymToBeUsed());
390            }
391            clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference(), childNode.getSynonymToBeUsed());
392
393
394            //childClone.addChildNode(childNode.cloneDescendants());
395        }
396        return clone;
397    }
398
399    /**
400     * Returns a
401     *
402     * @return
403     */
404    protected Set<TaxonNode> getAncestors(){
405        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
406
407
408        nodeSet.add(this);
409
410        if(this.getParent() != null){
411            nodeSet.addAll(((TaxonNode) this.getParent()).getAncestors());
412        }
413
414        return nodeSet;
415    }
416
417    /**
418     * The reference for the parent child relationship
419     *
420     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#getReference()
421     */
422    public Reference getReference() {
423        return referenceForParentChildRelation;
424    }
425
426    /* (non-Javadoc)
427     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setReference(eu.etaxonomy.cdm.model.reference.Reference)
428     */
429    public void setReference(Reference reference) {
430        this.referenceForParentChildRelation = reference;
431    }
432
433    /**
434     *
435     *
436     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#getMicroReference()
437     */
438    public String getMicroReference() {
439        return microReferenceForParentChildRelation;
440    }
441
442    /* (non-Javadoc)
443     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setMicroReference(java.lang.String)
444     */
445    public void setMicroReference(String microReference) {
446        this.microReferenceForParentChildRelation = microReference;
447    }
448
449    /**
450     * @return the count of children this taxon node has
451     */
452    public int getCountChildren() {
453        return countChildren;
454    }
455
456    /**
457     * @param countChildren
458     */
459    protected void setCountChildren(int countChildren) {
460        this.countChildren = countChildren;
461    }
462//      public Taxon getOriginalConcept() {
463//              return originalConcept;
464//      }
465//      public void setOriginalConcept(Taxon originalConcept) {
466//              this.originalConcept = originalConcept;
467//      }
468    public Synonym getSynonymToBeUsed() {
469        return synonymToBeUsed;
470    }
471    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
472        this.synonymToBeUsed = synonymToBeUsed;
473    }
474
475    /**
476     * Whether this TaxonNode is a direct child of the classification TreeNode
477     * @return
478     */
479    @Transient
480    public boolean isTopmostNode(){
481        return parent == null;
482    }
483
484    /**
485     * Whether this TaxonNode is a descendant of the given TaxonNode
486     *
487     * Caution: use this method with care on big branches. -> performance and memory hungry
488     *
489     * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
490     * other direction (up). It will always result in a rather small set of consecutive parents beeing
491     * generated.
492     *
493     * TODO implement more efficiently without generating the set of descendants first
494     *
495     * @param possibleParent
496     * @return true if this is a descendant
497     */
498    @Transient
499    public boolean isDescendant(TaxonNode possibleParent){
500        return possibleParent.getDescendants().contains(this);
501    }
502
503    /**
504     * Whether this TaxonNode is an ascendant of the given TaxonNode
505     *
506     *
507     * @param possibleChild
508     * @return true if there are ascendants
509     */
510    @Transient
511    public boolean isAncestor(TaxonNode possibleChild){
512        return possibleChild.getAncestors().contains(this);
513    }
514
515    /**
516     * Whether this taxon has child nodes
517     *
518     * @return true if the taxonNode has childNodes
519     */
520    @Transient
521    public boolean hasChildNodes(){
522        return childNodes.size() > 0;
523    }
524
525//*********************** CLONE ********************************************************/
526    /**
527     * Clones <i>this</i> taxon node. This is a shortcut that enables to create
528     * a new instance that differs only slightly from <i>this</i> taxon node by
529     * modifying only some of the attributes.<BR><BR>
530     * The child nodes are not copied.<BR>
531     * The taxon and parent are the same as for the original taxon node. <BR>
532     *
533     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
534     * @see java.lang.Object#clone()
535     */
536    @Override
537    public Object clone()  {
538        TaxonNode result;
539        try{
540        result = (TaxonNode)super.clone();
541        result.getTaxon().addTaxonNode(result);
542        result.childNodes = new HashSet<TaxonNode>();
543        result.countChildren = 0;
544
545        return result;
546        }catch (CloneNotSupportedException e) {
547            logger.warn("Object does not implement cloneable");
548            e.printStackTrace();
549            return null;
550        }
551    }
552}
Note: See TracBrowser for help on using the browser.