2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.model
.description
;
12 import java
.util
.ArrayList
;
13 import java
.util
.HashMap
;
14 import java
.util
.List
;
17 import javax
.persistence
.Entity
;
18 import javax
.persistence
.FetchType
;
19 import javax
.persistence
.JoinColumn
;
20 import javax
.persistence
.ManyToOne
;
21 import javax
.persistence
.OneToMany
;
22 import javax
.persistence
.OrderBy
;
23 import javax
.persistence
.Transient
;
24 import javax
.xml
.bind
.annotation
.XmlAccessType
;
25 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
26 import javax
.xml
.bind
.annotation
.XmlElement
;
27 import javax
.xml
.bind
.annotation
.XmlElementWrapper
;
28 import javax
.xml
.bind
.annotation
.XmlIDREF
;
29 import javax
.xml
.bind
.annotation
.XmlRootElement
;
30 import javax
.xml
.bind
.annotation
.XmlSchemaType
;
31 import javax
.xml
.bind
.annotation
.XmlType
;
32 import javax
.xml
.bind
.annotation
.adapters
.XmlJavaTypeAdapter
;
34 import org
.apache
.log4j
.Logger
;
35 import org
.hibernate
.annotations
.Cascade
;
36 import org
.hibernate
.annotations
.CascadeType
;
37 import org
.hibernate
.annotations
.IndexColumn
;
38 import org
.hibernate
.envers
.Audited
;
40 import eu
.etaxonomy
.cdm
.jaxb
.MultilanguageTextAdapter
;
41 import eu
.etaxonomy
.cdm
.model
.common
.IMultiLanguageTextHolder
;
42 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
43 import eu
.etaxonomy
.cdm
.model
.common
.LanguageString
;
44 import eu
.etaxonomy
.cdm
.model
.common
.MultilanguageText
;
45 import eu
.etaxonomy
.cdm
.model
.common
.TermVocabulary
;
46 import eu
.etaxonomy
.cdm
.model
.common
.VersionableEntity
;
47 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
50 * The class represents a node within a {@link PolytomousKey polytomous key} structure.
51 * A polytomous key node can be referenced from multiple other nodes. Therefore a node does
52 * not have a single parent. Nevertheless it always belongs to a main key though it may be
53 * referenced also by other key nodes.
56 * @created 13-Oct-2010
59 @SuppressWarnings("serial")
60 @XmlAccessorType(XmlAccessType
.FIELD
)
61 @XmlType(name
= "PolytomousKeyNode", propOrder
= {
74 @XmlRootElement(name
= "FeatureNode")
77 public class PolytomousKeyNode
extends VersionableEntity
implements IMultiLanguageTextHolder
{
78 @SuppressWarnings("unused")
79 private static final Logger logger
= Logger
.getLogger(PolytomousKeyNode
.class);
81 //This is the main key a node belongs to. Although other keys may also reference
82 //<code>this</code> node, a node usually belongs to a given key.
83 @XmlElement(name
= "PolytomousKey")
85 @XmlSchemaType(name
= "IDREF")
86 @ManyToOne(fetch
= FetchType
.LAZY
)
87 private PolytomousKey key
;
89 @XmlElementWrapper(name
= "Children")
90 @XmlElement(name
= "Child")
91 // @OrderColumn("sortIndex") //JPA 2.0 same as @IndexColumn
92 // @IndexColumn does not work because not every FeatureNode has a parent. But only NotNull will solve the problem (otherwise
93 // we will need a join table
94 // http://stackoverflow.com/questions/2956171/jpa-2-0-ordercolumn-annotation-in-hibernate-3-5
95 // http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-collection-extratype-indexbidir
96 //see also https://forum.hibernate.org/viewtopic.php?p=2392563
97 //http://opensource.atlassian.com/projects/hibernate/browse/HHH-4390
98 // reading works, but writing doesn't
100 @IndexColumn(name
="sortIndex", base
= 0)
101 @JoinColumn(name
="parent_id")
102 @OrderBy("sortIndex")
103 @OneToMany(fetch
= FetchType
.LAZY
/*, mappedBy="parent"*/)
104 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
})
105 private List
<PolytomousKeyNode
> children
= new ArrayList
<PolytomousKeyNode
>();
107 //see comment on children @IndexColumn
108 private Integer sortIndex
;
110 @XmlElement(name
= "Statement")
112 @XmlSchemaType(name
= "IDREF")
113 @ManyToOne(fetch
= FetchType
.LAZY
)
114 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.DELETE_ORPHAN
})
115 private KeyStatement statement
;
117 @XmlElement(name
= "Question")
119 @XmlSchemaType(name
= "IDREF")
120 @ManyToOne(fetch
= FetchType
.LAZY
)
121 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.DELETE_ORPHAN
})
122 private KeyStatement question
;
124 @XmlElement(name
= "Feature")
126 @XmlSchemaType(name
= "IDREF")
127 @ManyToOne(fetch
= FetchType
.LAZY
)
128 private Feature feature
;
130 @XmlElement(name
= "Taxon")
132 @XmlSchemaType(name
= "IDREF")
133 @ManyToOne(fetch
= FetchType
.LAZY
)
134 @Cascade(CascadeType
.SAVE_UPDATE
)
137 //Refers to an entire key
138 //<code>this</code> node, a node usually belongs to a given key.
139 @XmlElement(name
= "SubKey")
141 @XmlSchemaType(name
= "IDREF")
142 @ManyToOne(fetch
= FetchType
.LAZY
)
143 @Cascade(CascadeType
.SAVE_UPDATE
)
144 private PolytomousKey subkey
;
146 //Refers to an other node within this key or an other key
147 @XmlElement(name
= "PolytomousKey")
149 @XmlSchemaType(name
= "IDREF")
150 @ManyToOne(fetch
= FetchType
.LAZY
)
151 private PolytomousKeyNode otherNode
;
153 private Integer nodeNumber
= 0;
156 //a modifying text may be a text like "an unusual form of", commenting the taxa
157 //TODO should be available for each taxon/result
158 @XmlElement(name
= "ModifyingText")
159 @XmlJavaTypeAdapter(MultilanguageTextAdapter
.class)
160 @OneToMany(fetch
= FetchType
.LAZY
)
161 // @JoinTable(name = "DescriptionElementBase_ModifyingText")
162 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
163 private Map
<Language
,LanguageString
> modifyingText
= new HashMap
<Language
,LanguageString
>();
168 * Class constructor: creates a new empty feature node instance.
170 protected PolytomousKeyNode() {
175 * Creates a new empty polytomous key node instance.
177 public static PolytomousKeyNode
NewInstance(){
178 return new PolytomousKeyNode();
182 * Creates a new empty polytomous key node instance and sets the node number to 0.
184 public static PolytomousKeyNode
NewRootInstance(){
185 PolytomousKeyNode result
= new PolytomousKeyNode();
186 result
.setNodeNumber(0);
191 * Creates a new polytomous key node instance.
194 public static PolytomousKeyNode
NewInstance(String statement
){
195 PolytomousKeyNode result
= new PolytomousKeyNode();
196 result
.setStatement(KeyStatement
.NewInstance(statement
));
201 * Creates a new polytomous key node instance.
204 public static PolytomousKeyNode
NewInstance(String statement
, String question
, Taxon taxon
, Feature feature
){
205 PolytomousKeyNode result
= new PolytomousKeyNode();
206 result
.setTaxon(taxon
);
207 result
.setStatement(KeyStatement
.NewInstance(statement
));
208 result
.setQuestion(KeyStatement
.NewInstance(question
));
209 result
.setFeature(feature
);
214 //** ********************** CHILDREN ******************************/
220 public PolytomousKey
getKey() {
227 public void setKey(PolytomousKey key
) {
233 * The node number is the number of the node within the key. This corresponds to the
234 * number for key choices in written keys.
236 public Integer
getNodeNumber() {
241 * Is computed automatically and therefore should not be set by the user.
243 private void setNodeNumber(Integer nodeNumber
) {
244 this.nodeNumber
= nodeNumber
;
250 * Returns the (ordered) list of feature nodes which are children nodes of
251 * <i>this</i> feature node.
253 public List
<PolytomousKeyNode
> getChildren() {
258 * Adds the given polytomous key node at the end of the list of children of
259 * <i>this</i> polytomous key node.
261 * @param child the feature node to be added
262 * @see #getChildren()
263 * @see #setChildren(List)
264 * @see #addChild(PolytomousKeyNode, int)
265 * @see #removeChild(PolytomousKeyNode)
266 * @see #removeChild(int)
268 public void addChild(PolytomousKeyNode child
){
269 addChild(child
, children
.size());
272 * Inserts the given child node in the list of children of <i>this</i> polytomous key node
273 * at the given (index + 1) position. If the given index is out of bounds
274 * an exception will be thrown.<BR>
276 * @param child the polytomous key node to be added
277 * @param index the integer indicating the position at which the child
279 * @see #getChildren()
280 * @see #setChildren(List)
281 * @see #addChild(PolytomousKeyNode)
282 * @see #removeChild(PolytomousKeyNode)
283 * @see #removeChild(int)
285 public void addChild(PolytomousKeyNode child
, int index
){
286 if (index
< 0 || index
> children
.size() + 1){
287 throw new IndexOutOfBoundsException("Wrong index: " + index
);
290 children
.add(index
, child
);
291 child
.setKey(this.getKey());
292 //TODO workaround (see sortIndex doc)
293 for(int i
= 0; i
< children
.size(); i
++){
294 children
.get(i
).sortIndex
= i
;
296 child
.sortIndex
= index
;
300 private void updateNodeNumber() {
302 PolytomousKeyNode root
= getKey().getRoot();
303 root
.setNodeNumber(nodeNumber
++);
304 nodeNumber
= updateChildNodeNumbers(nodeNumber
, root
);
308 private int updateChildNodeNumbers(int nodeNumber
, PolytomousKeyNode parent
) {
309 if (parent
.isLeaf()){
310 parent
.setNodeNumber(null);
312 for (PolytomousKeyNode child
: parent
.getChildren()){
313 child
.setNodeNumber(nodeNumber
++);
314 nodeNumber
= updateChildNodeNumbers(nodeNumber
, child
);
321 * Removes the given polytomous key node from the list of {@link #getChildren() children}
322 * of <i>this</i> polytomous key node.
324 * @param child the feature node which should be removed
325 * @see #getChildren()
326 * @see #addChild(PolytomousKeyNode, int)
327 * @see #addChild(PolytomousKeyNode)
328 * @see #removeChild(int)
330 public void removeChild(PolytomousKeyNode child
){
331 int index
= children
.indexOf(child
);
337 * Removes the feature node placed at the given (index + 1) position from
338 * the list of {@link #getChildren() children} of <i>this</i> feature node.
339 * If the given index is out of bounds no child will be removed.
341 * @param index the integer indicating the position of the feature node to
343 * @see #getChildren()
344 * @see #addChild(PolytomousKeyNode, int)
345 * @see #addChild(PolytomousKeyNode)
346 * @see #removeChild(PolytomousKeyNode)
348 public void removeChild(int index
){
349 children
.remove(index
);
351 PolytomousKeyNode child
= children
.get(index
);
353 children
.remove(index
);
354 // child.setParent(null);
355 //TODO workaround (see sortIndex doc)
356 for(int i
= 0; i
< children
.size(); i
++){
357 PolytomousKeyNode childAt
= children
.get(i
);
358 childAt
.sortIndex
= i
;
360 child
.sortIndex
= null;
366 * Returns the feature node placed at the given (childIndex + 1) position
367 * within the list of {@link #getChildren() children} of <i>this</i> feature node.
368 * If the given index is out of bounds no child will be returned.
370 * @param childIndex the integer indicating the position of the feature node
371 * @see #getChildren()
372 * @see #addChild(PolytomousKeyNode, int)
373 * @see #removeChild(int)
375 public PolytomousKeyNode
getChildAt(int childIndex
) {
376 return children
.get(childIndex
);
380 * Returns the number of children nodes of <i>this</i> feature node.
382 * @see #getChildren()
385 public int childCount() {
386 return children
.size();
390 * Returns the integer indicating the position of the given feature node
391 * within the list of {@link #getChildren() children} of <i>this</i> feature node.
392 * If the list does not contain this node then -1 will be returned.
394 * @param node the feature node the position of which is being searched
395 * @see #addChild(PolytomousKeyNode, int)
396 * @see #removeChild(int)
398 public int getIndex(PolytomousKeyNode node
) {
399 if (! children
.contains(node
)){
402 return children
.indexOf(node
);
407 * Returns the boolean value indicating if <i>this</i> feature node has
408 * children (false) or not (true). A node without children is at the
409 * bottommost level of a tree and is called a leaf.
411 * @see #getChildren()
412 * @see #getChildCount()
415 public boolean isLeaf() {
416 return children
.size() < 1;
420 //** ********************** QUESTIONS AND STATEMENTS ******************************/
423 * Returns the statement for <code>this</code> PolytomousKeyNode. If the user
424 * agrees with the statement, the node will be followed.
425 * @return the statement
426 * @see #getQuestion()
428 public KeyStatement
getStatement() {
433 * This is a convenience method to set the statement text for this node
434 * in the given language. <BR>
435 * If no statement exists yet a new statement is created. <BR>
436 * If a statement text in the given language exists already it is overwritten
437 * and the old text is returned.
438 * If language is <code>null</code> the default language is used instead.
440 * @param text the statement text
441 * @param language the language of the statement text
442 * @return the old statement text in the given language as LanguageString
444 public LanguageString
addStatementText(String text
, Language language
){
445 if (language
== null){
446 language
= Language
.DEFAULT();
448 if (this.statement
== null){
449 setStatement(KeyStatement
.NewInstance());
451 return getStatement().putLabel(text
, language
);
456 * @see #getStatement()
458 public void setStatement(KeyStatement statement
) {
459 this.statement
= statement
;
463 * Returns the question for <code>this</code> PolytomousKeyNode. <BR>
464 * A question is answered by statements in leads below this tree node.
465 * Questions are optional and are usually empty in traditional keys.
466 * @return the statement
467 * @see #getStatement()
469 public KeyStatement
getQuestion() {
474 * This is a convenience method to sets the question text for this node
475 * in the given language. <BR>
476 * If no question exists yet a new question is created. <BR>
477 * If a question text in the given language exists already it is overwritten
478 * and the old text is returned.
479 * If language is <code>null</code> the default language is used instead.
485 public LanguageString
addQuestionText(String text
, Language language
){
486 if (language
== null){
487 language
= Language
.DEFAULT();
489 if (this.question
== null){
490 setQuestion(KeyStatement
.NewInstance());
492 return getQuestion().putLabel(text
, language
);
497 * @see #getQuestion()
499 public void setQuestion(KeyStatement question
) {
500 this.question
= question
;
504 //**************** modifying text ***************************************
507 * Returns the {@link MultilanguageText multilanguage text} used to qualify the validity
508 * of <i>this</i> description element. The different {@link LanguageString language strings}
509 * contained in the multilanguage text should all have the same meaning.<BR>
510 * A multilanguage text does not belong to a controlled {@link TermVocabulary term vocabulary}
511 * as a {@link Modifier modifier} does.
513 * NOTE: the actual content of <i>this</i> description element is NOT
514 * stored in the modifying text. This is only metainformation
515 * (like "Some experts express doubt about this assertion").
517 public Map
<Language
,LanguageString
> getModifyingText(){
518 return this.modifyingText
;
524 * Adds a translated {@link LanguageString text in a particular language}
525 * to the {@link MultilanguageText multilanguage text} used to qualify the validity
526 * of <i>this</i> description element.
528 * @param description the language string describing the validity
529 * in a particular language
530 * @see #getModifyingText()
531 * @see #addModifyingText(String, Language)
533 public LanguageString
addModifyingText(LanguageString description
){
534 return this.modifyingText
.put(description
.getLanguage(),description
);
537 * Creates a {@link LanguageString language string} based on the given text string
538 * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
539 * used to qualify the validity of <i>this</i> description element.
541 * @param text the string describing the validity
542 * in a particular language
543 * @param language the language in which the text string is formulated
544 * @see #getModifyingText()
545 * @see #addModifyingText(LanguageString)
547 public LanguageString
addModifyingText(String text
, Language language
){
548 return this.modifyingText
.put(language
, LanguageString
.NewInstance(text
, language
));
551 * Removes from the {@link MultilanguageText multilanguage text} used to qualify the validity
552 * of <i>this</i> description element the one {@link LanguageString language string}
553 * with the given {@link Language language}.
555 * @param language the language in which the language string to be removed
556 * has been formulated
557 * @see #getModifyingText()
559 public LanguageString
removeModifyingText(Language language
){
560 return this.modifyingText
.remove(language
);
565 * Returns the taxon this node links to. This is usually the case when this node is a leaf.
568 * @see #setTaxon(Taxon)
570 * @see #getChildren()
571 * @see #getOtherNode()
573 public Taxon
getTaxon() {
578 * Sets the taxon this node links to. <BR>
583 public void setTaxon(Taxon taxon
) {
589 * @see #setSubkey(PolytomousKey)
591 * @see #getChildren()
592 * @see #getOtherNode()
594 public PolytomousKey
getSubkey() {
602 public void setSubkey(PolytomousKey subkey
) {
603 this.subkey
= subkey
;
608 * @see #setOtherNode(PolytomousKeyNode)
610 * @see #getChildren()
613 public PolytomousKeyNode
getOtherNode() {
619 * @see #getOtherNode()
621 public void setOtherNode(PolytomousKeyNode otherNode
) {
622 this.otherNode
= otherNode
;
626 public void setFeature(Feature feature
) {
627 this.feature
= feature
;
630 public Feature
getFeature() {