ref #6794 add TermRelation and rename FeatureNode -> TermTreeNode
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / term / TermTreeNode.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.term;
11
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Set;
17
18 import javax.persistence.Column;
19 import javax.persistence.Entity;
20 import javax.persistence.FetchType;
21 import javax.persistence.JoinColumn;
22 import javax.persistence.JoinTable;
23 import javax.persistence.ManyToMany;
24 import javax.persistence.ManyToOne;
25 import javax.persistence.OneToMany;
26 import javax.persistence.OrderBy;
27 import javax.persistence.OrderColumn;
28 import javax.persistence.Transient;
29 import javax.xml.bind.annotation.XmlAccessType;
30 import javax.xml.bind.annotation.XmlAccessorType;
31 import javax.xml.bind.annotation.XmlElement;
32 import javax.xml.bind.annotation.XmlElementWrapper;
33 import javax.xml.bind.annotation.XmlIDREF;
34 import javax.xml.bind.annotation.XmlRootElement;
35 import javax.xml.bind.annotation.XmlSchemaType;
36 import javax.xml.bind.annotation.XmlType;
37
38 import org.apache.log4j.Logger;
39 import org.hibernate.annotations.Cascade;
40 import org.hibernate.annotations.CascadeType;
41 import org.hibernate.envers.Audited;
42
43 import eu.etaxonomy.cdm.hibernate.HHH_9751_Util;
44 import eu.etaxonomy.cdm.model.common.ITreeNode;
45 import eu.etaxonomy.cdm.model.description.CategoricalData;
46 import eu.etaxonomy.cdm.model.description.Feature;
47 import eu.etaxonomy.cdm.model.description.State;
48
49 /**
50 * The class for tree nodes within a {@link TermTree feature tree} structure.
51 * Feature nodes are the elementary components of such a tree since they might
52 * be related to other nodes as a parent or as a child. A feature node belongs
53 * at most to one feature tree. It cannot have more than one parent node but
54 * may have several child nodes. Parent/child relations are bidirectional:
55 * a node N1 is the parent of a node N2 if and only if the node N2 is a child of
56 * the node N1.
57 *
58 * @author m.doering
59 * @since 08-Nov-2007 13:06:16
60 */
61 @SuppressWarnings("serial")
62 @XmlAccessorType(XmlAccessType.FIELD)
63 @XmlType(name = "TermTreeNode", propOrder = {
64 "parent",
65 "treeIndex",
66 "sortIndex",
67 "children",
68 "onlyApplicableIf",
69 "inapplicableIf"
70 })
71 @XmlRootElement(name = "TermTreeNode")
72 @Entity
73 @Audited
74 public class TermTreeNode <T extends DefinedTermBase>
75 extends TermRelationBase<T, TermTreeNode<T>, TermTree>
76 implements ITreeNode<TermTreeNode<T>> {
77
78 private static final Logger logger = Logger.getLogger(TermTreeNode.class);
79
80 @XmlElement(name = "Parent")
81 @XmlIDREF
82 @XmlSchemaType(name = "IDREF")
83 @ManyToOne(fetch = FetchType.LAZY, targetEntity=TermTreeNode.class)
84 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
85 @JoinColumn(name="parent_id")
86 private TermTreeNode<T> parent;
87
88
89 @XmlElement(name = "treeIndex")
90 @Column(length=255)
91 private String treeIndex;
92
93 @XmlElementWrapper(name = "Children")
94 @XmlElement(name = "Child")
95 //see https://dev.e-taxonomy.eu/trac/ticket/3722
96 @OrderColumn(name="sortIndex")
97 @OrderBy("sortIndex")
98 @OneToMany(fetch = FetchType.LAZY, mappedBy="parent", targetEntity=TermTreeNode.class)
99 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
100 private List<TermTreeNode<T>> children = new ArrayList<>();
101
102 //see https://dev.e-taxonomy.eu/trac/ticket/3722
103 private Integer sortIndex;
104
105 @XmlElementWrapper(name = "OnlyApplicableIf")
106 @XmlElement(name = "OnlyApplicableIf")
107 @XmlIDREF
108 @XmlSchemaType(name="IDREF")
109 @ManyToMany(fetch = FetchType.LAZY)
110 // @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE}) remove cascade #5755
111 @JoinTable(name="TermTreeNode_DefinedTermBase_OnlyApplicable")
112 private final Set<State> onlyApplicableIf = new HashSet<>();
113
114 @XmlElementWrapper(name = "InapplicableIf")
115 @XmlElement(name = "InapplicableIf")
116 @XmlIDREF
117 @XmlSchemaType(name="IDREF")
118 @ManyToMany(fetch = FetchType.LAZY)
119 // @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE}) remove cascade #5755
120 @JoinTable(name="TermTreeNode_DefinedTermBase_InapplicableIf")
121 private final Set<State> inapplicableIf = new HashSet<>();
122
123 // ***************************** FACTORY *********************************/
124
125 //no factory methods should be provided as FeatureNodes should only
126 //be created as children of their parent node (#8257)
127
128 // ******************** CONSTRUCTOR ***************************************/
129
130 //TODO needed?
131 @Deprecated
132 protected TermTreeNode(){}
133
134 /**
135 * Class constructor: creates a new empty feature node instance.
136 */
137 protected TermTreeNode(TermType termType) {
138 super(termType);
139 }
140
141 //************************* PARENT ******************************/
142
143 /**
144 * Returns the feature node <i>this</i> feature node is a child of.
145 *
146 * @see #getChildNodes()
147 */
148 @Override
149 public TermTreeNode<T> getParent() {
150 return parent;
151 }
152 /**
153 * Assigns the given feature node as the parent of <i>this</i> feature node.
154 * Due to bidirectionality this method must also add <i>this</i> feature node
155 * to the list of children of the given parent.
156 *
157 * @param parent the feature node to be set as parent
158 * @see #getParent()
159 */
160 protected void setParent(TermTreeNode<T> parent) {
161 this.parent = parent;
162 }
163
164 //** ********************** CHILDREN ******************************/
165
166
167 /**
168 * @deprecated for internal use only.
169 */
170 //see #4278 , #4200
171 @Deprecated
172 protected void setSortIndex(Integer sortIndex) {
173 this.sortIndex = sortIndex;
174 }
175
176 /**
177 * Returns the (ordered) list of feature nodes which are children nodes of
178 * <i>this</i> feature node.
179 */
180 @Override
181 public List<TermTreeNode<T>> getChildNodes() {
182 return children;
183 }
184
185 /**
186 * Adds the given feature node at the end of the list of children of
187 * <i>this</i> feature node. Due to bidirectionality this method must also
188 * assign <i>this</i> feature node as the parent of the given child.
189 *
190 * @param child the feature node to be added
191 * @see #getChildNodes()
192 * @see #setChildren(List)
193 * @see #addChild(TermTreeNode, int)
194 * @see #removeChild(TermTreeNode)
195 * @see #removeChild(int)
196 */
197 public void addChild(TermTreeNode<T> child){
198 addChild(child, children.size());
199 }
200
201 /**
202 * Creates a new node without a term and adds it to the end of
203 * the list of children of
204 * <i>this</i> node. Due to bidirectionality this method must also
205 * assign <i>this</i> feature node as the parent of the new child.
206 *
207 * @return the newly created child node
208 * @see #getChildNodes()
209 * @see #setChildren(List)
210 * @see #removeChild(FeatureNode)
211 * @see #removeChild(int)
212 */
213 public TermTreeNode<T> addChild(){
214 return addChild((T)null, children.size());
215 }
216
217 /**
218 * Creates a new node for the given term and adds it to the end of
219 * the list of children of
220 * <i>this</i> node. Due to bidirectionality this method must also
221 * assign <i>this</i> feature node as the parent of the new child.
222 *
223 * @param term the term to be added
224 * @return the newly created child node
225 * @see #getChildNodes()
226 * @see #setChildren(List)
227 * @see #removeChild(FeatureNode)
228 * @see #removeChild(int)
229 */
230 public TermTreeNode<T> addChild(T term){
231 return addChild(term, children.size());
232 }
233
234 /**
235 * Creates a new node for the given term and adds it at the
236 * given (index + 1) position of the list of children of
237 * <i>this</i> node. Due to bidirectionality this method must also
238 * assign <i>this</i> feature node as the parent of the new child.
239 *
240 * @param term the term to be added
241 * @return the newly created child node
242 * @see #getChildNodes()
243 * @see #setChildren(List)
244 * @see #removeChild(FeatureNode)
245 * @see #removeChild(int)
246 */
247 public TermTreeNode<T> addChild(T term, int index){
248 TermTreeNode<T> child = new TermTreeNode<>(getTermType());
249 if(term!=null){
250 child.setTerm(term);
251 }
252 checkTermType(child);
253
254 List<TermTreeNode<T>> children = this.getChildNodes();
255 if (index < 0 || index > children.size() + 1){
256 throw new IndexOutOfBoundsException("Wrong index: " + index);
257 }
258 child.setParent(this);
259 child.setGraph(this.getGraph());
260 children.add(index, child);
261 //TODO workaround (see sortIndex doc)
262 for(int i = 0; i < children.size(); i++){
263 children.get(i).setSortIndex(i);
264 }
265 child.setSortIndex(index);
266 return child;
267 }
268
269 /**
270 * Inserts the given feature node in the list of children of <i>this</i> feature node
271 * at the given (index + 1) position. If the given index is out of bounds
272 * an exception will arise.<BR>
273 * Due to bidirectionality this method must also assign <i>this</i> feature node
274 * as the parent of the given child.
275 *
276 * @param child the feature node to be added
277 * @param index the integer indicating the position at which the child
278 * should be added
279 * @see #getChildNodes()
280 * @see #setChildren(List)
281 * @see #addChild(TermTreeNode)
282 * @see #removeChild(TermTreeNode)
283 * @see #removeChild(int)
284 */
285 public void addChild(TermTreeNode<T> child, int index){
286 checkTermType(child);
287 List<TermTreeNode<T>> children = this.getChildNodes();
288 if (index < 0 || index > children.size() + 1){
289 throw new IndexOutOfBoundsException("Wrong index: " + index);
290 }
291 if (child.getParent() != null){
292 child.getParent().removeChild(child);
293 }
294 child.setParent(this);
295 child.setGraph(this.getGraph());
296 children.add(index, child);
297 //TODO workaround (see sortIndex doc)
298 for(int i = 0; i < children.size(); i++){
299 children.get(i).setSortIndex(i);
300 }
301 child.setSortIndex(index);
302 }
303
304
305 /**
306 * Removes the given feature node from the list of {@link #getChildNodes() children}
307 * of <i>this</i> feature node.
308 *
309 * @param child the feature node which should be removed
310 * @see #getChildNodes()
311 * @see #addChild(TermTreeNode, int)
312 * @see #addChild(TermTreeNode)
313 * @see #removeChild(int)
314 */
315 public void removeChild(TermTreeNode<T> child){
316
317 int index = children.indexOf(child);
318 if (index >= 0){
319 removeChild(index);
320 }
321 }
322 /**
323 * Removes the feature node placed at the given (index + 1) position from
324 * the list of {@link #getChildNodes() children} of <i>this</i> feature node.
325 * If the given index is out of bounds no child will be removed.
326 *
327 * @param index the integer indicating the position of the feature node to
328 * be removed
329 * @see #getChildNodes()
330 * @see #addChild(TermTreeNode, int)
331 * @see #addChild(TermTreeNode)
332 * @see #removeChild(TermTreeNode)
333 */
334 public void removeChild(int index){
335 TermTreeNode<T> child = children.get(index);
336 if (child != null){
337 children.remove(index);
338 child.setParent(null);
339 child.setGraph(null);
340 //TODO workaround (see sortIndex doc)
341 for(int i = 0; i < children.size(); i++){
342 TermTreeNode<T> childAt = children.get(i);
343 childAt.setSortIndex(i);
344 }
345 child.setSortIndex(null);
346 }
347 }
348
349 /**
350 * Returns the feature node placed at the given (childIndex + 1) position
351 * within the list of {@link #getChildNodes() children} of <i>this</i> feature node.
352 * If the given index is out of bounds no child will be returned.
353 *
354 * @param childIndex the integer indicating the position of the feature node
355 * @see #getChildNodes()
356 * @see #addChild(TermTreeNode, int)
357 * @see #removeChild(int)
358 */
359 public TermTreeNode<T> getChildAt(int childIndex) {
360 return children.get(childIndex);
361 }
362
363 /**
364 * Returns the number of children nodes of <i>this</i> feature node.
365 *
366 * @see #getChildNodes()
367 */
368 @Transient
369 public int getChildCount() {
370 return children.size();
371 }
372
373 /**
374 * Returns the integer indicating the position of the given feature node
375 * within the list of {@link #getChildNodes() children} of <i>this</i> feature node.
376 * If the list does not contain this node then -1 will be returned.
377 *
378 * @param node the feature node the position of which is being searched
379 * @see #addChild(TermTreeNode, int)
380 * @see #removeChild(int)
381 */
382 public int getIndex(TermTreeNode<T> node) {
383 if (! children.contains(node)){
384 return -1;
385 }else{
386 return children.indexOf(node);
387 }
388 }
389
390 /**
391 * Returns the boolean value indicating if <i>this</i> feature node has
392 * children (false) or not (true). A node without children is at the
393 * bottommost level of a tree and is called a leaf.
394 *
395 * @see #getChildNodes()
396 * @see #getChildCount()
397 */
398 @Transient
399 public boolean isLeaf() {
400 return children.size() < 1;
401 }
402
403 /**
404 * Whether <code>this</code> node is the root node of the associated {@link TermTree feature tree}.
405 *
406 * @return <code>true</code> if <code>this</code> is the feature trees root node, <code>false</code> if not
407 */
408 @Transient
409 public boolean isRoot(){
410 if(getGraph() != null){
411 return this.equals(getGraph().getRoot());
412 }
413 return false;
414 }
415
416 /**
417 * Returns the set of {@link State states} implying rendering the
418 * concerned {@link Feature feature} applicable.
419 * If at least one state is present in this set, in a given description
420 * the {@link Feature feature} in <i>this</i> feature node is inapplicable
421 * unless any of the listed controlling states is present in the parent
422 * {@link Feature feature} description element {@link CategoricalData
423 * categoricalData}.
424 * This attribute is not equivalent to onlyApplicableIf in SDD as it is
425 * attached directly to the child feature rather than the parent, which
426 * allow having different applicable states for each child feature.
427 *
428 * @see #addApplicableState(State)
429 * @see #removeApplicableState(State)
430 */
431 public Set<State> getOnlyApplicableIf() {
432 return onlyApplicableIf;
433 }
434
435 /**
436 * Adds an existing {@link State applicable state} to the set of
437 * {@link #getOnlyApplicableIf() applicable states} described in
438 * <i>this</i> feature node.<BR>
439 *
440 * @param applicableState the applicable state to be added to <i>this</i> feature node
441 * @see #getApplicableState()
442 */
443 public void addApplicableState(State applicableState) {
444 this.onlyApplicableIf.add(applicableState);
445 }
446
447 /**
448 * Removes one element from the set of
449 * {@link #getOnlyApplicableIf() applicable states} described in
450 * <i>this</i> feature node.<BR>
451 *
452 * @param applicableState the applicable state which should be removed
453 * @see #getApplicableState()
454 * @see #addApplicableState(State)
455 */
456 public void removeApplicableState(State applicableState) {
457 this.onlyApplicableIf.remove(applicableState);
458 }
459
460 /**
461 * Returns the set of {@link State states} implying rendering the
462 * concerned {@link Feature feature} inapplicable.
463 * If at least one {@link State inapplicable state} is defined in the set,
464 * in a given description the {@link Feature feature} attribute of
465 * <i>this</i> feature node is inapplicable when any of the listed
466 * controlling states is present.
467 * This attribute is not equivalent to inapplicableIf in SDD as it is
468 * attached directly to the child feature rather than the parent, which
469 * allow having different inapplicability rules for each child feature.
470 *
471 * @see #addInapplicableState(State)
472 * @see #removeInapplicableState(State)
473 */
474 public Set<State> getInapplicableIf() {
475 return inapplicableIf;
476 }
477
478 /**
479 * Adds an existing {@link State inapplicable state} to the set of
480 * {@link #getInapplicableIf() inapplicable states} described in
481 * <i>this</i> feature node.<BR>
482 *
483 * @param inapplicableState the inapplicable state to be added to <i>this</i> feature node
484 * @see #getInapplicableState()
485 */
486 public void addInapplicableState(State inapplicableState) {
487 this.inapplicableIf.add(inapplicableState);
488 }
489
490 /**
491 * Removes one element from the set of
492 * {@link #getInapplicableIf() inapplicable states} described in
493 * <i>this</i> feature node.<BR>
494 *
495 * @param inapplicableState the inapplicable state which should be removed
496 * @see #getInapplicableState()
497 * @see #addInapplicableState(State)
498 */
499 public void removeInapplicableState(State inapplicableState) {
500 this.inapplicableIf.remove(inapplicableState);
501 }
502
503 // //** ********************** QUESTIONS ******************************/
504 //
505 // /**
506 // * Returns the {@link Representation question} formulation that
507 // * corresponds to <i>this</i> feature node and the corresponding
508 // * {@link Feature feature} in case it is part of a
509 // * {@link PolytomousKey polytomous key}.
510 // */
511 // public Set<Representation> getQuestions() {
512 // return this.questions;
513 // }
514 //
515 // public void addQuestion(Representation question) {
516 // this.questions.add(question);
517 // }
518 //
519 // public void removeQuestion(Representation question) {
520 // this.questions.remove(question);
521 // }
522 //
523 // @Transient
524 // public Representation getQuestion(Language lang) {
525 // for (Representation question : questions){
526 // Language reprLanguage = question.getLanguage();
527 // if (reprLanguage != null && reprLanguage.equals(lang)){
528 // return question;
529 // }
530 // }
531 // return null;
532 // }
533
534
535 /**
536 * Throws {@link IllegalArgumentException} if the given
537 * term has not the same term type as this term or if it is no sub or super type
538 * or if term type is null.
539 * @param term
540 */
541 private void checkTermTypeKindOf(IHasTermType descendant) {
542 IHasTermType.checkTermTypeEqualOrDescendant(this, descendant);
543 }
544
545 //*********************** Terms ************************************/
546
547 /**
548 * Returns all terms that are contained in this node or a child node
549 *
550 * @param terms
551 * @return
552 */
553 @Transient
554 public Set<T> getDistinctTermsRecursive(Set<T> terms){
555 T term = this.getTerm();
556 if(term != null){
557 terms.add(term);
558 }
559 for(TermTreeNode<T> childNode : this.getChildNodes()){
560 terms.addAll(childNode.getDistinctTermsRecursive(terms));
561 }
562 return terms;
563 }
564
565 /**
566 * @return a list of terms which includes first the
567 * term of this node and then recursively the list
568 * of all children and grandChildren
569 */
570 public Collection<? extends T> asTermListRecursive(List<T> terms) {
571 T term = this.getTerm();
572 if(term != null){
573 terms.add(term);
574 }
575 for(TermTreeNode<T> childNode : this.getChildNodes()){
576 terms.addAll(childNode.asTermListRecursive(terms));
577 }
578 return terms;
579 }
580
581
582 //*********************** CLONE ********************************************************/
583
584 /**
585 * Clones <i>this</i> {@link TermTreeNode}. This is a shortcut that enables to create
586 * a new instance that differs only slightly from <i>this</i> tree node by
587 * modifying only some of the attributes.
588 * The parent, the feature and the featureTree are the same as for the original feature node
589 * the children are removed
590 *
591 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
592 * @see java.lang.Object#clone()
593 */
594 @Override
595 public Object clone() {
596 TermTreeNode<T> result;
597 try {
598 result = (TermTreeNode<T>)super.clone();
599 result.children = new ArrayList<>();
600 return result;
601 }catch (CloneNotSupportedException e) {
602 logger.warn("Object does not implement cloneable");
603 e.printStackTrace();
604 return null;
605 }
606 }
607
608 public TermTreeNode<T> cloneDescendants(){
609 TermTreeNode<T> clone = (TermTreeNode<T>)this.clone();
610 TermTreeNode<T> childClone;
611
612 for(TermTreeNode<T> childNode : this.getChildNodes()){
613 childClone = (TermTreeNode<T>) childNode.clone();
614 for (TermTreeNode<T> childChild:childNode.getChildNodes()){
615 childClone.addChild(childChild.cloneDescendants());
616 }
617 clone.addChild(childClone);
618
619 }
620 return clone;
621 }
622
623 // ********************** TREE NODE METHODS ******************************/
624
625 @Override
626 public String treeIndex() {
627 return this.treeIndex;
628 } @Override
629 public String treeIndexLike() {
630 return treeIndex + "%";
631 }
632 @Override
633 public String treeIndexWc() {
634 return treeIndex + "*";
635 }
636
637 @Override
638 @Deprecated
639 public void setTreeIndex(String newTreeIndex) {
640 this.treeIndex = newTreeIndex;
641 }
642
643
644 @Override
645 @Deprecated
646 public int treeId() {
647 if (this.getGraph() == null){
648 return -1;
649 }else{
650 return this.getGraph().getId();
651 }
652 }
653
654 private void updateSortIndex(){
655 // TODO workaround (see sortIndex doc)
656 for (int i = 0; i < children.size(); i++) {
657 children.get(i).setSortIndex(i);
658 }
659 }
660
661 public void removeNullValueFromChildren(){
662 HHH_9751_Util.removeAllNull(children);
663 updateSortIndex();
664 }
665 <<<<<<< HEAD
666 =======
667
668 >>>>>>> ref #6794 add TermRelation and rename FeatureNode -> TermTreeNode
669 }