alternative for protected: deproxy #4200
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / description / PolytomousKeyNode.java
1 /**
2 * European Distributed Institute of Taxonomy
3 * http://www.e-taxonomy.eu
4 *
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.
7 */
8
9 package eu.etaxonomy.cdm.model.description;
10
11 import java.util.ArrayList;
12 import java.util.HashMap;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Map.Entry;
16
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;
35
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;
41
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;
49
50 /**
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
56 * does not exist.
57 * <BR>
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>
69 *
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>
74 *
75 * However, there are exceptions to this simple structure:
76 *
77 * <li>Subnodes and taxon link<br>
78 *
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>
85 *
86 * <li>{@link #getOtherNode() Other nodes}: <br>
87 *
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
90 * or another key.
91 * <br>
92 * <b>NOTE: </b>
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>
97 *
98 * <li>{@link #getSubkey() Subkey}:<br>
99 *
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
106 * species.
107 * Another usecase for subkeys is the existence of an alternative key for a certain part
108 * of the decision tree.
109 *
110 * <li>Multiple taxa<br>
111 *
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
119 * case.</li>
120 *
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.
124 *
125 * <h4>Notes</h4>
126 * <p>
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
130 * one parent.
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.
135 *
136 * @author a.mueller
137 * @created 13-Oct-2010
138 *
139 */
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")
146 @Entity
147 @Audited
148 public class PolytomousKeyNode extends VersionableEntity implements IMultiLanguageTextHolder {
149 private static final Logger logger = Logger.getLogger(PolytomousKeyNode.class);
150
151 // This is the main key a node belongs to. Although other keys may also
152 // reference
153 // <code>this</code> node, a node usually belongs to a given key.
154 @XmlElement(name = "PolytomousKey")
155 @XmlIDREF
156 @XmlSchemaType(name = "IDREF")
157 // @ManyToOne(fetch = FetchType.LAZY, optional=false)
158 // @JoinColumn(nullable=false)
159 // @NotNull
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;
164
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
176 //
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>();
182
183 @XmlElement(name = "Parent")
184 @XmlIDREF
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,
190 * nullable=false
191 */)
192 private PolytomousKeyNode parent;
193
194 // see comment on children @IndexColumn
195 private Integer sortIndex;
196
197 @XmlElement(name = "Statement")
198 @XmlIDREF
199 @XmlSchemaType(name = "IDREF")
200 @ManyToOne(fetch = FetchType.LAZY)
201 @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.DELETE })
202 private KeyStatement statement;
203
204 @XmlElement(name = "Question")
205 @XmlIDREF
206 @XmlSchemaType(name = "IDREF")
207 @ManyToOne(fetch = FetchType.LAZY)
208 @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.DELETE })
209 private KeyStatement question;
210
211 @XmlElement(name = "Feature")
212 @XmlIDREF
213 @XmlSchemaType(name = "IDREF")
214 @ManyToOne(fetch = FetchType.LAZY)
215 private Feature feature;
216
217 @XmlElement(name = "Taxon")
218 @XmlIDREF
219 @XmlSchemaType(name = "IDREF")
220 @ManyToOne(fetch = FetchType.LAZY)
221 @Cascade(CascadeType.SAVE_UPDATE)
222 private Taxon taxon;
223
224 // Refers to an entire key
225 // <code>this</code> node, a node usually belongs to a given key.
226 @XmlElement(name = "SubKey")
227 @XmlIDREF
228 @XmlSchemaType(name = "IDREF")
229 @ManyToOne(fetch = FetchType.LAZY)
230 @Cascade(CascadeType.SAVE_UPDATE)
231 private PolytomousKey subkey;
232
233 // Refers to an other node within this key or an other key
234 @XmlElement(name = "PolytomousKey")
235 @XmlIDREF
236 @XmlSchemaType(name = "IDREF")
237 @ManyToOne(fetch = FetchType.LAZY)
238 private PolytomousKeyNode otherNode;
239
240 private Integer nodeNumber = null;
241
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>();
250
251 /**
252 * Class constructor: creates a new empty feature node instance.
253 */
254 protected PolytomousKeyNode() {
255 super();
256 }
257
258 /**
259 * Creates a new empty polytomous key node instance.
260 */
261 public static PolytomousKeyNode NewInstance() {
262 return new PolytomousKeyNode();
263 }
264
265 /**
266 * Creates a new polytomous key node instance.
267 *
268 */
269 public static PolytomousKeyNode NewInstance(String statement) {
270 PolytomousKeyNode result = new PolytomousKeyNode();
271 result.setStatement(KeyStatement.NewInstance(statement));
272 return result;
273 }
274
275 /**
276 * Creates a new polytomous key node instance.
277 *
278 */
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);
286 return result;
287 }
288
289 // ** ********************** CHILDREN ******************************/
290
291 /**
292 * @return
293 */
294 public PolytomousKey getKey() {
295 return key;
296 }
297
298 /**
299 * @param key
300 */
301 public void setKey(PolytomousKey key) {
302 this.key = key;
303 }
304
305 /**
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.
308 */
309 public Integer getNodeNumber() {
310 return nodeNumber;
311 }
312
313 /**
314 * Is computed automatically and therefore should not be set by the user.
315 */
316 public void setNodeNumber(Integer nodeNumber) {
317 this.nodeNumber = nodeNumber;
318 }
319
320 /**
321 * Returns the parent node of <code>this</code> child.
322 *
323 * @return
324 */
325 public PolytomousKeyNode getParent() {
326 return parent;
327 }
328
329 /**
330 * For bidirectional use only !
331 *
332 * @param parent
333 */
334 protected void setParent(PolytomousKeyNode parent) {
335 this.parent = parent;
336 }
337
338 /**
339 * Returns the (ordered) list of feature nodes which are children nodes of
340 * <i>this</i> feature node.
341 */
342 public List<PolytomousKeyNode> getChildren() {
343 return children;
344 }
345
346 /**
347 * Adds the given polytomous key node at the end of the list of children of
348 * <i>this</i> polytomous key node.
349 *
350 * @param child
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)
357 */
358 public void addChild(PolytomousKeyNode child) {
359 addChild(child, children.size());
360 }
361
362 /**
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>
366 *
367 * @param child
368 * the polytomous key node to be added
369 * @param index
370 * the integer indicating the position at which the child should
371 * be added
372 * @see #getChildren()
373 * @see #setChildren(List)
374 * @see #addChild(PolytomousKeyNode)
375 * @see #removeChild(PolytomousKeyNode)
376 * @see #removeChild(int)
377 */
378 public void addChild(PolytomousKeyNode child, int index) {
379 if (index < 0 || index > children.size() + 1) {
380 throw new IndexOutOfBoundsException("Wrong index: " + index);
381 }
382
383 if(nodeNumber == null) {
384 nodeNumber = getMaxNodeNumberFromRoot() + 1;
385 }
386
387 children.add(index, child);
388 child.setKey(this.getKey());
389
390 // TODO workaround (see sortIndex doc)
391 for (int i = 0; i < children.size(); i++) {
392 children.get(i).sortIndex = i;
393 }
394 child.sortIndex = index;
395 child.setParent(this);
396 }
397
398 /**
399 * Returns the current maximum value of the node number in the entire key
400 * starting from the root.
401 *
402 * @return
403 */
404 private int getMaxNodeNumberFromRoot() {
405 PolytomousKeyNode rootKeyNode = this.getKey().getRoot();
406 int rootNumber = this.getKey().getStartNumber();
407 return getMaxNodeNumber(rootNumber, rootKeyNode);
408 }
409
410 /**
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.
413 *
414 * @return
415 */
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.");
422 }else{
423 maxNumber = getMaxNodeNumber(maxNumber, child);
424 }
425 }
426 }
427 return maxNumber;
428 }
429
430 /**
431 * Refresh numbering of key nodes starting from root.
432 *
433 */
434 public void refreshNodeNumbering() {
435 updateNodeNumbering(getKey().getRoot(), getKey().getStartNumber());
436 }
437
438 /**
439 * Recursively (depth-first) refresh numbering of key nodes starting from the given key node,
440 * starting with a given node number.
441 *
442 * @return new starting node number value
443 */
444 private int updateNodeNumbering(PolytomousKeyNode node,int nodeN) {
445 int newNodeN = nodeN;
446 if (node.isLeaf()) {
447 node.setNodeNumber(null);
448 } else {
449 node.setNodeNumber(nodeN);
450 newNodeN++;
451 for (PolytomousKeyNode child : node.getChildren()) {
452 if (node == child){
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.");
454 }else{
455 newNodeN = updateNodeNumbering(child, newNodeN);
456 }
457 }
458 }
459 return newNodeN;
460 }
461
462
463
464 /**
465 * Removes the given polytomous key node from the list of
466 * {@link #getChildren() children} of <i>this</i> polytomous key node.
467 *
468 * @param child
469 * the feature node which should be removed
470 * @see #getChildren()
471 * @see #addChild(PolytomousKeyNode, int)
472 * @see #addChild(PolytomousKeyNode)
473 * @see #removeChild(int)
474 */
475 public void removeChild(PolytomousKeyNode child) {
476 int index = children.indexOf(child);
477 if (index >= 0) {
478 removeChild(index);
479 }
480 }
481
482 /**
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.
486 *
487 * @param index
488 * the integer indicating the position of the feature node to be
489 * removed
490 * @see #getChildren()
491 * @see #addChild(PolytomousKeyNode, int)
492 * @see #addChild(PolytomousKeyNode)
493 * @see #removeChild(PolytomousKeyNode)
494 */
495 public void removeChild(int index) {
496 PolytomousKeyNode child = children.get(index);
497 if (child != null) {
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;
504 }
505 child.sortIndex = null;
506 child.setNodeNumber(null);
507 }
508 refreshNodeNumbering();
509 }
510
511
512 /**
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.
516 *
517 * @param childIndex
518 * the integer indicating the position of the feature node
519 * @see #getChildren()
520 * @see #addChild(PolytomousKeyNode, int)
521 * @see #removeChild(int)
522 */
523 public PolytomousKeyNode getChildAt(int childIndex) {
524 return children.get(childIndex);
525 }
526
527 /**
528 * Returns the number of children nodes of <i>this</i> feature node.
529 *
530 * @see #getChildren()
531 */
532 @Transient
533 public int childCount() {
534 return children.size();
535 }
536
537 /**
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.
541 *
542 * @param node
543 * the feature node the position of which is being searched
544 * @see #addChild(PolytomousKeyNode, int)
545 * @see #removeChild(int)
546 */
547 public int getIndex(PolytomousKeyNode node) {
548 if (!children.contains(node)) {
549 return -1;
550 } else {
551 return children.indexOf(node);
552 }
553 }
554
555 /**
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.
559 *
560 * @see #getChildren()
561 * @see #getChildCount()
562 */
563 @Transient
564 public boolean isLeaf() {
565 return children.size() < 1;
566 }
567
568 // ** ********************** QUESTIONS AND STATEMENTS ************************/
569
570 /**
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.
577 *
578 * @return the statement
579 * @see #getQuestion()
580 */
581 public KeyStatement getStatement() {
582 return statement;
583 }
584
585 /**
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.
592 *
593 * @param text
594 * the statement text
595 * @param language
596 * the language of the statement text
597 * @return the old statement text in the given language as LanguageString
598 */
599 public LanguageString addStatementText(String text, Language language) {
600 if (language == null) {
601 language = Language.DEFAULT();
602 }
603 if (this.statement == null) {
604 setStatement(KeyStatement.NewInstance());
605 }
606 return getStatement().putLabel(language, text);
607 }
608
609 /**
610 * @param statement
611 * @see #getStatement()
612 */
613 public void setStatement(KeyStatement statement) {
614 this.statement = statement;
615 }
616
617 /**
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.
621 *
622 * @return the question
623 * @see #getStatement()
624 */
625 public KeyStatement getQuestion() {
626 return question;
627 }
628
629 /**
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.
636 *
637 * @param text
638 * @param language
639 * @return
640 */
641 public LanguageString addQuestionText(String text, Language language) {
642 if (language == null) {
643 language = Language.DEFAULT();
644 }
645 if (this.question == null) {
646 setQuestion(KeyStatement.NewInstance());
647 }
648 return getQuestion().putLabel(language, text);
649 }
650
651 /**
652 * @param question
653 * @see #getQuestion()
654 */
655 public void setQuestion(KeyStatement question) {
656 this.question = question;
657 }
658
659 // **************** modifying text ***************************************
660
661 /**
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
666 * <p>
667 * All {@link LanguageString language strings} contained in the
668 * multilanguage texts should all have the same meaning.<BR>
669 */
670 public Map<Language, LanguageString> getModifyingText() {
671 return this.modifyingText;
672 }
673
674 /**
675 * See {@link #getModifyingText}
676 *
677 * @param description
678 * the language string describing the validity in a particular
679 * language
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}
685 * method instead
686 */
687 @Deprecated
688 public LanguageString addModifyingText(LanguageString description) {
689 return this.putModifyingText(description);
690 }
691
692 /**
693 * See {@link #getModifyingText}
694 *
695 * @param description
696 * the language string describing the validity in a particular
697 * language
698 * @see #getModifyingText()
699 * @see #putModifyingText(Language, String)
700 */
701 public LanguageString putModifyingText(LanguageString description) {
702 return this.modifyingText.put(description.getLanguage(), description);
703 }
704
705 /**
706 * See {@link #getModifyingText}
707 *
708 * @param text
709 * the string describing the validity in a particular language
710 * @param 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}
717 * method instead
718 */
719 @Deprecated
720 public LanguageString addModifyingText(String text, Language language) {
721 return this.putModifyingText(language, text);
722 }
723
724 /**
725 * See {@link #getModifyingText}
726 *
727 * @param text
728 * the string describing the validity in a particular language
729 * @param language
730 * the language in which the text string is formulated
731 * @see #getModifyingText()
732 * @see #putModifyingText(LanguageString)
733 */
734 public LanguageString putModifyingText(Language language, String text) {
735 return this.modifyingText.put(language,
736 LanguageString.NewInstance(text, language));
737 }
738
739 /**
740 * See {@link #getModifyingText}
741 *
742 * @param language
743 * the language in which the language string to be removed has
744 * been formulated
745 * @see #getModifyingText()
746 */
747 public LanguageString removeModifyingText(Language language) {
748 return this.modifyingText.remove(language);
749 }
750
751 /**
752 * Returns the taxon this node links to. This is usually the case when this
753 * node is a leaf.
754 *
755 * @return
756 * @see #setTaxon(Taxon)
757 * @see #getSubkey()
758 * @see #getChildren()
759 * @see #getOtherNode()
760 */
761 public Taxon getTaxon() {
762 return taxon;
763 }
764
765 /**
766 * Sets the taxon this node links to. <BR>
767 * If a tax
768 *
769 * @param taxon
770 * @see #getTaxon()
771 */
772 public void setTaxon(Taxon taxon) {
773 this.taxon = taxon;
774 }
775
776 /**
777 * @return
778 * @see #setSubkey(PolytomousKey)
779 * @see #getTaxon()
780 * @see #getChildren()
781 * @see #getOtherNode()
782 */
783 public PolytomousKey getSubkey() {
784 return subkey;
785 }
786
787 /**
788 * @param subkey
789 * @see #getSubkey()
790 */
791 public void setSubkey(PolytomousKey subkey) {
792 this.subkey = subkey;
793 }
794
795 /**
796 * @return
797 * @see #setOtherNode(PolytomousKeyNode)
798 * @see #getTaxon()
799 * @see #getChildren()
800 * @see #getSubkey()
801 */
802 public PolytomousKeyNode getOtherNode() {
803 return otherNode;
804 }
805
806 /**
807 * @param otherNode
808 * @see #getOtherNode()
809 */
810 public void setOtherNode(PolytomousKeyNode otherNode) {
811 this.otherNode = otherNode;
812 }
813
814 // TODO
815 public void setFeature(Feature feature) {
816 this.feature = feature;
817 }
818
819 public Feature getFeature() {
820 return feature;
821 }
822
823 // *********************** CLONE
824 // ********************************************************/
825
826 /**
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.
832 *
833 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
834 * @see java.lang.Object#clone()
835 */
836 @Override
837 public Object clone() {
838 PolytomousKeyNode result;
839 try {
840 result = (PolytomousKeyNode) super.clone();
841 result.children = new ArrayList<PolytomousKeyNode>();
842
843 result.modifyingText = new HashMap<Language, LanguageString>();
844 for (Entry<Language, LanguageString> entry : this.modifyingText
845 .entrySet()) {
846 result.putModifyingText(entry.getValue());
847 }
848
849 return result;
850 } catch (CloneNotSupportedException e) {
851 logger.warn("Object does not implement cloneable");
852 e.printStackTrace();
853 return null;
854 }
855 }
856
857 }