* The contents of this file are subject to the Mozilla Public License Version 1.1
* See LICENSE.TXT at the top of this package for the full license terms.
*/
-
package eu.etaxonomy.cdm.model.taxon;
import java.util.ArrayList;
import javax.persistence.FetchType;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
-import javax.persistence.MapKeyJoinColumn;
import javax.persistence.OneToMany;
-import javax.persistence.OrderBy;
import javax.persistence.OrderColumn;
+import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-import org.apache.log4j.Logger;
-import org.hibernate.LazyInitializationException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
-import org.hibernate.annotations.Index;
-import org.hibernate.annotations.Table;
+import org.hibernate.annotations.Parameter;
+import org.hibernate.annotations.Type;
import org.hibernate.envers.Audited;
+import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.ContainedIn;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.IndexedEmbedded;
+import org.hibernate.search.annotations.Store;
-import eu.etaxonomy.cdm.hibernate.HHH_9751_Util;
+import eu.etaxonomy.cdm.common.CdmUtils;
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
-import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
import eu.etaxonomy.cdm.model.common.CdmBase;
-import eu.etaxonomy.cdm.model.common.DefinedTerm;
import eu.etaxonomy.cdm.model.common.ITreeNode;
import eu.etaxonomy.cdm.model.common.Language;
import eu.etaxonomy.cdm.model.common.LanguageString;
import eu.etaxonomy.cdm.model.common.MultilanguageText;
+import eu.etaxonomy.cdm.model.common.SingleSourcedEntityBase;
import eu.etaxonomy.cdm.model.name.Rank;
import eu.etaxonomy.cdm.model.name.TaxonName;
+import eu.etaxonomy.cdm.model.reference.NamedSource;
import eu.etaxonomy.cdm.model.reference.Reference;
+import eu.etaxonomy.cdm.model.term.DefinedTerm;
import eu.etaxonomy.cdm.validation.Level3;
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustBeLowerRankThanParent;
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustDeriveNameFromParent;
/**
* @author a.mueller
- * @created 31.03.2009
+ * @since 31.03.2009
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "TaxonNode", propOrder = {
"taxon",
"parent",
"treeIndex",
- "sortIndex",
"childNodes",
- "referenceForParentChildRelation",
- "microReferenceForParentChildRelation",
+ "sortIndex",
"countChildren",
"agentRelations",
"synonymToBeUsed",
- "excludedNote"
+ "status",
+ "statusNote"
})
@XmlRootElement(name = "TaxonNode")
@Entity
-//@Indexed disabled to reduce clutter in indexes, since this type is not used by any search
-//@Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonNode")
@Audited
-@Table(appliesTo="TaxonNode", indexes = { @Index(name = "taxonNodeTreeIndex", columnNames = { "treeIndex" }) })
+@Table(name="TaxonNode", indexes = { @javax.persistence.Index(name = "taxonNodeTreeIndex", columnList = "treeIndex") })
@ChildTaxaMustBeLowerRankThanParent(groups = Level3.class)
@ChildTaxaMustNotSkipRanks(groups = Level3.class)
@ChildTaxaMustDeriveNameFromParent(groups = Level3.class)
-public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITreeNode<TaxonNode>, Cloneable{
+public class TaxonNode
+ extends SingleSourcedEntityBase
+ implements ITaxonTreeNode, ITreeNode<TaxonNode>{
+
private static final long serialVersionUID = -4743289894926587693L;
- private static final Logger logger = Logger.getLogger(TaxonNode.class);
+ private static final Logger logger = LogManager.getLogger(TaxonNode.class);
@XmlElement(name = "taxon")
@XmlIDREF
@ContainedIn
private Taxon taxon;
-
@XmlElement(name = "parent")
@XmlIDREF
@XmlSchemaType(name = "IDREF")
@ManyToOne(fetch = FetchType.LAZY)
- @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
+// @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
private TaxonNode parent;
+ @XmlElementWrapper(name = "childNodes")
+ @XmlElement(name = "childNode")
+ @XmlIDREF
+ @XmlSchemaType(name = "IDREF")
+ //see https://dev.e-taxonomy.eu/redmine/issues/3722
+ //see https://dev.e-taxonomy.eu/redmine/issues/4200
+ //see https://dev.e-taxonomy.eu/redmine/issues/8127
+ //see https://dev.e-taxonomy.eu/redmine/issues/10067
+ @OrderColumn(name="sortIndex", nullable=true)
+ @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
+ //do not cascade
+ private List<TaxonNode> childNodes = new ArrayList<>();
+
+ @XmlElement(name = "countChildren")
+ private int countChildren;
@XmlElement(name = "treeIndex")
@Column(length=255)
+ @Field(store = Store.YES, index = Index.YES, analyze = Analyze.NO)
private String treeIndex;
-
@XmlElement(name = "classification")
@XmlIDREF
@XmlSchemaType(name = "IDREF")
@IndexedEmbedded(includeEmbeddedObjectId=true)
private Classification classification;
- @XmlElementWrapper(name = "childNodes")
- @XmlElement(name = "childNode")
- @XmlIDREF
- @XmlSchemaType(name = "IDREF")
- //see https://dev.e-taxonomy.eu/trac/ticket/3722
- @OrderColumn(name="sortIndex")
- @OrderBy("sortIndex")
- @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
- //@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
- private List<TaxonNode> childNodes = new ArrayList<>();
-
- //see https://dev.e-taxonomy.eu/trac/ticket/3722
- //see https://dev.e-taxonomy.eu/trac/ticket/4200
- private Integer sortIndex = -1;
-
- @XmlElement(name = "reference")
- @XmlIDREF
- @XmlSchemaType(name = "IDREF")
- @ManyToOne(fetch = FetchType.LAZY)
- @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
- private Reference referenceForParentChildRelation;
-
- @XmlElement(name = "microReference")
- private String microReferenceForParentChildRelation;
-
- @XmlElement(name = "countChildren")
- private int countChildren;
-
@XmlElementWrapper(name = "agentRelations")
@XmlElement(name = "agentRelation")
@XmlIDREF
@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
private Set<TaxonNodeAgentRelation> agentRelations = new HashSet<>();
- @XmlAttribute(name= "unplaced")
- private boolean unplaced = false;
- public boolean isUnplaced() {return unplaced;}
- public void setUnplaced(boolean unplaced) {this.unplaced = unplaced;}
-
- @XmlAttribute(name= "excluded")
- private boolean excluded = false;
- public boolean isExcluded() {return excluded;}
- public void setExcluded(boolean excluded) {this.excluded = excluded;}
-
- @XmlElement(name = "excludedNote")
+ /**
+ * The {@link TaxonNodeStatus status} of this taxon node.
+ */
+ @XmlAttribute(name ="TaxonNodeStatus")
+ @Column(name="status", length=10)
+ @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
+ parameters = {@Parameter(name = "enumClass", value = "eu.etaxonomy.cdm.model.taxon.TaxonNodeStatus")}
+ )
+ @Audited
+ private TaxonNodeStatus status;
+
+ @XmlElement(name = "statusNote")
@XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
@OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
- @MapKeyJoinColumn(name="excludedNote_mapkey_id")
- @JoinTable(name = "TaxonNode_ExcludedNote") //to make possible to add also unplacedNote
+// @MapKeyJoinColumn(name="statusNote_mapkey_id")
+ @JoinTable(name = "TaxonNode_StatusNote") //to make possible to add also unplacedNote
@Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
- private Map<Language,LanguageString> excludedNote = new HashMap<>();
+ private Map<Language,LanguageString> statusNote = new HashMap<>();
// private Taxon originalConcept;
// //or
// ******************** CONSTRUCTOR **********************************************/
- protected TaxonNode(){super();}
+ //for hibernate use only, *packet* private required by bytebuddy
+ @Deprecated
+ TaxonNode(){}
/**
* to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
}
/**
- * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
+ * To create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
* or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
*
* @param taxon
// ************************* GETTER / SETTER *******************************/
- @Transient
- public Integer getSortIndex() {
- return sortIndex;
- }
- /**
- * SortIndex shall be handled only internally, therefore not public.
- * However, as javaassist only supports protected methods it needs to be protected, not private.
- * Alternatively we could use deproxy on every call of this method (see commented code)
- * @param i
- * @return
- * @deprecated for internal use only
- */
- @Deprecated
- protected void setSortIndex(Integer i) {
-// CdmBase.deproxy(this, TaxonNode.class).sortIndex = i; //alternative solution for private, DON'T remove
- sortIndex = i;
- }
-
-
public Taxon getTaxon() {
return taxon;
}
- protected void setTaxon(Taxon taxon) {
+ public void setTaxon(Taxon taxon) {
this.taxon = taxon;
if (taxon != null){
taxon.addTaxonNode(this);
}
}
-
@Override
public List<TaxonNode> getChildNodes() {
return childNodes;
this.childNodes = childNodes;
}
-
public Classification getClassification() {
return classification;
}
/**
* THIS METHOD SHOULD NOT BE CALLED!
- * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
+ * invisible part of the bidirectional relationship, for public use Classification.addRoot() or TaxonNode.addChild()
* @param classification
* @deprecated for internal use only
*/
this.classification = classification;
}
+ public boolean isUnplaced() {return hasStatus(TaxonNodeStatus.UNPLACED);}
- @Override
- public String getMicroReference() {
- return microReferenceForParentChildRelation;
- }
- public void setMicroReference(String microReference) {
- this.microReferenceForParentChildRelation = microReference;
- }
+ //#8281 indicates a preliminary placement
+ public boolean isDoubtful() {return hasStatus(TaxonNodeStatus.DOUBTFUL);}
+ /**
+ * <code>true</code> if status is {@link TaxonNodeStatus#EXCLUDED} or any
+ * of its child status
+ */
+ public boolean isExcluded() {return isOrIsKindOf(TaxonNodeStatus.EXCLUDED);}
- @Override
- public Reference getReference() {
- return referenceForParentChildRelation;
- }
- public void setReference(Reference reference) {
- this.referenceForParentChildRelation = reference;
+ /**
+ * <code>true</code> if status is {@link TaxonNodeStatus#EXCLUDED} but not
+ * a sub-status (more specific excluded status)
+ */
+ public boolean isExcludedExact() {return hasStatus(TaxonNodeStatus.EXCLUDED);}
+
+ public boolean isGeographicallyExcluded() {return hasStatus(TaxonNodeStatus.EXCLUDED_GEO);}
+
+ public boolean isTaxonomicallyExcluded() {return hasStatus(TaxonNodeStatus.EXCLUDED_TAX);}
+
+ public boolean isNomenclaturallyExcluded() {return hasStatus(TaxonNodeStatus.EXCLUDED_NOM);}
+
+ public boolean isUncertainApplication() {return hasStatus(TaxonNodeStatus.UNCERTAIN_APPLICATION);}
+
+ public boolean isUnresolved() {return hasStatus(TaxonNodeStatus.UNRESOLVED);}
+
+//************************************************************/
+
+ /**
+ * The computed order index of this node being a child in the parents
+ * childnode list.
+ */
+ @Transient
+ public Integer getSortIndex() {
+ return getParent() == null ? null : getParent().getChildNodes().indexOf(CdmBase.deproxy(this));
}
//countChildren
this.countChildren = countChildren;
}
-
//parent
@Override
public TaxonNode getParent(){
*/
protected void setParent(TaxonNode parent) {
this.parent = parent;
+// this.treeIndex = parent.treeIndex() +
+ }
+
+ public TaxonNodeStatus getStatus() {
+ return status;
+ }
+ public void setStatus(TaxonNodeStatus status) {
+ this.status = status;
}
- // *************** Excluded Note ***************
+// *************** Status Note ***************
/**
* Returns the {@link MultilanguageText multi-language text} to add a note to the
- * excluded flag. The different {@link LanguageString language strings}
+ * status. The different {@link LanguageString language strings}
* contained in the multi-language text should all have the same meaning.
- * @see #getExcludedNote()
- * @see #putExcludedNote(Language, String)
+ * @see #getStatusNote(Language)
+ * @see #putStatusNote(Language, String)
*/
- public Map<Language,LanguageString> getExcludedNote(){
- return this.excludedNote;
+ public Map<Language,LanguageString> getStatusNote(){
+ return this.statusNote;
}
/**
- * Returns the excluded note string in the given {@link Language language}
+ * Returns the status note string in the given {@link Language language}
*
* @param language the language in which the description string looked for is formulated
- * @see #getExcludedNote()
- * @see #putExcludedNote(Language, String)
+ * @see #getStatusNote()
+ * @see #putStatusNote(Language, String)
*/
- public String getExcludedNote(Language language){
- LanguageString languageString = excludedNote.get(language);
+ public String getStatusNote(Language language){
+ LanguageString languageString = statusNote.get(language);
if (languageString == null){
return null;
}else{
/**
* Adds a translated {@link LanguageString text in a particular language}
* to the {@link MultilanguageText multilanguage text} used to add a note to
- * the {@link #isExcluded() excluded} flag.
+ * the {@link #getStatus() status}.
*
- * @param excludedNote the language string adding a note to the excluded flag
+ * @param statusNote the language string adding a note to the status
* in a particular language
- * @see #getExcludedNote()
- * @see #putExcludedNote(String, Language)
+ * @see #getStatusNote()
+ * @see #putStatusNote(String, Language)
*/
- public void putExcludedNote(LanguageString excludedNote){
- this.excludedNote.put(excludedNote.getLanguage(), excludedNote);
+ public void putStatusNote(LanguageString statusNote){
+ this.statusNote.put(statusNote.getLanguage(), statusNote);
}
/**
* Creates a {@link LanguageString language string} based on the given text string
* and the given {@link Language language} and adds it to the {@link MultilanguageText
- * multi-language text} used to annotate the excluded flag.
+ * multi-language text} used to annotate the status.
*
- * @param text the string annotating the excluded flag
+ * @param text the string annotating the status
* in a particular language
* @param language the language in which the text string is formulated
- * @see #getExcludedNote()
- * @see #putExcludedNote(LanguageString)
- * @see #removeExcludedNote(Language)
+ * @see #getStatusNote()
+ * @see #putStatusNote(LanguageString)
+ * @see #removeStatusNote(Language)
*/
- public void putExcludedNote(Language language, String text){
- this.excludedNote.put(language, LanguageString.NewInstance(text, language));
+ public void putStatusNote(Language language, String text){
+ this.statusNote.put(language, LanguageString.NewInstance(text, language));
}
/**
* Removes from the {@link MultilanguageText multilanguage text} used to annotate
- * the excluded flag the one {@link LanguageString language string}
+ * the status the one {@link LanguageString language string}
* with the given {@link Language language}.
*
* @param lang the language in which the language string to be removed
* has been formulated
- * @see #getExcludedNote()
+ * @see #getStatusNote()
*/
- public void removeExcludedNote(Language lang){
- this.excludedNote.remove(lang);
+ public void removeStatusNote(Language lang){
+ this.statusNote.remove(lang);
}
// ****************** Agent Relations ****************************/
-
- /**
- * @return
- */
public Set<TaxonNodeAgentRelation> getAgentRelations() {
return this.agentRelations;
}
-
public TaxonNodeAgentRelation addAgentRelation(DefinedTerm type, TeamOrPersonBase<?> agent){
TaxonNodeAgentRelation result = TaxonNodeAgentRelation.NewInstance(this, agent, type);
return result;
}
- /**
- * @param nodeAgentRelation
- */
- protected void addAgentRelation(TaxonNodeAgentRelation agentRelation) {
+ public void addAgentRelation(TaxonNodeAgentRelation agentRelation) {
agentRelation.setTaxonNode(this);
this.agentRelations.add(agentRelation);
}
-
- /**
- * @param nodeAgentRelation
- */
- public void removeNodeAgent(TaxonNodeAgentRelation agentRelation) {
+ public void removeAgentRelation(TaxonNodeAgentRelation agentRelation) {
agentRelation.setTaxonNode(this);
agentRelations.remove(agentRelation);
}
this.synonymToBeUsed = synonymToBeUsed;
}
-
//treeindex
@Override
public String treeIndex() {
public void setTreeIndex(String treeIndex) {
this.treeIndex = treeIndex;
}
-
-
+ @Override
+ public String treeIndexLike() {
+ return treeIndex + "%";
+ }
+ @Override
+ public String treeIndexWc() {
+ return treeIndex + "*";
+ }
//************************ METHODS **************************/
return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
}
+ @Override
+ public TaxonNode addChildTaxon(Taxon taxon, NamedSource source) {
+ return addChildTaxon(taxon, this.childNodes.size(), source);
+ }
@Override
public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
+ return addChildTaxon(taxon, index, NamedSource.NewPrimarySourceInstance(citation, microCitation));
+ }
+
+ @Override
+ public TaxonNode addChildTaxon(Taxon taxon, int index, NamedSource source) {
Classification classification = CdmBase.deproxy(this.getClassification());
taxon = HibernateProxyHelper.deproxy(taxon, Taxon.class);
if (classification.isTaxonInTree(taxon)){
throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));
}
- return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
+ return addChildNode(new TaxonNode(taxon), index, source);
}
/**
*/
@Override
public TaxonNode addChildNode(TaxonNode child, int index, Reference reference, String microReference){
+ return addChildNode(child, index, NamedSource.NewPrimarySourceInstance(reference, microReference));
+ }
+
+
+ /**
+ * Inserts the given taxon node in the list of children of <i>this</i> taxon node
+ * at the given (index + 1) position. If the given index is out of bounds
+ * an exception will arise.<BR>
+ * Due to bidirectionality this method must also assign <i>this</i> taxon node
+ * as the parent of the given child.
+ *
+ * @param child the taxon node to be added
+ * @param index the integer indicating the position at which the child
+ * should be added
+ * @see #getChildNodes()
+ * @see #addChildNode(TaxonNode, Reference, String, Synonym)
+ * @see #deleteChildNode(TaxonNode)
+ * @see #deleteChildNode(int)
+ */
+ @Override
+ public TaxonNode addChildNode(TaxonNode child, int index, NamedSource source){
if (index < 0 || index > childNodes.size() + 1){
throw new IndexOutOfBoundsException("Wrong index: " + index);
}
// check if this node is a descendant of the childNode
if(child.getParent() != this && child.isAncestor(this)){
- throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
+ throw new IllegalStateException("New parent node is a descendant of the node to be moved.");
}
child.setParentTreeNode(this, index);
- child.setReference(reference);
- child.setMicroReference(microReference);
+ child.setSource(source);
return child;
}
}
}
+
+
+
@Override
public boolean deleteChildNode(TaxonNode node) {
boolean result = removeChildNode(node);
- Taxon taxon = HibernateProxyHelper.deproxy(node.getTaxon(), Taxon.class);
- node = HibernateProxyHelper.deproxy(node, TaxonNode.class);
+ Taxon taxon = deproxy(node.getTaxon());
+ node = deproxy(node);
node.setTaxon(null);
-
- ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
+ ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
for(TaxonNode childNode : childNodes){
HibernateProxyHelper.deproxy(childNode, TaxonNode.class);
node.deleteChildNode(childNode);
node.setTaxon(null);
taxon.removeTaxonNode(node);
if (deleteChildren){
- ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
+ ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
for(TaxonNode childNode : childNodes){
node.deleteChildNode(childNode, deleteChildren);
}
} else{
- ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
+ ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
for(TaxonNode childNode : childNodes){
this.addChildNode(childNode, null, null);
}
* from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
* Sets the parent and the classification of the child
* node to null.
- * If the given index is out of bounds no child will be removed.
+ * If the given index is out of bounds no child will be removed.<BR>
+ * NOTE: this is more for inner use. It does not remove the node from the taxon!!
+ * Use deleteChildNode(TaxonNode) instead
*
* @param index the integer indicating the position of the taxon node to
* be removed
//TODO: Only as a workaround. We have to find out why merge creates null entries.
TaxonNode child = childNodes.get(index);
- child = HibernateProxyHelper.deproxy(child, TaxonNode.class); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
+ child = HibernateProxyHelper.deproxy(child); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
if (child != null){
- TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent(), TaxonNode.class);
- TaxonNode thisNode = HibernateProxyHelper.deproxy(this, TaxonNode.class);
+ TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent());
+ TaxonNode thisNode = HibernateProxyHelper.deproxy(this);
if(parent != null && parent != thisNode){
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());
}else if (parent == null){
childNodes.remove(index);
child.setClassification(null);
- //update sortindex
- //TODO workaround (see sortIndex doc)
this.countChildren = childNodes.size();
child.setParent(null);
child.setTreeIndex(null);
- updateSortIndex(index);
- child.setSortIndex(null);
}
}
-
/**
* Remove this taxonNode From its taxonomic parent
*
/**
* Sets the parent of this taxon node to the given parent. Cleans up references to
* old parents and sets the classification to the new parents classification
- *
- * @param parent
*/
@Transient
protected void setParentTreeNode(TaxonNode parent, int index){
// add this node to the parent's child nodes
parent = CdmBase.deproxy(parent);
List<TaxonNode> parentChildren = parent.getChildNodes();
- //TODO: Only as a workaround. We have to find out why merge creates null entries.
-// HHH_9751_Util.removeAllNull(parentChildren);
-// parent.updateSortIndex(0);
if (index > parent.getChildNodes().size()){
index = parent.getChildNodes().size();
}
parentChildren.add(index, this);
}
-
- //sortIndex
- //TODO workaround (see sortIndex doc)
- // this.getParent().removeNullValueFromChildren();
- this.getParent().updateSortIndex(index);
- //only for debugging
- if (! this.getSortIndex().equals(index)){
- logger.warn("index and sortindex are not equal: " + this.getSortIndex() + ";" + index);
- }
+// //only for debugging
+// if (this.getSortIndex() == null){
+// logger.warn("sortindex is null. This should not happen.");
+// }else if (! this.getSortIndex().equals(index)){
+// logger.warn("index and sortindex are not equal: " + this.getSortIndex() + ";" + index);
+// }
// update the children count
parent.setCountChildren(parent.getChildNodes().size());
}
- /**
- * As long as the sort index is not correctly handled through hibernate this is a workaround method
- * to update the sort index manually
- * @param parentChildren
- * @param index
- */
- private void updateSortIndex(int index) {
- if (this.hasChildNodes()){
- List<TaxonNode> children = this.getChildNodes();
- HHH_9751_Util.removeAllNull(children);
- for(int i = index; i < children.size(); i++){
- TaxonNode child = children.get(i);
- if (child != null){
- // child = CdmBase.deproxy(child, TaxonNode.class); //deproxy not needed as long as setSortIndex is protected or public #4200
- child.setSortIndex(i);
- }else{
- String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";
- throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));
- }
- }
- }
- }
-
-
/**
- * Returns a set containing this node and all nodes that are descendants of this node
- *
- * @return
+ * Returns a set containing this node and all nodes that are descendants of this node.
*/
-
+ @Transient
protected Set<TaxonNode> getDescendants(){
- Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
+ Set<TaxonNode> nodeSet = new HashSet<>();
nodeSet.add(this);
*/
protected TaxonNode cloneDescendants(){
- TaxonNode clone = (TaxonNode)this.clone();
+ TaxonNode clone = this.clone();
TaxonNode childClone;
for(TaxonNode childNode : getChildNodes()){
- childClone = (TaxonNode) childNode.clone();
+ childClone = childNode.clone();
for (TaxonNode childChild:childNode.getChildNodes()){
childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());
}
*
* @return a set of all parent nodes
*/
+ @Transient
protected Set<TaxonNode> getAncestors(){
Set<TaxonNode> nodeSet = new HashSet<>();
if(this.getParent() != null){
* @param rank the rank the ancestor should have
* @return the first found instance of a parent taxon node with the given rank
*/
+ @Transient
public TaxonNode getAncestorOfRank(Rank rank){
Taxon taxon = CdmBase.deproxy(this.getTaxon());
if (taxon == null){
* Returns the ancestor taxa, starting with the highest (e.g. kingdom)
* @return
*/
+ @Transient
public List<Taxon> getAncestorTaxaList(){
List<Taxon> result = new ArrayList<>();
TaxonNode current = this;
/**
* Returns the ancestor taxon nodes, that do have a taxon attached
* (excludes the root node) starting with the highest
- *
- * @return
*/
+ @Transient
public List<TaxonNode> getAncestorList(){
List<TaxonNode> result = new ArrayList<>();
TaxonNode current = this.getParent();
/**
* Whether this TaxonNode is a direct child of the classification TreeNode
- * @return
*/
@Transient
public boolean isTopmostNode(){
}
/**
- * Whether this TaxonNode is a descendant of the given TaxonNode
- *
- * Caution: use this method with care on big branches. -> performance and memory hungry
- *
- * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
- * other direction (up). It will always result in a rather small set of consecutive parents beeing
- * generated.
- *
- * TODO implement more efficiently without generating the set of descendants first
+ * Whether this TaxonNode is a descendant of (or equal to) the given TaxonNode
*
* @param possibleParent
- * @return true if this is a descendant
+ * @return <code>true</code> if <b>this</b> is a descendant
*/
@Transient
public boolean isDescendant(TaxonNode possibleParent){
- if (this.treeIndex() == null || possibleParent.treeIndex() == null) {
+ if (possibleParent == null || this.treeIndex() == null
+ || possibleParent.treeIndex() == null) {
return false;
}
- return possibleParent == null ? false : this.treeIndex().startsWith(possibleParent.treeIndex() );
+ return this.treeIndex().startsWith(possibleParent.treeIndex() );
}
/**
- * Whether this TaxonNode is an ascendant of the given TaxonNode
+ * Whether this TaxonNode is an ascendant of (or equal to) the given TaxonNode.
*
* @param possibleChild
- * @return true if there are ascendants
+ * @return <code>true</code> if <b>this</b> is a ancestor of the given child parameter
*/
@Transient
public boolean isAncestor(TaxonNode possibleChild){
- if (this.treeIndex() == null || possibleChild.treeIndex() == null) {
+ if (possibleChild == null || this.treeIndex() == null || possibleChild.treeIndex() == null) {
return false;
}
// return possibleChild == null ? false : possibleChild.getAncestors().contains(this);
- return possibleChild == null ? false : possibleChild.treeIndex().startsWith(this.treeIndex());
+ return possibleChild.treeIndex().startsWith(this.treeIndex());
}
/**
return childNodes.size() > 0;
}
-
public boolean hasTaxon() {
return (taxon!= null);
}
- /**
- * @return
- */
@Transient
public Rank getNullSafeRank() {
return hasTaxon() ? getTaxon().getNullSafeRank() : null;
}
- public void removeNullValueFromChildren(){
- try {
- //HHH_9751_Util.removeAllNull(childNodes);
- this.updateSortIndex(0);
- } catch (LazyInitializationException e) {
- logger.info("Cannot clean up uninitialized children without a session, skipping.");
- }
+ @Transient
+ public TaxonName getNullSafeName() {
+ return getTaxon() == null? null: getTaxon().getName();
}
+ private boolean hasStatus(TaxonNodeStatus status) {
+ return CdmUtils.nullSafeEqual(this.status, status);
+ }
+ private boolean isOrIsKindOf(TaxonNodeStatus status) {
+ if (this.status == null) {
+ return status == null;
+ }else {
+ return hasStatus(status) || this.status.isKindOf(status);
+ }
+ }
//*********************** CLONE ********************************************************/
/**
* modifying only some of the attributes.<BR><BR>
* The child nodes are not copied.<BR>
* The taxon and parent are the same as for the original taxon node. <BR>
+ * <BR>
+ * Note: Cloning taxon nodes with cloning taxa (and children) is a complex
+ * issue which is better be handled in service layer logic. See according
+ * clone method in classification service
+ * or taxon node service there.
*
- * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
* @see java.lang.Object#clone()
*/
@Override
- public Object clone() {
+ public TaxonNode clone() {
+
try{
TaxonNode result = (TaxonNode)super.clone();
result.getTaxon().addTaxonNode(result);
//agents
result.agentRelations = new HashSet<>();
for (TaxonNodeAgentRelation rel : this.agentRelations){
- result.addAgentRelation((TaxonNodeAgentRelation)rel.clone());
+ result.addAgentRelation(rel.clone());
}
- //excludedNote
- result.excludedNote = new HashMap<>();
- for(Language lang : this.excludedNote.keySet()){
- result.excludedNote.put(lang, this.excludedNote.get(lang));
+ //statusNote
+ result.statusNote = new HashMap<>();
+ for(Language lang : this.statusNote.keySet()){
+ result.statusNote.put(lang, this.statusNote.get(lang));
}
-
return result;
}catch (CloneNotSupportedException e) {
logger.warn("Object does not implement cloneable");
}
}
-}
+//To be removed once SingleSourcedEntity attributes have been renamed to reference and microReference
+
+ @Override
+ @Transient
+ public Reference getReference() {
+ return getCitation();
+ }
+
+ @Override
+ @Transient
+ public String getMicroReference() {
+ return getCitationMicroReference();
+ }
+
+}
\ No newline at end of file