Project

General

Profile

Download (18.7 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.Index;
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.Table;
29
import javax.persistence.Transient;
30
import javax.xml.bind.annotation.XmlAccessType;
31
import javax.xml.bind.annotation.XmlAccessorType;
32
import javax.xml.bind.annotation.XmlElement;
33
import javax.xml.bind.annotation.XmlElementWrapper;
34
import javax.xml.bind.annotation.XmlIDREF;
35
import javax.xml.bind.annotation.XmlRootElement;
36
import javax.xml.bind.annotation.XmlSchemaType;
37
import javax.xml.bind.annotation.XmlType;
38

    
39
import org.apache.log4j.Logger;
40
import org.hibernate.annotations.Cascade;
41
import org.hibernate.annotations.CascadeType;
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
 * @since 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(name="FeatureNode", indexes = { @Index(name = "featureNodeTreeIndex", columnList = "treeIndex") })
76
public class FeatureNode extends VersionableEntity
77
            implements ITreeNode<FeatureNode>, Cloneable {
78
	private static final Logger logger = Logger.getLogger(FeatureNode.class);
79

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

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

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

    
105

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

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

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

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

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

    
140
// ***************************** FACTORY *********************************/
141

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

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

    
164
// ******************** CONSTRUCTOR ***************************************/
165

    
166

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

    
174

    
175
//*************************** TREE ************************************/
176

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

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

    
185
//** ********************** FEATURE ******************************/
186

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

    
200
//** ********************** PARENT ******************************/
201

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

    
223
//** ********************** CHILDREN ******************************/
224

    
225

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

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

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

    
293

    
294
    /**
295
	 * Removes the given feature node from the list of {@link #getChildNodes() children}
296
	 * of <i>this</i> feature node.
297
	 *
298
	 * @param  child	the feature node which should be removed
299
	 * @see     		#getChildNodes()
300
	 * @see				#addChild(FeatureNode, int)
301
	 * @see				#addChild(FeatureNode)
302
	 * @see				#removeChild(int)
303
	 */
304
	public void removeChild(FeatureNode child){
305

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
534
		if(feature!=null){
535
		    features.add(feature);
536
		}
537

    
538
		for(FeatureNode childNode : this.getChildNodes()){
539
			features.addAll(childNode.getDistinctFeaturesRecursive(features));
540
		}
541

    
542
		return features;
543
	}
544

    
545
	public FeatureNode cloneDescendants(){
546
		FeatureNode clone = (FeatureNode)this.clone();
547
		FeatureNode childClone;
548

    
549
		for(FeatureNode childNode : this.getChildNodes()){
550
			childClone = (FeatureNode) childNode.clone();
551
			for (FeatureNode childChild:childNode.getChildNodes()){
552
				childClone.addChild(childChild.cloneDescendants());
553
			}
554
			clone.addChild(childClone);
555

    
556
		}
557
		return clone;
558
	}
559

    
560
//*********************** CLONE ********************************************************/
561

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

    
585

    
586

    
587
	}
588

    
589
// ********************** TREE NODE METHODS ******************************/
590

    
591
	@Override
592
	public String treeIndex() {
593
		return this.treeIndex;
594
	}    @Override
595
    public String treeIndexLike() {
596
        return treeIndex + "%";
597
    }
598
    @Override
599
    public String treeIndexWc() {
600
        return treeIndex + "*";
601
    }
602

    
603
	@Override
604
	@Deprecated
605
	public void setTreeIndex(String newTreeIndex) {
606
		this.treeIndex = newTreeIndex;
607
	}
608

    
609

    
610
	@Override
611
	@Deprecated
612
	public int treeId() {
613
		if (this.featureTree == null){
614
			return -1;
615
		}else{
616
			return this.featureTree.getId();
617
		}
618
	}
619

    
620
	private void updateSortIndex(){
621
	 // TODO workaround (see sortIndex doc)
622
        for (int i = 0; i < children.size(); i++) {
623
            children.get(i).setSortIndex(i);
624
        }
625
	}
626

    
627
	public void removeNullValueFromChildren(){
628
	    HHH_9751_Util.removeAllNull(children);
629
	    updateSortIndex();
630
	}
631

    
632

    
633
}
(11-11/37)