Project

General

Profile

Download (18.1 KB) Statistics
| Branch: | Tag: | Revision:
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.MERGE, 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,CascadeType.MERGE})
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,CascadeType.MERGE})
126
	@JoinTable(name="FeatureNode_DefinedTermBase_OnlyApplicable")
127
	private final 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,CascadeType.MERGE})
135
	@JoinTable(name="FeatureNode_DefinedTermBase_InapplicableIf")
136
	private final Set<State> inapplicableIf = new HashSet<State>();
137

    
138
// ***************************** FACTORY *********************************/
139

    
140
	/**
141
	 * Creates a new empty feature node instance.
142
	 *
143
	 * @see #NewInstance(Feature)
144
	 */
145
	public static FeatureNode NewInstance(){
146
		return new FeatureNode();
147
	}
148

    
149
	/**
150
	 * Creates a new feature node instance only with the given {@link Feature feature}
151
	 * (without parent and children).
152
	 *
153
	 * @param	feature	the feature assigned to the new feature node
154
	 * @see 			#NewInstance()
155
	 */
156
	public static FeatureNode NewInstance(Feature feature){
157
		FeatureNode result = new FeatureNode();
158
		result.setFeature(feature);
159
		return result;
160
	}
161

    
162
// ******************** CONSTRUCTOR ***************************************/
163

    
164

    
165
	/**
166
	 * Class constructor: creates a new empty feature node instance.
167
	 */
168
	protected FeatureNode() {
169
		super();
170
	}
171

    
172

    
173
//*************************** TREE ************************************/
174

    
175
	public FeatureTree getFeatureTree() {
176
		return featureTree;
177
	}
178

    
179
	protected void setFeatureTree(FeatureTree featureTree) {
180
		this.featureTree = featureTree;
181
	}
182

    
183
//** ********************** FEATURE ******************************/
184

    
185
	/**
186
	 * Returns the {@link Feature feature} <i>this</i> feature node is based on.
187
	 */
188
	public Feature getFeature() {
189
		return feature;
190
	}
191
	/**
192
	 * @see	#getFeature()
193
	 */
194
	public void setFeature(Feature feature) {
195
		this.feature = feature;
196
	}
197

    
198
//** ********************** PARENT ******************************/
199

    
200
	/**
201
	 * Returns the feature node <i>this</i> feature node is a child of.
202
	 *
203
	 * @see	#getChildNodes()
204
	 */
205
	@Override
206
    public FeatureNode getParent() {
207
		return parent;
208
	}
209
	/**
210
	 * Assigns the given feature node as the parent of <i>this</i> feature node.
211
	 * Due to bidirectionality this method must also add <i>this</i> feature node
212
	 * to the list of children of the given parent.
213
	 *
214
	 * @param	parent	the feature node to be set as parent
215
	 * @see				#getParent()
216
	 */
217
	protected void setParent(FeatureNode parent) {
218
		this.parent = parent;
219
	}
220

    
221
//** ********************** CHILDREN ******************************/
222

    
223

    
224
	/**
225
	 * @deprecated for internal use only.
226
	 */
227
	//see #4278 , #4200
228
	@Deprecated
229
    protected void setSortIndex(Integer sortIndex) {
230
		this.sortIndex = sortIndex;
231
	}
232

    
233
	/**
234
	 * Returns the (ordered) list of feature nodes which are children nodes of
235
	 * <i>this</i> feature node.
236
	 */
237
	@Override
238
    public List<FeatureNode> getChildNodes() {
239
		return children;
240
	}
241

    
242
	/**
243
	 * Adds the given feature node at the end of the list of children of
244
	 * <i>this</i> feature node. Due to bidirectionality this method must also
245
	 * assign <i>this</i> feature node as the parent of the given child.
246
	 *
247
	 * @param	child	the feature node to be added
248
	 * @see				#getChildNodes()
249
	 * @see				#setChildren(List)
250
	 * @see				#addChild(FeatureNode, int)
251
	 * @see				#removeChild(FeatureNode)
252
	 * @see				#removeChild(int)
253
	 */
254
	public void addChild(FeatureNode child){
255
		addChild(child, children.size());
256
	}
