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.
10 package eu
.etaxonomy
.cdm
.model
.term
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Collection
;
14 import java
.util
.HashSet
;
15 import java
.util
.List
;
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
;
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
;
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
;
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
62 * @since 08-Nov-2007 13:06:16
64 @SuppressWarnings("serial")
65 @XmlAccessorType(XmlAccessType
.FIELD
)
66 @XmlType(name
= "TermNode", propOrder
= {
74 @XmlRootElement(name
= "TermNode")
77 public class TermNode
<T
extends DefinedTermBase
>
78 extends TermRelationBase
<T
, TermNode
<T
>, TermTree
>
79 implements ITreeNode
<TermNode
<T
>> {
81 private static final Logger logger
= LogManager
.getLogger(TermNode
.class);
83 @XmlElement(name
= "Parent")
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
;
91 @XmlElement(name
= "treeIndex")
93 private String treeIndex
;
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
<>();
104 //see https://dev.e-taxonomy.eu/redmine/issues/3722
106 private Integer sortIndex
;
108 @XmlElementWrapper(name
= "OnlyApplicableIf")
109 @XmlElement(name
= "OnlyApplicableIf")
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
<>();
117 @XmlElementWrapper(name
= "InapplicableIf")
118 @XmlElement(name
= "InapplicableIf")
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
<>();
126 // ***************************** FACTORY *********************************/
128 //no factory methods should be provided as TermNodes should only
129 //be created as children of their parent node (#8257)
131 // ******************** CONSTRUCTOR ***************************************/
133 //for hibernate use only, *packet* private required by bytebuddy
138 * Class constructor: creates a new empty feature node instance.
140 protected TermNode(TermType termType
) {
144 //************************* PARENT ******************************/
147 * Returns the feature node <i>this</i> feature node is a child of.
149 * @see #getChildNodes()
152 public TermNode
<T
> getParent() {
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.
160 * @param parent the feature node to be set as parent
163 protected void setParent(TermNode
<T
> parent
) {
164 this.parent
= parent
;
167 //** ********************** CHILDREN ******************************/
170 * @deprecated for internal use only.
172 //see #4278 , #4200, #3722
174 protected void setSortIndex(Integer sortIndex
) {
175 // sortIndex = sortIndex; old #3722
180 * Returns the (ordered) list of tree nodes which are children nodes of
184 public List
<TermNode
<T
>> getChildNodes() {
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.
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)
200 public TermNode
<T
> addChild(TermNode
<T
> child
){
201 return addChild(child
, children
.size());
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.
210 * @return the newly created child node
211 * @see #getChildNodes()
212 * @see #setChildren(List)
213 * @see #removeChild(TermNode)
214 * @see #removeChild(int)
216 public TermNode
<T
> addChild(){
217 return addChild((T
)null, children
.size());
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.
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)
233 public TermNode
<T
> addChild(T term
){
234 return addChild(term
, children
.size());
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.
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)
250 public TermNode
<T
> addChild(T term
, int index
){
251 TermNode
<T
> child
= new TermNode
<>(getTermType());
255 checkTermType(child
);
257 List
<TermNode
<T
>> children
= this.getChildNodes();
258 if (index
< 0 || index
> children
.size() + 1){
259 throw new IndexOutOfBoundsException("Wrong index: " + index
);
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
);
270 child
.setSortIndex(index
);
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.
281 * @param child the feature node to be added
282 * @param index the integer indicating the position at which the child
284 * @see #getChildNodes()
285 * @see #setChildren(List)
286 * @see #addChild(TermNode)
287 * @see #removeChild(TermNode)
288 * @see #removeChild(int)
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
);
296 if (child
.getParent() != null){
297 child
.getParent().removeChild(child
);
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
);
306 child
.setSortIndex(index
);
312 * Removes the given feature node from the list of {@link #getChildNodes() children}
313 * of <i>this</i> feature node.
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)
321 public void removeChild(TermNode
<T
> child
){
323 int index
= children
.indexOf(child
);
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.
333 * @param index the integer indicating the position of the feature node to
335 * @see #getChildNodes()
336 * @see #addChild(TermNode, int)
337 * @see #addChild(TermNode)
338 * @see #removeChild(TermNode)
340 public void removeChild(int index
){
341 TermNode
<T
> child
= children
.get(index
);
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
);
353 child
.setSortIndex(null);
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.
362 * @param childIndex the integer indicating the position of the feature node
363 * @see #getChildNodes()
364 * @see #addChild(TermNode, int)
365 * @see #removeChild(int)
367 public TermNode
<T
> getChildAt(int childIndex
) {
368 return children
.get(childIndex
);
372 * Returns the number of children nodes of <i>this</i> feature node.
374 * @see #getChildNodes()
377 public int getChildCount() {
378 return children
.size();
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.
386 * @param node the feature node the position of which is being searched
387 * @see #addChild(TermNode, int)
388 * @see #removeChild(int)
390 public int getIndex(TermNode
<T
> node
) {
391 if (! children
.contains(node
)){
394 return children
.indexOf(node
);
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.
403 * @see #getChildNodes()
404 * @see #getChildCount()
407 public boolean isLeaf() {
408 return children
.size() < 1;
412 * Whether <code>this</code> node is the root node of the associated {@link TermTree feature tree}.
414 * @return <code>true</code> if <code>this</code> is the feature trees root node, <code>false</code> if not
417 public boolean isRoot(){
418 if(getGraph() != null){
419 return this.equals(getGraph().getRoot());
424 // *************************** APPLICABLE IF ********************************/
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
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.
438 * @see #addApplicableState(State)
439 * @see #removeApplicableState(State)
441 public Set
<FeatureState
> getOnlyApplicableIf() {
442 return onlyApplicableIf
;
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>
450 * @param applicableState the applicable state to be added to <i>this</i> feature node
451 * @see #getOnlyApplicableIf()
453 public void addApplicableState(FeatureState applicableState
) {
454 this.onlyApplicableIf
.add(applicableState
);
456 public FeatureState
addApplicableState(Feature feature
, State applicableState
) {
457 FeatureState featureState
= FeatureState
.NewInstance(feature
, applicableState
);
458 addApplicableState(featureState
);
463 * Removes one element from the set of
464 * {@link #getOnlyApplicableIf() applicable states} described in
465 * <i>this</i> feature node.<BR>
467 * @param applicableState the applicable state which should be removed
468 * @see #getApplicableState()
469 * @see #addApplicableState(State)
471 public void removeApplicableState(FeatureState applicableState
) {
472 this.onlyApplicableIf
.remove(applicableState
);
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.
486 * @see #addInapplicableState(State)
487 * @see #removeInapplicableState(State)
489 public Set
<FeatureState
> getInapplicableIf() {
490 return inapplicableIf
;
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>
498 * @param inapplicableState the inapplicable state to be added to <i>this</i> feature node
499 * @see #getInapplicableState()
501 public void addInapplicableState(FeatureState inapplicableState
) {
502 this.inapplicableIf
.add(inapplicableState
);
505 public FeatureState
addInapplicableState(Feature feature
, State inapplicableState
) {
506 FeatureState featureState
= FeatureState
.NewInstance(feature
, inapplicableState
);
507 addInapplicableState(featureState
);
512 * Removes one element from the set of
513 * {@link #getInapplicableIf() inapplicable states} described in
514 * <i>this</i> feature node.<BR>
516 * @param inapplicableState the inapplicable state which should be removed
517 * @see #getInapplicableState()
518 * @see #addInapplicableState(State)
521 public void removeInapplicableState(FeatureState inapplicableState
) {
522 this.inapplicableIf
.remove(inapplicableState
);
525 // //** ********************** QUESTIONS ******************************/
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}.
533 // public Set<Representation> getQuestions() {
534 // return this.questions;
537 // public void addQuestion(Representation question) {
538 // this.questions.add(question);
541 // public void removeQuestion(Representation question) {
542 // this.questions.remove(question);
546 // public Representation getQuestion(Language lang) {
547 // for (Representation question : questions){
548 // Language reprLanguage = question.getLanguage();
549 // if (reprLanguage != null && reprLanguage.equals(lang)){
556 //*********************** Terms ************************************/
559 * Returns all terms that are contained in this node or a child node
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
568 public Set
<T
> getDistinctTermsRecursive(Set
<T
> terms
){
569 T term
= this.getTerm();
573 for(TermNode
<T
> childNode
: this.getChildNodes()){
574 if (childNode
!= null){
575 terms
.addAll(childNode
.getDistinctTermsRecursive(terms
));
582 public String
getPath(){
584 if (parent
!= null && parent
.getTerm() != null){
585 result
= parent
.getPath() ;
587 if (getTerm()!= null){
588 String sep
= StringUtils
.isBlank(result
)?
"":"/";
589 result
+= sep
+ getTerm().getLabel();
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.
600 public Set
<T
> getIndependentTermsRecursive(){
601 Set
<T
> terms
= new HashSet
<>();
603 T term
= this.getTerm();
607 for(TermNode
<T
> childNode
: this.getChildNodes()){
608 terms
.addAll(childNode
.getIndependentTermsRecursive());
615 * @return <code>true</code> if any of the sets {@link #getInapplicableIf() inapplicableIf}
616 * and {@link #getOnlyApplicableIf() onlyApplicableIf} are not empty
620 public boolean isDependent() {
621 return inapplicableIf
.size()>0 || onlyApplicableIf
.size()>0;
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
629 public Collection
<?
extends T
> asTermListRecursive() {
630 List
<T
> result
= new ArrayList
<>();
631 T term
= this.getTerm();
635 for(TermNode
<T
> childNode
: this.getChildNodes()){
636 result
.addAll(childNode
.asTermListRecursive());
641 //*********************** CLONE ********************************************************/
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
650 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
651 * @see java.lang.Object#clone()
654 public TermNode
<T
> clone() {
657 result
= (TermNode
<T
>)super.clone();
658 result
.children
= new ArrayList
<>();
660 }catch (CloneNotSupportedException e
) {
661 logger
.warn("Object does not implement cloneable");
667 public TermNode
<T
> cloneDescendants(){
668 TermNode
<T
> clone
= this.clone();
669 TermNode
<T
> childClone
;
671 for(TermNode
<T
> childNode
: this.getChildNodes()){
672 childClone
= childNode
.clone();
673 for (TermNode
<T
> childChild
:childNode
.getChildNodes()){
674 childClone
.addChild(childChild
.cloneDescendants());
676 clone
.addChild(childClone
);
682 // ********************** TREE NODE METHODS ******************************/
685 public String
treeIndex() {
686 return this.treeIndex
;
688 public String
treeIndexLike() {
689 return treeIndex
+ "%";
692 public String
treeIndexWc() {
693 return treeIndex
+ "*";
698 public void setTreeIndex(String newTreeIndex
) {
699 this.treeIndex
= newTreeIndex
;
704 public int treeId() {
705 if (this.getGraph() == null){
708 return this.getGraph().getId();
712 void updateSortIndex(){
713 // TODO workaround (see sortIndex doc)
715 for (int i
= 0; i
< children
.size(); i
++) {
716 children
.get(i
).setSortIndex(i
);
718 } catch (LazyInitializationException e
) {
719 logger
.info("Cannot clean up uninitialized children without a session, skipping.");
724 public void removeNullValueFromChildren(){
725 HHH_9751_Util
.removeAllNull(children
);