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.
9 package eu
.etaxonomy
.cdm
.model
.term
;
11 import java
.util
.ArrayList
;
12 import java
.util
.Collection
;
13 import java
.util
.HashSet
;
14 import java
.util
.List
;
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
;
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
;
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
;
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
59 * @since 08-Nov-2007 13:06:16
61 @SuppressWarnings("serial")
62 @XmlAccessorType(XmlAccessType
.FIELD
)
63 @XmlType(name
= "TermNode", propOrder
= {
70 @XmlRootElement(name
= "TermNode")
73 public class TermNode
<T
extends DefinedTermBase
>
74 extends TermRelationBase
<T
, TermNode
<T
>, TermTree
>
75 implements ITreeNode
<TermNode
<T
>> {
77 private static final Logger logger
= LogManager
.getLogger(TermNode
.class);
79 @XmlElement(name
= "Parent")
81 @XmlSchemaType(name
= "IDREF")
82 @ManyToOne(fetch
= FetchType
.LAZY
, targetEntity
=TermNode
.class)
83 @JoinColumn(name
="parent_id")
84 private TermNode
<T
> parent
;
86 @XmlElement(name
= "treeIndex")
88 private String treeIndex
;
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
<>();
98 @XmlElementWrapper(name
= "OnlyApplicableIf")
99 @XmlElement(name
= "OnlyApplicableIf")
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
<>();
107 @XmlElementWrapper(name
= "InapplicableIf")
108 @XmlElement(name
= "InapplicableIf")
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
<>();
116 // ***************************** FACTORY *********************************/
118 //no factory methods should be provided as TermNodes should only
119 //be created as children of their parent node (#8257)
121 // ******************** CONSTRUCTOR ***************************************/
123 //for hibernate use only, *packet* private required by bytebuddy
128 * Class constructor: creates a new empty feature node instance.
130 protected TermNode(TermType termType
) {
134 //************************* PARENT ******************************/
137 * Returns the feature node <i>this</i> feature node is a child of.
139 * @see #getChildNodes()
142 public TermNode
<T
> getParent() {
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.
150 * @param parent the feature node to be set as parent
153 protected void setParent(TermNode
<T
> parent
) {
154 this.parent
= parent
;
157 //** ********************** CHILDREN ******************************/
160 * Returns the (ordered) list of tree nodes which are children nodes of
164 public List
<TermNode
<T
>> getChildNodes() {
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.
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)
180 public TermNode
<T
> addChild(TermNode
<T
> child
){
181 return addChild(child
, children
.size());
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.
190 * @return the newly created child node
191 * @see #getChildNodes()
192 * @see #setChildren(List)
193 * @see #removeChild(TermNode)
194 * @see #removeChild(int)
196 public TermNode
<T
> addChild(){
197 return addChild((T
)null, children
.size());
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.
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)
213 public TermNode
<T
> addChild(T term
){
214 return addChild(term
, children
.size());
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.
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)
230 public TermNode
<T
> addChild(T term
, int index
){
231 TermNode
<T
> child
= new TermNode
<>(getTermType());
235 checkTermType(child
);
237 List
<TermNode
<T
>> children
= this.getChildNodes();
238 if (index
< 0 || index
> children
.size() + 1){
239 throw new IndexOutOfBoundsException("Wrong index: " + index
);
241 child
.setParent(this);
242 child
.setGraph(this.getGraph());
243 children
.add(index
, child
);
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.
254 * @param child the feature node to be added
255 * @param index the integer indicating the position at which the child
257 * @see #getChildNodes()
258 * @see #setChildren(List)
259 * @see #addChild(TermNode)
260 * @see #removeChild(TermNode)
261 * @see #removeChild(int)
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
);
269 if (child
.getParent() != null){
270 child
.getParent().removeChild(child
);
272 child
.setParent(this);
273 child
.setGraph(this.getGraph());
274 children
.add(index
, child
);
280 * Removes the given feature node from the list of {@link #getChildNodes() children}
281 * of <i>this</i> feature node.
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)
289 public void removeChild(TermNode
<T
> child
){
291 int index
= children
.indexOf(child
);
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.
301 * @param index the integer indicating the position of the feature node to
303 * @see #getChildNodes()
304 * @see #addChild(TermNode, int)
305 * @see #addChild(TermNode)
306 * @see #removeChild(TermNode)
308 public void removeChild(int index
){
309 TermNode
<T
> child
= children
.get(index
);
311 children
.remove(index
);
312 child
.setParent(null);
313 child
.setGraph(null);
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.
322 * @param childIndex the integer indicating the position of the feature node
323 * @see #getChildNodes()
324 * @see #addChild(TermNode, int)
325 * @see #removeChild(int)
327 public TermNode
<T
> getChildAt(int childIndex
) {
328 return children
.get(childIndex
);
332 * Returns the number of children nodes of <i>this</i> feature node.
334 * @see #getChildNodes()
337 public int getChildCount() {
338 return children
.size();
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.
346 * @param node the feature node the position of which is being searched
347 * @see #addChild(TermNode, int)
348 * @see #removeChild(int)
350 public int getIndex(TermNode
<T
> node
) {
351 if (! children
.contains(node
)){
354 return children
.indexOf(node
);
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.
363 * @see #getChildNodes()
364 * @see #getChildCount()
367 public boolean isLeaf() {
368 return children
.size() < 1;
372 * Whether <code>this</code> node is the root node of the associated {@link TermTree feature tree}.
374 * @return <code>true</code> if <code>this</code> is the feature trees root node, <code>false</code> if not
377 public boolean isRoot(){
378 if(getGraph() != null){
379 return this.equals(getGraph().getRoot());
384 // *************************** APPLICABLE IF ********************************/
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
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.
398 * @see #addApplicableState(State)
399 * @see #removeApplicableState(State)
401 public Set
<FeatureState
> getOnlyApplicableIf() {
402 return onlyApplicableIf
;
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>
410 * @param applicableState the applicable state to be added to <i>this</i> feature node
411 * @see #getOnlyApplicableIf()
413 public void addApplicableState(FeatureState applicableState
) {
414 this.onlyApplicableIf
.add(applicableState
);
416 public FeatureState
addApplicableState(Feature feature
, State applicableState
) {
417 FeatureState featureState
= FeatureState
.NewInstance(feature
, applicableState
);
418 addApplicableState(featureState
);
423 * Removes one element from the set of
424 * {@link #getOnlyApplicableIf() applicable states} described in
425 * <i>this</i> feature node.<BR>
427 * @param applicableState the applicable state which should be removed
428 * @see #getApplicableState()
429 * @see #addApplicableState(State)
431 public void removeApplicableState(FeatureState applicableState
) {
432 this.onlyApplicableIf
.remove(applicableState
);
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.
446 * @see #addInapplicableState(State)
447 * @see #removeInapplicableState(State)
449 public Set
<FeatureState
> getInapplicableIf() {
450 return inapplicableIf
;
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>
458 * @param inapplicableState the inapplicable state to be added to <i>this</i> feature node
459 * @see #getInapplicableState()
461 public void addInapplicableState(FeatureState inapplicableState
) {
462 this.inapplicableIf
.add(inapplicableState
);
465 public FeatureState
addInapplicableState(Feature feature
, State inapplicableState
) {
466 FeatureState featureState
= FeatureState
.NewInstance(feature
, inapplicableState
);
467 addInapplicableState(featureState
);
472 * Removes one element from the set of
473 * {@link #getInapplicableIf() inapplicable states} described in
474 * <i>this</i> feature node.<BR>
476 * @param inapplicableState the inapplicable state which should be removed
477 * @see #getInapplicableState()
478 * @see #addInapplicableState(State)
481 public void removeInapplicableState(FeatureState inapplicableState
) {
482 this.inapplicableIf
.remove(inapplicableState
);
485 // //** ********************** QUESTIONS ******************************/
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}.
493 // public Set<Representation> getQuestions() {
494 // return this.questions;
497 // public void addQuestion(Representation question) {
498 // this.questions.add(question);
501 // public void removeQuestion(Representation question) {
502 // this.questions.remove(question);
506 // public Representation getQuestion(Language lang) {
507 // for (Representation question : questions){
508 // Language reprLanguage = question.getLanguage();
509 // if (reprLanguage != null && reprLanguage.equals(lang)){
516 //*********************** Terms ************************************/
519 * Returns all terms that are contained in this node or a child node
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
528 public Set
<T
> getDistinctTermsRecursive(Set
<T
> terms
){
529 T term
= this.getTerm();
533 for(TermNode
<T
> childNode
: this.getChildNodes()){
534 if (childNode
!= null){
535 terms
.addAll(childNode
.getDistinctTermsRecursive(terms
));
542 public String
getPath(){
544 if (parent
!= null && parent
.getTerm() != null){
545 result
= parent
.getPath() ;
547 if (getTerm()!= null){
548 String sep
= StringUtils
.isBlank(result
)?
"":"/";
549 result
+= sep
+ getTerm().getLabel();
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.
560 public Set
<T
> getIndependentTermsRecursive(){
561 Set
<T
> terms
= new HashSet
<>();
563 T term
= this.getTerm();
567 for(TermNode
<T
> childNode
: this.getChildNodes()){
568 terms
.addAll(childNode
.getIndependentTermsRecursive());
575 * @return <code>true</code> if any of the sets {@link #getInapplicableIf() inapplicableIf}
576 * and {@link #getOnlyApplicableIf() onlyApplicableIf} are not empty
580 public boolean isDependent() {
581 return inapplicableIf
.size()>0 || onlyApplicableIf
.size()>0;
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
589 public Collection
<?
extends T
> asTermListRecursive() {
590 List
<T
> result
= new ArrayList
<>();
591 T term
= this.getTerm();
595 for(TermNode
<T
> childNode
: this.getChildNodes()){
596 result
.addAll(childNode
.asTermListRecursive());
601 //*********************** CLONE ********************************************************/
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
610 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
611 * @see java.lang.Object#clone()
614 public TermNode
<T
> clone() {
617 result
= (TermNode
<T
>)super.clone();
618 result
.children
= new ArrayList
<>();
620 }catch (CloneNotSupportedException e
) {
621 logger
.warn("Object does not implement cloneable");
627 public TermNode
<T
> cloneDescendants(){
628 TermNode
<T
> clone
= this.clone();
629 TermNode
<T
> childClone
;
631 for(TermNode
<T
> childNode
: this.getChildNodes()){
632 childClone
= childNode
.clone();
633 for (TermNode
<T
> childChild
:childNode
.getChildNodes()){
634 childClone
.addChild(childChild
.cloneDescendants());
636 clone
.addChild(childClone
);
642 // ********************** TREE NODE METHODS ******************************/
645 public String
treeIndex() {
646 return this.treeIndex
;
648 public String
treeIndexLike() {
649 return treeIndex
+ "%";
652 public String
treeIndexWc() {
653 return treeIndex
+ "*";
658 public void setTreeIndex(String newTreeIndex
) {
659 this.treeIndex
= newTreeIndex
;
664 public int treeId() {
665 if (this.getGraph() == null){
668 return this.getGraph().getId();