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
;
15 import java
.util
.List
;
18 import javax
.persistence
.Entity
;
19 import javax
.persistence
.FetchType
;
20 import javax
.persistence
.ManyToOne
;
21 import javax
.persistence
.OneToMany
;
22 import javax
.persistence
.OrderBy
;
23 import javax
.persistence
.OrderColumn
;
24 import javax
.persistence
.Transient
;
25 import javax
.validation
.constraints
.Size
;
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
;
35 import org
.apache
.log4j
.Logger
;
36 import org
.hibernate
.annotations
.Cascade
;
37 import org
.hibernate
.annotations
.CascadeType
;
38 import org
.hibernate
.envers
.Audited
;
39 import org
.hibernate
.search
.annotations
.ContainedIn
;
40 import org
.hibernate
.search
.annotations
.Indexed
;
41 import org
.hibernate
.search
.annotations
.IndexedEmbedded
;
43 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
44 import eu
.etaxonomy
.cdm
.model
.common
.AnnotatableEntity
;
45 import eu
.etaxonomy
.cdm
.model
.common
.ITreeNode
;
46 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
52 @XmlAccessorType(XmlAccessType
.FIELD
)
53 @XmlType(name
= "TaxonNode", propOrder
= {
60 "referenceForParentChildRelation",
61 "microReferenceForParentChildRelation",
65 @XmlRootElement(name
= "TaxonNode")
67 @Indexed(index
= "eu.etaxonomy.cdm.model.taxon.TaxonNode")
69 public class TaxonNode
extends AnnotatableEntity
implements ITaxonTreeNode
, ITreeNode
<TaxonNode
>, Cloneable
{
70 private static final long serialVersionUID
= -4743289894926587693L;
71 private static final Logger logger
= Logger
.getLogger(TaxonNode
.class);
73 @XmlElement(name
= "taxon")
75 @XmlSchemaType(name
= "IDREF")
76 @ManyToOne(fetch
= FetchType
.LAZY
)
77 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
82 @XmlElement(name
= "parent")
84 @XmlSchemaType(name
= "IDREF")
85 @ManyToOne(fetch
= FetchType
.LAZY
)
86 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
87 private TaxonNode parent
;
90 @XmlElement(name
= "treeIndex")
92 private String treeIndex
;
95 @XmlElement(name
= "classification")
97 @XmlSchemaType(name
= "IDREF")
98 @ManyToOne(fetch
= FetchType
.LAZY
)
99 @Cascade({CascadeType
.SAVE_UPDATE
})
100 // TODO @NotNull // avoids creating a UNIQUE key for this field
102 private Classification classification
;
104 @XmlElementWrapper(name
= "childNodes")
105 @XmlElement(name
= "childNode")
107 @XmlSchemaType(name
= "IDREF")
108 //see https://dev.e-taxonomy.eu/trac/ticket/3722
109 @OrderColumn(name
="sortIndex")
110 @OrderBy("sortIndex")
111 @OneToMany(mappedBy
="parent", fetch
=FetchType
.LAZY
)
112 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
113 private List
<TaxonNode
> childNodes
= new ArrayList
<TaxonNode
>();
115 //see https://dev.e-taxonomy.eu/trac/ticket/3722
116 private Integer sortIndex
= -1;
118 @XmlElement(name
= "reference")
120 @XmlSchemaType(name
= "IDREF")
121 @ManyToOne(fetch
= FetchType
.LAZY
)
122 @Cascade({CascadeType
.SAVE_UPDATE
})
123 private Reference
<?
> referenceForParentChildRelation
;
125 @XmlElement(name
= "microReference")
126 private String microReferenceForParentChildRelation
;
128 @XmlElement(name
= "countChildren")
129 private int countChildren
;
131 // private Taxon originalConcept;
133 @XmlElement(name
= "synonymToBeUsed")
135 @XmlSchemaType(name
= "IDREF")
136 @ManyToOne(fetch
= FetchType
.LAZY
)
137 @Cascade({CascadeType
.SAVE_UPDATE
})
138 private Synonym synonymToBeUsed
;
141 protected TaxonNode(){
146 * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
147 * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
149 * @param classification
150 * @deprecated setting of classification is handled in the addTaxonNode() method,
151 * use TaxonNode(taxon) instead
154 protected TaxonNode (Taxon taxon
, Classification classification
){
156 setClassification(classification
);
160 * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
161 * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
165 protected TaxonNode(Taxon taxon
){
171 //************************ METHODS **************************/
174 public TaxonNode
addChildTaxon(Taxon taxon
, Reference citation
, String microCitation
) {
175 return addChildTaxon(taxon
, this.childNodes
.size(), citation
, microCitation
);
181 public TaxonNode
addChildTaxon(Taxon taxon
, int index
, Reference citation
, String microCitation
) {
182 if (this.getClassification().isTaxonInTree(taxon
)){
183 throw new IllegalArgumentException(String
.format("Taxon may not be in a taxonomic view twice: %s", taxon
.getTitleCache()));
186 return addChildNode(new TaxonNode(taxon
), index
, citation
, microCitation
);
190 * Moves a taxon node to a new parent. Descendents of the node are moved as well
192 * @param childNode the taxon node to be moved to the new parent
193 * @return the child node in the state of having a new parent
196 public TaxonNode
addChildNode(TaxonNode childNode
, Reference reference
, String microReference
){
198 addChildNode(childNode
, childNodes
.size(), reference
, microReference
);
203 * Inserts the given taxon node in the list of children of <i>this</i> taxon node
204 * at the given (index + 1) position. If the given index is out of bounds
205 * an exception will arise.<BR>
206 * Due to bidirectionality this method must also assign <i>this</i> taxon node
207 * as the parent of the given child.
209 * @param child the taxon node to be added
210 * @param index the integer indicating the position at which the child
212 * @see #getChildNodes()
213 * @see #addChildNode(TaxonNode, Reference, String, Synonym)
214 * @see #deleteChildNode(TaxonNode)
215 * @see #deleteChildNode(int)
218 public TaxonNode
addChildNode(TaxonNode child
, int index
, Reference reference
, String microReference
){
219 if (index
< 0 || index
> childNodes
.size() + 1){
220 throw new IndexOutOfBoundsException("Wrong index: " + index
);
222 // check if this node is a descendant of the childNode
223 if(child
.getParentTreeNode() != this && child
.isAncestor(this)){
224 throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
228 // if (child.getParent() != null){
229 // child.getParent().deleteChildNode(child);
232 child
.setParentTreeNode(this, index
);
234 //TODO workaround (see sortIndex doc) => not really required anymore here, as it is done in child.setParentTreeNode already
235 for(int i
= 0; i
< childNodes
.size(); i
++){
236 childNodes
.get(i
).sortIndex
= i
;
238 child
.sortIndex
= index
;
240 //TODO workaround (see sortIndex doc)
241 for(int i
= 0; i
< childNodes
.size(); i
++){
242 childNodes
.get(i
).sortIndex
= i
;
244 child
.sortIndex
= index
;
246 child
.setReference(reference
);
247 child
.setMicroReference(microReference
);
254 * Sets this nodes classification. Updates classification of child nodes recursively
256 * If the former and the actual tree are equal() this method does nothing
261 private void setClassificationRecursively(Classification newTree
) {
262 if(! newTree
.equals(this.getClassification())){
263 this.setClassification(newTree
);
264 for(TaxonNode childNode
: this.getChildNodes()){
265 childNode
.setClassificationRecursively(newTree
);
271 public boolean deleteChildNode(TaxonNode node
) {
272 boolean result
= removeChildNode(node
);
273 Taxon taxon
= node
.getTaxon();
275 taxon
.removeTaxonNode(node
);
278 ArrayList
<TaxonNode
> childNodes
= new ArrayList
<TaxonNode
>(node
.getChildNodes());
279 for(TaxonNode childNode
: childNodes
){
280 node
.deleteChildNode(childNode
);
283 // // two iterations because of ConcurrentModificationErrors
284 // Set<TaxonNode> removeNodes = new HashSet<TaxonNode>();
285 // for (TaxonNode grandChildNode : node.getChildNodes()) {
286 // removeNodes.add(grandChildNode);
288 // for (TaxonNode childNode : removeNodes) {
289 // childNode.deleteChildNode(node);
296 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#removeChildNode(eu.etaxonomy.cdm.model.taxon.TaxonNode)
298 public boolean deleteChildNode(TaxonNode node
, boolean deleteChildren
) {
299 boolean result
= removeChildNode(node
);
300 Taxon taxon
= node
.getTaxon();
302 taxon
.removeTaxonNode(node
);
304 ArrayList
<TaxonNode
> childNodes
= new ArrayList
<TaxonNode
>(node
.getChildNodes());
305 for(TaxonNode childNode
: childNodes
){
306 node
.deleteChildNode(childNode
, deleteChildren
);
310 // // two iterations because of ConcurrentModificationErrors
311 // Set<TaxonNode> removeNodes = new HashSet<TaxonNode>();
312 // for (TaxonNode grandChildNode : node.getChildNodes()) {
313 // removeNodes.add(grandChildNode);
315 // for (TaxonNode childNode : removeNodes) {
316 // childNode.deleteChildNode(node);
323 * Removes the child node from this node. Sets the parent and the classification of the child
329 protected boolean removeChildNode(TaxonNode childNode
){
330 boolean result
= true;
332 if(childNode
== null){
333 throw new IllegalArgumentException("TaxonNode may not be null");
335 int index
= childNodes
.indexOf(childNode
);
344 // if(HibernateProxyHelper.deproxy(childNode.getParent(), TaxonNode.class) != this){
345 // throw new IllegalArgumentException("TaxonNode must be a child of this node");
348 // result = childNodes.remove(childNode);
349 // this.countChildren--;
350 // if (this.countChildren < 0){
351 // throw new IllegalStateException("Children count must not be negative ");
353 // childNode.setParent(null);
354 // childNode.setClassification(null);
360 * Removes the child node placed at the given (index + 1) position
361 * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
362 * Sets the parent and the classification of the child
364 * If the given index is out of bounds no child will be removed.
366 * @param index the integer indicating the position of the taxon node to
368 * @see #getChildNodes()
369 * @see #addChildNode(TaxonNode, Reference, String)
370 * @see #addChildNode(TaxonNode, int, Reference, String)
371 * @see #deleteChildNode(TaxonNode)
373 public void removeChild(int index
){
375 TaxonNode child
= childNodes
.get(index
);
376 child
= HibernateProxyHelper
.deproxy(child
, TaxonNode
.class); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
380 TaxonNode parent
= HibernateProxyHelper
.deproxy(child
.getParent(), TaxonNode
.class);
381 TaxonNode thisNode
= HibernateProxyHelper
.deproxy(this, TaxonNode
.class);
382 if(parent
!= null && parent
!= thisNode
){
383 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());
384 }else if (parent
== null){
385 throw new IllegalStateException("Parent of child is null in TaxonNode.removeChild(int). This should not happen.");
387 childNodes
.remove(index
);
388 this.countChildren
= childNodes
.size();
389 child
.setParent(null);
390 //TODO workaround (see sortIndex doc)
391 for(int i
= 0; i
< countChildren
; i
++){
392 TaxonNode childAt
= childNodes
.get(i
);
393 childAt
.sortIndex
= i
;
395 child
.sortIndex
= null;
396 child
.setClassification(null);
402 * Remove this taxonNode From its taxonomic parent
404 * @return true on success
406 public boolean delete(){
408 return classification
.deleteChildNode(this);
410 return getParent().deleteChildNode(this);
415 * Remove this taxonNode From its taxonomic parent
417 * @return true on success
419 public boolean delete(boolean deleteChildren
){
421 return classification
.deleteChildNode(this, deleteChildren
);
423 return getParent().deleteChildNode(this, deleteChildren
);
427 //*********** GETTER / SETTER ***********************************/
430 public String
treeIndex() {
435 @Deprecated //for CDM lib internal use only, may be removed in future versions
436 public void setTreeIndex(String treeIndex
) {
437 this.treeIndex
= treeIndex
;
442 @Deprecated //for CDM lib internal use only, may be removed in future versions
443 public int treeId() {
444 return this.classification
.getId();
447 public Taxon
getTaxon() {
450 protected void setTaxon(Taxon taxon
) {
453 taxon
.addTaxonNode(this);
457 public ITaxonTreeNode
getParentTreeNode() {
459 return getClassification();
465 public TaxonNode
getParent(){
470 * Sets the parent of this taxon node.
472 * In most cases you would want to call setParentTreeNode(ITreeNode) which
473 * handles updating of the bidirectional relationship
477 * @see setParentTreeNode(ITreeNode)
479 protected void setParent(ITaxonTreeNode parent
) {
480 if(parent
instanceof Classification
){
484 this.parent
= (TaxonNode
) parent
;
488 * Sets the parent of this taxon node to the given parent. Cleans up references to
489 * old parents and sets the classification to the new parents classification
494 protected void setParentTreeNode(ITaxonTreeNode parent
, int index
){
495 // remove ourselves from the old parent
496 ITaxonTreeNode formerParent
= this.getParentTreeNode();
498 //special case, child already exists for same parent
499 if (formerParent
!= null && formerParent
.equals(parent
)){
500 int currentIndex
= formerParent
.getChildNodes().indexOf(this);
501 if (currentIndex
!= -1 && currentIndex
< index
){
506 if(formerParent
instanceof TaxonNode
){ //child was a child itself
507 ((TaxonNode
) formerParent
).removeChildNode(this);
508 } else if((formerParent
instanceof Classification
) && ! formerParent
.equals(parent
)){ //child was root in old tree
509 ((Classification
) formerParent
).removeChildNode(this);
512 // set the new parent
515 // set the classification to the parents classification
516 Classification classification
= (parent
instanceof Classification
) ?
(Classification
) parent
: ((TaxonNode
) parent
).getClassification();
517 setClassificationRecursively(classification
);
519 // add this node to the parent child nodes
520 List
<TaxonNode
> parentChildren
= parent
.getChildNodes();
521 if (parentChildren
.contains(this)){
523 if (parentChildren
.indexOf(this) < index
){
526 parentChildren
.remove(this);
527 parentChildren
.add(index
, this);
529 parentChildren
.add(index
, this);
532 //TODO workaround (see sortIndex doc)
533 //TODO check if it is correct to use the parentChildren here
534 for(int i
= 0; i
< parentChildren
.size(); i
++){
535 parentChildren
.get(i
).sortIndex
= i
;
537 // this.sortIndex = index;
539 // update the children count
540 if(parent
instanceof TaxonNode
){
541 TaxonNode parentTaxonNode
= (TaxonNode
) parent
;
542 parentTaxonNode
.setCountChildren(parent
.getChildNodes().size());
546 public Classification
getClassification() {
547 return classification
;
550 * THIS METHOD SHOULD NOT BE CALLED!
551 * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
552 * @param classification
554 protected void setClassification(Classification classification
) {
555 this.classification
= classification
;
559 public List
<TaxonNode
> getChildNodes() {
564 * Returns a set containing this node and all nodes that are descendants of this node
568 protected Set
<TaxonNode
> getDescendants(){
569 Set
<TaxonNode
> nodeSet
= new HashSet
<TaxonNode
>();
573 for(TaxonNode childNode
: getChildNodes()){
574 nodeSet
.addAll(childNode
.getDescendants());
581 * Returns a set containing a clone of this node and of all nodes that are descendants of this node
585 protected TaxonNode
cloneDescendants(){
587 TaxonNode clone
= (TaxonNode
)this.clone();
588 TaxonNode childClone
;
590 for(TaxonNode childNode
: getChildNodes()){
591 childClone
= (TaxonNode
) childNode
.clone();
592 for (TaxonNode childChild
:childNode
.getChildNodes()){
593 childClone
.addChildNode(childChild
.cloneDescendants(), childChild
.getReference(), childChild
.getMicroReference());
595 clone
.addChildNode(childClone
, childNode
.getReference(), childNode
.getMicroReference());
598 //childClone.addChildNode(childNode.cloneDescendants());
608 protected Set
<TaxonNode
> getAncestors(){
609 Set
<TaxonNode
> nodeSet
= new HashSet
<TaxonNode
>();
611 if(this.getParent() != null){
612 nodeSet
.addAll(this.getParent().getAncestors());
619 public Reference
getReference() {
620 return referenceForParentChildRelation
;
624 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setReference(eu.etaxonomy.cdm.model.reference.Reference)
626 public void setReference(Reference reference
) {
627 this.referenceForParentChildRelation
= reference
;
632 public String
getMicroReference() {
633 return microReferenceForParentChildRelation
;
637 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setMicroReference(java.lang.String)
639 public void setMicroReference(String microReference
) {
640 this.microReferenceForParentChildRelation
= microReference
;
644 * @return the count of children this taxon node has
646 public int getCountChildren() {
647 return countChildren
;
651 * @param countChildren
653 protected void setCountChildren(int countChildren
) {
654 this.countChildren
= countChildren
;
657 public Synonym
getSynonymToBeUsed() {
658 return synonymToBeUsed
;
660 public void setSynonymToBeUsed(Synonym synonymToBeUsed
) {
661 this.synonymToBeUsed
= synonymToBeUsed
;
665 * Whether this TaxonNode is a direct child of the classification TreeNode
669 public boolean isTopmostNode(){
670 return parent
== null;
674 * Whether this TaxonNode is a descendant of the given TaxonNode
676 * Caution: use this method with care on big branches. -> performance and memory hungry
678 * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
679 * other direction (up). It will always result in a rather small set of consecutive parents beeing
682 * TODO implement more efficiently without generating the set of descendants first
684 * @param possibleParent
685 * @return true if this is a descendant
688 public boolean isDescendant(TaxonNode possibleParent
){
689 return possibleParent
.getDescendants().contains(this);
693 * Whether this TaxonNode is an ascendant of the given TaxonNode
696 * @param possibleChild
697 * @return true if there are ascendants
700 public boolean isAncestor(TaxonNode possibleChild
){
701 return possibleChild
.getAncestors().contains(this);
705 * Whether this taxon has child nodes
707 * @return true if the taxonNode has childNodes
711 public boolean hasChildNodes(){
712 return childNodes
.size() > 0;
715 //*********************** CLONE ********************************************************/
717 * Clones <i>this</i> taxon node. This is a shortcut that enables to create
718 * a new instance that differs only slightly from <i>this</i> taxon node by
719 * modifying only some of the attributes.<BR><BR>
720 * The child nodes are not copied.<BR>
721 * The taxon and parent are the same as for the original taxon node. <BR>
723 * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
724 * @see java.lang.Object#clone()
727 public Object
clone() {
730 result
= (TaxonNode
)super.clone();
731 result
.getTaxon().addTaxonNode(result
);
732 result
.childNodes
= new ArrayList
<TaxonNode
>();
733 result
.countChildren
= 0;
736 }catch (CloneNotSupportedException e
) {
737 logger
.warn("Object does not implement cloneable");