ref #9331 add NamedSource and NamedSourceBase and use it, SecundumSource inherits...
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / taxon / Classification.java
index 634b74a1d706ae5d02a84c1d42c0d368cc3590f0..60249ea1d1c98b9569accd3934dbdcae28cebe68 100644 (file)
-// $Id$\r
-/**\r
-* Copyright (C) 2007 EDIT\r
-* European Distributed Institute of Taxonomy\r
-* http://www.e-taxonomy.eu\r
-*\r
-* The contents of this file are subject to the Mozilla Public License Version 1.1\r
-* See LICENSE.TXT at the top of this package for the full license terms.\r
-*/\r
-\r
-package eu.etaxonomy.cdm.model.taxon;\r
-\r
-import java.util.ArrayList;\r
-import java.util.HashSet;\r
-import java.util.Iterator;\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.JoinColumn;\r
-import javax.persistence.JoinTable;\r
-import javax.persistence.ManyToMany;\r
-import javax.persistence.ManyToOne;\r
-import javax.persistence.OneToOne;\r
-import javax.persistence.Transient;\r
-import javax.xml.bind.annotation.XmlAccessType;\r
-import javax.xml.bind.annotation.XmlAccessorType;\r
-import javax.xml.bind.annotation.XmlElement;\r
-import javax.xml.bind.annotation.XmlElementWrapper;\r
-import javax.xml.bind.annotation.XmlIDREF;\r
-import javax.xml.bind.annotation.XmlRootElement;\r
-import javax.xml.bind.annotation.XmlSchemaType;\r
-import javax.xml.bind.annotation.XmlType;\r
-\r
-import org.apache.log4j.Logger;\r
-import org.hibernate.annotations.Cascade;\r
-import org.hibernate.annotations.CascadeType;\r
-import org.hibernate.envers.Audited;\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.common.IReferencedEntity;\r
-import eu.etaxonomy.cdm.model.common.IdentifiableEntity;\r
-import eu.etaxonomy.cdm.model.common.Language;\r
-import eu.etaxonomy.cdm.model.common.LanguageString;\r
-import eu.etaxonomy.cdm.model.common.TimePeriod;\r
-import eu.etaxonomy.cdm.model.location.NamedArea;\r
-import eu.etaxonomy.cdm.model.reference.Reference;\r
-import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;\r
-\r
-/**\r
- * @author a.mueller\r
- * @created 31.03.2009\r
- */\r
-@XmlAccessorType(XmlAccessType.FIELD)\r
-@XmlType(name = "Classification", propOrder = {\r
-    "name",\r
-    "rootNode",\r
-    "reference",\r
-    "microReference",\r
-    "timeperiod"\r
-})\r
-@XmlRootElement(name = "Classification")\r
-@Entity\r
-@Audited\r
-@Indexed(index = "eu.etaxonomy.cdm.model.taxon.Classification")\r
-public class Classification extends IdentifiableEntity<IIdentifiableEntityCacheStrategy<Classification>> implements IReferencedEntity, ITaxonTreeNode, Cloneable{\r
-    private static final long serialVersionUID = -753804821474209635L;\r
-    private static final Logger logger = Logger.getLogger(Classification.class);\r
-\r
-    @XmlElement(name = "Name")\r
-    @OneToOne(fetch = FetchType.LAZY)\r
-    @Cascade({CascadeType.SAVE_UPDATE})\r
-    @JoinColumn(name = "name_id", referencedColumnName = "id")\r
-    @IndexedEmbedded\r
-    private LanguageString name;\r
-\r
-\r
-    @XmlElement(name = "rootNode")\r
-    @XmlIDREF\r
-    @XmlSchemaType(name = "IDREF")\r
-    @OneToOne(fetch=FetchType.LAZY)\r
-    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})\r
-    private TaxonNode rootNode;\r
-\r
-    @XmlElement(name = "reference")\r
-    @XmlIDREF\r
-    @XmlSchemaType(name = "IDREF")\r
-    @ManyToOne(fetch = FetchType.LAZY)\r
-    @Cascade({CascadeType.SAVE_UPDATE})\r
-    private Reference<?> reference;\r
-\r
-    @XmlElement(name = "microReference")\r
-    private String microReference;\r
-    \r
-       @XmlElement(name = "TimePeriod")\r
-    private TimePeriod timeperiod = TimePeriod.NewInstance();\r
-       \r
-    @XmlElementWrapper( name = "GeoScopes")\r
-    @XmlElement( name = "GeoScope")\r
-    @XmlIDREF\r
-    @XmlSchemaType(name="IDREF")\r
-    @ManyToMany(fetch = FetchType.LAZY)\r
-    @JoinTable(name="Classification_GeoScope")\r
-    @Cascade({CascadeType.SAVE_UPDATE})\r
-    private Set<NamedArea> geoScopes = new HashSet<NamedArea>();\r
-\r
-\r
-\r
-//     /**\r
-//      * If this classification is an alternative classification for a subclassification in\r
-//      * an other classification(parent view),\r
-//      * the alternativeViewRoot is the connection node from this classification to the parent classification.\r
-//      * It replaces another node in the parent view.\r
-//      */\r
-//     private AlternativeViewRoot alternativeViewRoot;\r
-\r
-// ********************** FACTORY METHODS *********************************************/\r
-\r
-    public static Classification NewInstance(String name){\r
-        return NewInstance(name, null, Language.DEFAULT());\r
-    }\r
-\r
-    public static Classification NewInstance(String name, Language language){\r
-        return NewInstance(name, null, language);\r
-    }\r
-\r
-    public static Classification NewInstance(String name, Reference reference){\r
-        return NewInstance(name, reference, Language.DEFAULT());\r
-    }\r
-\r
-    public static Classification NewInstance(String name, Reference reference, Language language){\r
-        return new Classification(name, reference, language);\r
-    }\r
-\r
-// **************************** CONSTRUCTOR *********************************/\r
-\r
-    //for hibernate use only, protected required by Javassist\r
-    protected Classification(){super();}\r
-\r
-    protected Classification(String name, Reference reference, Language language){\r
-        this();\r
-        LanguageString langName = LanguageString.NewInstance(name, language);\r
-        setName(langName);\r
-        setReference(reference);\r
-        this.rootNode = new TaxonNode();\r
-        rootNode.setClassification(this);\r
-    }\r
-\r
-//********************** xxxxxxxxxxxxx ******************************************/\r
-    /**\r
-     * Returns the topmost {@link TaxonNode taxon node} (root node) of <i>this</i>\r
-     * classification. The root node does not have any parent and no taxon. Since taxon nodes\r
-     * recursively point to their child nodes the complete classification is\r
-     * defined by its root node.\r
-     */\r
-    public TaxonNode getRootNode(){\r
-        return rootNode;\r
-    }\r
-\r
-    public void setRootNode(TaxonNode root){\r
-        this.rootNode = root;\r
-    }\r
-\r
-    @Override\r
-    public TaxonNode addChildNode(TaxonNode childNode, Reference citation, String microCitation) {\r
-        return addChildNode(childNode, rootNode.getCountChildren(), citation, microCitation);\r
-    }\r
-\r
-    @Override\r
-    public TaxonNode addChildNode(TaxonNode childNode, int index, Reference citation, String microCitation) {\r
-\r
-        childNode.setParentTreeNode(this.rootNode, index);\r
-\r
-        childNode.setReference(citation);\r
-        childNode.setMicroReference(microCitation);\r
-//             childNode.setSynonymToBeUsed(synonymToBeUsed);\r
-\r
-        return childNode;\r
-    }\r
-\r
-    @Override\r
-    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {\r
-        return addChildTaxon(taxon, rootNode.getCountChildren(), citation, microCitation);\r
-    }\r
-\r
-    @Override\r
-    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {\r
-        return addChildNode(new TaxonNode(taxon), index, citation, microCitation);\r
-    }\r
-\r
-    @Override\r
-    public boolean deleteChildNode(TaxonNode node) {\r
-        boolean result = removeChildNode(node);\r
-\r
-        if (node.hasTaxon()){\r
-            node.getTaxon().removeTaxonNode(node);\r
-            node.setTaxon(null);\r
-        }\r
-\r
-        ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());\r
-        for (TaxonNode childNode : childNodes){\r
-            if (childNode != null){\r
-                node.deleteChildNode(childNode);\r
-            }\r
-        }\r
-        return result;\r
-    }\r
-\r
-    public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {\r
-        boolean result = removeChildNode(node);\r
-\r
-        node.getTaxon().removeTaxonNode(node);\r
-        //node.setTaxon(null);\r
-        if (deleteChildren){\r
-            ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());\r
-            for (TaxonNode childNode : childNodes){\r
-                node.deleteChildNode(childNode);\r
-            }\r
-        }\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     *\r
-     * @param node\r
-     * @return\r
-     */\r
-    protected boolean removeChildNode(TaxonNode node){\r
-        boolean result = false;\r
-        if(!rootNode.getChildNodes().contains(node)){\r
-            throw new IllegalArgumentException("TaxonNode is a not a root node of this classification");\r
-        }\r
-\r
-        result = rootNode.removeChildNode(node);\r
-\r
-        node.setParent(null);\r
-        node.setClassification(null);\r
-\r
-        return result;\r
-    }\r
-\r
-    public boolean removeRootNode(){\r
-        boolean result = false;\r
-\r
-        if (rootNode != null){\r
-            this.rootNode.setChildNodes(new ArrayList<TaxonNode>());\r
-            this.rootNode.setParent(null);\r
-            rootNode = null;\r
-            result = true;\r
-        }\r
-        return result;\r
-\r
-    }\r
-\r
-    /**\r
-     * Appends an existing topmost node to another node of this tree. The existing topmost node becomes\r
-     * an ordinary node.\r
-     * @param topmostNode\r
-     * @param otherNode\r
-     * @param ref\r
-     * @param microReference\r
-     * @throws IllegalArgumentException\r
-     */\r
-    public void makeTopmostNodeChildOfOtherNode(TaxonNode topmostNode, TaxonNode otherNode, Reference ref, String microReference)\r
-                throws IllegalArgumentException{\r
-        if (otherNode == null){\r
-            throw new NullPointerException("other node must not be null");\r
-        }\r
-        if (! getChildNodes().contains(topmostNode)){\r
-            throw new IllegalArgumentException("root node to be added as child must already be root node within this tree");\r
-        }\r
-        if (otherNode.getClassification() == null || ! otherNode.getClassification().equals(this)){\r
-            throw new IllegalArgumentException("other node must already be node within this tree");\r
-        }\r
-        if (otherNode.equals(topmostNode)){\r
-            throw new IllegalArgumentException("root node and other node must not be the same");\r
-        }\r
-        otherNode.addChildNode(topmostNode, ref, microReference);\r
-    }\r
-\r
-\r
-    /**\r
-     * Checks if the given taxon is part of <b>this</b> tree.\r
-     * @param taxon\r
-     * @return\r
-     */\r
-    public boolean isTaxonInTree(Taxon taxon){\r
-        return (getNode(taxon) != null);\r
-    }\r
-\r
-    /**\r
-     * Checks if the given taxon is part of <b>this</b> tree. If so the according TaxonNode is returned.\r
-     * Otherwise null is returned.\r
-     * @param taxon\r
-     * @return\r
-     */\r
-    public TaxonNode getNode(Taxon taxon){\r
-        if (taxon == null){\r
-            return null;\r
-        }\r
-        \r
-        for (TaxonNode taxonNode: taxon.getTaxonNodes()){\r
-               Classification classification = HibernateProxyHelper.deproxy(taxonNode.getClassification(), Classification.class);\r
-            if (classification.equals(this)){\r
-                return taxonNode;\r
-            }\r
-        }\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * Checks if the given taxon is one of the topmost taxa in <b>this</b> tree.\r
-     * @param taxon\r
-     * @return\r
-     */\r
-    public boolean isTopmostInTree(Taxon taxon){\r
-        return (getTopmostNode(taxon) != null);\r
-    }\r
-\r
-\r
-    /**\r
-     * Checks if the taxon is a direct child of <b>this</b> tree and returns the according node if true.\r
-     * Returns null otherwise.\r
-     * @param taxon\r
-     * @return\r
-     */\r
-    public TaxonNode getTopmostNode(Taxon taxon){\r
-        if (taxon == null){\r
-            return null;\r
-        }\r
-        for (TaxonNode taxonNode: taxon.getTaxonNodes()){\r
-            if (taxonNode.getClassification().equals(this)){\r
-                if (this.getChildNodes().contains(taxonNode)){\r
-                    if (taxonNode.getParent() == null){\r
-                        logger.warn("A topmost node should always have the root node as parent but actually has no parent");\r
-                    }else if (taxonNode.getParent().getParent() != null){\r
-                        logger.warn("The root node should have not parent but actually has one");\r
-                    }else if (taxonNode.getParent().getTaxon() != null){\r
-                        logger.warn("The root node should have not taxon but actually has one");\r
-                    }\r
-                    return taxonNode;\r
-                }\r
-            }\r
-        }\r
-        return null;\r
-    }\r
-\r
-    private boolean handleCitationOverwrite(TaxonNode childNode, Reference citation, String microCitation){\r
-        if (citation != null){\r
-            if (childNode.getReference() != null && ! childNode.getReference().equals(citation)){\r
-                logger.warn("ReferenceForParentChildRelation will be overwritten");\r
-            }\r
-            childNode.setReference(citation);\r
-        }\r
-        if (microCitation != null){\r
-            if (childNode.getMicroReference() != null && ! childNode.getMicroReference().equals(microCitation)){\r
-                logger.warn("MicroReferenceForParentChildRelation will be overwritten");\r
-            }\r
-            childNode.setMicroReference(microCitation);\r
-        }\r
-        return true;\r
-    }\r
-\r
-    /**\r
-     * Relates two taxa as parent-child nodes within a classification. <BR>\r
-     * If the taxa are not yet part of the tree they are added to it.<Br>\r
-     * If the child taxon is a topmost node still it is added as child and deleted from the rootNode set.<Br>\r
-     * If the child is a child of another parent already an IllegalStateException is thrown because a child can have only\r
-     * one parent. <Br>\r
-     * If the parent-child relationship between these two taxa already exists nothing is changed. Only\r
-     * citation and microcitation are overwritten by the new values if these values are not null.\r
-     *\r
-     * @param parent\r
-     * @param child\r
-     * @param citation\r
-     * @param microCitation\r
-     * @return the childNode\r
-     * @throws IllegalStateException If the child is a child of another parent already\r
-     */\r
-    public TaxonNode addParentChild (Taxon parent, Taxon child, Reference citation, String microCitation)\r
-            throws IllegalStateException{\r
-        try {\r
-            if (parent == null || child == null){\r
-                logger.warn("Child or parent taxon is null.");\r
-                return null;\r
-            }\r
-            if (parent == child){\r
-                logger.warn("A taxon should never be its own child. Child not added");\r
-                return null;\r
-            }\r
-            TaxonNode parentNode = this.getNode(parent);\r
-            TaxonNode childNode = this.getNode(child);\r
-\r
-            //if child exists in tree and has a parent\r
-            //no multiple parents are allowed in the tree\r
-            if (childNode != null && ! childNode.isTopmostNode()){\r
-                //...different to the parent taxon  throw exception\r
-                if ( !(childNode.getParent().getTaxon().equals(parent) )){\r
-                       if (childNode.getParent().getTaxon().getId() != 0 && childNode.getParent().getTaxon().getName().equals(parent.getName())){\r
-                               throw new IllegalStateException("The child taxon is already part of the tree but has an other parent taxon than the parent to be added. Child: " + child.toString() + ", new parent:" + parent.toString() + ", old parent: " + childNode.getParent().getTaxon().toString()) ;\r
-                       } \r
-                    //... same as the parent taxon do nothing but overwriting citation and microCitation\r
-                }else{\r
-                    handleCitationOverwrite(childNode, citation, microCitation);\r
-                    return childNode;\r
-                }\r
-            }\r
-\r
-            //add parent node if not exist\r
-            if (parentNode == null){\r
-                parentNode = this.addChildTaxon(parent, null, null);\r
-            }\r
-\r
-            //add child if not exists\r
-            if (childNode == null){\r
-                childNode = parentNode.addChildTaxon(child, citation, microCitation);\r
-            }else{\r
-                //child is still topmost node\r
-                //TODO test if child is topmostNode otherwise throw IllegalStateException\r
-                if (! this.isTopmostInTree(child)){\r
-                    //throw new IllegalStateException("Child is not a topmost node but must be");\r
-                    if (childNode.getClassification() != null && childNode.getParent() == null){\r
-                        logger.warn("Child has no parent and is not a topmost node, child: " + child.getId() + " classification: " + childNode.getClassification().getId());\r
-                    }else{\r
-                        logger.warn("ChildNode has no classification: " + childNode.getId());\r
-                    }\r
-                    parentNode.addChildNode(childNode, citation, microCitation);\r
-                    if (!parentNode.isTopmostNode()){\r
-                        this.addChildNode(parentNode, citation, microCitation);\r
-                        logger.warn("parent is added as a topmost node");\r
-                    }else{\r
-                        logger.warn("parent is already a topmost node");\r
-                    }\r
-                }else{\r
-                    this.makeTopmostNodeChildOfOtherNode(childNode, parentNode, citation, microCitation);\r
-                }\r
-            }\r
-            return childNode;\r
-        } catch (IllegalStateException e) {\r
-            throw e;\r
-        } catch (RuntimeException e){\r
-            throw e;\r
-        }\r
-    }\r
-\r
-\r
-    @Override\r
-    @Transient\r
-    public Reference getCitation() {\r
-        return reference;\r
-    }\r
-\r
-    public LanguageString getName() {\r
-        return name;\r
-    }\r
-\r
-    public void setName(LanguageString name) {\r
-        this.name = name;\r
-    }\r
-\r
-    /**\r
-     * Returns a set containing all nodes in this classification.\r
-     *\r
-     * Caution: Use this method with care. It can be very time and resource consuming and might\r
-     * run into OutOfMemoryExceptions for big trees.\r
-     *\r
-     * @return\r
-     */\r
-    @Transient\r
-    public Set<TaxonNode> getAllNodes() {\r
-        Set<TaxonNode> allNodes = new HashSet<TaxonNode>();\r
-\r
-        for(TaxonNode rootNode : getChildNodes()){\r
-            allNodes.addAll(rootNode.getDescendants());\r
-        }\r
-\r
-        return allNodes;\r
-    }\r
-\r
-    @Override\r
-    @Transient\r
-    public List<TaxonNode> getChildNodes() {\r
-        return rootNode.getChildNodes();\r
-    }\r
-\r
-    @Override\r
-    public Reference getReference() {\r
-        return reference;\r
-    }\r
-\r
-    public void setReference(Reference reference) {\r
-        this.reference = reference;\r
-    }\r
-\r
-\r
-    @Override\r
-    public String getMicroReference() {\r
-        return microReference;\r
-    }\r
-\r
-    /**\r
-     * @param microReference the microReference to set\r
-     */\r
-    public void setMicroReference(String microReference) {\r
-        this.microReference = microReference;\r
-    }\r
-    \r
-\r
-    /**\r
-        * The point in time, the time period or the season for which this description element \r
-        * is valid. A season may be expressed by not filling the year part(s) of the time period. \r
-        */\r
-       public TimePeriod getTimeperiod() {\r
-               return timeperiod;\r
-       }\r
-\r
-       /**\r
-        * @see #getTimeperiod()\r
-        */\r
-       public void setTimeperiod(TimePeriod timeperiod) {\r
-               if (timeperiod == null){\r
-                       timeperiod = TimePeriod.NewInstance();\r
-               }\r
-               this.timeperiod = timeperiod;\r
-       }\r
-\r
-    @Override\r
-    public String generateTitle() {\r
-        return name.getText();\r
-    }\r
-\r
-    public int compareTo(Object o) {\r
-        return 0;\r
-    }\r
-\r
-\r
-    @Override\r
-    public boolean hasChildNodes() {\r
-        return getChildNodes().size() > 0;\r
-    }\r
-\r
-    //*********************** CLONE ********************************************************/\r
-    /**\r
-     * Clones <i>this</i> classification. This is a shortcut that enables to create\r
-     * a new instance that differs only slightly from <i>this</i> classification by\r
-     * modifying only some of the attributes.<BR><BR>\r
-\r
-     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()\r
-     * @see java.lang.Object#clone()\r
-     */\r
-    @Override\r
-    public Object clone() {\r
-        Classification result;\r
-        try{\r
-            result = (Classification)super.clone();\r
-            //result.rootNode.childNodes = new ArrayList<TaxonNode>();\r
-            List<TaxonNode> rootNodes = new ArrayList<TaxonNode>();\r
-            TaxonNode rootNodeClone;\r
-\r
-\r
-            rootNodes.addAll(rootNode.getChildNodes());\r
-            TaxonNode rootNode;\r
-            Iterator<TaxonNode> iterator = rootNodes.iterator();\r
-\r
-            while (iterator.hasNext()){\r
-                rootNode = iterator.next();\r
-                rootNodeClone = rootNode.cloneDescendants();\r
-                rootNodeClone.setClassification(result);\r
-                result.addChildNode(rootNodeClone, rootNode.getReference(), rootNode.getMicroReference());\r
-                rootNodeClone.setSynonymToBeUsed(rootNode.getSynonymToBeUsed());\r
-            }\r
-\r
-            return result;\r
-\r
-        }catch (CloneNotSupportedException e) {\r
-            logger.warn("Object does not implement cloneable");\r
-            e.printStackTrace();\r
-            return null;\r
-        }\r
-\r
-\r
-\r
-\r
-\r
-    }\r
-\r
-\r
-}\r
+/**
+* Copyright (C) 2007 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* 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 java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.MapKeyJoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Transient;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlIDREF;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.apache.log4j.Logger;
+import org.hibernate.annotations.Cascade;
+import org.hibernate.annotations.CascadeType;
+import org.hibernate.envers.Audited;
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.IndexedEmbedded;
+
+import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
+import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
+import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
+import eu.etaxonomy.cdm.model.common.IdentifiableSource;
+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.TimePeriod;
+import eu.etaxonomy.cdm.model.location.NamedArea;
+import eu.etaxonomy.cdm.model.reference.NamedSource;
+import eu.etaxonomy.cdm.model.reference.Reference;
+import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
+import eu.etaxonomy.cdm.strategy.cache.taxon.ClassificationDefaultCacheStrategy;
+
+/**
+ * @author a.mueller
+ * @since 31.03.2009
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "Classification", propOrder = {
+    "name",
+    "description",
+    "rootNode",
+    "reference",
+    "microReference",
+    "source",
+    "timeperiod",
+    "geoScopes"
+})
+@XmlRootElement(name = "Classification")
+@Entity
+@Audited
+@Indexed(index = "eu.etaxonomy.cdm.model.taxon.Classification")
+public class Classification
+            extends IdentifiableEntity<IIdentifiableEntityCacheStrategy<Classification>>
+            implements ITaxonTreeNode{
+
+    private static final long serialVersionUID = -753804821474209635L;
+    private static final Logger logger = Logger.getLogger(Classification.class);
+
+    @XmlElement(name = "Name")
+    @OneToOne(fetch = FetchType.LAZY)
+    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
+    @JoinColumn(name = "name_id", referencedColumnName = "id")
+    @IndexedEmbedded
+    private LanguageString name;
+
+    @XmlElement(name = "rootNode")
+    @XmlIDREF
+    @XmlSchemaType(name = "IDREF")
+    @OneToOne(fetch=FetchType.LAZY)
+    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
+    private TaxonNode rootNode;
+
+    @XmlElement(name = "reference")
+    @XmlIDREF
+    @XmlSchemaType(name = "IDREF")
+    @ManyToOne(fetch = FetchType.LAZY)
+    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
+    private Reference reference;
+
+    @XmlElement(name = "microReference")
+    private String microReference;
+
+       @XmlElement(name = "TimePeriod")
+    private TimePeriod timeperiod = TimePeriod.NewInstance();
+
+    @XmlElementWrapper( name = "GeoScopes")
+    @XmlElement( name = "GeoScope")
+    @XmlIDREF
+    @XmlSchemaType(name="IDREF")
+    @ManyToMany(fetch = FetchType.LAZY)
+    @JoinTable(name="Classification_GeoScope")
+//    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})  remove cascade #5755
+    private Set<NamedArea> geoScopes = new HashSet<>();
+
+       @XmlElement(name = "Description")
+       @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
+       @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
+       @MapKeyJoinColumn(name="description_mapkey_id")
+    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE })
+       @JoinTable(name = "Classification_Description")
+//     @Field(name="text", store=Store.YES)
+//    @FieldBridge(impl=MultilanguageTextFieldBridge.class)
+    private Map<Language,LanguageString> description = new HashMap<>();
+
+       //TODO remove, due to #9211
+       //the source for this single classification
+    @XmlElement(name = "source")
+    @XmlIDREF
+    @XmlSchemaType(name = "IDREF")
+    @OneToOne(fetch = FetchType.LAZY, orphanRemoval=true)
+    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
+    private IdentifiableSource source;
+
+
+//     /**
+//      * If this classification is an alternative classification for a subclassification in
+//      * an other classification(parent view),
+//      * the alternativeViewRoot is the connection node from this classification to the parent classification.
+//      * It replaces another node in the parent view.
+//      */
+//     private AlternativeViewRoot alternativeViewRoot;
+
+// ********************** FACTORY METHODS *********************************************/
+
+    public static Classification NewInstance(String name){
+        return NewInstance(name, null, Language.DEFAULT());
+    }
+
+    public static Classification NewInstance(String name, Language language){
+        return NewInstance(name, null, language);
+    }
+
+    public static Classification NewInstance(String name, Reference reference){
+        return NewInstance(name, reference, Language.DEFAULT());
+    }
+
+    public static Classification NewInstance(String name, Reference reference, Language language){
+        return new Classification(name, reference, language);
+    }
+
+// **************************** CONSTRUCTOR *********************************/
+
+    //for hibernate use only, protected required by Javassist
+    protected Classification(){super();}
+
+    protected Classification(String name, Reference reference, Language language){
+        this();
+        LanguageString langName = LanguageString.NewInstance(name, language);
+        setName(langName);
+        setReference(reference);
+        this.rootNode = new TaxonNode();
+        rootNode.setClassification(this);
+    }
+
+    @Override
+    protected void initDefaultCacheStrategy() {
+        this.cacheStrategy = ClassificationDefaultCacheStrategy.NewInstance();
+    }
+
+//********************** xxxxxxxxxxxxx ******************************************/
+
+    /**
+     * Returns the topmost {@link TaxonNode taxon node} (root node) of <i>this</i>
+     * classification. The root node does not have any parent and no taxon. Since taxon nodes
+     * recursively point to their child nodes the complete classification is
+     * defined by its root node.
+     */
+    public TaxonNode getRootNode(){
+        return rootNode;
+    }
+
+    public void setRootNode(TaxonNode root){
+        this.rootNode = root;
+    }
+
+    @Override
+    public TaxonNode addChildNode(TaxonNode childNode, Reference citation, String microCitation) {
+        return addChildNode(childNode, rootNode.getCountChildren(), citation, microCitation);
+    }
+
+    @Override
+    public TaxonNode addChildNode(TaxonNode childNode, int index, Reference citation, String microCitation) {
+
+        childNode.setParentTreeNode(this.rootNode, index);
+
+        childNode.setCitation(citation);
+        childNode.setCitationMicroReference(microCitation);
+//             childNode.setSynonymToBeUsed(synonymToBeUsed);
+
+        return childNode;
+    }
+
+    @Override
+    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
+        return addChildTaxon(taxon, rootNode.getCountChildren(), citation, microCitation);
+    }
+
+    @Override
+    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
+        return addChildNode(new TaxonNode(taxon), index, NamedSource.NewPrimarySourceInstance(citation, microCitation));
+    }
+
+    @Override
+    public TaxonNode addChildTaxon(Taxon taxon, NamedSource source) {
+        return addChildTaxon(taxon, rootNode.getCountChildren(), source);
+    }
+
+    @Override
+    public TaxonNode addChildTaxon(Taxon taxon, int index, NamedSource source) {
+        return addChildNode(new TaxonNode(taxon), index, source);
+    }
+
+    @Override
+    public TaxonNode addChildNode(TaxonNode childNode, int index, NamedSource source) {
+        childNode.setParentTreeNode(this.rootNode, index);
+        childNode.setSource(source);
+
+        return childNode;
+    }
+
+    @Override
+    public boolean deleteChildNode(TaxonNode node) {
+        boolean result = removeChildNode(node);
+
+        if (node.hasTaxon()){
+            node.getTaxon().removeTaxonNode(node);
+            node.setTaxon(null);
+        }
+
+        ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
+        for (TaxonNode childNode : childNodes){
+            if (childNode != null){
+                node.deleteChildNode(childNode);
+            }
+        }
+        return result;
+    }
+
+    public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
+        boolean result = removeChildNode(node);
+        if (node.hasTaxon()){
+            node.getTaxon().removeTaxonNode(node);
+            node.setTaxon(null);
+        }
+
+        if (deleteChildren){
+            ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
+            for (TaxonNode childNode : childNodes){
+                node.deleteChildNode(childNode);
+            }
+        }
+        return result;
+    }
+
+    protected boolean removeChildNode(TaxonNode node){
+        boolean result = false;
+        rootNode = HibernateProxyHelper.deproxy(rootNode, TaxonNode.class);
+        if(!rootNode.getChildNodes().contains(node)){
+            throw new IllegalArgumentException("TaxonNode is a not a root node of this classification");
+        }
+//        rootNode = HibernateProxyHelper.deproxy(rootNode, TaxonNode.class);
+        result = rootNode.removeChildNode(node);
+
+        node.setParent(null);
+        node.setClassification(null);
+
+        return result;
+    }
+
+    public boolean removeRootNode(){
+        boolean result = false;
+
+        if (rootNode != null){
+            this.rootNode.setChildNodes(new ArrayList<TaxonNode>());
+            this.rootNode.setParent(null);
+            rootNode = null;
+            result = true;
+        }
+        return result;
+    }
+
+    /**
+     * Appends an existing topmost node to another node of this tree. The existing topmost node becomes
+     * an ordinary node.
+     * @param topmostNode
+     * @param otherNode
+     * @param ref
+     * @param microReference
+     * @throws IllegalArgumentException
+     */
+    public void makeTopmostNodeChildOfOtherNode(TaxonNode topmostNode, TaxonNode otherNode, Reference ref, String microReference)
+                throws IllegalArgumentException{
+        if (otherNode == null){
+            throw new NullPointerException("other node must not be null");
+        }
+        if (! getChildNodes().contains(topmostNode)){
+            throw new IllegalArgumentException("root node to be added as child must already be root node within this tree");
+        }
+        if (otherNode.getClassification() == null || ! otherNode.getClassification().equals(this)){
+            throw new IllegalArgumentException("other node must already be node within this tree");
+        }
+        if (otherNode.equals(topmostNode)){
+            throw new IllegalArgumentException("root node and other node must not be the same");
+        }
+        otherNode.addChildNode(topmostNode, ref, microReference);
+    }
+
+    /**
+     * Checks if the given taxon is part of <b>this</b> tree.
+     * @param taxon
+     * @return
+     */
+    public boolean isTaxonInTree(Taxon taxon){
+        taxon = HibernateProxyHelper.deproxy(taxon, Taxon.class);
+        return (getNode(taxon) != null);
+    }
+
+    /**
+     * Checks if the given taxon is part of <b>this</b> tree. If so the according TaxonNode is returned.
+     * Otherwise <code>null</code> is returned.
+     * @param taxon
+     * @return
+     */
+    public TaxonNode getNode(Taxon taxon){
+        if (taxon == null){
+            return null;
+        }
+
+        for (TaxonNode taxonNode: taxon.getTaxonNodes()){
+               Classification classification = deproxy(taxonNode.getClassification());
+            if (classification.equals(this)){
+                return taxonNode;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Checks if the given taxon is one of the topmost taxa in <b>this</b> tree.
+     * @param taxon
+     * @return
+     */
+    public boolean isTopmostInTree(Taxon taxon){
+        return (getTopmostNode(taxon) != null);
+    }
+
+    /**
+     * Checks if the taxon is a direct child of the root of <b>this</b> tree and returns the according node if true.
+     * Returns <code>null</code> otherwise.
+     * @param taxon
+     * @return
+     */
+    public TaxonNode getTopmostNode(Taxon taxon){
+        if (taxon == null){
+            return null;
+        }
+        for (TaxonNode taxonNode: taxon.getTaxonNodes()){
+            if (taxonNode.getClassification().equals(this)){
+                if (this.getChildNodes().contains(taxonNode)){
+                    if (taxonNode.getParent() == null){
+                        logger.warn("A topmost node should always have the root node as parent but actually has no parent");
+                    }else if (taxonNode.getParent().getParent() != null){
+                        logger.warn("The root node should have not parent but actually has one");
+                    }else if (taxonNode.getParent().getTaxon() != null){
+                        logger.warn("The root node should have not taxon but actually has one");
+                    }
+                    return taxonNode;
+                }
+            }
+        }
+        return null;
+    }
+
+    private boolean handleCitationOverwrite(TaxonNode childNode, Reference citation, String microCitation){
+        if (citation != null){
+            if (childNode.getReference() != null && ! childNode.getReference().equals(citation)){
+                logger.warn("TaxonNode source will be overwritten");
+            }
+            childNode.setCitation(citation);
+        }
+        if (microCitation != null){
+            if (childNode.getMicroReference() != null && ! childNode.getMicroReference().equals(microCitation)){
+                logger.warn("TaxonNode source detail will be overwritten");
+            }
+            childNode.setCitationMicroReference(microCitation);
+        }
+        return true;
+    }
+
+    /**
+     * Relates two taxa as parent-child nodes within a classification. <BR>
+     * If the taxa are not yet part of the tree they are added to it.<Br>
+     * If the child taxon is a topmost node still it is added as child and deleted from the rootNode set.<Br>
+     * If the child is a child of another parent already an IllegalStateException is thrown because a child can have only
+     * one parent. <Br>
+     * If the parent-child relationship between these two taxa already exists nothing is changed. Only
+     * citation and microcitation are overwritten by the new values if these values are not null.
+     *
+     * @param parent
+     * @param child
+     * @param citation
+     * @param microCitation
+     * @return the childNode
+     * @throws IllegalStateException If the child is a child of another parent already
+     */
+    public TaxonNode addParentChild (Taxon parent, Taxon child, Reference citation, String microCitation)
+            throws IllegalStateException{
+        try {
+            if (parent == null || child == null){
+                logger.warn("Child or parent taxon is null.");
+                return null;
+            }
+            if (parent == child){
+                logger.warn("A taxon should never be its own child. Child not added");
+                return null;
+            }
+            TaxonNode parentNode = this.getNode(parent);
+            TaxonNode childNode = this.getNode(child);
+
+            //if child exists in tree and has a parent
+            //no multiple parents are allowed in the tree
+            if (childNode != null && ! childNode.isTopmostNode()){
+                //...different to the parent taxon  throw exception
+                if ( !(childNode.getParent().getTaxon().equals(parent) )){
+                       if (childNode.getParent().getTaxon().getId() != 0 && childNode.getParent().getTaxon().getName().equals(parent.getName())){
+                               throw new IllegalStateException("The child taxon is already part of the tree but has an other parent taxon than the parent to be added. Child: " + child.toString() + ", new parent:" + parent.toString() + ", old parent: " + childNode.getParent().getTaxon().toString()) ;
+                       }
+                    //... same as the parent taxon do nothing but overwriting citation and microCitation
+                }else{
+                    handleCitationOverwrite(childNode, citation, microCitation);
+                    return childNode;
+                }
+            }
+
+            //add parent node if not exist
+            if (parentNode == null){
+                parentNode = this.addChildTaxon(parent, null, null);
+            }
+
+            //add child if not exists
+            if (childNode == null){
+                childNode = parentNode.addChildTaxon(child, citation, microCitation);
+            }else{
+                //child is still topmost node
+                //TODO test if child is topmostNode otherwise throw IllegalStateException
+                if (! this.isTopmostInTree(child)){
+                    //throw new IllegalStateException("Child is not a topmost node but must be");
+                    if (childNode.getClassification() != null && childNode.getParent() == null){
+                        logger.warn("Child has no parent and is not a topmost node, child: " + child.getId() + " classification: " + childNode.getClassification().getId());
+                    }else{
+                        logger.warn("ChildNode has no classification: " + childNode.getId());
+                    }
+                    parentNode.addChildNode(childNode, citation, microCitation);
+                    if (!parentNode.isTopmostNode()){
+                        this.addChildNode(parentNode, citation, microCitation);
+                        logger.warn("parent is added as a topmost node");
+                    }else{
+                        logger.warn("parent is already a topmost node");
+                    }
+                }else{
+                    this.makeTopmostNodeChildOfOtherNode(childNode, parentNode, citation, microCitation);
+                }
+            }
+            return childNode;
+        } catch (IllegalStateException e) {
+            throw e;
+        } catch (RuntimeException e){
+            throw e;
+        }
+    }
+
+    @Transient
+    public Reference getCitation() {
+        return reference;
+    }
+
+    public LanguageString getName() {
+        return name;
+    }
+    public void setName(LanguageString name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns a set containing all nodes in this classification.
+     *
+     * Caution: Use this method with care. It can be very time and resource consuming and might
+     * run into OutOfMemoryExceptions for big trees.
+     *
+     * @return
+     */
+    @Transient
+    public Set<TaxonNode> getAllNodes() {
+        Set<TaxonNode> allNodes = new HashSet<>();
+
+        for(TaxonNode rootNode : getChildNodes()){
+            allNodes.addAll(rootNode.getDescendants());
+        }
+
+        return allNodes;
+    }
+
+    @Override
+    @Transient
+    public List<TaxonNode> getChildNodes() {
+        return rootNode.getChildNodes();
+    }
+
+    @Override
+    public Reference getReference() {
+        return reference;
+    }
+    public void setReference(Reference reference) {
+        this.reference = reference;
+    }
+
+    @Override
+    public String getMicroReference() {
+        return microReference;
+    }
+    public void setMicroReference(String microReference) {
+        this.microReference = microReference;
+    }
+
+    /**
+        * The point in time, the time period or the season for which this description element
+        * is valid. A season may be expressed by not filling the year part(s) of the time period.
+        */
+       public TimePeriod getTimeperiod() {
+               return timeperiod;
+       }
+       /**
+        * @see #getTimeperiod()
+        */
+       public void setTimeperiod(TimePeriod timeperiod) {
+               if (timeperiod == null){
+                       timeperiod = TimePeriod.NewInstance();
+               }
+               this.timeperiod = timeperiod;
+       }
+
+    /**
+     * Returns the set of {@link NamedArea named areas} indicating the geospatial
+     * data where <i>this</i> {@link Classification} is valid.
+     */
+    public Set<NamedArea> getGeoScopes(){
+        return this.geoScopes;
+    }
+
+    /**
+     * Adds a {@link NamedArea named area} to the set of {@link #getGeoScopes() named areas}
+     * delimiting the geospatial area where <i>this</i> {@link Classification} is valid.
+     *
+     * @param geoScope the named area to be additionally assigned to <i>this</i> taxon description
+     * @see                            #getGeoScopes()
+     */
+    public void addGeoScope(NamedArea geoScope){
+        this.geoScopes.add(geoScope);
+    }
+
+    /**
+     * Removes one element from the set of {@link #getGeoScopes() named areas} delimiting
+     * the geospatial area where <i>this</i> {@link Classification} is valid.
+     *
+     * @param  geoScope   the named area which should be removed
+     * @see                      #getGeoScopes()
+     * @see                      #addGeoScope(NamedArea)
+     */
+    public void removeGeoScope(NamedArea geoScope){
+        this.geoScopes.remove(geoScope);
+    }
+
+       /**
+        * Returns the i18n description used to describe
+        * <i>this</i> {@link Classification}. The different {@link LanguageString language strings}
+        * contained in the multilanguage text should all have the same meaning.
+        */
+       public Map<Language,LanguageString> getDescription(){
+               return this.description;
+       }
+
+       /**
+        * Adds a translated {@link LanguageString text in a particular language}
+        * to the {@link MultilanguageText description} used to describe
+        * <i>this</i> {@link Classification}.
+        *
+        * @param description   the language string describing the individuals association
+        *                                              in a particular language
+        * @see                                 #getDescription()
+        * @see                                 #putDescription(Language, String)
+        *
+        */
+       public void putDescription(LanguageString description){
+               this.description.put(description.getLanguage(),description);
+       }
+       /**
+        * 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 multilanguage text}
+        * used to describe <i>this</i> {@link Classification}.
+        *
+        * @param text          the string describing the individuals association
+        *                                      in a particular language
+        * @param language      the language in which the text string is formulated
+        * @see                         #getDescription()
+        * @see                         #putDescription(LanguageString)
+        */
+       public void putDescription(Language language, String text){
+               this.description.put(language, LanguageString.NewInstance(text, language));
+       }
+       /**
+        * Removes from the {@link MultilanguageText description} used to describe
+        * <i>this</i>  {@link Classification} the one {@link LanguageString language string}
+        * with the given {@link Language language}.
+        *
+        * @param  language     the language in which the language string to be removed
+        *                                      has been formulated
+        * @see                 #getDescription()
+        */
+       public void removeDescription(Language language){
+               this.description.remove(language);
+       }
+
+    public int compareTo(Object o) {
+        //TODO needs to be implemented
+        return 0;
+    }
+
+    @Override
+    public boolean hasChildNodes() {
+        return getChildNodes().size() > 0;
+    }
+
+    //*********************** CLONE ********************************************************/
+
+    /**
+     * Clones <i>this</i> classification. This is a shortcut that enables to create
+     * a new instance that differs only slightly from <i>this</i> classification by
+     * modifying only some of the attributes.<BR><BR>
+
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public Classification clone() {
+        Classification result;
+        try{
+            result = (Classification)super.clone();
+            //result.rootNode.childNodes = new ArrayList<TaxonNode>();
+            List<TaxonNode> rootNodes = new ArrayList<>();
+            TaxonNode rootNodeClone;
+
+            rootNodes.addAll(rootNode.getChildNodes());
+            TaxonNode rootNode;
+            Iterator<TaxonNode> iterator = rootNodes.iterator();
+
+            result.description = cloneLanguageString(this.description);
+
+            while (iterator.hasNext()){
+                rootNode = iterator.next();
+                rootNodeClone = rootNode.cloneDescendants();
+                rootNodeClone.setClassification(result);
+                result.addChildNode(rootNodeClone, rootNode.getReference(), rootNode.getMicroReference());
+                rootNodeClone.setSynonymToBeUsed(rootNode.getSynonymToBeUsed());
+            }
+
+            //geo-scopes
+            result.geoScopes = new HashSet<>();
+            for (NamedArea namedArea : getGeoScopes()){
+                result.geoScopes.add(namedArea);
+            }
+
+            return result;
+
+        }catch (CloneNotSupportedException e) {
+            logger.warn("Object does not implement cloneable");
+            e.printStackTrace();
+            return null;
+        }
+    }
+}