alternative for protected: deproxy #4200
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / description / FeatureNode.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.description;
11
12 import java.util.ArrayList;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Set;
16
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;
36
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;
43
44 import eu.etaxonomy.cdm.model.common.ITreeNode;
45 import eu.etaxonomy.cdm.model.common.VersionableEntity;
46
47 /**
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
54 * the node N1.
55 *
56 * @author m.doering
57 * @created 08-Nov-2007 13:06:16
58 */
59 @SuppressWarnings("serial")
60 @XmlAccessorType(XmlAccessType.FIELD)
61 @XmlType(name = "FeatureNode", propOrder = {
62 "featureTree",
63 "feature",
64 "parent",
65 "treeIndex",
66 "sortIndex",
67 "children",
68 "onlyApplicableIf",
69 "inapplicableIf"
70 })
71 @XmlRootElement(name = "FeatureNode")
72 @Entity
73 @Audited
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);
77
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")
81 @XmlIDREF
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??
85 //TODO Val #3379
86 // @NotNull
87 private FeatureTree featureTree;
88
89 @XmlElement(name = "Feature")
90 @XmlIDREF
91 @XmlSchemaType(name = "IDREF")
92 @ManyToOne(fetch = FetchType.LAZY)
93 private Feature feature;
94
95 @XmlElement(name = "Parent")
96 @XmlIDREF
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;
102
103
104 @XmlElement(name = "treeIndex")
105 @Size(max=255)
106 private String treeIndex;
107
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>();
116
117 //see https://dev.e-taxonomy.eu/trac/ticket/3722
118 private Integer sortIndex;
119
120 @XmlElementWrapper(name = "OnlyApplicableIf")
121 @XmlElement(name = "OnlyApplicableIf")
122 @XmlIDREF
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>();
128
129 @XmlElementWrapper(name = "InapplicableIf")
130 @XmlElement(name = "InapplicableIf")
131 @XmlIDREF
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>();
137
138
139 /**
140 * Creates a new empty feature node instance.
141 *
142 * @see #NewInstance(Feature)
143 */
144 public static FeatureNode NewInstance(){
145 return new FeatureNode();
146 }
147
148 /**
149 * Creates a new feature node instance only with the given {@link Feature feature}
150 * (without parent and children).
151 *
152 * @param feature the feature assigned to the new feature node
153 * @see #NewInstance()
154 */
155 public static FeatureNode NewInstance(Feature feature){
156 FeatureNode result = new FeatureNode();
157 result.setFeature(feature);
158 return result;
159 }
160
161
162 /**
163 * Class constructor: creates a new empty feature node instance.
164 */
165 protected FeatureNode() {
166 super();
167 }
168
169
170 //*************************** TREE ************************************/
171
172 public FeatureTree getFeatureTree() {
173 return featureTree;
174 }
175
176 protected void setFeatureTree(FeatureTree featureTree) {
177 this.featureTree = featureTree;
178 }
179
180 //** ********************** FEATURE ******************************/
181
182 /**
183 * Returns the {@link Feature feature} <i>this</i> feature node is based on.
184 */
185 public Feature getFeature() {
186 return feature;
187 }
188 /**
189 * @see #getFeature()
190 */
191 public void setFeature(Feature feature) {
192 this.feature = feature;
193 }
194
195 //** ********************** PARENT ******************************/
196
197 /**
198 * Returns the feature node <i>this</i> feature node is a child of.
199 *
200 * @see #getChildNodes()
201 */
202 public FeatureNode getParent() {
203 return parent;
204 }
205 /**
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.
209 *
210 * @param parent the feature node to be set as parent
211 * @see #getParent()
212 */
213 protected void setParent(FeatureNode parent) {
214 this.parent = parent;
215 }
216
217 //** ********************** CHILDREN ******************************/
218
219 /**
220 * Returns the (ordered) list of feature nodes which are children nodes of
221 * <i>this</i> feature node.
222 */
223 public List<FeatureNode> getChildNodes() {
224 return children;
225 }
226
227 /**
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.
231 *
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)
238 */
239 public void addChild(FeatureNode child){
240 addChild(child, children.size());
241 }
242 /**
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.
248 *
249 * @param child the feature node to be added
250 * @param index the integer indicating the position at which the child
251 * should be added
252 * @see #getChildNodes()
253 * @see #setChildren(List)
254 * @see #addChild(FeatureNode)
255 * @see #removeChild(FeatureNode)
256 * @see #removeChild(int)
257 */
258 public void addChild(FeatureNode child, int index){
259 if (index < 0 || index > children.size() + 1){
260 throw new IndexOutOfBoundsException("Wrong index: " + index);
261 }
262 if (child.getParent() != null){
263 child.getParent().removeChild(child);
264 }
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;
271 }
272 child.sortIndex = index;
273 }
274 /**
275 * Removes the given feature node from the list of {@link #getChildNodes() children}
276 * of <i>this</i> feature node.
277 *
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)
283 */
284 public void removeChild(FeatureNode child){
285 int index = children.indexOf(child);
286 if (index >= 0){
287 removeChild(index);
288 }
289 }
290 /**
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.
294 *
295 * @param index the integer indicating the position of the feature node to
296 * be removed
297 * @see #getChildNodes()
298 * @see #addChild(FeatureNode, int)
299 * @see #addChild(FeatureNode)
300 * @see #removeChild(FeatureNode)
301 */
302 public void removeChild(int index){
303 FeatureNode child = children.get(index);
304 if (child != null){
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;
311 }
312 child.sortIndex = null;
313 }
314 }
315
316 /**
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.
320 *
321 * @param childIndex the integer indicating the position of the feature node
322 * @see #getChildNodes()
323 * @see #addChild(FeatureNode, int)
324 * @see #removeChild(int)
325 */
326 public FeatureNode getChildAt(int childIndex) {
327 return children.get(childIndex);
328 }
329
330 /**
331 * Returns the number of children nodes of <i>this</i> feature node.
332 *
333 * @see #getChildNodes()
334 */
335 @Transient
336 public int getChildCount() {
337 return children.size();
338 }
339
340 /**
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.
344 *
345 * @param node the feature node the position of which is being searched
346 * @see #addChild(FeatureNode, int)
347 * @see #removeChild(int)
348 */
349 public int getIndex(FeatureNode node) {
350 if (! children.contains(node)){
351 return -1;
352 }else{
353 return children.indexOf(node);
354 }
355 }
356
357 /**
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.
361 *
362 * @see #getChildNodes()
363 * @see #getChildCount()
364 */
365 @Transient
366 public boolean isLeaf() {
367 return children.size() < 1;
368 }
369
370 /**
371 * Whether <code>this</code> node is the root node of the associated {@link FeatureTree feature tree}.
372 *
373 * @return <code>true</code> if <code>this</code> is the feature trees root node, <code>false</code> if not
374 */
375 @Transient
376 public boolean isRoot(){
377 if(getFeatureTree() != null){
378 return this.equals(getFeatureTree().getRoot());
379 }
380 return false;
381 }
382
383 /**
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
390 * 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.
394 *
395 * @see #addApplicableState(State)
396 * @see #removeApplicableState(State)
397 */
398 public Set<State> getOnlyApplicableIf() {
399 return onlyApplicableIf;
400 }
401
402 /**
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>
406 *
407 * @param applicableState the applicable state to be added to <i>this</i> feature node
408 * @see #getApplicableState()
409 */
410 public void addApplicableState(State applicableState) {
411 logger.debug("addApplicableState");
412 this.onlyApplicableIf.add(applicableState);
413 }
414
415 /**
416 * Removes one element from the set of
417 * {@link #getOnlyApplicableIf() applicable states} described in
418 * <i>this</i> feature node.<BR>
419 *
420 * @param applicableState the applicable state which should be removed
421 * @see #getApplicableState()
422 * @see #addApplicableState(State)
423 */
424 public void removeApplicableState(State applicableState) {
425 this.onlyApplicableIf.remove(applicableState);
426 }
427
428 /**
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.
438 *
439 * @see #addInapplicableState(State)
440 * @see #removeInapplicableState(State)
441 */
442 public Set<State> getInapplicableIf() {
443 return inapplicableIf;
444 }
445
446 /**
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>
450 *
451 * @param inapplicableState the inapplicable state to be added to <i>this</i> feature node
452 * @see #getInapplicableState()
453 */
454 public void addInapplicableState(State inapplicableState) {
455 logger.debug("addInapplicableState");
456 this.inapplicableIf.add(inapplicableState);
457 }
458
459 /**
460 * Removes one element from the set of
461 * {@link #getInapplicableIf() inapplicable states} described in
462 * <i>this</i> feature node.<BR>
463 *
464 * @param inapplicableState the inapplicable state which should be removed
465 * @see #getInapplicableState()
466 * @see #addInapplicableState(State)
467 */
468 public void removeInapplicableState(State inapplicableState) {
469 this.inapplicableIf.remove(inapplicableState);
470 }
471
472 // //** ********************** QUESTIONS ******************************/
473 //
474 // /**
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}.
479 // */
480 // public Set<Representation> getQuestions() {
481 // return this.questions;
482 // }
483 //
484 // public void addQuestion(Representation question) {
485 // this.questions.add(question);
486 // }
487 //
488 // public void removeQuestion(Representation question) {
489 // this.questions.remove(question);
490 // }
491 //
492 // @Transient
493 // public Representation getQuestion(Language lang) {
494 // for (Representation question : questions){
495 // Language reprLanguage = question.getLanguage();
496 // if (reprLanguage != null && reprLanguage.equals(lang)){
497 // return question;
498 // }
499 // }
500 // return null;
501 // }
502
503 /**
504 * Returns all features that are contained in this node or a child node
505 *
506 * @param featureNode
507 * @param features
508 * @return
509 */
510 @Transient
511 public Set<Feature> getDistinctFeaturesRecursive(Set<Feature> features){
512 Feature feature = this.getFeature();
513
514 features.add(feature);
515
516 for(FeatureNode childNode : this.getChildNodes()){
517 features.addAll(childNode.getDistinctFeaturesRecursive(features));
518 }
519
520 return features;
521 }
522
523 public FeatureNode cloneDescendants(){
524 FeatureNode clone = (FeatureNode)this.clone();
525 FeatureNode childClone;
526
527 for(FeatureNode childNode : this.getChildNodes()){
528 childClone = (FeatureNode) childNode.clone();
529 for (FeatureNode childChild:childNode.getChildNodes()){
530 childClone.addChild(childChild.cloneDescendants());
531 }
532 clone.addChild(childClone);
533
534 }
535 return clone;
536 }
537
538 //*********************** CLONE ********************************************************/
539
540 /**
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
546 *
547 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
548 * @see java.lang.Object#clone()
549 */
550 @Override
551 public Object clone() {
552 FeatureNode result;
553 try {
554 result = (FeatureNode)super.clone();
555 result.children = new ArrayList<FeatureNode>();
556 return result;
557 }catch (CloneNotSupportedException e) {
558 logger.warn("Object does not implement cloneable");
559 e.printStackTrace();
560 return null;
561 }
562
563
564
565 }
566
567 // ********************** TREE NODE METHODS ******************************/
568
569 @Override
570 public String treeIndex() {
571 return this.treeIndex;
572 }
573
574 @Override
575 @Deprecated
576 public void setTreeIndex(String newTreeIndex) {
577 this.treeIndex = newTreeIndex;
578 }
579
580
581 @Override
582 @Deprecated
583 public int treeId() {
584 if (this.featureTree == null){
585 return -1;
586 }else{
587 return this.featureTree.getId();
588 }
589 }
590
591
592 // //** ********************** TAXON ******************************/
593 //
594 // /**
595 // * Returns the {@link Taxon taxon} <i>this</i> terminal node is
596 // * associated with.
597 // */
598 // public Taxon getTaxon() {
599 // return taxon;
600 // }
601 //
602 // /**
603 // * Assigns the given taxon to <i>this</i> feature node.
604 // *
605 // * @param taxon the taxon to be set
606 // * @see #getTaxon()
607 // */
608 // public void setTaxon(Taxon taxon) {
609 // this.taxon = taxon;
610 // }
611 }