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