Merge branch 'master' of wp5.e-taxonomy.eu:/var/git/cdmlib into remoting-4.0
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / taxon / TaxonNode.java
index 1451932ed7a5a843f097905b8f7ab47238a4bf6b..fbb8a71d2e09793274d5fa2b0c790a9f24ae2fdc 100644 (file)
@@ -12,13 +12,17 @@ package eu.etaxonomy.cdm.model.taxon;
 \r
 import java.util.ArrayList;\r
 import java.util.HashSet;\r
+import java.util.List;\r
 import java.util.Set;\r
 \r
 import javax.persistence.Entity;\r
 import javax.persistence.FetchType;\r
 import javax.persistence.ManyToOne;\r
 import javax.persistence.OneToMany;\r
+import javax.persistence.OrderBy;\r
+import javax.persistence.OrderColumn;\r
 import javax.persistence.Transient;\r
+import javax.validation.constraints.Size;\r
 import javax.xml.bind.annotation.XmlAccessType;\r
 import javax.xml.bind.annotation.XmlAccessorType;\r
 import javax.xml.bind.annotation.XmlElement;\r
@@ -31,36 +35,54 @@ import javax.xml.bind.annotation.XmlType;
 import org.apache.log4j.Logger;\r
 import org.hibernate.annotations.Cascade;\r
 import org.hibernate.annotations.CascadeType;\r
+import org.hibernate.annotations.Index;\r
+import org.hibernate.annotations.Table;\r
 import org.hibernate.envers.Audited;\r
 import org.hibernate.search.annotations.ContainedIn;\r
 import org.hibernate.search.annotations.Indexed;\r
 import org.hibernate.search.annotations.IndexedEmbedded;\r
 \r
 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;\r
+import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;\r
 import eu.etaxonomy.cdm.model.common.AnnotatableEntity;\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.model.common.DefinedTerm;\r
+import eu.etaxonomy.cdm.model.common.ITreeNode;\r
+import eu.etaxonomy.cdm.model.name.Rank;\r
+import eu.etaxonomy.cdm.model.name.TaxonNameBase;\r
 import eu.etaxonomy.cdm.model.reference.Reference;\r
+import eu.etaxonomy.cdm.validation.Level3;\r
+import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustBeLowerRankThanParent;\r
+import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustDeriveNameFromParent;\r
+import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustNotSkipRanks;\r
 \r
 /**\r
  * @author a.mueller\r
  * @created 31.03.2009\r
- * @version 1.0\r
  */\r
 @XmlAccessorType(XmlAccessType.FIELD)\r
 @XmlType(name = "TaxonNode", propOrder = {\r
+    "classification",\r
     "taxon",\r
     "parent",\r
-    "classification",\r
+    "treeIndex",\r
+    "sortIndex",\r
     "childNodes",\r
     "referenceForParentChildRelation",\r
     "microReferenceForParentChildRelation",\r
     "countChildren",\r
+    "agentRelations",\r
     "synonymToBeUsed"\r
 })\r
 @XmlRootElement(name = "TaxonNode")\r
 @Entity\r
 @Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonNode")\r
 @Audited\r
-public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable{\r
+@Table(appliesTo="TaxonNode", indexes = { @Index(name = "taxonNodeTreeIndex", columnNames = { "treeIndex" }) })\r
+@ChildTaxaMustBeLowerRankThanParent(groups = Level3.class)\r
+@ChildTaxaMustNotSkipRanks(groups = Level3.class)\r
+@ChildTaxaMustDeriveNameFromParent(groups = Level3.class)\r
+public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITreeNode<TaxonNode>, Cloneable{\r
     private static final long serialVersionUID = -4743289894926587693L;\r
     private static final Logger logger = Logger.getLogger(TaxonNode.class);\r
 \r
@@ -81,11 +103,16 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
     private TaxonNode parent;\r
 \r
 \r
+    @XmlElement(name = "treeIndex")\r
+    @Size(max=255)\r
+    private String treeIndex;\r
+\r
+\r
     @XmlElement(name = "classification")\r
     @XmlIDREF\r
     @XmlSchemaType(name = "IDREF")\r
     @ManyToOne(fetch = FetchType.LAZY)\r
-    @Cascade({CascadeType.SAVE_UPDATE})\r
+    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})\r
 //     TODO @NotNull // avoids creating a UNIQUE key for this field\r
     @IndexedEmbedded\r
     private Classification classification;\r
