Project

General

Profile

Download (18.5 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.Column;
18
import javax.persistence.Entity;
19
import javax.persistence.FetchType;
20
import javax.persistence.JoinColumn;
21
import javax.persistence.JoinTable;
22
import javax.persistence.ManyToMany;
23
import javax.persistence.ManyToOne;
24
import javax.persistence.OneToMany;
25
import javax.persistence.OrderBy;
26
import javax.persistence.OrderColumn;
27
import javax.persistence.Transient;
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.hibernate.HHH_9751_Util;
45
import eu.etaxonomy.cdm.model.common.ITreeNode;
46
import eu.etaxonomy.cdm.model.common.VersionableEntity;
47

    
48
/**
49
 * The class for tree nodes within a {@link FeatureTree feature tree} structure.
50
 * Feature nodes are the elementary components of such a tree since they might
51
 * be related to other nodes as a parent or as a child. A feature node belongs
52
 * at most to one feature tree. It cannot have more than one parent node but
53
 * may have several child nodes. Parent/child relations are bidirectional:
54
 * a node N1 is the parent of a node N2 if and only if the node N2 is a child of
55
 * the node N1.
56
 *
57
 * @author  m.doering
58
 * @created 08-Nov-2007 13:06:16
59
 */
60
@SuppressWarnings("serial")
61
@XmlAccessorType(XmlAccessType.FIELD)
62
@XmlType(name = "FeatureNode", propOrder = {
63
		"featureTree",
64
		"feature",
65
		"parent",
66
		"treeIndex",
67
		"sortIndex",
68
		"children",
69
		"onlyApplicableIf",
70
		"inapplicableIf"
71
})
72
@XmlRootElement(name = "FeatureNode")
73
@Entity
74
@Audited
75
@Table(appliesTo="FeatureNode", indexes = { @Index(name = "featureNodeTreeIndex", columnNames = { "treeIndex" }) })
76
public class FeatureNode extends VersionableEntity implements ITreeNode<FeatureNode>, Cloneable {
77
	private static final Logger logger = Logger.getLogger(FeatureNode.class);
78

    
79
    //This is the main key a node belongs to. Although other keys may also reference
80
	//<code>this</code> node, a node usually belongs to a given key.
81
	@XmlElement(name = "FeatureTree")
82
    @XmlIDREF
83
    @XmlSchemaType(name = "IDREF")
84
    @ManyToOne(fetch = FetchType.LAZY)
85
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE}) //TODO this usage is incorrect, needed only for OneToMany, check why it is here, can it be removed??
86
	 //TODO Val #3379
87
//    @NotNull
88
	private FeatureTree featureTree;
89

    
90
	@XmlElement(name = "Feature")
91
    @XmlIDREF
92
    @XmlSchemaType(name = "IDREF")
93
    @ManyToOne(fetch = FetchType.LAZY)
94
	private Feature feature;
95

    
96
    @XmlElement(name = "Parent")
97
    @XmlIDREF
98
    @XmlSchemaType(name = "IDREF")
99
    @ManyToOne(fetch = FetchType.LAZY, targetEntity=FeatureNode.class)
100
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
101
	@JoinColumn(name="parent_id")
102
	private FeatureNode parent;
103

    
104

    
105
    @XmlElement(name = "treeIndex")
106
    @Column(length=255)
107
    private String treeIndex;
108

    
109
    @XmlElementWrapper(name = "Children")
110
    @XmlElement(name = "Child")
111
    //see https://dev.e-taxonomy.eu/trac/ticket/3722
112
    @OrderColumn(name="sortIndex")
113
    @OrderBy("sortIndex")
114
	@OneToMany(fetch = FetchType.LAZY, mappedBy="parent")
115
	@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
116
	private List<FeatureNode> children = new ArrayList<FeatureNode>();
117

    
118
    //see https://dev.e-taxonomy.eu/trac/ticket/3722
119
    private Integer sortIndex;
120

    
121
	@XmlElementWrapper(name = "OnlyApplicableIf")
122
	@XmlElement(name = "OnlyApplicableIf")
123
	@XmlIDREF
124
	@XmlSchemaType(name="IDREF")
