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
.description
;
12 import java
.util
.ArrayList
;
13 import java
.util
.HashSet
;
14 import java
.util
.List
;
17 import javax
.persistence
.Entity
;
18 import javax
.persistence
.FetchType
;
19 import javax
.persistence
.JoinColumn
;
20 import javax
.persistence
.JoinTable
;
21 import javax
.persistence
.ManyToMany
;
22 import javax
.persistence
.ManyToOne
;
23 import javax
.persistence
.OneToMany
;
24 import javax
.persistence
.OrderBy
;
25 import javax
.persistence
.OrderColumn
;
26 import javax
.persistence
.Transient
;
27 import javax
.validation
.constraints
.Size
;
28 import javax
.xml
.bind
.annotation
.XmlAccessType
;
29 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
30 import javax
.xml
.bind
.annotation
.XmlElement
;
31 import javax
.xml
.bind
.annotation
.XmlElementWrapper
;
32 import javax
.xml
.bind
.annotation
.XmlIDREF
;
33 import javax
.xml
.bind
.annotation
.XmlRootElement
;
34 import javax
.xml
.bind
.annotation
.XmlSchemaType
;
35 import javax
.xml
.bind
.annotation
.XmlType
;
37 import org
.apache
.log4j
.Logger
;
38 import org
.hibernate
.annotations
.Cascade
;
39 import org
.hibernate
.annotations
.CascadeType
;
40 import org
.hibernate
.annotations
.Index
;
41 import org
.hibernate
.annotations
.Table
;
42 import org
.hibernate
.envers
.Audited
;
44 import eu
.etaxonomy
.cdm
.model
.common
.ITreeNode
;
45 import eu
.etaxonomy
.cdm
.model
.common
.VersionableEntity
;
48 * The class for tree nodes within a {@link FeatureTree feature tree} structure.
49 * Feature nodes are the elementary components of such a tree since they might
50 * be related to other nodes as a parent or as a child. A feature node belongs
51 * at most to one feature tree. It cannot have more than one parent node but
52 * may have several child nodes. Parent/child relations are bidirectional:
53 * a node N1 is the parent of a node N2 if and only if the node N2 is a child of
57 * @created 08-Nov-2007 13:06:16
59 @SuppressWarnings("serial")
60 @XmlAccessorType(XmlAccessType
.FIELD
)
61 @XmlType(name
= "FeatureNode", propOrder
= {
71 @XmlRootElement(name
= "FeatureNode")
74 @Table(appliesTo
="FeatureNode", indexes
= { @Index(name
= "featureNodeTreeIndex", columnNames
= { "treeIndex" }) })
75 public class FeatureNode
extends VersionableEntity
implements ITreeNode
<FeatureNode
>, Cloneable
{
76 private static final Logger logger
= Logger
.getLogger(FeatureNode
.class);
78 //This is the main key a node belongs to. Although other keys may also reference
79 //<code>this</code> node, a node usually belongs to a given key.
80 @XmlElement(name
= "FeatureTree")
82 @XmlSchemaType(name
= "IDREF")
83 @ManyToOne(fetch
= FetchType
.LAZY
)
84 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.DELETE_ORPHAN
}) //TODO this usage is incorrect, needed only for OneToMany, check why it is here, can it be removed??
87 private FeatureTree featureTree
;
89 @XmlElement(name
= "Feature")
91 @XmlSchemaType(name
= "IDREF")
92 @ManyToOne(fetch
= FetchType
.LAZY
)
93 private Feature feature
;
95 @XmlElement(name
= "Parent")
97 @XmlSchemaType(name
= "IDREF")
98 @ManyToOne(fetch
= FetchType
.LAZY
, targetEntity
=FeatureNode
.class)
99 @Cascade(CascadeType
.SAVE_UPDATE
)
100 @JoinColumn(name
="parent_id")
101 private FeatureNode parent
;
104 @XmlElement(name
= "treeIndex")
106 private String treeIndex
;
108 @XmlElementWrapper(name
= "Children")
109 @XmlElement(name
= "Child")
110 //see https://dev.e-taxonomy.eu/trac/ticket/3722
111 @OrderColumn(name
="sortIndex")
112 @OrderBy("sortIndex")
113 @OneToMany(fetch
= FetchType
.LAZY
, mappedBy
="parent")
114 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
115 private List
<FeatureNode
> children
= new ArrayList
<FeatureNode
>();
117 //see https://dev.e-taxonomy.eu/trac/ticket/3722
118 private Integer sortIndex
;
120 @XmlElementWrapper(name
= "OnlyApplicableIf")
121 @XmlElement(name
= "OnlyApplicableIf")
123 @XmlSchemaType(name
="IDREF")
124 @ManyToMany(fetch
= FetchType
.LAZY
)
125 @Cascade(CascadeType
.SAVE_UPDATE
)
126 @JoinTable(name
="FeatureNode_DefinedTermBase_OnlyApplicable")
127 private Set
<State
> onlyApplicableIf
= new HashSet
<State
>();
129 @XmlElementWrapper(name
= "InapplicableIf")
130 @XmlElement(name
= "InapplicableIf")
132 @XmlSchemaType(name
="IDREF")
133 @ManyToMany(fetch
= FetchType
.LAZY
)
134 @Cascade(CascadeType
.SAVE_UPDATE
)
135 @JoinTable(name
="FeatureNode_DefinedTermBase_InapplicableIf")
136 private Set
<State
> inapplicableIf
= new HashSet
<State
>();
140 * Creates a new empty feature node instance.
142 * @see #NewInstance(Feature)
144 public static FeatureNode
NewInstance(){
145 return new FeatureNode();
149 * Creates a new feature node instance only with the given {@link Feature feature}
150 * (without parent and children).
152 * @param feature the feature assigned to the new feature node
153 * @see #NewInstance()
155 public static FeatureNode
NewInstance(Feature feature
){
156 FeatureNode result
= new FeatureNode();
157 result
.setFeature(feature
);
163 * Class constructor: creates a new empty feature node instance.
165 protected FeatureNode() {
170 //*************************** TREE ************************************/
172 public FeatureTree
getFeatureTree() {
176 protected void setFeatureTree(FeatureTree featureTree
) {
177 this.featureTree
= featureTree
;
180 //** ********************** FEATURE ******************************/
183 * Returns the {@link Feature feature} <i>this</i> feature node is based on.
185 public Feature
getFeature() {
191 public void setFeature(Feature feature
) {
192 this.feature
= feature
;
195 //** ********************** PARENT ******************************/
198 * Returns the feature node <i>this</i> feature node is a child of.
200 * @see #getChildNodes()
202 public FeatureNode
getParent() {
206 * Assigns the given feature node as the parent of <i>this</i> feature node.
207 * Due to bidirectionality this method must also add <i>this</i> feature node
208 * to the list of children of the given parent.
210 * @param parent the feature node to be set as parent
213 protected void setParent(FeatureNode parent
) {
214 this.parent
= parent
;
217 //** ********************** CHILDREN ******************************/
220 * Returns the (ordered) list of feature nodes which are children nodes of
221 * <i>this</i> feature node.
223 public List
<FeatureNode
> getChildNodes() {
228 * Adds the given feature node at the end of the list of children of
229 * <i>this</i> feature node. Due to bidirectionality this method must also
230 * assign <i>this</i> feature node as the parent of the given child.
232 * @param child the feature node to be added
233 * @see #getChildNodes()
234 * @see #setChildren(List)
235 * @see #addChild(FeatureNode, int)
236 * @see #removeChild(FeatureNode)
237 * @see #removeChild(int)
239 public void addChild(FeatureNode child
){
240 addChild(child
, children
.size());
243 * Inserts the given feature node in the list of children of <i>this</i> feature node
244 * at the given (index + 1) position. If the given index is out of bounds
245 * an exception will arise.<BR>
246 * Due to bidirectionality this method must also assign <i>this</i> feature node
247 * as the parent of the given child.
249 * @param child the feature node to be added
250 * @param index the integer indicating the position at which the child
252 * @see #getChildNodes()
253 * @see #setChildren(List)
254 * @see #addChild(FeatureNode)
255 * @see #removeChild(FeatureNode)
256 * @see #removeChild(int)
258 public void addChild(FeatureNode child
, int index
){
259 if (index
< 0 || index
> children
.size() + 1){
260 throw new IndexOutOfBoundsException("Wrong index: " + index
);
262 if (child
.getParent() != null){
263 child
.getParent().removeChild(child
);
265 child
.setParent(this);
266 child
.setFeatureTree(this.getFeatureTree());
267 children
.add(index
, child
);
268 //TODO workaround (see sortIndex doc)
269 for(int i
= 0; i
< children
.size(); i
++){
270 children
.get(i
).sortIndex
= i
;
272 child
.sortIndex
= index
;
275 * Removes the given feature node from the list of {@link #getChildNodes() children}
276 * of <i>this</i> feature node.
278 * @param child the feature node which should be removed
279 * @see #getChildNodes()
280 * @see #addChild(FeatureNode, int)
281 * @see #addChild(FeatureNode)
282 * @see #removeChild(int)
284 public void removeChild(FeatureNode child
){
285 int index
= children
.indexOf(child
);
291 * Removes the feature node placed at the given (index + 1) position from
292 * the list of {@link #getChildNodes() children} of <i>this</i> feature node.
293 * If the given index is out of bounds no child will be removed.
295 * @param index the integer indicating the position of the feature node to
297 * @see #getChildNodes()
298 * @see #addChild(FeatureNode, int)
299 * @see #addChild(FeatureNode)
300 * @see #removeChild(FeatureNode)
302 public void removeChild(int index
){
303 FeatureNode child
= children
.get(index
);
305 children
.remove(index
);
306 child
.setParent(null);
307 //TODO workaround (see sortIndex doc)
308 for(int i
= 0; i
< children
.size(); i
++){
309 FeatureNode childAt
= children
.get(i
);
310 childAt
.sortIndex
= i
;
312 child
.sortIndex
= null;
317 * Returns the feature node placed at the given (childIndex + 1) position
318 * within the list of {@link #getChildNodes() children} of <i>this</i> feature node.
319 * If the given index is out of bounds no child will be returned.
321 * @param childIndex the integer indicating the position of the feature node
322 * @see #getChildNodes()
323 * @see #addChild(FeatureNode, int)
324 * @see #removeChild(int)
326 public FeatureNode
getChildAt(int childIndex
) {
327 return children
.get(childIndex
);
331 * Returns the number of children nodes of <i>this</i> feature node.
333 * @see #getChildNodes()
336 public int getChildCount() {
337 return children
.size();
341 * Returns the integer indicating the position of the given feature node
342 * within the list of {@link #getChildNodes() children} of <i>this</i> feature node.
343 * If the list does not contain this node then -1 will be returned.
345 * @param node the feature node the position of which is being searched
346 * @see #addChild(FeatureNode, int)
347 * @see #removeChild(int)
349 public int getIndex(FeatureNode node
) {
350 if (! children
.contains(node
)){
353 return children
.indexOf(node
);
358 * Returns the boolean value indicating if <i>this</i> feature node has
359 * children (false) or not (true). A node without children is at the
360 * bottommost level of a tree and is called a leaf.
362 * @see #getChildNodes()
363 * @see #getChildCount()
366 public boolean isLeaf() {
367 return children
.size() < 1;
371 * Whether <code>this</code> node is the root node of the associated {@link FeatureTree feature tree}.
373 * @return <code>true</code> if <code>this</code> is the feature trees root node, <code>false</code> if not
376 public boolean isRoot(){
377 if(getFeatureTree() != null){
378 return this.equals(getFeatureTree().getRoot());
384 * Returns the set of {@link State states} implying rendering the
385 * concerned {@link Feature feature} applicable.
386 * If at least one state is present in this set, in a given description
387 * the {@link Feature feature} in <i>this</i> feature node is inapplicable
388 * unless any of the listed controlling states is present in the parent
389 * {@link Feature feature} description element {@link CategoricalData
391 * This attribute is not equivalent to onlyApplicableIf in SDD as it is
392 * attached directly to the child feature rather than the parent, which
393 * allow having different applicable states for each child feature.
395 * @see #addApplicableState(State)
396 * @see #removeApplicableState(State)
398 public Set
<State
> getOnlyApplicableIf() {
399 return onlyApplicableIf
;
403 * Adds an existing {@link State applicable state} to the set of
404 * {@link #getOnlyApplicableIf() applicable states} described in
405 * <i>this</i> feature node.<BR>
407 * @param applicableState the applicable state to be added to <i>this</i> feature node
408 * @see #getApplicableState()
410 public void addApplicableState(State applicableState
) {
411 logger
.debug("addApplicableState");
412 this.onlyApplicableIf
.add(applicableState
);
416 * Removes one element from the set of
417 * {@link #getOnlyApplicableIf() applicable states} described in
418 * <i>this</i> feature node.<BR>
420 * @param applicableState the applicable state which should be removed
421 * @see #getApplicableState()
422 * @see #addApplicableState(State)
424 public void removeApplicableState(State applicableState
) {
425 this.onlyApplicableIf
.remove(applicableState
);
429 * Returns the set of {@link State states} implying rendering the
430 * concerned {@link Feature feature} inapplicable.
431 * If at least one {@link State inapplicable state} is defined in the set,
432 * in a given description the {@link Feature feature} attribute of
433 * <i>this</i> feature node is inapplicable when any of the listed
434 * controlling states is present.
435 * This attribute is not equivalent to inapplicableIf in SDD as it is
436 * attached directly to the child feature rather than the parent, which
437 * allow having different inapplicability rules for each child feature.
439 * @see #addInapplicableState(State)
440 * @see #removeInapplicableState(State)
442 public Set
<State
> getInapplicableIf() {
443 return inapplicableIf
;
447 * Adds an existing {@link State inapplicable state} to the set of
448 * {@link #getInapplicableIf() inapplicable states} described in
449 * <i>this</i> feature node.<BR>
451 * @param inapplicableState the inapplicable state to be added to <i>this</i> feature node
452 * @see #getInapplicableState()
454 public void addInapplicableState(State inapplicableState
) {
455 logger
.debug("addInapplicableState");
456 this.inapplicableIf
.add(inapplicableState
);
460 * Removes one element from the set of
461 * {@link #getInapplicableIf() inapplicable states} described in
462 * <i>this</i> feature node.<BR>
464 * @param inapplicableState the inapplicable state which should be removed
465 * @see #getInapplicableState()
466 * @see #addInapplicableState(State)
468 public void removeInapplicableState(State inapplicableState
) {
469 this.inapplicableIf
.remove(inapplicableState
);
472 // //** ********************** QUESTIONS ******************************/
475 // * Returns the {@link Representation question} formulation that
476 // * corresponds to <i>this</i> feature node and the corresponding
477 // * {@link Feature feature} in case it is part of a
478 // * {@link PolytomousKey polytomous key}.
480 // public Set<Representation> getQuestions() {
481 // return this.questions;
484 // public void addQuestion(Representation question) {
485 // this.questions.add(question);
488 // public void removeQuestion(Representation question) {
489 // this.questions.remove(question);
493 // public Representation getQuestion(Language lang) {
494 // for (Representation question : questions){
495 // Language reprLanguage = question.getLanguage();
496 // if (reprLanguage != null && reprLanguage.equals(lang)){
504 * Returns all features that are contained in this node or a child node
511 public Set
<Feature
> getDistinctFeaturesRecursive(Set
<Feature
> features
){
512 Feature feature
= this.getFeature();
514 features
.add(feature
);
516 for(FeatureNode childNode
: this.getChildNodes()){
517 features
.addAll(childNode
.getDistinctFeaturesRecursive(features
));
523 public FeatureNode
cloneDescendants(){
524 FeatureNode clone
= (FeatureNode
)this.clone();
525 FeatureNode childClone
;
527 for(FeatureNode childNode
: this.getChildNodes()){
528 childClone
= (FeatureNode
) childNode
.clone();
529 for (FeatureNode childChild
:childNode
.getChildNodes()){
530 childClone
.addChild(childChild
.cloneDescendants());
532 clone
.addChild(childClone
);
538 //*********************** CLONE ********************************************************/
541 * Clones <i>this</i> FeatureNode. This is a shortcut that enables to create
542 * a new instance that differs only slightly from <i>this</i> FeatureNode by
543 * modifying only some of the attributes.
544 * The parent, the feature and the featureTree are the are the same as for the original feature node
545 * the children are removed
547 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
548 * @see java.lang.Object#clone()
551 public Object
clone() {
554 result
= (FeatureNode
)super.clone();
555 result
.children
= new ArrayList
<FeatureNode
>();
557 }catch (CloneNotSupportedException e
) {
558 logger
.warn("Object does not implement cloneable");
567 // ********************** TREE NODE METHODS ******************************/
570 public String
treeIndex() {
571 return this.treeIndex
;
576 public void setTreeIndex(String newTreeIndex
) {
577 this.treeIndex
= newTreeIndex
;
583 public int treeId() {
584 if (this.featureTree
== null){
587 return this.featureTree
.getId();
592 // //** ********************** TAXON ******************************/
595 // * Returns the {@link Taxon taxon} <i>this</i> terminal node is
596 // * associated with.
598 // public Taxon getTaxon() {
603 // * Assigns the given taxon to <i>this</i> feature node.
605 // * @param taxon the taxon to be set
606 // * @see #getTaxon()
608 // public void setTaxon(Taxon taxon) {
609 // this.taxon = taxon;