upper case for JoinTable WorkingSet_DescriptionBase
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / description / PolytomousKeyNode.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
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.
8 */
9
10 package eu.etaxonomy.cdm.model.description;
11
12 import java.util.ArrayList;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.Map;
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.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;
33
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;
39
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;
48
49 /**
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.
54 *
55 * @author a.mueller
56 * @created 13-Oct-2010
57 *
58 */
59 @SuppressWarnings("serial")
60 @XmlAccessorType(XmlAccessType.FIELD)
61 @XmlType(name = "PolytomousKeyNode", propOrder = {
62 "key",
63 "children",
64 "sortIndex",
65 "nodeNumber",
66 "statement",
67 "question",
68 "feature",
69 "taxon",
70 "subkey",
71 "otherNode",
72 "modifyingText"
73 })
74 @XmlRootElement(name = "FeatureNode")
75 @Entity
76 @Audited
77 public class PolytomousKeyNode extends VersionableEntity implements IMultiLanguageTextHolder {
78 @SuppressWarnings("unused")
79 private static final Logger logger = Logger.getLogger(PolytomousKeyNode.class);
80
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")
84 @XmlIDREF
85 @XmlSchemaType(name = "IDREF")
86 @ManyToOne(fetch = FetchType.LAZY)
87 private PolytomousKey key;
88
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
99 //
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>();
106
107 //see comment on children @IndexColumn
108 private Integer sortIndex;
109
110 @XmlElement(name = "Statement")
111 @XmlIDREF
112 @XmlSchemaType(name = "IDREF")
113 @ManyToOne(fetch = FetchType.LAZY)
114 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE_ORPHAN})
115 private KeyStatement statement;
116
117 @XmlElement(name = "Question")
118 @XmlIDREF
119 @XmlSchemaType(name = "IDREF")
120 @ManyToOne(fetch = FetchType.LAZY)
121 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE_ORPHAN})
122 private KeyStatement question;
123
124 @XmlElement(name = "Feature")
125 @XmlIDREF
126 @XmlSchemaType(name = "IDREF")
127 @ManyToOne(fetch = FetchType.LAZY)
128 private Feature feature;
129
130 @XmlElement(name = "Taxon")
131 @XmlIDREF
132 @XmlSchemaType(name = "IDREF")
133 @ManyToOne(fetch = FetchType.LAZY)
134 @Cascade(CascadeType.SAVE_UPDATE)
135 private Taxon taxon;
136
137 //Refers to an entire key
138 //<code>this</code> node, a node usually belongs to a given key.
139 @XmlElement(name = "SubKey")
140 @XmlIDREF
141 @XmlSchemaType(name = "IDREF")
142 @ManyToOne(fetch = FetchType.LAZY)
143 @Cascade(CascadeType.SAVE_UPDATE)
144 private PolytomousKey subkey;
145
146 //Refers to an other node within this key or an other key
147 @XmlElement(name = "PolytomousKey")
148 @XmlIDREF
149 @XmlSchemaType(name = "IDREF")
150 @ManyToOne(fetch = FetchType.LAZY)
151 private PolytomousKeyNode otherNode;
152
153 private Integer nodeNumber = 0;
154
155
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>();
164
165
166
167 /**
168 * Class constructor: creates a new empty feature node instance.
169 */
170 protected PolytomousKeyNode() {
171 super();
172 }
173
174 /**
175 * Creates a new empty polytomous key node instance.
176 */
177 public static PolytomousKeyNode NewInstance(){
178 return new PolytomousKeyNode();
179 }
180
181 /**
182 * Creates a new empty polytomous key node instance and sets the node number to 0.
183 */
184 public static PolytomousKeyNode NewRootInstance(){
185 PolytomousKeyNode result = new PolytomousKeyNode();
186 result.setNodeNumber(0);
187 return result;
188 }
189
190 /**
191 * Creates a new polytomous key node instance.
192 *
193 */
194 public static PolytomousKeyNode NewInstance(String statement){
195 PolytomousKeyNode result = new PolytomousKeyNode();
196 result.setStatement(KeyStatement.NewInstance(statement));
197 return result;
198 }
199
200 /**
201 * Creates a new polytomous key node instance.
202 *
203 */
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);
210 return result;
211 }
212
213
214 //** ********************** CHILDREN ******************************/
215
216
217 /**
218 * @return
219 */
220 public PolytomousKey getKey() {
221 return key;
222 }
223
224 /**
225 * @param key
226 */
227 public void setKey(PolytomousKey key) {
228 this.key = key;
229 }
230
231
232 /**
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.
235 */
236 public Integer getNodeNumber() {
237 return nodeNumber;
238 }
239
240 /**
241 * Is computed automatically and therefore should not be set by the user.
242 */
243 private void setNodeNumber(Integer nodeNumber) {
244 this.nodeNumber = nodeNumber;
245 }
246
247
248
249 /**
250 * Returns the (ordered) list of feature nodes which are children nodes of
251 * <i>this</i> feature node.
252 */
253 public List<PolytomousKeyNode> getChildren() {
254 return children;
255 }
256
257 /**
258 * Adds the given polytomous key node at the end of the list of children of
259 * <i>this</i> polytomous key node.
260 *
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)
267 */
268 public void addChild(PolytomousKeyNode child){
269 addChild(child, children.size());
270 }
271 /**
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>
275 *
276 * @param child the polytomous key node to be added
277 * @param index the integer indicating the position at which the child
278 * should be added
279 * @see #getChildren()
280 * @see #setChildren(List)
281 * @see #addChild(PolytomousKeyNode)
282 * @see #removeChild(PolytomousKeyNode)
283 * @see #removeChild(int)
284 */
285 public void addChild(PolytomousKeyNode child, int index){
286 if (index < 0 || index > children.size() + 1){
287 throw new IndexOutOfBoundsException("Wrong index: " + index);
288 }
289
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;
295 }
296 child.sortIndex = index;
297 updateNodeNumber();
298
299 }
300 private void updateNodeNumber() {
301 int nodeNumber = 0;
302 PolytomousKeyNode root = getKey().getRoot();
303 root.setNodeNumber(nodeNumber++);
304 nodeNumber = updateChildNodeNumbers(nodeNumber, root);
305
306 }
307
308 private int updateChildNodeNumbers(int nodeNumber, PolytomousKeyNode parent) {
309 if (parent.isLeaf()){
310 parent.setNodeNumber(null);
311 }else{
312 for (PolytomousKeyNode child : parent.getChildren()){
313 child.setNodeNumber(nodeNumber++);
314 nodeNumber = updateChildNodeNumbers(nodeNumber, child);
315 }
316 }
317 return nodeNumber;
318 }
319
320 /**
321 * Removes the given polytomous key node from the list of {@link #getChildren() children}
322 * of <i>this</i> polytomous key node.
323 *
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)
329 */
330 public void removeChild(PolytomousKeyNode child){
331 int index = children.indexOf(child);
332 if (index >= 0){
333 removeChild(index);
334 }
335 }
336 /**
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.
340 *
341 * @param index the integer indicating the position of the feature node to
342 * be removed
343 * @see #getChildren()
344 * @see #addChild(PolytomousKeyNode, int)
345 * @see #addChild(PolytomousKeyNode)
346 * @see #removeChild(PolytomousKeyNode)
347 */
348 public void removeChild(int index){
349 children.remove(index);
350
351 PolytomousKeyNode child = children.get(index);
352 if (child != null){
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;
359 }
360 child.sortIndex = null;
361 }
362 updateNodeNumber();
363 }
364
365 /**
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.
369 *
370 * @param childIndex the integer indicating the position of the feature node
371 * @see #getChildren()
372 * @see #addChild(PolytomousKeyNode, int)
373 * @see #removeChild(int)
374 */
375 public PolytomousKeyNode getChildAt(int childIndex) {
376 return children.get(childIndex);
377 }
378
379 /**
380 * Returns the number of children nodes of <i>this</i> feature node.
381 *
382 * @see #getChildren()
383 */
384 @Transient
385 public int childCount() {
386 return children.size();
387 }
388
389 /**
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.
393 *
394 * @param node the feature node the position of which is being searched
395 * @see #addChild(PolytomousKeyNode, int)
396 * @see #removeChild(int)
397 */
398 public int getIndex(PolytomousKeyNode node) {
399 if (! children.contains(node)){
400 return -1;
401 }else{
402 return children.indexOf(node);
403 }
404 }
405
406 /**
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.
410 *
411 * @see #getChildren()
412 * @see #getChildCount()
413 */
414 @Transient
415 public boolean isLeaf() {
416 return children.size() < 1;
417 }
418
419
420 //** ********************** QUESTIONS AND STATEMENTS ******************************/
421
422 /**
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()
427 */
428 public KeyStatement getStatement() {
429 return statement;
430 }
431
432 /**
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.
439 *
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
443 */
444 public LanguageString addStatementText(String text, Language language){
445 if (language == null){
446 language = Language.DEFAULT();
447 }
448 if (this.statement == null){
449 setStatement(KeyStatement.NewInstance());
450 }
451 return getStatement().putLabel(text, language);
452 }
453
454 /**
455 * @param statement
456 * @see #getStatement()
457 */
458 public void setStatement(KeyStatement statement) {
459 this.statement = statement;
460 }
461
462 /**
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()
468 */
469 public KeyStatement getQuestion() {
470 return question;
471 }
472
473 /**
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.
480 *
481 * @param text
482 * @param language
483 * @return
484 */
485 public LanguageString addQuestionText(String text, Language language){
486 if (language == null){
487 language = Language.DEFAULT();
488 }
489 if (this.question == null){
490 setQuestion(KeyStatement.NewInstance());
491 }
492 return getQuestion().putLabel(text, language);
493 }
494
495 /**
496 * @param question
497 * @see #getQuestion()
498 */
499 public void setQuestion(KeyStatement question) {
500 this.question = question;
501 }
502
503
504 //**************** modifying text ***************************************
505
506 /**
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.
512 * <P>
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").
516 */
517 public Map<Language,LanguageString> getModifyingText(){
518 return this.modifyingText;
519 }
520
521
522
523 /**
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.
527 *
528 * @param description the language string describing the validity
529 * in a particular language
530 * @see #getModifyingText()
531 * @see #addModifyingText(String, Language)
532 */
533 public LanguageString addModifyingText(LanguageString description){
534 return this.modifyingText.put(description.getLanguage(),description);
535 }
536 /**
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.
540 *
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)
546 */
547 public LanguageString addModifyingText(String text, Language language){
548 return this.modifyingText.put(language, LanguageString.NewInstance(text, language));
549 }
550 /**
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}.
554 *
555 * @param language the language in which the language string to be removed
556 * has been formulated
557 * @see #getModifyingText()
558 */
559 public LanguageString removeModifyingText(Language language){
560 return this.modifyingText.remove(language);
561 }
562
563
564 /**
565 * Returns the taxon this node links to. This is usually the case when this node is a leaf.
566 *
567 * @return
568 * @see #setTaxon(Taxon)
569 * @see #getSubkey()
570 * @see #getChildren()
571 * @see #getOtherNode()
572 */
573 public Taxon getTaxon() {
574 return taxon;
575 }
576
577 /**
578 * Sets the taxon this node links to. <BR>
579 * If a tax
580 * @param taxon
581 * @see #getTaxon()
582 */
583 public void setTaxon(Taxon taxon) {
584 this.taxon = taxon;
585 }
586
587 /**
588 * @return
589 * @see #setSubkey(PolytomousKey)
590 * @see #getTaxon()
591 * @see #getChildren()
592 * @see #getOtherNode()
593 */
594 public PolytomousKey getSubkey() {
595 return subkey;
596 }
597
598 /**
599 * @param subkey
600 * @see #getSubkey()
601 */
602 public void setSubkey(PolytomousKey subkey) {
603 this.subkey = subkey;
604 }
605
606 /**
607 * @return
608 * @see #setOtherNode(PolytomousKeyNode)
609 * @see #getTaxon()
610 * @see #getChildren()
611 * @see #getSubkey()
612 */
613 public PolytomousKeyNode getOtherNode() {
614 return otherNode;
615 }
616
617 /**
618 * @param otherNode
619 * @see #getOtherNode()
620 */
621 public void setOtherNode(PolytomousKeyNode otherNode) {
622 this.otherNode = otherNode;
623 }
624
625 // TODO
626 public void setFeature(Feature feature) {
627 this.feature = feature;
628 }
629
630 public Feature getFeature() {
631 return feature;
632 }
633
634
635
636 }