257
	/**
258
	 * Inserts the given feature node in the list of children of <i>this</i> feature node
259
	 * at the given (index + 1) position. If the given index is out of bounds
260
	 * an exception will arise.<BR>
261
	 * Due to bidirectionality this method must also assign <i>this</i> feature node
262
	 * as the parent of the given child.
263
	 *
264
	 * @param	child	the feature node to be added
265
	 * @param	index	the integer indicating the position at which the child
266
	 * 					should be added
267
	 * @see				#getChildNodes()
268
	 * @see				#setChildren(List)
269
	 * @see				#addChild(FeatureNode)
270
	 * @see				#removeChild(FeatureNode)
271
	 * @see				#removeChild(int)
272
	 */
273
	public void addChild(FeatureNode child, int index){
274
	    List<FeatureNode> children = this.getChildNodes();
275
		if (index < 0 || index > children.size() + 1){
276
			throw new IndexOutOfBoundsException("Wrong index: " + index);
277
		}
278
		if (child.getParent() != null){
279
			child.getParent().removeChild(child);
280
		}
281
		child.setParent(this);
282
		child.setFeatureTree(this.getFeatureTree());
283
		children.add(index, child);
284
		//TODO workaround (see sortIndex doc)
285
		for(int i = 0; i < children.size(); i++){
286
			children.get(i).setSortIndex(i);
287
		}
288
		child.setSortIndex(index);
289
	}
290
	/**
291
	 * Removes the given feature node from the list of {@link #getChildNodes() children}
292
	 * of <i>this</i> feature node.
293
	 *
294
	 * @param  child	the feature node which should be removed
295
	 * @see     		#getChildNodes()
296
	 * @see				#addChild(FeatureNode, int)
297
	 * @see				#addChild(FeatureNode)
298
	 * @see				#removeChild(int)
299
	 */
300
	public void removeChild(FeatureNode child){
301
		int index = children.indexOf(child);
302
		if (index >= 0){
303
			removeChild(index);
304
		}
305
	}
306
	/**
307
	 * Removes the feature node placed at the given (index + 1) position from
308
	 * the list of {@link #getChildNodes() children} of <i>this</i> feature node.
309
	 * If the given index is out of bounds no child will be removed.
310
	 *
311
	 * @param  index	the integer indicating the position of the feature node to
312
	 * 					be removed
313
	 * @see     		#getChildNodes()
314
	 * @see				#addChild(FeatureNode, int)
315
	 * @see				#addChild(FeatureNode)
316
	 * @see				#removeChild(FeatureNode)
317
	 */
318
	public void removeChild(int index){
319
		FeatureNode child = children.get(index);
320
		if (child != null){
321
			children.remove(index);
322
			child.setParent(null);
323
			//TODO workaround (see sortIndex doc)
324
			for(int i = 0; i < children.size(); i++){
325
				FeatureNode childAt = children.get(i);
326
				childAt.setSortIndex(i);
327
			}
328
			child.setSortIndex(null);
329
		}
330
	}
331

    
332
	/**
333
	 * Returns the feature node placed at the given (childIndex + 1) position
334
	 * within the list of {@link #getChildNodes() children} of <i>this</i> feature node.
335
	 * If the given index is out of bounds no child will be returned.
336
	 *
337
	 * @param  childIndex	the integer indicating the position of the feature node
338
	 * @see     			#getChildNodes()
339
	 * @see					#addChild(FeatureNode, int)
340
	 * @see					#removeChild(int)
341
	 */
342
	public FeatureNode getChildAt(int childIndex) {
343
			return children.get(childIndex);
344
	}
345

    
346
	/**
347
	 * Returns the number of children nodes of <i>this</i> feature node.
348
	 *
349
	 * @see	#getChildNodes()
350
	 */
351
	@Transient
352
	public int getChildCount() {
353
		return children.size();
354
	}
355

    
356
	/**
357
	 * Returns the integer indicating the position of the given feature node
358
	 * within the list of {@link #getChildNodes() children} of <i>this</i> feature node.
359
	 * If the list does not contain this node then -1 will be returned.
360
	 *
361
	 * @param  node	the feature node the position of which is being searched
362
	 * @see			#addChild(FeatureNode, int)
363
	 * @see			#removeChild(int)
364
	 */