@@ -94,15 +121,22 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
     @XmlElement(name = "childNode")\r
     @XmlIDREF\r
     @XmlSchemaType(name = "IDREF")\r
+    //see https://dev.e-taxonomy.eu/trac/ticket/3722\r
+    @OrderColumn(name="sortIndex")\r
+    @OrderBy("sortIndex")\r
     @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)\r
     @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})\r
-    private Set<TaxonNode> childNodes = new HashSet<TaxonNode>();\r
+    private List<TaxonNode> childNodes = new ArrayList<TaxonNode>();\r
+\r
+    //see https://dev.e-taxonomy.eu/trac/ticket/3722\r
+    //see https://dev.e-taxonomy.eu/trac/ticket/4200\r
+    private Integer sortIndex = -1;\r
 \r
-    @XmlElement(name = "reference")\r
+       @XmlElement(name = "reference")\r
     @XmlIDREF\r
     @XmlSchemaType(name = "IDREF")\r
     @ManyToOne(fetch = FetchType.LAZY)\r
-    @Cascade({CascadeType.SAVE_UPDATE})\r
+    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})\r
     private Reference<?> referenceForParentChildRelation;\r
 \r
     @XmlElement(name = "microReference")\r
@@ -111,19 +145,27 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
     @XmlElement(name = "countChildren")\r
     private int countChildren;\r
 \r
+    @XmlElementWrapper(name = "agentRelations")\r
+    @XmlElement(name = "agentRelation")\r
+    @XmlIDREF\r
+    @XmlSchemaType(name = "IDREF")\r
+    @OneToMany(mappedBy="taxonNode", fetch=FetchType.LAZY)\r
+    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})\r
+    private Set<TaxonNodeAgentRelation> agentRelations = new HashSet<TaxonNodeAgentRelation>();\r
+\r
+\r
 //     private Taxon originalConcept;\r
 //     //or\r
     @XmlElement(name = "synonymToBeUsed")\r
     @XmlIDREF\r
     @XmlSchemaType(name = "IDREF")\r
     @ManyToOne(fetch = FetchType.LAZY)\r
-    @Cascade({CascadeType.SAVE_UPDATE})\r
+    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})\r
     private Synonym synonymToBeUsed;\r
 \r
+// ******************** CONSTRUCTOR **********************************************/\r
 \r
-    protected TaxonNode(){\r
-        super();\r
-    }\r
+    protected TaxonNode(){super();}\r
 \r
     /**\r
      * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}\r
@@ -133,6 +175,7 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
      * @deprecated setting of classification is handled in the addTaxonNode() method,\r
      * use TaxonNode(taxon) instead\r
      */\r
+    @Deprecated\r
     protected TaxonNode (Taxon taxon, Classification classification){\r
         this(taxon);\r
         setClassification(classification);\r
@@ -148,19 +191,179 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
         setTaxon(taxon);\r
     }\r
 \r
+// ************************* GETTER / SETTER *******************************/\r
 \r
+    public Integer getSortIndex() {\r
+               return sortIndex;\r
+       }\r
+    /**\r
+     * SortIndex shall be handled only internally, therefore not public.\r
+     * However, as javaassist only supports protected methods it needs to be protected, not private.\r
+     * Alternatively we could use deproxy on every call of this method (see commented code)\r
+     * @param i\r
+     * @return\r
+     * @deprecated for internal use only\r
+     */\r
+     @Deprecated\r
+    protected void setSortIndex(Integer i) {\r
+//      CdmBase.deproxy(this, TaxonNode.class).sortIndex = i;  //alternative solution for private, DON'T remove\r
+        sortIndex = i;\r
+       }\r
 \r
-//************************ METHODS **************************/\r
 \r
