\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
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
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
@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
@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
* @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
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
* @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
* @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
}\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
* @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
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
*/\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
*/\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
*/\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
* @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
*/\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