365
	public int getIndex(FeatureNode node) {
366
		if (! children.contains(node)){
367
			return -1;
368
		}else{
369
			return children.indexOf(node);
370
		}
371
	}
372

    
373
	/**
374
	 * Returns the boolean value indicating if <i>this</i> feature node has
375
	 * children (false) or not (true). A node without children is at the
376
	 * bottommost level of a tree and is called a leaf.
377
	 *
378
	 * @see	#getChildNodes()
379
	 * @see	#getChildCount()
380
	 */
381
	@Transient
382
	public boolean isLeaf() {
383
		return children.size() < 1;
384
	}
385

    
386
	/**
387
	 * Whether <code>this</code> node is the root node of the associated {@link FeatureTree feature tree}.
388
	 *
389
	 * @return <code>true</code> if <code>this</code> is the feature trees root node, <code>false</code> if not
390
	 */
391
	@Transient
392
	public boolean isRoot(){
393
		if(getFeatureTree() != null){
394
			return this.equals(getFeatureTree().getRoot());
395
		}
396
		return false;
397
	}
398

    
399
	/**
400
	 * Returns the set of {@link State states} implying rendering the
401
	 * concerned {@link Feature feature} applicable.
402
	 * If at least one state is present in this set, in a given description
403
	 * the {@link Feature feature} in <i>this</i> feature node is inapplicable
404
	 * unless any of the listed controlling states is present in the parent
405
	 * {@link Feature feature} description element {@link CategoricalData
406
	 * categoricalData}.
407
	 * This attribute is not equivalent to onlyApplicableIf in SDD as it is
408
	 * attached directly to the child feature rather than the parent, which
409
	 * allow having different applicable states for each child feature.
410
	 *
411
	 * @see    #addApplicableState(State)
412
	 * @see    #removeApplicableState(State)
413
	 */
414
	public Set<State> getOnlyApplicableIf() {
415
		return onlyApplicableIf;
416
	}
417

    
418
	/**
419
	 * Adds an existing {@link State applicable state} to the set of
420
	 * {@link #getOnlyApplicableIf() applicable states} described in
421
	 * <i>this</i> feature node.<BR>
422
	 *
423
	 * @param applicableState	the applicable state to be added to <i>this</i> feature node
424
	 * @see    	   								#getApplicableState()
425
	 */
426
	public void addApplicableState(State applicableState) {
427
		this.onlyApplicableIf.add(applicableState);
428
	}
429

    
430
	/**
431
	 * Removes one element from the set of
432
	 * {@link #getOnlyApplicableIf() applicable states} described in
433
	 * <i>this</i> feature node.<BR>
434
	 *
435
	 * @param  applicableState   the applicable state which should be removed
436
	 * @see    	   								#getApplicableState()
437
	 * @see     		  						#addApplicableState(State)
438
	 */
439
	public void removeApplicableState(State applicableState) {
440
		this.onlyApplicableIf.remove(applicableState);
441
	}
442

    
443
	/**
444
	 * Returns the set of {@link State states} implying rendering the
445
	 * concerned {@link Feature feature} inapplicable.
446
	 * If at least one {@link State inapplicable state} is defined in the set,
447
	 * in a given description the {@link Feature feature} attribute of
448
	 * <i>this</i> feature node is inapplicable when any of the listed
449
	 * controlling states is present.
450
	 * This attribute is not equivalent to inapplicableIf in SDD as it is
451
	 * attached directly to the child feature rather than the parent, which
452
	 * allow having different inapplicability rules for each child feature.
453
	 *
454
	 * @see    #addInapplicableState(State)
455
	 * @see    #removeInapplicableState(State)
456
	 */
457
	public Set<State> getInapplicableIf() {
458
		return inapplicableIf;
459
	}
460

    
461
	/**
462
	 * Adds an existing {@link State inapplicable state} to the set of
463
	 * {@link #getInapplicableIf() inapplicable states} described in
464
	 * <i>this</i> feature node.<BR>
465
	 *
466
	 * @param inapplicableState	the inapplicable state to be added to <i>this</i> feature node
467
	 * @see    	   								#getInapplicableState()
468
	 */
469
	public void addInapplicableState(State inapplicableState) {
470
		this.inapplicableIf.add(inapplicableState);
471
	}