-    /* (non-Javadoc)\r
-     * @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)\r
-     */\r
-    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation, Synonym synonymToBeUsed) {\r
-        if (this.getClassification().isTaxonInTree(taxon)){\r
-             throw new IllegalArgumentException(String.format("Taxon may not be in a taxonomic view twice: %s", taxon.getTitleCache()));\r
+    public Taxon getTaxon() {\r
+        return taxon;\r
+    }\r
+    protected void setTaxon(Taxon taxon) {\r
+        this.taxon = taxon;\r
+        if (taxon != null){\r
+            taxon.addTaxonNode(this);\r
         }\r
+    }\r
 \r
-        return addChildNode(new TaxonNode(taxon), citation, microCitation, synonymToBeUsed);\r
+\r
+    @Override\r
+    public List<TaxonNode> getChildNodes() {\r
+        return childNodes;\r
+    }\r
+       protected void setChildNodes(List<TaxonNode> childNodes) {\r
+               this.childNodes = childNodes;\r
+       }\r
+\r
+\r
+    public Classification getClassification() {\r
+        return classification;\r
+    }\r
+    /**\r
+     * THIS METHOD SHOULD NOT BE CALLED!\r
+     * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()\r
+     * @param classification\r
+     * @deprecated for internal use only\r
+     */\r
+    @Deprecated\r
+    protected void setClassification(Classification classification) {\r
+        this.classification = classification;\r
+    }\r
+\r
+\r
+    @Override\r
+    public String getMicroReference() {\r
+        return microReferenceForParentChildRelation;\r
+    }\r
+    public void setMicroReference(String microReference) {\r
+        this.microReferenceForParentChildRelation = microReference;\r
+    }\r
+\r
+\r
+    @Override\r
+    public Reference getReference() {\r
+        return referenceForParentChildRelation;\r
+    }\r
+    public void setReference(Reference reference) {\r
+        this.referenceForParentChildRelation = reference;\r
+    }\r
+\r
+    //countChildren\r
+    public int getCountChildren() {\r
+        return countChildren;\r
+    }\r
+    /**\r
+     * @deprecated for internal use only\r
+     * @param countChildren\r
+     */\r
+    @Deprecated\r
+    protected void setCountChildren(int countChildren) {\r
+        this.countChildren = countChildren;\r
+    }\r
+\r
+\r
+    //parent\r
+    @Override\r
+    public TaxonNode getParent(){\r
+        return parent;\r
+    }\r
+    /**\r
+     * Sets the parent of this taxon node.<BR>\r
+     *\r
+     * In most cases you would want to call setParentTreeNode(ITreeNode) which\r
+     * handles updating of the bidirectional relationship\r
+     *\r
+     * @see setParentTreeNode(ITreeNode)\r
+     * @param parent\r
+     *\r
+     */\r
+    protected void setParent(TaxonNode parent) {\r
+        this.parent = parent;\r
+    }\r
+\r
+// ****************** Agent Relations ****************************/\r
+\r
+\r
+    /**\r
+     * @return\r
+     */\r
+    public Set<TaxonNodeAgentRelation> getAgentRelations() {\r
+        return this.agentRelations;\r
+    }\r
+\r
+    public TaxonNodeAgentRelation addAgentRelation(DefinedTerm type, TeamOrPersonBase<?> agent){\r
+        TaxonNodeAgentRelation result = TaxonNodeAgentRelation.NewInstance(this, agent, type);\r
+        return result;\r
+    }\r
+    /**\r
+     * @param nodeAgentRelation\r
+     */\r
+    protected void addAgentRelation(TaxonNodeAgentRelation agentRelation) {\r
+        agentRelation.setTaxonNode(this);\r
+        this.agentRelations.add(agentRelation);\r
+    }\r
+\r
+    /**\r
+     * @param nodeAgentRelation\r
+     */\r
+    public void removeNodeAgent(TaxonNodeAgentRelation agentRelation) {\r
+        agentRelation.setTaxonNode(this);\r
+        agentRelations.remove(agentRelation);\r
+    }\r
+\r
+//********************\r
+\r
+    //synonymToBeused\r
+    public Synonym getSynonymToBeUsed() {\r
+        return synonymToBeUsed;\r
+    }\r
+    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {\r
+        this.synonymToBeUsed = synonymToBeUsed;\r
+    }\r
+\r
+\r
+    //treeindex\r
+    @Override\r
+    public String treeIndex() {\r
+        return treeIndex;\r
+    }\r
+    @Override\r
+    @Deprecated //for CDM lib internal use only, may be removed in future versions\r
+    public void setTreeIndex(String treeIndex) {\r
+        this.treeIndex = treeIndex;\r
+    }\r
+\r
+\r
+\r
+//************************ METHODS **************************/\r
+\r
+   @Override\r
+    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {\r
+        return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);\r
+    }\r
+\r
+\r
+    @Override\r
+    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {\r
+        if (this.getClassification().isTaxonInTree(taxon)){\r
+            throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));\r
+       }\r
+       return addChildNode(new TaxonNode(taxon), index, citation, microCitation);\r
     }\r
 \r
     /**\r
@@ -169,62 +372,105 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
      * @param childNode the taxon node to be moved to the new parent\r
      * @return the child node in the state of having a new parent\r
      */\r
