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
.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
;
38 import org
.apache
.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
.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
;
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
= "TermTreeNode", propOrder
= {
71 @XmlRootElement(name
= "TermTreeNode")
74 public class TermTreeNode
<T
extends DefinedTermBase
>
75 extends TermRelationBase
<T
, TermTreeNode
<T
>, TermTree
>
76 implements ITreeNode
<TermTreeNode
<T
>> {
78 private static final Logger logger
= Logger
.getLogger(TermTreeNode
.class);
80 @XmlElement(name
= "Parent")
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
;
89 @XmlElement(name
= "treeIndex")
91 private String treeIndex
;
93 @XmlElementWrapper(name
= "Children")
94 @XmlElement(name
= "Child")
95 //see https://dev.e-taxonomy.eu/trac/ticket/3722
96 @OrderColumn(name
="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
<>();
102 //see https://dev.e-taxonomy.eu/trac/ticket/3722
103 private Integer sortIndex
;
105 @XmlElementWrapper(name
= "OnlyApplicableIf")
106 @XmlElement(name
= "OnlyApplicableIf")
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
<>();
114 @XmlElementWrapper(name
= "InapplicableIf")
115 @XmlElement(name
= "InapplicableIf")
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
<>();
123 // ***************************** FACTORY *********************************/
125 //no factory methods should be provided as FeatureNodes should only
126 //be created as children of their parent node (#8257)
128 // ******************** CONSTRUCTOR ***************************************/
132 protected TermTreeNode(){}
135 * Class constructor: creates a new empty feature node instance.
137 protected TermTreeNode(TermType termType
) {
141 //************************* PARENT ******************************/
144 * Returns the feature node <i>this</i> feature node is a child of.
146 * @see #getChildNodes()
149 public TermTreeNode
<T
> getParent() {
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.
157 * @param parent the feature node to be set as parent
160 protected void setParent(TermTreeNode
<T
> parent
) {
161 this.parent
= parent
;
164 //** ********************** CHILDREN ******************************/
168 * @deprecated for internal use only.
172 protected void setSortIndex(Integer sortIndex
) {
173 this.sortIndex
= sortIndex
;
177 * Returns the (ordered) list of feature nodes which are children nodes of
178 * <i>this</i> feature node.
181 public List
<TermTreeNode
<T
>> getChildNodes() {
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.
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)
197 public void addChild(TermTreeNode
<T
> child
){
198 addChild(child
, children
.size());
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.
207 * @return the newly created child node
208 * @see #getChildNodes()
209 * @see #setChildren(List)
210 * @see #removeChild(FeatureNode)
211 * @see #removeChild(int)
213 public TermTreeNode
<T
> addChild(){
214 return addChild((T
)null, children
.size());
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.
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)
230 public TermTreeNode
<T
> addChild(T term
){
231 return addChild(term
, children
.size());
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.
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)
247 public TermTreeNode
<T
> addChild(T term
, int index
){
248 TermTreeNode
<T
> child
= new TermTreeNode
<>(getTermType());
252 checkTermType(child
);
254 List
<TermTreeNode
<T
>> children
= this.getChildNodes();
255 if (index
< 0 || index
> children
.size() + 1){
256 throw new IndexOutOfBoundsException("Wrong index: " + index
);
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
);
265 child
.setSortIndex(index
);
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.
276 * @param child the feature node to be added
277 * @param index the integer indicating the position at which the child
279 * @see #getChildNodes()
280 * @see #setChildren(List)
281 * @see #addChild(TermTreeNode)
282 * @see #removeChild(TermTreeNode)
283 * @see #removeChild(int)
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
);
291 if (child
.getParent() != null){
292 child
.getParent().removeChild(child
);
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
);
301 child
.setSortIndex(index
);
306 * Removes the given feature node from the list of {@link #getChildNodes() children}
307 * of <i>this</i> feature node.
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)
315 public void removeChild(TermTreeNode
<T
> child
){
317 int index
= children
.indexOf(child
);
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.
327 * @param index the integer indicating the position of the feature node to
329 * @see #getChildNodes()
330 * @see #addChild(TermTreeNode, int)
331 * @see #addChild(TermTreeNode)
332 * @see #removeChild(TermTreeNode)
334 public void removeChild(int index
){
335 TermTreeNode
<T
> child
= children
.get(index
);
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
);
345 child
.setSortIndex(null);
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.
354 * @param childIndex the integer indicating the position of the feature node
355 * @see #getChildNodes()
356 * @see #addChild(TermTreeNode, int)
357 * @see #removeChild(int)
359 public TermTreeNode
<T
> getChildAt(int childIndex
) {
360 return children
.get(childIndex
);
364 * Returns the number of children nodes of <i>this</i> feature node.
366 * @see #getChildNodes()
369 public int getChildCount() {
370 return children
.size();
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.
378 * @param node the feature node the position of which is being searched
379 * @see #addChild(TermTreeNode, int)
380 * @see #removeChild(int)
382 public int getIndex(TermTreeNode
<T
> node
) {
383 if (! children
.contains(node
)){
386 return children
.indexOf(node
);
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.
395 * @see #getChildNodes()
396 * @see #getChildCount()
399 public boolean isLeaf() {
400 return children
.size() < 1;
404 * Whether <code>this</code> node is the root node of the associated {@link TermTree feature tree}.
406 * @return <code>true</code> if <code>this</code> is the feature trees root node, <code>false</code> if not
409 public boolean isRoot(){
410 if(getGraph() != null){
411 return this.equals(getGraph().getRoot());
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
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.
428 * @see #addApplicableState(State)
429 * @see #removeApplicableState(State)
431 public Set
<State
> getOnlyApplicableIf() {
432 return onlyApplicableIf
;
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>
440 * @param applicableState the applicable state to be added to <i>this</i> feature node
441 * @see #getApplicableState()
443 public void addApplicableState(State applicableState
) {
444 this.onlyApplicableIf
.add(applicableState
);
448 * Removes one element from the set of
449 * {@link #getOnlyApplicableIf() applicable states} described in
450 * <i>this</i> feature node.<BR>
452 * @param applicableState the applicable state which should be removed
453 * @see #getApplicableState()
454 * @see #addApplicableState(State)
456 public void removeApplicableState(State applicableState
) {
457 this.onlyApplicableIf
.remove(applicableState
);
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.
471 * @see #addInapplicableState(State)
472 * @see #removeInapplicableState(State)
474 public Set
<State
> getInapplicableIf() {
475 return inapplicableIf
;
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>
483 * @param inapplicableState the inapplicable state to be added to <i>this</i> feature node
484 * @see #getInapplicableState()
486 public void addInapplicableState(State inapplicableState
) {
487 this.inapplicableIf
.add(inapplicableState
);
491 * Removes one element from the set of
492 * {@link #getInapplicableIf() inapplicable states} described in
493 * <i>this</i> feature node.<BR>
495 * @param inapplicableState the inapplicable state which should be removed
496 * @see #getInapplicableState()
497 * @see #addInapplicableState(State)
499 public void removeInapplicableState(State inapplicableState
) {
500 this.inapplicableIf
.remove(inapplicableState
);
503 // //** ********************** QUESTIONS ******************************/
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}.
511 // public Set<Representation> getQuestions() {
512 // return this.questions;
515 // public void addQuestion(Representation question) {
516 // this.questions.add(question);
519 // public void removeQuestion(Representation question) {
520 // this.questions.remove(question);
524 // public Representation getQuestion(Language lang) {
525 // for (Representation question : questions){
526 // Language reprLanguage = question.getLanguage();
527 // if (reprLanguage != null && reprLanguage.equals(lang)){
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.
541 private void checkTermTypeKindOf(IHasTermType descendant
) {
542 IHasTermType
.checkTermTypeEqualOrDescendant(this, descendant
);
545 //*********************** Terms ************************************/
548 * Returns all terms that are contained in this node or a child node
554 public Set
<T
> getDistinctTermsRecursive(Set
<T
> terms
){
555 T term
= this.getTerm();
559 for(TermTreeNode
<T
> childNode
: this.getChildNodes()){
560 terms
.addAll(childNode
.getDistinctTermsRecursive(terms
));
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
570 public Collection
<?
extends T
> asTermListRecursive(List
<T
> terms
) {
571 T term
= this.getTerm();
575 for(TermTreeNode
<T
> childNode
: this.getChildNodes()){
576 terms
.addAll(childNode
.asTermListRecursive(terms
));
582 //*********************** CLONE ********************************************************/
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
591 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
592 * @see java.lang.Object#clone()
595 public Object
clone() {
596 TermTreeNode
<T
> result
;
598 result
= (TermTreeNode
<T
>)super.clone();
599 result
.children
= new ArrayList
<>();
601 }catch (CloneNotSupportedException e
) {
602 logger
.warn("Object does not implement cloneable");
608 public TermTreeNode
<T
> cloneDescendants(){
609 TermTreeNode
<T
> clone
= (TermTreeNode
<T
>)this.clone();
610 TermTreeNode
<T
> childClone
;
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());
617 clone
.addChild(childClone
);
623 // ********************** TREE NODE METHODS ******************************/
626 public String
treeIndex() {
627 return this.treeIndex
;
629 public String
treeIndexLike() {
630 return treeIndex
+ "%";
633 public String
treeIndexWc() {
634 return treeIndex
+ "*";
639 public void setTreeIndex(String newTreeIndex
) {
640 this.treeIndex
= newTreeIndex
;
646 public int treeId() {
647 if (this.getGraph() == null){
650 return this.getGraph().getId();
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
);
661 public void removeNullValueFromChildren(){
662 HHH_9751_Util
.removeAllNull(children
);
668 >>>>>>> ref #
6794 add TermRelation and rename FeatureNode
-> TermTreeNode