2 * European Distributed Institute of Taxonomy
3 * http://www.e-taxonomy.eu
5 * The contents of this file are subject to the Mozilla Public License Version 1.1
6 * See LICENSE.TXT at the top of this package for the full license terms.
9 package eu
.etaxonomy
.cdm
.model
.description
;
11 import java
.util
.ArrayList
;
12 import java
.util
.HashMap
;
13 import java
.util
.List
;
15 import java
.util
.Map
.Entry
;
17 import javax
.persistence
.Entity
;
18 import javax
.persistence
.FetchType
;
19 import javax
.persistence
.JoinColumn
;
20 import javax
.persistence
.ManyToOne
;
21 import javax
.persistence
.MapKeyJoinColumn
;
22 import javax
.persistence
.OneToMany
;
23 import javax
.persistence
.OrderBy
;
24 import javax
.persistence
.Transient
;
25 import javax
.validation
.constraints
.NotNull
;
26 import javax
.xml
.bind
.annotation
.XmlAccessType
;
27 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
28 import javax
.xml
.bind
.annotation
.XmlElement
;
29 import javax
.xml
.bind
.annotation
.XmlElementWrapper
;
30 import javax
.xml
.bind
.annotation
.XmlIDREF
;
31 import javax
.xml
.bind
.annotation
.XmlRootElement
;
32 import javax
.xml
.bind
.annotation
.XmlSchemaType
;
33 import javax
.xml
.bind
.annotation
.XmlType
;
34 import javax
.xml
.bind
.annotation
.adapters
.XmlJavaTypeAdapter
;
36 import org
.apache
.log4j
.Logger
;
37 import org
.hibernate
.annotations
.Cascade
;
38 import org
.hibernate
.annotations
.CascadeType
;
39 import org
.hibernate
.annotations
.IndexColumn
;
40 import org
.hibernate
.envers
.Audited
;
42 import eu
.etaxonomy
.cdm
.jaxb
.MultilanguageTextAdapter
;
43 import eu
.etaxonomy
.cdm
.model
.common
.IMultiLanguageTextHolder
;
44 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
45 import eu
.etaxonomy
.cdm
.model
.common
.LanguageString
;
46 import eu
.etaxonomy
.cdm
.model
.common
.MultilanguageText
;
47 import eu
.etaxonomy
.cdm
.model
.common
.VersionableEntity
;
48 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
51 * This class represents a node within a {@link PolytomousKey polytomous key}
52 * structure. The structure of such a key is a directed tree like acyclic graph
53 * of <code>PolytomousKeyNode</code>s.
54 * A <code>PolytomousKeyNode</code> represents both the node and the edges that lead
55 * to <code>this</code> node, therefore an extra class representing the edges
58 * The attribute representing the edge leading from its parent node to <code>this</code>
59 * node is the {@link #getStatement() statement}, attributes leading to the child nodes
60 * are either the {@link #getQuestion() question} or the {@link #getFeature() feature}.
61 * While {@link #getStatement() statements} are required, {@link #getQuestion() questions} and
62 * {@link #getFeature() features} are optional and do typically not exist in classical keys.
63 * Both, {@link #getQuestion() questions} and {@link #getFeature() features}, will be "answered" by the
64 * {@link #getStatement() statements} of the child nodes, where {@link #getQuestion() questions}
65 * are usually free text used in manually created keys while {@link #getFeature() features} are
66 * typically used in automatically created keys based on structured descriptive data.
67 * Only one of them should be defined in a node. However, if both exist the {@link #getQuestion() question}
68 * should always be given <b>priority</b> over the {@link #getFeature() feature}.<br>
70 * Typically a node either links to its child nodes (subnodes) or represents a link
71 * to a {@link Taxon taxon}. The later, if taken as part of the tree, are usually
72 * the leaves of the represented tree like structure (taxonomically they are the
73 * end point of the decision process).<br>
75 * However, there are exceptions to this simple structure:
77 * <li>Subnodes and taxon link<br>
79 * In rare cases a node can have both, subnodes and a {@link #getTaxon() link to a taxon}.
80 * In this case the taxonomic determination process may be either terminated
81 * at the given {@link Taxon taxon} or can proceed with the children if a more accurate
82 * determination is wanted. This may be the case e.g. in a key that generally
83 * covers all taxa of rank species and at the same time allows identification of
84 * subspecies or varieties of these taxa.</li>
86 * <li>{@link #getOtherNode() Other nodes}: <br>
88 * A node may not only link to its subnodes or to a taxon but it may
89 * also link to {@link #getOtherNode() another node} (with a different parent) of either the same key
93 * If an {@link #getOtherNode() otherNode} represents a node
94 * of the same tree the key does not represent a strict tree structure
95 * anymore. However, as this is a rare case we will still use this term
96 * at some places.</li>
98 * <li>{@link #getSubkey() Subkey}:<br>
100 * A node may also link to another key ({@link #getSubkey() subkey}) as a whole, which is
101 * equal to an {@link #getOtherNode() otherNode} link to the root node of the other key.
102 * In this case the path in the decision graph spans over multiple keys.</li>
103 * This structure is typically used when a key covers taxa down to a certain rank, whereas
104 * taxa below this rank are covered by extra keys (e.g. a parent key may cover all taxa
105 * of rank species while subspecies and varieties are covered by a subkeys for each of these
107 * Another usecase for subkeys is the existence of an alternative key for a certain part
108 * of the decision tree.
110 * <li>Multiple taxa<br>
112 * Some nodes in legacy keys do link to multiple taxa, meaning that the key ambigous at
113 * this point. To represent such nodes one must use child nodes with empty
114 * {@link #getStatement() statements} for each such taxon (in all other cases - except for
115 * root nodes - the <code>statement</code> is required).
116 * Applications that do visualize the key should handle such a node-subnode structure as one
117 * node with multiple taxon links. This complicated data structure has been chosen for
118 * this rare to avoid a more complicated <code>List<Taxon></code> structure for the standard
121 * The {@link PolytomousKey#getRoot() root node of the key} may represent the entry point
122 * question or feature but does naturally neither have a statement nor a linked taxon as
123 * there is no prior decision yet.
127 * A polytomous key node can be referenced from multiple other nodes via the
128 * {@link #getOtherNode() otherNode} attribute of the other nodes. Therefore, though
129 * we speek about a "decision tree" structure a node does not necessarily have only
131 * However, nodes are mainly represented in a tree structure and therefore do have
132 * a defined {@link #getParent() parent} which is the "main" parent. But when implementing
133 * visualizing or editing tools one should keep in mind that this parent may not be
134 * the only node linking the child node.
137 * @created 13-Oct-2010
140 @SuppressWarnings("serial")
141 @XmlAccessorType(XmlAccessType
.FIELD
)
142 @XmlType(name
= "PolytomousKeyNode", propOrder
= { "key", "parent", "children",
143 "sortIndex", "nodeNumber", "statement", "question", "feature", "taxon",
144 "subkey", "otherNode", "modifyingText" })
145 @XmlRootElement(name
= "FeatureNode")
148 public class PolytomousKeyNode
extends VersionableEntity
implements IMultiLanguageTextHolder
{
149 private static final Logger logger
= Logger
.getLogger(PolytomousKeyNode
.class);
151 // This is the main key a node belongs to. Although other keys may also
153 // <code>this</code> node, a node usually belongs to a given key.
154 @XmlElement(name
= "PolytomousKey")
156 @XmlSchemaType(name
= "IDREF")
157 // @ManyToOne(fetch = FetchType.LAZY, optional=false)
158 // @JoinColumn(nullable=false)
160 // @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE /*, CascadeType.DELETE_ORPHAN */})
161 @ManyToOne(fetch
= FetchType
.LAZY
)
162 @Cascade({ CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
163 private PolytomousKey key
;
165 @XmlElementWrapper(name
= "Children")
166 @XmlElement(name
= "Child")
167 // @OrderColumn("sortIndex") //JPA 2.0 same as @IndexColumn
168 // @IndexColumn does not work because not every FeatureNode has a parent.
169 // But only NotNull will solve the problem (otherwise
170 // we will need a join table
171 // http://stackoverflow.com/questions/2956171/jpa-2-0-ordercolumn-annotation-in-hibernate-3-5
172 // http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-collection-extratype-indexbidir
173 // see also https://forum.hibernate.org/viewtopic.php?p=2392563
174 // http://opensource.atlassian.com/projects/hibernate/browse/HHH-4390
175 // reading works, but writing doesn't
177 @IndexColumn(name
= "sortIndex", base
= 0)
178 @OrderBy("sortIndex")
179 @OneToMany(fetch
= FetchType
.LAZY
, mappedBy
= "parent")
180 @Cascade({ CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
})
181 private List
<PolytomousKeyNode
> children
= new ArrayList
<PolytomousKeyNode
>();
183 @XmlElement(name
= "Parent")
185 @XmlSchemaType(name
= "IDREF")
186 @Cascade(CascadeType
.SAVE_UPDATE
)
187 @ManyToOne(fetch
= FetchType
.LAZY
, targetEntity
= PolytomousKeyNode
.class)
188 @JoinColumn(name
= "parent_id" /*
189 * , insertable=false, updatable=false,
192 private PolytomousKeyNode parent
;
194 // see comment on children @IndexColumn
195 private Integer sortIndex
;
197 @XmlElement(name
= "Statement")
199 @XmlSchemaType(name
= "IDREF")
200 @ManyToOne(fetch
= FetchType
.LAZY
)
201 @Cascade({ CascadeType
.SAVE_UPDATE
, CascadeType
.DELETE
})
202 private KeyStatement statement
;
204 @XmlElement(name
= "Question")
206 @XmlSchemaType(name
= "IDREF")
207 @ManyToOne(fetch
= FetchType
.LAZY
)
208 @Cascade({ CascadeType
.SAVE_UPDATE
, CascadeType
.DELETE
})
209 private KeyStatement question
;
211 @XmlElement(name
= "Feature")
213 @XmlSchemaType(name
= "IDREF")
214 @ManyToOne(fetch
= FetchType
.LAZY
)
215 private Feature feature
;
217 @XmlElement(name
= "Taxon")
219 @XmlSchemaType(name
= "IDREF")
220 @ManyToOne(fetch
= FetchType
.LAZY
)
221 @Cascade(CascadeType
.SAVE_UPDATE
)
224 // Refers to an entire key
225 // <code>this</code> node, a node usually belongs to a given key.
226 @XmlElement(name
= "SubKey")
228 @XmlSchemaType(name
= "IDREF")
229 @ManyToOne(fetch
= FetchType
.LAZY
)
230 @Cascade(CascadeType
.SAVE_UPDATE
)
231 private PolytomousKey subkey
;
233 // Refers to an other node within this key or an other key
234 @XmlElement(name
= "PolytomousKey")
236 @XmlSchemaType(name
= "IDREF")
237 @ManyToOne(fetch
= FetchType
.LAZY
)
238 private PolytomousKeyNode otherNode
;
240 private Integer nodeNumber
= null;
242 // TODO should be available for each taxon/result
243 @XmlElement(name
= "ModifyingText")
244 @XmlJavaTypeAdapter(MultilanguageTextAdapter
.class)
245 @OneToMany(fetch
= FetchType
.LAZY
)
246 // @JoinTable(name = "DescriptionElementBase_ModifyingText")
247 @MapKeyJoinColumn(name
="modifyingtext_mapkey_id")
248 @Cascade({ CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
249 private Map
<Language
, LanguageString
> modifyingText
= new HashMap
<Language
, LanguageString
>();
252 * Class constructor: creates a new empty feature node instance.
254 protected PolytomousKeyNode() {
259 * Creates a new empty polytomous key node instance.
261 public static PolytomousKeyNode
NewInstance() {
262 return new PolytomousKeyNode();
266 * Creates a new polytomous key node instance.
269 public static PolytomousKeyNode
NewInstance(String statement
) {
270 PolytomousKeyNode result
= new PolytomousKeyNode();
271 result
.setStatement(KeyStatement
.NewInstance(statement
));
276 * Creates a new polytomous key node instance.
279 public static PolytomousKeyNode
NewInstance(String statement
,
280 String question
, Taxon taxon
, Feature feature
) {
281 PolytomousKeyNode result
= new PolytomousKeyNode();
282 result
.setTaxon(taxon
);
283 result
.setStatement(KeyStatement
.NewInstance(statement
));
284 result
.setQuestion(KeyStatement
.NewInstance(question
));
285 result
.setFeature(feature
);
289 // ** ********************** CHILDREN ******************************/
294 public PolytomousKey
getKey() {
301 public void setKey(PolytomousKey key
) {
306 * The node number is the number of the node within the key. This
307 * corresponds to the number for key choices in written keys.
309 public Integer
getNodeNumber() {
314 * Is computed automatically and therefore should not be set by the user.
316 public void setNodeNumber(Integer nodeNumber
) {
317 this.nodeNumber
= nodeNumber
;
321 * Returns the parent node of <code>this</code> child.
325 public PolytomousKeyNode
getParent() {
330 * For bidirectional use only !
334 protected void setParent(PolytomousKeyNode parent
) {
335 this.parent
= parent
;
339 * Returns the (ordered) list of feature nodes which are children nodes of
340 * <i>this</i> feature node.
342 public List
<PolytomousKeyNode
> getChildren() {
347 * Adds the given polytomous key node at the end of the list of children of
348 * <i>this</i> polytomous key node.
351 * the feature node to be added
352 * @see #getChildren()
353 * @see #setChildren(List)
354 * @see #addChild(PolytomousKeyNode, int)
355 * @see #removeChild(PolytomousKeyNode)
356 * @see #removeChild(int)
358 public void addChild(PolytomousKeyNode child
) {
359 addChild(child
, children
.size());
363 * Inserts the given child node in the list of children of <i>this</i>
364 * polytomous key node at the given (index + 1) position. If the given index
365 * is out of bounds an exception will be thrown.<BR>
368 * the polytomous key node to be added
370 * the integer indicating the position at which the child should
372 * @see #getChildren()
373 * @see #setChildren(List)
374 * @see #addChild(PolytomousKeyNode)
375 * @see #removeChild(PolytomousKeyNode)
376 * @see #removeChild(int)
378 public void addChild(PolytomousKeyNode child
, int index
) {
379 if (index
< 0 || index
> children
.size() + 1) {
380 throw new IndexOutOfBoundsException("Wrong index: " + index
);
383 if(nodeNumber
== null) {
384 nodeNumber
= getMaxNodeNumberFromRoot() + 1;
387 children
.add(index
, child
);
388 child
.setKey(this.getKey());
390 // TODO workaround (see sortIndex doc)
391 for (int i
= 0; i
< children
.size(); i
++) {
392 children
.get(i
).sortIndex
= i
;
394 child
.sortIndex
= index
;
395 child
.setParent(this);
399 * Returns the current maximum value of the node number in the entire key
400 * starting from the root.
404 private int getMaxNodeNumberFromRoot() {
405 PolytomousKeyNode rootKeyNode
= this.getKey().getRoot();
406 int rootNumber
= this.getKey().getStartNumber();
407 return getMaxNodeNumber(rootNumber
, rootKeyNode
);
411 * Returns the current maximum value of the node number in the entire key
412 * starting from the given key node, comparing with a given max value as input.
416 private int getMaxNodeNumber(int maxNumber
, PolytomousKeyNode parent
) {
417 if (parent
.getNodeNumber() != null) {
418 maxNumber
= (maxNumber
< parent
.getNodeNumber()) ? parent
.getNodeNumber() : maxNumber
;
419 for (PolytomousKeyNode child
: parent
.getChildren()) {
420 if (parent
== child
){
421 throw new RuntimeException("Parent and child are the same for the given key node. This will lead to an infinite loop when updating the max node number.");
423 maxNumber
= getMaxNodeNumber(maxNumber
, child
);
431 * Refresh numbering of key nodes starting from root.
434 public void refreshNodeNumbering() {
435 updateNodeNumbering(getKey().getRoot(), getKey().getStartNumber());
439 * Recursively (depth-first) refresh numbering of key nodes starting from the given key node,
440 * starting with a given node number.
442 * @return new starting node number value
444 private int updateNodeNumbering(PolytomousKeyNode node
,int nodeN
) {
445 int newNodeN
= nodeN
;
447 node
.setNodeNumber(null);
449 node
.setNodeNumber(nodeN
);
451 for (PolytomousKeyNode child
: node
.getChildren()) {
453 throw new RuntimeException("Parent and child are the same for the given key node. This will lead to an infinite loop when updating node numbers.");
455 newNodeN
= updateNodeNumbering(child
, newNodeN
);
465 * Removes the given polytomous key node from the list of
466 * {@link #getChildren() children} of <i>this</i> polytomous key node.
469 * the feature node which should be removed
470 * @see #getChildren()
471 * @see #addChild(PolytomousKeyNode, int)
472 * @see #addChild(PolytomousKeyNode)
473 * @see #removeChild(int)
475 public void removeChild(PolytomousKeyNode child
) {
476 int index
= children
.indexOf(child
);
483 * Removes the feature node placed at the given (index + 1) position from
484 * the list of {@link #getChildren() children} of <i>this</i> feature node.
485 * If the given index is out of bounds no child will be removed.
488 * the integer indicating the position of the feature node to be
490 * @see #getChildren()
491 * @see #addChild(PolytomousKeyNode, int)
492 * @see #addChild(PolytomousKeyNode)
493 * @see #removeChild(PolytomousKeyNode)
495 public void removeChild(int index
) {
496 PolytomousKeyNode child
= children
.get(index
);
498 children
.remove(index
);
499 child
.setParent(null);
500 // TODO workaround (see sortIndex doc)
501 for (int i
= 0; i
< children
.size(); i
++) {
502 PolytomousKeyNode childAt
= children
.get(i
);
503 childAt
.sortIndex
= i
;
505 child
.sortIndex
= null;
506 child
.setNodeNumber(null);
508 refreshNodeNumbering();
513 * Returns the feature node placed at the given (childIndex + 1) position
514 * within the list of {@link #getChildren() children} of <i>this</i> feature
515 * node. If the given index is out of bounds no child will be returned.
518 * the integer indicating the position of the feature node
519 * @see #getChildren()
520 * @see #addChild(PolytomousKeyNode, int)
521 * @see #removeChild(int)
523 public PolytomousKeyNode
getChildAt(int childIndex
) {
524 return children
.get(childIndex
);
528 * Returns the number of children nodes of <i>this</i> feature node.
530 * @see #getChildren()
533 public int childCount() {
534 return children
.size();
538 * Returns the integer indicating the position of the given feature node
539 * within the list of {@link #getChildren() children} of <i>this</i> feature
540 * node. If the list does not contain this node then -1 will be returned.
543 * the feature node the position of which is being searched
544 * @see #addChild(PolytomousKeyNode, int)
545 * @see #removeChild(int)
547 public int getIndex(PolytomousKeyNode node
) {
548 if (!children
.contains(node
)) {
551 return children
.indexOf(node
);
556 * Returns the boolean value indicating if <i>this</i> feature node has
557 * children (false) or not (true). A node without children is at the
558 * bottommost level of a tree and is called a leaf.
560 * @see #getChildren()
561 * @see #getChildCount()
564 public boolean isLeaf() {
565 return children
.size() < 1;
568 // ** ********************** QUESTIONS AND STATEMENTS ************************/
571 * Returns the statement for <code>this</code> PolytomousKeyNode. When coming
572 * from the parent node the user needs to agree with the statement (and disagree
573 * with all statements of sibling nodes) to follow <code>this</code> node.<BR>
574 * The statement may stand alone (standard in classical keys) or it may be
575 * either the answer to the {@link #getQuestion() question} or the
576 * value for the {@link #getFeature() feature} of the parent node.
578 * @return the statement
579 * @see #getQuestion()
581 public KeyStatement
getStatement() {
586 * This is a convenience method to set the statement text for this node in
587 * the given language. <BR>
588 * If no statement exists yet a new statement is created. <BR>
589 * If a statement text in the given language exists already it is
590 * overwritten and the old text is returned. If language is
591 * <code>null</code> the default language is used instead.
596 * the language of the statement text
597 * @return the old statement text in the given language as LanguageString
599 public LanguageString
addStatementText(String text
, Language language
) {
600 if (language
== null) {
601 language
= Language
.DEFAULT();
603 if (this.statement
== null) {
604 setStatement(KeyStatement
.NewInstance());
606 return getStatement().putLabel(language
, text
);
611 * @see #getStatement()
613 public void setStatement(KeyStatement statement
) {
614 this.statement
= statement
;
618 * Returns the question for <code>this</code> PolytomousKeyNode. <BR>
619 * A question is answered by statements in leads below this tree node.
620 * Questions are optional and are usually empty in traditional keys.
622 * @return the question
623 * @see #getStatement()
625 public KeyStatement
getQuestion() {
630 * This is a convenience method to sets the question text for this node in
631 * the given language. <BR>
632 * If no question exists yet a new question is created. <BR>
633 * If a question text in the given language exists already it is overwritten
634 * and the old text is returned. If language is <code>null</code> the
635 * default language is used instead.
641 public LanguageString
addQuestionText(String text
, Language language
) {
642 if (language
== null) {
643 language
= Language
.DEFAULT();
645 if (this.question
== null) {
646 setQuestion(KeyStatement
.NewInstance());
648 return getQuestion().putLabel(language
, text
);
653 * @see #getQuestion()
655 public void setQuestion(KeyStatement question
) {
656 this.question
= question
;
659 // **************** modifying text ***************************************
662 * Returns the {@link MultilanguageText} like "an unusual form of",
663 * commenting the determined taxon. That is a modifyingText may by used to
664 * comment or to constraint the decision step represented by the edge
665 * leading to <i>this</i> node
667 * All {@link LanguageString language strings} contained in the
668 * multilanguage texts should all have the same meaning.<BR>
670 public Map
<Language
, LanguageString
> getModifyingText() {
671 return this.modifyingText
;
675 * See {@link #getModifyingText}
678 * the language string describing the validity in a particular
680 * @see #getModifyingText()
681 * @see #putModifyingText(Language, String)
682 * @deprecated should follow the put semantic of maps, this method will be
683 * removed in v4.0 Use the
684 * {@link #putModifyingText(LanguageString) putModifyingText}
688 public LanguageString
addModifyingText(LanguageString description
) {
689 return this.putModifyingText(description
);
693 * See {@link #getModifyingText}
696 * the language string describing the validity in a particular
698 * @see #getModifyingText()
699 * @see #putModifyingText(Language, String)
701 public LanguageString
putModifyingText(LanguageString description
) {
702 return this.modifyingText
.put(description
.getLanguage(), description
);
706 * See {@link #getModifyingText}
709 * the string describing the validity in a particular language
711 * the language in which the text string is formulated
712 * @see #getModifyingText()
713 * @see #putModifyingText(LanguageString)
714 * @deprecated should follow the put semantic of maps, this method will be
715 * removed in v4.0 Use the
716 * {@link #putModifyingText(Language, String) putModifyingText}
720 public LanguageString
addModifyingText(String text
, Language language
) {
721 return this.putModifyingText(language
, text
);
725 * See {@link #getModifyingText}
728 * the string describing the validity in a particular language
730 * the language in which the text string is formulated
731 * @see #getModifyingText()
732 * @see #putModifyingText(LanguageString)
734 public LanguageString
putModifyingText(Language language
, String text
) {
735 return this.modifyingText
.put(language
,
736 LanguageString
.NewInstance(text
, language
));
740 * See {@link #getModifyingText}
743 * the language in which the language string to be removed has
745 * @see #getModifyingText()
747 public LanguageString
removeModifyingText(Language language
) {
748 return this.modifyingText
.remove(language
);
752 * Returns the taxon this node links to. This is usually the case when this
756 * @see #setTaxon(Taxon)
758 * @see #getChildren()
759 * @see #getOtherNode()
761 public Taxon
getTaxon() {
766 * Sets the taxon this node links to. <BR>
772 public void setTaxon(Taxon taxon
) {
778 * @see #setSubkey(PolytomousKey)
780 * @see #getChildren()
781 * @see #getOtherNode()
783 public PolytomousKey
getSubkey() {
791 public void setSubkey(PolytomousKey subkey
) {
792 this.subkey
= subkey
;
797 * @see #setOtherNode(PolytomousKeyNode)
799 * @see #getChildren()
802 public PolytomousKeyNode
getOtherNode() {
808 * @see #getOtherNode()
810 public void setOtherNode(PolytomousKeyNode otherNode
) {
811 this.otherNode
= otherNode
;
815 public void setFeature(Feature feature
) {
816 this.feature
= feature
;
819 public Feature
getFeature() {
823 // *********************** CLONE
824 // ********************************************************/
827 * Clones <i>this</i> PolytomousKeyNode. This is a shortcut that enables to
828 * create a new instance that differs only slightly from <i>this</i>
829 * PolytomousKeyNode by modifying only some of the attributes. The parent,
830 * the feature and the key are the are the same as for the original feature
831 * node the children are removed.
833 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
834 * @see java.lang.Object#clone()
837 public Object
clone() {
838 PolytomousKeyNode result
;
840 result
= (PolytomousKeyNode
) super.clone();
841 result
.children
= new ArrayList
<PolytomousKeyNode
>();
843 result
.modifyingText
= new HashMap
<Language
, LanguageString
>();
844 for (Entry
<Language
, LanguageString
> entry
: this.modifyingText
846 result
.putModifyingText(entry
.getValue());
850 } catch (CloneNotSupportedException e
) {
851 logger
.warn("Object does not implement cloneable");