-    public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference, Synonym synonymToBeUsed){\r
+    @Override\r
+    public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference){\r
+        addChildNode(childNode, childNodes.size(), reference, microReference);\r
+        return childNode;\r
+    }\r
 \r
-        // check if this node is a descendant of the childNode\r
-        if(childNode.getParentTreeNode() != this && childNode.isAncestor(this)){\r
+    /**\r
+     * Inserts the given taxon node in the list of children of <i>this</i> taxon node\r
+     * at the given (index + 1) position. If the given index is out of bounds\r
+     * an exception will arise.<BR>\r
+     * Due to bidirectionality this method must also assign <i>this</i> taxon node\r
+     * as the parent of the given child.\r
+     *\r
+     * @param  child   the taxon node to be added\r
+     * @param  index   the integer indicating the position at which the child\r
+     *                                         should be added\r
+     * @see                            #getChildNodes()\r
+     * @see                            #addChildNode(TaxonNode, Reference, String, Synonym)\r
+     * @see                            #deleteChildNode(TaxonNode)\r
+     * @see                            #deleteChildNode(int)\r
+     */\r
+    @Override\r
+    public TaxonNode addChildNode(TaxonNode child, int index, Reference reference, String microReference){\r
+        if (index < 0 || index > childNodes.size() + 1){\r
+            throw new IndexOutOfBoundsException("Wrong index: " + index);\r
+        }\r
+           // check if this node is a descendant of the childNode\r
+        if(child.getParent() != this && child.isAncestor(this)){\r
             throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");\r
         }\r
 \r
-        childNode.setParentTreeNode(this);\r
+        child.setParentTreeNode(this, index);\r
 \r
-        childNode.setReference(reference);\r
-        childNode.setMicroReference(microReference);\r
-        childNode.setSynonymToBeUsed(synonymToBeUsed);\r
+        child.setReference(reference);\r
+        child.setMicroReference(microReference);\r
 \r
-        return childNode;\r
+        return child;\r
     }\r
 \r
     /**\r
-     * Sets this nodes classification. Updates classification of child nodes recursively\r
+     * Sets this nodes classification. Updates classification of child nodes recursively.\r
+     *\r
+     * If the former and the actual tree are equal() this method does nothing.\r
      *\r
-     * If the former and the actual tree are equal() this method does nothing\r
+     * @throws IllegalArgumentException if newClassifciation is null\r
      *\r
-     * @param newTree\r
+     * @param newClassification\r
      */\r
     @Transient\r
-    private void setClassificationRecursively(Classification newTree) {\r
-        if(! newTree.equals(this.getClassification())){\r
-            this.setClassification(newTree);\r
+    private void setClassificationRecursively(Classification newClassification) {\r
+        if (newClassification == null){\r
+               throw new IllegalArgumentException("New Classification must not be 'null' when setting new classification.");\r
+        }\r
+       if(! newClassification.equals(this.getClassification())){\r
+            this.setClassification(newClassification);\r
             for(TaxonNode childNode : this.getChildNodes()){\r
-                childNode.setClassificationRecursively(newTree);\r
+                childNode.setClassificationRecursively(newClassification);\r
             }\r
         }\r
     }\r
 \r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#removeChildNode(eu.etaxonomy.cdm.model.taxon.TaxonNode)\r
-     */\r
+    @Override\r
     public boolean deleteChildNode(TaxonNode node) {\r
         boolean result = removeChildNode(node);\r
-\r
-        node.getTaxon().removeTaxonNode(node);\r
+        Taxon taxon = node.getTaxon();\r
         node.setTaxon(null);\r
+        taxon.removeTaxonNode(node);\r
 \r
         ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());\r
         for(TaxonNode childNode : childNodes){\r
             node.deleteChildNode(childNode);\r
         }\r
 \r
-//             // two iterations because of ConcurrentModificationErrors\r
-//        Set<TaxonNode> removeNodes = new HashSet<TaxonNode>();\r
-//        for (TaxonNode grandChildNode : node.getChildNodes()) {\r
-//                removeNodes.add(grandChildNode);\r
-//        }\r
-//        for (TaxonNode childNode : removeNodes) {\r
-//                childNode.deleteChildNode(node);\r
-//        }\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * Deletes the child node and also removes children of childnode\r
+     * recursively if delete children is <code>true</code>\r
+     * @param node\r
+     * @param deleteChildren\r
+     * @return\r
+     */\r
+    public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {\r
+        boolean result = removeChildNode(node);\r
+        Taxon taxon = node.getTaxon();\r
+        node.setTaxon(null);\r
+        taxon.removeTaxonNode(node);\r
+        if (deleteChildren){\r
+            ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());\r
+            for(TaxonNode childNode : childNodes){\r
+                node.deleteChildNode(childNode, deleteChildren);\r
+            }\r
+        } else{\r
+               ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());\r
+            for(TaxonNode childNode : childNodes){\r
+             this.addChildNode(childNode, null, null);\r
+            }\r
+        }\r
 \r
         return result;\r
     }\r
@@ -237,24 +483,59 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
      * @return\r
      */\r
     protected boolean removeChildNode(TaxonNode childNode){\r
-        boolean result = false;\r
+        boolean result = true;\r
 \r
         if(childNode == null){\r
             throw new IllegalArgumentException("TaxonNode may not be null");\r
         }\r
-        if(HibernateProxyHelper.deproxy(childNode.getParent(), TaxonNode.class) != this){\r
-            throw new IllegalArgumentException("TaxonNode must be a child of this node");\r
+        int index = childNodes.indexOf(childNode);\r
+        if (index >= 0){\r
+            removeChild(index);\r
+        } else {\r
+            result = false;\r
         }\r
+        return result;\r
+    }\r
 \r
-        result = childNodes.remove(childNode);\r
-        this.countChildren--;\r
-        if (this.countChildren < 0){\r
-            throw new IllegalStateException("children count must not be negative ");\r
-        }\r
-        childNode.setParent(null);\r
-        childNode.setClassification(null);\r
+    /**\r
+     * Removes the child node placed at the given (index + 1) position\r
+     * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.\r
+     * Sets the parent and the classification of the child\r
+     * node to null.\r
+     * If the given index is out of bounds no child will be removed.\r
+     *\r
+     * @param  index   the integer indicating the position of the taxon node to\r
+     *                                         be removed\r
+     * @see                    #getChildNodes()\r
+     * @see                            #addChildNode(TaxonNode, Reference, String)\r
+     * @see                            #addChildNode(TaxonNode, int, Reference, String)\r
+     * @see                            #deleteChildNode(TaxonNode)\r
+     */\r
+    public void removeChild(int index){\r
 \r
-        return result;\r
+        TaxonNode child = childNodes.get(index);\r
+        child = HibernateProxyHelper.deproxy(child, TaxonNode.class); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.\r
+\r
+        if (child != null){\r
+\r
+            TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent(), TaxonNode.class);\r
+            TaxonNode thisNode = HibernateProxyHelper.deproxy(this, TaxonNode.class);\r
+            if(parent != null && parent != thisNode){\r
+                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());\r
+            }else if (parent == null){\r
+                throw new IllegalStateException("Parent of child is null in TaxonNode.removeChild(int). This should not happen.");\r
+            }\r
+            childNodes.remove(index);\r
+            child.setClassification(null);\r
+\r
+            //update sortindex\r
+            //TODO workaround (see sortIndex doc)\r
+            this.countChildren = childNodes.size();\r
+            child.setParent(null);\r
+            child.setTreeIndex(null);\r
+            updateSortIndex(childNodes, index);\r
+            child.setSortIndex(null);\r
+        }\r
     }\r
 \r
 \r