472

    
473
	/**
474
	 * Removes one element from the set of
475
	 * {@link #getInapplicableIf() inapplicable states} described in
476
	 * <i>this</i> feature node.<BR>
477
	 *
478
	 * @param  inapplicableState   the inapplicable state which should be removed
479
	 * @see    	   								#getInapplicableState()
480
	 * @see     		  						#addInapplicableState(State)
481
	 */
482
	public void removeInapplicableState(State inapplicableState) {
483
		this.inapplicableIf.remove(inapplicableState);
484
	}
485

    
486
//	//** ********************** QUESTIONS ******************************/
487
//
488
//	/**
489
//	 * Returns the {@link Representation question} formulation that
490
//	 * corresponds to <i>this</i> feature node and the corresponding
491
//	 * {@link Feature feature} in case it is part of a
492
//	 * {@link PolytomousKey polytomous key}.
493
//	 */
494
//	public Set<Representation> getQuestions() {
495
//		return this.questions;
496
//	}
497
//
498
//	public void addQuestion(Representation question) {
499
//		this.questions.add(question);
500
//	}
501
//
502
//	public void removeQuestion(Representation question) {
503
//		this.questions.remove(question);
504
//	}
505
//
506
//	@Transient
507
//	public Representation getQuestion(Language lang) {
508
//		for (Representation question : questions){
509
//			Language reprLanguage = question.getLanguage();
510
//			if (reprLanguage != null && reprLanguage.equals(lang)){
511
//				return question;
512
//			}
513
//		}
514
//		return null;
515
//	}
516

    
517
	/**
518
	 * Returns all features that are contained in this node or a child node
519
	 *
520
	 * @param featureNode
521
	 * @param features
522
	 * @return
523
	 */
524
	@Transient
525
	public Set<Feature> getDistinctFeaturesRecursive(Set<Feature> features){
526
		Feature feature = this.getFeature();
527

    
528
		features.add(feature);
529

    
530
		for(FeatureNode childNode : this.getChildNodes()){
531
			features.addAll(childNode.getDistinctFeaturesRecursive(features));
532
		}
533

    
534
		return features;
535
	}
536

    
537
	public FeatureNode cloneDescendants(){
538
		FeatureNode clone = (FeatureNode)this.clone();
539
		FeatureNode childClone;
540

    
541
		for(FeatureNode childNode : this.getChildNodes()){
542
			childClone = (FeatureNode) childNode.clone();
543
			for (FeatureNode childChild:childNode.getChildNodes()){
544
				childClone.addChild(childChild.cloneDescendants());
545
			}
546
			clone.addChild(childClone);
547

    
548
		}
549
		return clone;
550
	}
551

    
552
//*********************** CLONE ********************************************************/
553

    
554
	/**
555
	 * Clones <i>this</i> FeatureNode. This is a shortcut that enables to create
556
	 * a new instance that differs only slightly from <i>this</i> FeatureNode by
557
	 * modifying only some of the attributes.
558
	 * The parent, the feature and the featureTree are the are the same as for the original feature node
559
	 * the children are removed
560
	 *
561
	 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
562
	 * @see java.lang.Object#clone()
563
	 */
564
	@Override
565
	public Object clone() {
566
		FeatureNode result;
567
		try {
568
			result = (FeatureNode)super.clone();
569
			result.children = new ArrayList<FeatureNode>();
570
			return result;
571
		}catch (CloneNotSupportedException e) {
572
			logger.warn("Object does not implement cloneable");
573
			e.printStackTrace();
574
			return null;
575
		}
576

    
577

    
578

    
579
	}
580

    
581
// ********************** TREE NODE METHODS ******************************/
582

    
583
	@Override
584
	public String treeIndex() {
585
		return this.treeIndex;
586
	}
587

    
588
	@Override
589
	@Deprecated
590
	public void setTreeIndex(String newTreeIndex) {
591
		this.treeIndex = newTreeIndex;
592
	}
593

    
594

    
595
	@Override
596
	@Deprecated
597
	public int treeId() {
598
		if (this.featureTree == null){
599
			return -1;
600
		}else{
601
			return this.featureTree.getId();
602
		}
603
	}
604

    
605

    
606
}
(9-9/36)