3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
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.
11 package eu
.etaxonomy
.cdm
.model
.taxon
;
13 import java
.util
.ArrayList
;
14 import java
.util
.HashSet
;
17 import javax
.persistence
.Entity
;
18 import javax
.persistence
.FetchType
;
19 import javax
.persistence
.ManyToOne
;
20 import javax
.persistence
.OneToMany
;
21 import javax
.persistence
.Transient
;
22 import javax
.xml
.bind
.annotation
.XmlAccessType
;
23 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
24 import javax
.xml
.bind
.annotation
.XmlElement
;
25 import javax
.xml
.bind
.annotation
.XmlElementWrapper
;
26 import javax
.xml
.bind
.annotation
.XmlIDREF
;
27 import javax
.xml
.bind
.annotation
.XmlRootElement
;
28 import javax
.xml
.bind
.annotation
.XmlSchemaType
;
29 import javax
.xml
.bind
.annotation
.XmlType
;
31 import org
.apache
.log4j
.Logger
;
32 import org
.hibernate
.annotations
.Cascade
;
33 import org
.hibernate
.annotations
.CascadeType
;
34 import org
.hibernate
.envers
.Audited
;
36 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
37 import eu
.etaxonomy
.cdm
.model
.common
.AnnotatableEntity
;
38 import eu
.etaxonomy
.cdm
.model
.reference
.ReferenceBase
;
45 @XmlAccessorType(XmlAccessType
.FIELD
)
46 @XmlType(name
= "TaxonNode", propOrder
= {
51 "referenceForParentChildRelation",
52 "microReferenceForParentChildRelation",
56 @XmlRootElement(name
= "TaxonNode")
59 public class TaxonNode
extends AnnotatableEntity
implements ITreeNode
{
60 private static final long serialVersionUID
= -4743289894926587693L;
61 @SuppressWarnings("unused")
62 private static final Logger logger
= Logger
.getLogger(TaxonNode
.class);
64 @XmlElement(name
= "taxon")
66 @XmlSchemaType(name
= "IDREF")
67 @ManyToOne(fetch
= FetchType
.LAZY
)
68 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
72 @XmlElement(name
= "parent")
74 @XmlSchemaType(name
= "IDREF")
75 @ManyToOne(fetch
= FetchType
.LAZY
)
76 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
77 private TaxonNode parent
;
80 @XmlElement(name
= "taxonomicTree")
82 @XmlSchemaType(name
= "IDREF")
83 @ManyToOne(fetch
= FetchType
.LAZY
)
84 @Cascade({CascadeType
.SAVE_UPDATE
})
85 private TaxonomicTree taxonomicTree
;
87 @XmlElementWrapper(name
= "childNodes")
88 @XmlElement(name
= "childNode")
90 @XmlSchemaType(name
= "IDREF")
91 @OneToMany(mappedBy
="parent", fetch
=FetchType
.LAZY
)
92 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
93 private Set
<TaxonNode
> childNodes
= new HashSet
<TaxonNode
>();
95 @XmlElement(name
= "reference")
97 @XmlSchemaType(name
= "IDREF")
98 @ManyToOne(fetch
= FetchType
.LAZY
)
99 @Cascade({CascadeType
.SAVE_UPDATE
})
100 private ReferenceBase referenceForParentChildRelation
;
102 @XmlElement(name
= "microReference")
103 private String microReferenceForParentChildRelation
;
105 @XmlElement(name
= "countChildren")
106 private int countChildren
;
108 // private Taxon originalConcept;
110 @XmlElement(name
= "synonymToBeUsed")
112 @XmlSchemaType(name
= "IDREF")
113 @ManyToOne(fetch
= FetchType
.LAZY
)
114 @Cascade({CascadeType
.SAVE_UPDATE
})
115 private Synonym synonymToBeUsed
;
118 protected TaxonNode(){
123 * to create nodes either use TaxonomicView.addRoot() or TaxonNode.addChild();
125 * @param taxonomicTree
126 * @deprecated setting of taxonomic tree is handled in the addTaxonNode() method,
127 * use TaxonNode(taxon) instead
129 protected TaxonNode (Taxon taxon
, TaxonomicTree taxonomicTree
){
131 setTaxonomicTree(taxonomicTree
);
135 * to create nodes either use TaxonomicView.addChildTaxon() or TaxonNode.addChildTaxon();
139 protected TaxonNode(Taxon taxon
){
145 //************************ METHODS **************************/
147 * @deprecated developers should be forced to pass in null values if they choose so.
150 public TaxonNode
addChild(Taxon taxon
){
151 return addChild(taxon
, null, null, null);
155 * @deprecated developers should be forced to pass in null values if they choose so.
158 public TaxonNode
addChild(Taxon taxon
, ReferenceBase ref
, String microReference
){
159 return addChild(taxon
, ref
, microReference
, null);
166 * @param microReference
169 * @deprecated use addChildTaxon() instead
172 public TaxonNode
addChild(Taxon taxon
, ReferenceBase ref
, String microReference
, Synonym synonymUsed
){
173 return addChildTaxon(taxon
, ref
, microReference
, synonymUsed
);
178 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#addChildTaxon(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.model.reference.ReferenceBase, java.lang.String, eu.etaxonomy.cdm.model.taxon.Synonym)
180 public TaxonNode
addChildTaxon(Taxon taxon
, ReferenceBase citation
,
181 String microCitation
, Synonym synonymToBeUsed
) {
182 if (this.getTaxonomicTree().isTaxonInTree(taxon
)){
183 throw new IllegalArgumentException("Taxon may not be in a taxonomic view twice");
186 return addChildNode(new TaxonNode(taxon
), citation
, microCitation
, synonymToBeUsed
);
193 * @param microReference
196 * @deprecated use addChildNode instead
199 protected void addChildNote(TaxonNode childNode
, ReferenceBase ref
, String microReference
, Synonym synonymUsed
){
200 if (! childNode
.getTaxonomicTree().equals(this.getTaxonomicTree())){
201 throw new IllegalArgumentException("addChildNote(): both nodes must be part of the same view");
203 childNode
.setParent(this);
204 childNodes
.add(childNode
);
205 this.countChildren
++;
206 childNode
.setReferenceForParentChildRelation(ref
);
207 childNode
.setMicroReferenceForParentChildRelation(microReference
);
208 childNode
.setSynonymToBeUsed(synonymUsed
);
212 * Moves a taxon node to a new parent. Descendents of the node are moved as well
214 * @param childNode the taxon node to be moved to the new parent
215 * @return the child node in the state of having a new parent
217 public TaxonNode
addChildNode(TaxonNode childNode
, ReferenceBase reference
, String microReference
, Synonym synonymToBeUsed
){
219 // check if this node is a descendant of the childNode
220 if(childNode
.getParentTreeNode() != this && childNode
.isAncestor(this)){
221 throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
224 childNode
.setParentTreeNode(this);
226 childNode
.setReference(reference
);
227 childNode
.setMicroReference(microReference
);
228 childNode
.setSynonymToBeUsed(synonymToBeUsed
);
234 * Sets this nodes taxonomic tree. Updates taxonomic tree of child nodes recursively
236 * If the former and the actual tree are equal() this method does nothing
241 private void setTaxonomicTreeRecursively(TaxonomicTree newTree
) {
242 if(! newTree
.equals(this.getTaxonomicTree())){
243 this.setTaxonomicTree(newTree
);
244 for(TaxonNode childNode
: this.getChildNodes()){
245 childNode
.setTaxonomicTreeRecursively(newTree
);
251 * This removes recursively all child nodes from this node and from this taxonomic view.
252 * TODO remove orphan nodes completely
256 * @deprecated use deleteChildNode() instead
259 public boolean removeChild(TaxonNode node
){
260 return deleteChildNode(node
);
265 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#removeChildNode(eu.etaxonomy.cdm.model.taxon.TaxonNode)
267 public boolean deleteChildNode(TaxonNode node
) {
268 boolean result
= removeChildNode(node
);
270 node
.getTaxon().removeTaxonNode(node
);
273 ArrayList
<TaxonNode
> childNodes
= new ArrayList
<TaxonNode
>(node
.getChildNodes());
274 for(TaxonNode childNode
: childNodes
){
275 node
.deleteChildNode(childNode
);
278 // // two iterations because of ConcurrentModificationErrors
279 // Set<TaxonNode> removeNodes = new HashSet<TaxonNode>();
280 // for (TaxonNode grandChildNode : node.getChildNodes()) {
281 // removeNodes.add(grandChildNode);
283 // for (TaxonNode childNode : removeNodes) {
284 // childNode.deleteChildNode(node);
291 * Removes the child node from this node. Sets the parent and the taxonomic tree of the child
297 protected boolean removeChildNode(TaxonNode childNode
){
298 boolean result
= false;
300 if(childNode
== null){
301 throw new IllegalArgumentException("TaxonNode may not be null");
303 if(HibernateProxyHelper
.deproxy(childNode
.getParent(), TaxonNode
.class) != this){
304 throw new IllegalArgumentException("TaxonNode must be a child of this node");
307 result
= childNodes
.remove(childNode
);
308 this.countChildren
--;
309 if (this.countChildren
< 0){
310 throw new IllegalStateException("children count must not be negative ");
312 childNode
.setParent(null);
313 childNode
.setTaxonomicTree(null);
320 * Remove this taxonNode From its taxonomic parent
322 * @return true on success
324 public boolean delete(){
326 return taxonomicTree
.deleteChildNode(this);
328 return getParent().deleteChildNode(this);
332 //*********** GETTER / SETTER ***********************************/
334 public Taxon
getTaxon() {
337 protected void setTaxon(Taxon taxon
) {
340 taxon
.addTaxonNode(this);
344 public ITreeNode
getParentTreeNode() {
346 return getTaxonomicTree();
350 public TaxonNode
getParent(){
355 * Sets the parent of this taxon node.
357 * In most cases you would want to call setParentTreeNode(ITreeNode) which
358 * handles updating of the bidirectional relationship
362 * @see setParentTreeNode(ITreeNode)
364 protected void setParent(ITreeNode parent
) {
365 if(parent
instanceof TaxonomicTree
){
369 this.parent
= (TaxonNode
) parent
;
373 * Sets the parent of this taxon node to the given parent. Cleans up references to
374 * old parents and sets the taxonomic tree to the new parents taxonomic tree
379 protected void setParentTreeNode(ITreeNode parent
){
380 // remove ourselves from the old parent
381 ITreeNode formerParent
= this.getParentTreeNode();
382 if(formerParent
instanceof TaxonNode
){ //child was a child itself
383 ((TaxonNode
) formerParent
).removeChildNode(this);
385 else if((formerParent
instanceof TaxonomicTree
) && ! formerParent
.equals(parent
)){ //child was root in old tree
386 ((TaxonomicTree
) formerParent
).removeChildNode(this);
389 // set the new parent
392 // set the taxonomic tree to the parents taxonomic tree
393 TaxonomicTree classification
= (parent
instanceof TaxonomicTree
) ?
(TaxonomicTree
) parent
: ((TaxonNode
) parent
).getTaxonomicTree();
394 setTaxonomicTreeRecursively(classification
);
396 // add this node to the parent child nodes
397 parent
.getChildNodes().add(this);
399 // update the children count
400 if(parent
instanceof TaxonNode
){
401 TaxonNode parentTaxonNode
= (TaxonNode
) parent
;
402 parentTaxonNode
.setCountChildren(parentTaxonNode
.getCountChildren() + 1);
406 public TaxonomicTree
getTaxonomicTree() {
407 return taxonomicTree
;
410 * THIS METHOD SHOULD NOT BE CALLED!
411 * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
412 * @param taxonomicTree
414 protected void setTaxonomicTree(TaxonomicTree taxonomicTree
) {
415 this.taxonomicTree
= taxonomicTree
;
418 public Set
<TaxonNode
> getChildNodes() {
423 * Returns a set containing this node and all nodes that are descendants of this node
427 protected Set
<TaxonNode
> getDescendants(){
428 Set
<TaxonNode
> nodeSet
= new HashSet
<TaxonNode
>();
432 for(TaxonNode childNode
: getChildNodes()){
433 nodeSet
.addAll(childNode
.getDescendants());
444 protected Set
<TaxonNode
> getAncestors(){
445 Set
<TaxonNode
> nodeSet
= new HashSet
<TaxonNode
>();
450 if(this.getParent() != null){
451 nodeSet
.addAll(((TaxonNode
) this.getParent()).getAncestors());
457 // protected void setChildNodes(List<TaxonNode> childNodes) {
458 // this.childNodes = childNodes;
461 * The reference for the parent child relationship
463 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#getReference()
465 public ReferenceBase
getReference() {
466 return referenceForParentChildRelation
;
470 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setReference(eu.etaxonomy.cdm.model.reference.ReferenceBase)
472 public void setReference(ReferenceBase reference
) {
473 this.referenceForParentChildRelation
= reference
;
478 * @deprecated use getReference instead
481 public ReferenceBase
getReferenceForParentChildRelation() {
482 return getReference();
485 public void setReferenceForParentChildRelation(ReferenceBase referenceForParentChildRelation
) {
486 setReference(referenceForParentChildRelation
);
492 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#getMicroReference()
494 public String
getMicroReference() {
495 return microReferenceForParentChildRelation
;
499 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setMicroReference(java.lang.String)
501 public void setMicroReference(String microReference
) {
502 this.microReferenceForParentChildRelation
= microReference
;
506 public String
getMicroReferenceForParentChildRelation() {
507 return getMicroReference();
511 public void setMicroReferenceForParentChildRelation(
512 String microReferenceForParentChildRelation
) {
513 setMicroReference(microReferenceForParentChildRelation
);
518 * @return the count of children this taxon node has
520 public int getCountChildren() {
521 return countChildren
;
525 * @param countChildren
527 protected void setCountChildren(int countChildren
) {
528 this.countChildren
= countChildren
;
530 // public Taxon getOriginalConcept() {
531 // return originalConcept;
533 // public void setOriginalConcept(Taxon originalConcept) {
534 // this.originalConcept = originalConcept;
536 public Synonym
getSynonymToBeUsed() {
537 return synonymToBeUsed
;
539 public void setSynonymToBeUsed(Synonym synonymToBeUsed
) {
540 this.synonymToBeUsed
= synonymToBeUsed
;
544 * Whether this TaxonNode is a root node
546 * @deprecated use isTopmostNode() instead
549 public boolean isRootNode(){
550 return parent
== null;
554 * Whether this TaxonNode is a direct child of the taxonomic tree TreeNode
558 public boolean isTopmostNode(){
559 return parent
== null;
563 * Whether this TaxonNode is a descendant of the given TaxonNode
565 * Caution: use this method with care on big branches. -> performance and memory hungry
567 * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
568 * other direction (up). It will always result in a rather small set of consecutive parents beeing
571 * TODO implement more efficiently without generating the set of descendants first
573 * @param possibleParent
574 * @return true if this is a descendant
577 public boolean isDescendant(TaxonNode possibleParent
){
578 return possibleParent
.getDescendants().contains(this);
582 * Whether this TaxonNode is an ascendant of the given TaxonNode
585 * @param possibleChild
586 * @return true if there are ascendants
589 public boolean isAncestor(TaxonNode possibleChild
){
590 return possibleChild
.getAncestors().contains(this);
594 * Whether this taxon has child nodes
596 * @return true if the taxonNode has childNodes
599 public boolean hasChildNodes(){
600 return childNodes
.size() > 0;