@@ -271,46 +552,31 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
         }\r
     }\r
 \r
-//*********** GETTER / SETTER ***********************************/\r
-\r
-    public Taxon getTaxon() {\r
-        return taxon;\r
-    }\r
-    protected void setTaxon(Taxon taxon) {\r
-        this.taxon = taxon;\r
-        if (taxon != null){\r
-            taxon.addTaxonNode(this);\r
-        }\r
-    }\r
-    @Transient\r
-    public ITreeNode getParentTreeNode() {\r
-        if(isTopmostNode())\r
-            return getClassification();\r
-        return parent;\r
-    }\r
-\r
-    public TaxonNode getParent(){\r
-        return parent;\r
-    }\r
-\r
     /**\r
-     * Sets the parent of this taxon node.\r
-     *\r
-     * In most cases you would want to call setParentTreeNode(ITreeNode) which\r
-     * handles updating of the bidirectional relationship\r
-     *\r
-     * @param parent\r
+     * Remove this taxonNode From its taxonomic parent\r
      *\r
-     * @see setParentTreeNode(ITreeNode)\r
+     * @return true on success\r
      */\r
-    protected void setParent(ITreeNode parent) {\r
-        if(parent instanceof Classification){\r
-            this.parent = null;\r
-            return;\r
+    public boolean delete(boolean deleteChildren){\r
+        if(isTopmostNode()){\r
+            return classification.deleteChildNode(this, deleteChildren);\r
+        }else{\r
+            return getParent().deleteChildNode(this, deleteChildren);\r
         }\r
-        this.parent = (TaxonNode) parent;\r
     }\r
 \r
+    @Override\r
+    @Deprecated //for CDM lib internal use only, may be removed in future versions\r
+    public int treeId() {\r
+        if (this.classification == null){\r
+               logger.warn("TaxonNode has no classification. This should not happen.");  //#3840\r
+               return -1;\r
+        }else{\r
+               return this.classification.getId();\r
+        }\r
+    }\r
+\r
+\r
     /**\r
      * Sets the parent of this taxon node to the given parent. Cleans up references to\r
      * old parents and sets the classification to the new parents classification\r
@@ -318,54 +584,87 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
      * @param parent\r
      */\r
     @Transient\r
-    protected void setParentTreeNode(ITreeNode parent){\r
+    protected void setParentTreeNode(TaxonNode parent, int index){\r
         // remove ourselves from the old parent\r
-        ITreeNode formerParent = this.getParentTreeNode();\r
-        if(formerParent instanceof TaxonNode){  //child was a child itself\r
-            ((TaxonNode) formerParent).removeChildNode(this);\r
-        }\r
-        else if((formerParent instanceof Classification) && ! formerParent.equals(parent)){ //child was root in old tree\r
-            ((Classification) formerParent).removeChildNode(this);\r
+        TaxonNode formerParent = this.getParent();\r
+\r
+        if (formerParent != null){\r
+               //special case, child already exists for same parent\r
+            //FIXME document / check for correctness\r
+            if (formerParent.equals(parent)){\r
+                int currentIndex = formerParent.getChildNodes().indexOf(this);\r
+                if (currentIndex != -1 && currentIndex < index){\r
+                    index--;\r
+                }\r
+               }\r
+\r
+               //remove from old parent\r
+            formerParent.removeChildNode(this);\r
         }\r
 \r
         // set the new parent\r
         setParent(parent);\r
 \r
         // set the classification to the parents classification\r
-        Classification classification = (parent instanceof Classification) ? (Classification) parent : ((TaxonNode) parent).getClassification();\r
-        setClassificationRecursively(classification);\r
+\r
+               Classification classification = parent.getClassification();\r
+               //FIXME also set the tree index here for performance reasons\r
+               setClassificationRecursively(classification);\r
 \r
         // add this node to the parent child nodes\r
-        parent.getChildNodes().add(this);\r
+        List<TaxonNode> parentChildren = parent.getChildNodes();\r
+        if (parentChildren.contains(this)){\r
+            //avoid duplicates\r
+            if (parentChildren.indexOf(this) < index){\r
+                index = index -1;\r
+            }\r
+            parentChildren.remove(this);\r
+            parentChildren.add(index, this);\r
+        }else{\r
+            parentChildren.add(index, this);\r
+        }\r
+\r
+\r
+        //sortIndex\r
+        //TODO workaround (see sortIndex doc)\r
+        updateSortIndex(parentChildren, index);\r
+        //only for debugging\r
+        if (! this.getSortIndex().equals(index)){\r
+               logger.warn("index and sortindex are not equal");\r
+        }\r
 \r
         // update the children count\r
-        if(parent instanceof TaxonNode){\r
-            TaxonNode parentTaxonNode = (TaxonNode) parent;\r
-            parentTaxonNode.setCountChildren(parentTaxonNode.getCountChildren() + 1);\r
+        parent.setCountChildren(parent.getChildNodes().size());\r
+    }\r
+\r
+       /**\r
+        * As long as the sort index is not correctly handled through hibernate this is a workaround method\r
+        * to update the sort index manually\r
+        * @param parentChildren\r
+        * @param index\r
+        */\r
+       private void updateSortIndex(List<TaxonNode> children, int index) {\r
+               for(int i = index; i < children.size(); i++){\r
+               TaxonNode child = children.get(i);\r
+               if (child != null){\r
+//                     child = CdmBase.deproxy(child, TaxonNode.class);  //deproxy not needed as long as setSortIndex is protected or public #4200\r
+                       child.setSortIndex(i);\r
+               }else{\r
+                       String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";\r
+                       throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));\r
+               }\r
         }\r
-    }\r
+       }\r
+\r
 \r
-    public Classification getClassification() {\r
-        return classification;\r
-    }\r
-    /**\r
-     * THIS METHOD SHOULD NOT BE CALLED!\r
-     * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()\r
-     * @param classification\r
-     */\r
-    protected void setClassification(Classification classification) {\r
-        this.classification = classification;\r
-    }\r
 \r
-    public Set<TaxonNode> getChildNodes() {\r
-        return childNodes;\r
-    }\r
 \r
     /**\r
      * Returns a set containing this node and all nodes that are descendants of this node\r
      *\r
      * @return\r
      */\r
+\r
     protected Set<TaxonNode> getDescendants(){\r
         Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();\r
 \r
@@ -391,11 +690,9 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
         for(TaxonNode childNode : getChildNodes()){\r
             childClone = (TaxonNode) childNode.clone();\r
             for (TaxonNode childChild:childNode.getChildNodes()){\r
-                childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference(), childChild.getSynonymToBeUsed());\r
+                childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());\r
             }\r
-            clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference(), childNode.getSynonymToBeUsed());\r
-\r
-\r
+            clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference());\r
             //childClone.addChildNode(childNode.cloneDescendants());\r
         }\r
         return clone;\r