125
	@ManyToMany(fetch = FetchType.LAZY)
126
//	@Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})  remove cascade #5755
127
	@JoinTable(name="FeatureNode_DefinedTermBase_OnlyApplicable")
128
	private final Set<State> onlyApplicableIf = new HashSet<State>();
129

    
130
	@XmlElementWrapper(name = "InapplicableIf")
131
	@XmlElement(name = "InapplicableIf")
132
	@XmlIDREF
133
	@XmlSchemaType(name="IDREF")
134
	@ManyToMany(fetch = FetchType.LAZY)
135
//	@Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})  remove cascade #5755
136
	@JoinTable(name="FeatureNode_DefinedTermBase_InapplicableIf")
137
	private final Set<State> inapplicableIf = new HashSet<State>();
138

    
139
// ***************************** FACTORY *********************************/
140

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

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

    
163
// ******************** CONSTRUCTOR ***************************************/
164

    
165

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

    
173

    
174
//*************************** TREE ************************************/
175

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

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

    
184
//** ********************** FEATURE ******************************/
185

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

    
199
//** ********************** PARENT ******************************/
200

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

    
222
//** ********************** CHILDREN ******************************/
223

    
224

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

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

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

    
303
	    int index = children.indexOf(child);
304
		if (index >= 0){
305
			removeChild(index);
306
		}
307
	}
308
	/**
309
	 * Removes the feature node placed at the given (index + 1) position from
310
	 * the list of {@link #getChildNodes() children} of <i>this</i> feature node.
311
	 * If the given index is out of bounds no child will be removed.
312
	 *
313
	 * @param  index	the integer indicating the position of the feature node to
314
	 * 					be removed
315
	 * @see     		#getChildNodes()
316
	 * @see				#addChild(FeatureNode, int)
317
	 * @see				#addChild(FeatureNode)
318
	 * @see				#removeChild(FeatureNode)
319
	 */
320
	public void removeChild(int index){
321
	   FeatureNode child = children.get(index);
322
	   if (child != null){
323
			children.remove(index);
324
			child.setParent(null);
325
			child.setFeatureTree(null);
326
			//TODO workaround (see sortIndex doc)
327
			for(int i = 0; i < children.size(); i++){
328
				FeatureNode childAt = children.get(i);
329
				childAt.setSortIndex(i);
330
			}
331
			child.setSortIndex(null);
332
		}
333
	}
334

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

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

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

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

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

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

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

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

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

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

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

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

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

    
531
		features.add(feature);
532

    
533
		for(FeatureNode childNode : this.getChildNodes()){
534
			features.addAll(childNode.getDistinctFeaturesRecursive(features));
535
		}
536

    
537
		return features;
538
	}
539

    
540
	public FeatureNode cloneDescendants(){
541
		FeatureNode clone = (FeatureNode)this.clone();
542
		FeatureNode childClone;
543

    
544
		for(FeatureNode childNode : this.getChildNodes()){
545
			childClone = (FeatureNode) childNode.clone();
546
			for (FeatureNode childChild:childNode.getChildNodes()){
547
				childClone.addChild(childChild.cloneDescendants());
548
			}
549
			clone.addChild(childClone);
550

    
551
		}
552
		return clone;
553
	}
554

    
555
//*********************** CLONE ********************************************************/
556

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

    
580

    
581

    
582
	}
583

    
584
// ********************** TREE NODE METHODS ******************************/
585

    
586
	@Override
587
	public String treeIndex() {
588
		return this.treeIndex;
589
	}
590

    
591
	@Override
592
	@Deprecated
593
	public void setTreeIndex(String newTreeIndex) {
594
		this.treeIndex = newTreeIndex;
595
	}
596

    
597

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

    
608
	private void updateSortIndex(){
609
	 // TODO workaround (see sortIndex doc)
610
        for (int i = 0; i < children.size(); i++) {
611
            children.get(i).setSortIndex(i);
612
        }
613
	}
614

    
615
	public void removeNullValueFromChildren(){
616
	    HHH_9751_Util.removeAllNull(children);
617
	    updateSortIndex();
618
	}
619

    
620

    
621
}
(9-9/36)