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