@@ -408,73 +705,30 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
      */\r
     protected Set<TaxonNode> getAncestors(){\r
         Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();\r
-\r
-\r
-        nodeSet.add(this);\r
-\r
+       // nodeSet.add(this);\r
         if(this.getParent() != null){\r
-            nodeSet.addAll(((TaxonNode) this.getParent()).getAncestors());\r
+               TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);\r
+            nodeSet.addAll(parent.getAncestors());\r
         }\r
-\r
         return nodeSet;\r
     }\r
+    public TaxonNode getAncestorOfRank(Rank rank){\r
+        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();\r
+       // nodeSet.add(this);\r
+        TaxonBase taxon = CdmBase.deproxy(this.getTaxon(), Taxon.class);\r
+        TaxonNameBase name = CdmBase.deproxy(taxon.getName(), TaxonNameBase.class);\r
+        if (name.getRank().isHigher(rank)){\r
+               return null;\r
+        }\r
+        if (name.getRank().equals(rank)){\r
+               return this;\r
+        }\r
+        if(this.getParent() != null ){\r
+               TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);\r
+            return parent.getAncestorOfRank(rank);\r
+        }\r
+               return null;\r
 \r
-    /**\r
-     * The reference for the parent child relationship\r
-     *\r
-     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#getReference()\r
-     */\r
-    public Reference getReference() {\r
-        return referenceForParentChildRelation;\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setReference(eu.etaxonomy.cdm.model.reference.Reference)\r
-     */\r
-    public void setReference(Reference reference) {\r
-        this.referenceForParentChildRelation = reference;\r
-    }\r
-\r
-    /**\r
-     *\r
-     *\r
-     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#getMicroReference()\r
-     */\r
-    public String getMicroReference() {\r
-        return microReferenceForParentChildRelation;\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setMicroReference(java.lang.String)\r
-     */\r
-    public void setMicroReference(String microReference) {\r
-        this.microReferenceForParentChildRelation = microReference;\r
-    }\r
-\r
-    /**\r
-     * @return the count of children this taxon node has\r
-     */\r
-    public int getCountChildren() {\r
-        return countChildren;\r
-    }\r
-\r
-    /**\r
-     * @param countChildren\r
-     */\r
-    protected void setCountChildren(int countChildren) {\r
-        this.countChildren = countChildren;\r
-    }\r
-//     public Taxon getOriginalConcept() {\r
-//             return originalConcept;\r
-//     }\r
-//     public void setOriginalConcept(Taxon originalConcept) {\r
-//             this.originalConcept = originalConcept;\r
-//     }\r
-    public Synonym getSynonymToBeUsed() {\r
-        return synonymToBeUsed;\r
-    }\r
-    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {\r
-        this.synonymToBeUsed = synonymToBeUsed;\r
     }\r
 \r
     /**\r
@@ -483,7 +737,42 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
      */\r
     @Transient\r
     public boolean isTopmostNode(){\r
-        return parent == null;\r
+       boolean parentCheck = false;\r
+       boolean classificationCheck = false;\r
+\r
+       if(getParent() != null) {\r
+               if(getParent().getTaxon() == null) {\r
+                       parentCheck = true;\r
+               }\r
+       }\r
+\r
+       //TODO remove\r
+       // FIXME This should work but doesn't, due to missing sort indexes, can be removed after fixing #4200, #4098\r
+       if (classification != null){\r
+               classificationCheck = classification.getRootNode().getChildNodes().contains(this);\r
+       }else{\r
+               classificationCheck = false;\r
+       }\r
+\r
+       // The following is just for logging purposes for the missing sort indexes problem\r
+       // ticket #4098\r
+       if(parentCheck != classificationCheck) {\r
+               logger.warn("isTopmost node check " + parentCheck + " not same as classificationCheck : " + classificationCheck + " for taxon node ");\r
+               if(this.getParent() != null) {\r
+                       logger.warn("-- with parent uuid " + this.getParent().getUuid().toString());\r
+                       logger.warn("-- with parent id " + this.getParent().getId());\r
+                       for(TaxonNode node : this.getParent().getChildNodes()) {\r
+                               if(node == null) {\r
+                                       logger.warn("-- child node is null");\r
+                               } else if (node.getTaxon() == null) {\r
+                                       logger.warn("-- child node taxon is null");\r
+                               }\r
+                       }\r
+                       logger.warn("-- parent child count" + this.getParent().getChildNodes().size());\r
+               }\r
+       }\r
+\r
+       return parentCheck;\r
     }\r
 \r
     /**\r
@@ -502,19 +791,25 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
      */\r
     @Transient\r
     public boolean isDescendant(TaxonNode possibleParent){\r
-        return possibleParent.getDescendants().contains(this);\r
+       if (this.treeIndex() == null || possibleParent.treeIndex() == null) {\r
+               return false;\r
+       }\r
+       return possibleParent == null ? false : this.treeIndex().startsWith(possibleParent.treeIndex() );\r
     }\r
 \r
     /**\r
      * Whether this TaxonNode is an ascendant of the given TaxonNode\r
      *\r
-     *\r
      * @param possibleChild\r
      * @return true if there are ascendants\r
      */\r
     @Transient\r
     public boolean isAncestor(TaxonNode possibleChild){\r
-        return possibleChild.getAncestors().contains(this);\r
+       if (this.treeIndex() == null || possibleChild.treeIndex() == null) {\r
+               return false;\r
+       }\r
+       // return possibleChild == null ? false : possibleChild.getAncestors().contains(this);\r
+        return possibleChild == null ? false : possibleChild.treeIndex().startsWith(this.treeIndex());\r
     }\r
 \r
     /**\r
@@ -523,10 +818,26 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
      * @return true if the taxonNode has childNodes\r
      */\r
     @Transient\r
+    @Override\r
     public boolean hasChildNodes(){\r
         return childNodes.size() > 0;\r
     }\r
 \r
+\r
+    public boolean hasTaxon() {\r
+        return (taxon!= null);\r
+    }\r
+\r
+    /**\r
+     * @return\r
+     */\r
+    @Transient\r
+    public Rank getNullSafeRank() {\r
+        return hasTaxon() ? getTaxon().getNullSafeRank() : null;\r
+    }\r
+\r
+\r
+\r
 //*********************** CLONE ********************************************************/\r
     /**\r
      * Clones <i>this</i> taxon node. This is a shortcut that enables to create\r
@@ -540,18 +851,24 @@ public class TaxonNode extends AnnotatableEntity implements ITreeNode, Cloneable
      */\r
     @Override\r
     public Object clone()  {\r
-        TaxonNode result;\r
         try{\r
-        result = (TaxonNode)super.clone();\r
-        result.getTaxon().addTaxonNode(result);\r
-        result.childNodes = new HashSet<TaxonNode>();\r
-        result.countChildren = 0;\r
+            TaxonNode result = (TaxonNode)super.clone();\r
+            result.getTaxon().addTaxonNode(result);\r
+            result.childNodes = new ArrayList<TaxonNode>();\r
+            result.countChildren = 0;\r
+\r
+            //agents\r
+            result.agentRelations = new HashSet<TaxonNodeAgentRelation>();\r
+            for (TaxonNodeAgentRelation rel : this.agentRelations){\r
+                result.addAgentRelation((TaxonNodeAgentRelation)rel.clone());\r
+            }\r
 \r
-        return result;\r
+            return result;\r
         }catch (CloneNotSupportedException e) {\r
             logger.warn("Object does not implement cloneable");\r
             e.printStackTrace();\r
             return null;\r
         }\r
     }\r
+\r
 }\r