Project

General

Profile

Download (22.4 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.term;
11

    
12
import java.util.ArrayList;
13
import java.util.Collection;
14
import java.util.HashSet;
15
import java.util.List;
16
import java.util.Set;
17

    
18
import javax.persistence.Column;
19
import javax.persistence.Entity;
20
import javax.persistence.FetchType;
21
import javax.persistence.JoinColumn;
22
import javax.persistence.JoinTable;
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.XmlTransient;
36
import javax.xml.bind.annotation.XmlType;
37

    
38
import org.apache.commons.lang3.StringUtils;
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.description.CategoricalData;
47
import eu.etaxonomy.cdm.model.description.Feature;
48
import eu.etaxonomy.cdm.model.description.FeatureState;
49
import eu.etaxonomy.cdm.model.description.State;
50

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

    
80
    private static final Logger logger = Logger.getLogger(TermNode.class);
81

    
82
    @XmlElement(name = "Parent")
83
    @XmlIDREF
84
    @XmlSchemaType(name = "IDREF")
85
    @ManyToOne(fetch = FetchType.LAZY, targetEntity=TermNode.class)
86
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
87
	@JoinColumn(name="parent_id")
88
	private TermNode<T> parent;
89

    
90

    
91
    @XmlElement(name = "treeIndex")
92
    @Column(length=255)
93
    private String treeIndex;
94

    
95
    @XmlElementWrapper(name = "Children")
96
    @XmlElement(name = "Child")
97
    //see https://dev.e-taxonomy.eu/redmine/issues/3722
98
    @OrderColumn(name="sortIndex")
99
    @OrderBy("sortIndex")
100
	@OneToMany(fetch = FetchType.LAZY, mappedBy="parent", targetEntity=TermNode.class)
101
	@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
102
	private List<TermNode<T>> children = new ArrayList<>();
103

    
104
    //see https://dev.e-taxonomy.eu/redmine/issues/3722
105
    private Integer sortIndex;
106

    
107
    @XmlElementWrapper(name = "OnlyApplicableIf")
108
    @XmlElement(name = "OnlyApplicableIf")
109
    @XmlIDREF
110
    @XmlSchemaType(name="IDREF")
111
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
112
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
113
    @JoinTable(name="TermNode_OnlyApplicableIf")
114
    private final Set<FeatureState> onlyApplicableIf = new HashSet<>();
115

    
116
    @XmlElementWrapper(name = "InapplicableIf")
117
    @XmlElement(name = "InapplicableIf")
118
    @XmlIDREF
119
    @XmlSchemaType(name="IDREF")
120
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
121
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
122
    @JoinTable(name="TermNode_InapplicableIf")
123
    private final Set<FeatureState> inapplicableIf = new HashSet<>();
124

    
125
// ***************************** FACTORY *********************************/
126

    
127
	//no factory methods should be provided as TermNodes should only
128
	//be created as children of their parent node (#8257)
129

    
130
// ******************** CONSTRUCTOR ***************************************/
131

    
132
	//TODO needed?
133
    @Deprecated
134
    protected TermNode(){}
135

    
136
	/**
137
	 * Class constructor: creates a new empty feature node instance.
138
	 */
139
	protected TermNode(TermType termType) {
140
	    super(termType);
141
	}
142

    
143
//************************* PARENT ******************************/
144

    
145
	/**
146
	 * Returns the feature node <i>this</i> feature node is a child of.
147
	 *
148
	 * @see	#getChildNodes()
149
	 */
150
	@Override
151
    public TermNode<T> getParent() {
152
		return parent;
153
	}
154
	/**
155
	 * Assigns the given feature node as the parent of <i>this</i> feature node.
156
	 * Due to bidirectionality this method must also add <i>this</i> feature node
157
	 * to the list of children of the given parent.
158
	 *
159
	 * @param	parent	the feature node to be set as parent
160
	 * @see				#getParent()
161
	 */
162
	protected void setParent(TermNode<T> parent) {
163
	    this.parent = parent;
164
	}
165

    
166
//** ********************** CHILDREN ******************************/
167

    
168
	/**
169
	 * @deprecated for internal use only.
170
	 */
171
	//see #4278 , #4200
172
	@Deprecated
173
    protected void setSortIndex(Integer sortIndex) {
174
		this.sortIndex = sortIndex;
175
	}
176

    
177
	/**
178
     * Returns the (ordered) list of tree nodes which are children nodes of
179
     * <i>this</i> node.
180
	 */
181
	@Override
182
    public List<TermNode<T>> getChildNodes() {
183
	    return children;
184
	}
185

    
186
	/**
187
     * Adds the given term node at the end of the list of children of
188
     * <i>this</i> term node. Due to bidirectionality this method must
189
	 * also assign <i>this</i> feature node as the parent of the given child.
190
	 *
191
	 * @param	child	the feature node to be added
192
	 * @see				#getChildNodes()
193
	 * @see				#setChildren(List)
194
	 * @see				#addChild(TermNode, int)
195
	 * @see				#removeChild(TermNode)
196
	 * @see				#removeChild(int)
197
	 */
198
	public TermNode<T> addChild(TermNode<T> child){
199
		return addChild(child, children.size());
200
	}
201

    
202
	/**
203
     * Creates a new node without a term and adds it to the end of
204
     * the list of children of
205
     * <i>this</i> node. Due to bidirectionality this method must also
206
     * assign <i>this</i> feature node as the parent of the new child.
207
     *
208
     * @return the newly created child node
209
     * @see             #getChildNodes()
210
     * @see             #setChildren(List)
211
     * @see             #removeChild(TermNode)
212
     * @see             #removeChild(int)
213
     */
214
    public TermNode<T> addChild(){
215
        return addChild((T)null, children.size());
216
    }
217

    
218
	/**
219
	 * Creates a new node for the given term and adds it to the end of
220
	 * the list of children of
221
	 * <i>this</i> node. Due to bidirectionality this method must also
222
	 * assign <i>this</i> feature node as the parent of the new child.
223
	 *
224
	 * @param	term	the term to be added
225
	 * @return the newly created child node
226
	 * @see				#getChildNodes()
227
	 * @see				#setChildren(List)
228
	 * @see				#removeChild(TermNode)
229
	 * @see				#removeChild(int)
230
	 */
231
	public TermNode<T> addChild(T term){
232
	    return addChild(term, children.size());
233
	}
234

    
235
    /**
236
     * Creates a new node for the given term and adds it at the
237
     * given (index + 1) position of the list of children of
238
     * <i>this</i> node. Due to bidirectionality this method must also
239
     * assign <i>this</i> feature node as the parent of the new child.
240
     *
241
     * @param   term    the term to be added
242
     * @return the newly created child node
243
     * @see             #getChildNodes()
244
     * @see             #setChildren(List)
245
     * @see             #removeChild(TermNode)
246
     * @see             #removeChild(int)
247
     */
248
	public TermNode<T> addChild(T term, int index){
249
	    TermNode<T> child = new TermNode<>(getTermType());
250
	    if(term!=null){
251
	        child.setTerm(term);
252
	    }
253
	    checkTermType(child);
254

    
255
	    List<TermNode<T>> children = this.getChildNodes();
256
	    if (index < 0 || index > children.size() + 1){
257
	        throw new IndexOutOfBoundsException("Wrong index: " + index);
258
	    }
259
	    child.setParent(this);
260
	    child.setGraph(this.getGraph());
261
	    children.add(index, child);
262
	    //TODO workaround (see sortIndex doc)
263
	    for(int i = 0; i < children.size(); i++){
264
	        if (children.get(i) != null){
265
	            children.get(i).setSortIndex(i);
266
	        }
267
	    }
268
	    child.setSortIndex(index);
269
	    return child;
270
	}
271

    
272
	/**
273
	 * Inserts the given feature node in the list of children of <i>this</i> feature node
274
	 * at the given (index + 1) position. If the given index is out of bounds
275
	 * an exception will arise.<BR>
276
	 * Due to bidirectionality this method must also assign <i>this</i> feature node
277
	 * as the parent of the given child.
278
	 *
279
	 * @param	child	the feature node to be added
280
	 * @param	index	the integer indicating the position at which the child
281
	 * 					should be added
282
	 * @see				#getChildNodes()
283
	 * @see				#setChildren(List)
284
	 * @see				#addChild(TermNode)
285
	 * @see				#removeChild(TermNode)
286
	 * @see				#removeChild(int)
287
	 */
288
	public TermNode<T> addChild(TermNode<T> child, int index){
289
	    checkTermType(child);
290
	    List<TermNode<T>> children = this.getChildNodes();
291
		if (index < 0 || index > children.size() + 1){
292
			throw new IndexOutOfBoundsException("Wrong index: " + index);
293
		}
294
		if (child.getParent() != null){
295
			child.getParent().removeChild(child);
296
		}
297
		child.setParent(this);
298
		child.setGraph(this.getGraph());
299
		children.add(index, child);
300
		//TODO workaround (see sortIndex doc)
301
		for(int i = 0; i < children.size(); i++){
302
			children.get(i).setSortIndex(i);
303
		}
304
		child.setSortIndex(index);
305
	    return child;
306
	}
307

    
308

    
309
    /**
310
	 * Removes the given feature node from the list of {@link #getChildNodes() children}
311
	 * of <i>this</i> feature node.
312
	 *
313
	 * @param  child	the feature node which should be removed
314
	 * @see     		#getChildNodes()
315
	 * @see				#addChild(TermNode, int)
316
	 * @see				#addChild(TermNode)
317
	 * @see				#removeChild(int)
318
	 */
319
	public void removeChild(TermNode<T> child){
320

    
321
	    int index = children.indexOf(child);
322
		if (index >= 0){
323
			removeChild(index);
324
		}
325
	}
326
	/**
327
	 * Removes the feature node placed at the given (index + 1) position from
328
	 * the list of {@link #getChildNodes() children} of <i>this</i> feature node.
329
	 * If the given index is out of bounds no child will be removed.
330
	 *
331
	 * @param  index	the integer indicating the position of the feature node to
332
	 * 					be removed
333
	 * @see     		#getChildNodes()
334
	 * @see				#addChild(TermNode, int)
335
	 * @see				#addChild(TermNode)
336
	 * @see				#removeChild(TermNode)
337
	 */
338
	public void removeChild(int index){
339
	   TermNode<T> child = children.get(index);
340
	   if (child != null){
341
			children.remove(index);
342
			child.setParent(null);
343
			child.setGraph(null);
344
			//TODO workaround (see sortIndex doc)
345
			for(int i = 0; i < children.size(); i++){
346
				TermNode<T> childAt = children.get(i);
347
				if (childAt != null){
348
				    childAt.setSortIndex(i);
349
				}
350
			}
351
			child.setSortIndex(null);
352
		}
353
	}
354

    
355
	/**
356
	 * Returns the feature node placed at the given (childIndex + 1) position
357
	 * within the list of {@link #getChildNodes() children} of <i>this</i> feature node.
358
	 * If the given index is out of bounds no child will be returned.
359
	 *
360
	 * @param  childIndex	the integer indicating the position of the feature node
361
	 * @see     			#getChildNodes()
362
	 * @see					#addChild(TermNode, int)
363
	 * @see					#removeChild(int)
364
	 */
365
	public TermNode<T> getChildAt(int childIndex) {
366
	    return children.get(childIndex);
367
	}
368

    
369
	/**
370
	 * Returns the number of children nodes of <i>this</i> feature node.
371
	 *
372
	 * @see	#getChildNodes()
373
	 */
374
	@Transient
375
	public int getChildCount() {
376
		return children.size();
377
	}
378

    
379
	/**
380
	 * Returns the integer indicating the position of the given feature node
381
	 * within the list of {@link #getChildNodes() children} of <i>this</i> feature node.
382
	 * If the list does not contain this node then -1 will be returned.
383
	 *
384
	 * @param  node	the feature node the position of which is being searched
385
	 * @see			#addChild(TermNode, int)
386
	 * @see			#removeChild(int)
387
	 */
388
	public int getIndex(TermNode<T> node) {
389
	    if (! children.contains(node)){
390
			return -1;
391
		}else{
392
			return children.indexOf(node);
393
		}
394
	}
395

    
396
	/**
397
	 * Returns the boolean value indicating if <i>this</i> feature node has
398
	 * children (false) or not (true). A node without children is at the
399
	 * bottommost level of a tree and is called a leaf.
400
	 *
401
	 * @see	#getChildNodes()
402
	 * @see	#getChildCount()
403
	 */
404
	@Transient
405
	public boolean isLeaf() {
406
		return children.size() < 1;
407
	}
408

    
409
	/**
410
	 * Whether <code>this</code> node is the root node of the associated {@link TermTree feature tree}.
411
	 *
412
	 * @return <code>true</code> if <code>this</code> is the feature trees root node, <code>false</code> if not
413
	 */
414
	@Transient
415
	public boolean isRoot(){
416
		if(getGraph() != null){
417
			return this.equals(getGraph().getRoot());
418
		}
419
		return false;
420
	}
421

    
422
// *************************** APPLICABLE IF ********************************/
423

    
424
	/**
425
	 * Returns the set of {@link FeatureState feature states} implying rendering the
426
	 * concerned {@link Feature feature} applicable.
427
	 * If at least one state is present in this set, in a given description
428
	 * the {@link Feature feature} in <i>this</i> feature node is inapplicable
429
	 * unless any of the listed controlling states is present in the parent
430
	 * {@link Feature feature} description element {@link CategoricalData
431
	 * categoricalData}.
432
	 * This attribute is not equivalent to onlyApplicableIf in SDD as it is
433
	 * attached directly to the child feature rather than the parent, which
434
	 * allow having different applicable states for each child feature.
435
	 *
436
	 * @see    #addApplicableState(State)
437
	 * @see    #removeApplicableState(State)
438
	 */
439
	public Set<FeatureState> getOnlyApplicableIf() {
440
		return onlyApplicableIf;
441
	}
442

    
443
	/**
444
	 * Adds an existing {@link FeatureState applicable state} to the set of
445
	 * {@link #getOnlyApplicableIf() applicable states} described in
446
	 * <i>this</i> feature node.<BR>
447
	 *
448
	 * @param applicableState	the applicable state to be added to <i>this</i> feature node
449
	 * @see #getOnlyApplicableIf()
450
	 */
451
	public void addApplicableState(FeatureState applicableState) {
452
		this.onlyApplicableIf.add(applicableState);
453
	}
454
    public FeatureState addApplicableState(Feature feature, State applicableState) {
455
        FeatureState featureState = FeatureState.NewInstance(feature, applicableState);
456
        addApplicableState(featureState);
457
        return featureState;
458
    }
459

    
460
	/**
461
	 * Removes one element from the set of
462
	 * {@link #getOnlyApplicableIf() applicable states} described in
463
	 * <i>this</i> feature node.<BR>
464
	 *
465
	 * @param  applicableState   the applicable state which should be removed
466
	 * @see    #getApplicableState()
467
	 * @see     		  						#addApplicableState(State)
468
	 */
469
	public void removeApplicableState(FeatureState applicableState) {
470
		this.onlyApplicableIf.remove(applicableState);
471
	}
472

    
473
	/**
474
	 * Returns the set of {@link FeautreState states belonging to a feature}
475
	 * implying rendering the concerned {@link Feature feature} inapplicable.
476
	 * If at least one {@link State inapplicable state} is defined in the set,
477
	 * in a given description the {@link Feature feature} attribute of
478
	 * <i>this</i> feature node is inapplicable when any of the listed
479
	 * controlling states is present.
480
	 * This attribute is not equivalent to inapplicableIf in SDD as it is
481
	 * attached directly to the child feature rather than the parent, which
482
	 * allow having different inapplicability rules for each child feature.
483
	 *
484
	 * @see    #addInapplicableState(State)
485
	 * @see    #removeInapplicableState(State)
486
	 */
487
	public Set<FeatureState> getInapplicableIf() {
488
		return inapplicableIf;
489
	}
490

    
491
	/**
492
	 * Adds an existing {@link State inapplicable state} to the set of
493
	 * {@link #getInapplicableIf() inapplicable states} described in
494
	 * <i>this</i> feature node.<BR>
495
	 *
496
	 * @param inapplicableState	the inapplicable state to be added to <i>this</i> feature node
497
	 * @see    	   								#getInapplicableState()
498
	 */
499
	public void addInapplicableState(FeatureState inapplicableState) {
500
		this.inapplicableIf.add(inapplicableState);
501
	}
502

    
503
    public FeatureState addInapplicableState(Feature feature, State inapplicableState) {
504
        FeatureState featureState = FeatureState.NewInstance(feature, inapplicableState);
505
        addInapplicableState(featureState);
506
        return featureState;
507
    }
508

    
509
	/**
510
	 * Removes one element from the set of
511
	 * {@link #getInapplicableIf() inapplicable states} described in
512
	 * <i>this</i> feature node.<BR>
513
	 *
514
	 * @param  inapplicableState   the inapplicable state which should be removed
515
	 * @see    	   								#getInapplicableState()
516
	 * @see     		  						#addInapplicableState(State)
517
	 */
518

    
519
	public void removeInapplicableState(FeatureState inapplicableState) {
520
		this.inapplicableIf.remove(inapplicableState);
521
	}
522

    
523
//	//** ********************** QUESTIONS ******************************/
524
//
525
//	/**
526
//	 * Returns the {@link Representation question} formulation that
527
//	 * corresponds to <i>this</i> feature node and the corresponding
528
//	 * {@link Feature feature} in case it is part of a
529
//	 * {@link PolytomousKey polytomous key}.
530
//	 */
531
//	public Set<Representation> getQuestions() {
532
//		return this.questions;
533
//	}
534
//
535
//	public void addQuestion(Representation question) {
536
//		this.questions.add(question);
537
//	}
538
//
539
//	public void removeQuestion(Representation question) {
540
//		this.questions.remove(question);
541
//	}
542
//
543
//	@Transient
544
//	public Representation getQuestion(Language lang) {
545
//		for (Representation question : questions){
546
//			Language reprLanguage = question.getLanguage();
547
//			if (reprLanguage != null && reprLanguage.equals(lang)){
548
//				return question;
549
//			}
550
//		}
551
//		return null;
552
//	}
553

    
554
//*********************** Terms ************************************/
555

    
556
	/**
557
	 * Returns all terms that are contained in this node or a child node
558
	 *
559
	 * @param terms
560
	 * @return
561
	 */
562
	//TODO do we need to pass the terms parameter? Maybe a bit more performant
563
	// but more difficult to handle. We could use this internally but offer
564
	//the method with return value as public
565
	@Transient
566
	public Set<T> getDistinctTermsRecursive(Set<T> terms){
567
		T term = this.getTerm();
568
		if(term != null){
569
		    terms.add(term);
570
		}
571
		for(TermNode<T> childNode : this.getChildNodes()){
572
		    if (childNode != null){
573
		        terms.addAll(childNode.getDistinctTermsRecursive(terms));
574
		    }
575
		}
576
		return terms;
577
	}
578

    
579

    
580
	public String getPath(){
581
	    String result = "";
582
	    if (parent != null && parent.getTerm() != null){
583
	        result = parent.getPath() ;
584
	    }
585
	    if (getTerm()!= null){
586
	        String sep = StringUtils.isBlank(result)?"":"/";
587
	        result += sep+ getTerm().getLabel();
588
	    }
589
	    return result;
590
	}
591

    
592
    /**
593
     * Returns all terms that are contained in this node or a child node
594
     * as long as this node or the child nodes are not {@link #isDependent() dependent}
595
     * on higher nodes/feature states.
596
     */
597
    @Transient
598
    public Set<T> getIndependentTermsRecursive(){
599
        Set<T> terms = new HashSet<>();
600
        if (!isDependent()){
601
            T term = this.getTerm();
602
            if(term != null){
603
                terms.add(term);
604
            }
605
            for(TermNode<T> childNode : this.getChildNodes()){
606
                terms.addAll(childNode.getIndependentTermsRecursive());
607
            }
608
        }
609
        return terms;
610
    }
611

    
612
    /**
613
     * @return <code>true</code> if any of the sets {@link #getInapplicableIf() inapplicableIf}
614
     * and {@link #getOnlyApplicableIf() onlyApplicableIf} are not empty
615
     */
616
    @Transient
617
    @XmlTransient
618
    public boolean isDependent() {
619
        return inapplicableIf.size()>0 || onlyApplicableIf.size()>0;
620
    }
621

    
622
    /**
623
     * @return a list of terms which includes first the
624
     * term of this node and then recursively the list
625
     * of all children and grandChildren
626
     */
627
    public Collection<? extends T> asTermListRecursive() {
628
        List<T> result = new ArrayList<>();
629
        T term = this.getTerm();
630
        if(term != null){
631
            result.add(term);
632
        }
633
        for(TermNode<T> childNode : this.getChildNodes()){
634
            result.addAll(childNode.asTermListRecursive());
635
        }
636
        return result;
637
    }
638

    
639
//*********************** CLONE ********************************************************/
640

    
641
	/**
642
	 * Clones <i>this</i> {@link TermNode}. This is a shortcut that enables to create
643
	 * a new instance that differs only slightly from <i>this</i> tree node by
644
	 * modifying only some of the attributes.
645
	 * The parent, the feature and the featureTree are the same as for the original feature node
646
	 * the children are removed
647
	 *
648
	 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
649
	 * @see java.lang.Object#clone()
650
	 */
651
	@Override
652
	public TermNode<T> clone() {
653
		TermNode<T> result;
654
		try {
655
			result = (TermNode<T>)super.clone();
656
			result.children = new ArrayList<>();
657
			return result;
658
		}catch (CloneNotSupportedException e) {
659
			logger.warn("Object does not implement cloneable");
660
			e.printStackTrace();
661
			return null;
662
		}
663
	}
664

    
665
    public TermNode<T> cloneDescendants(){
666
        TermNode<T> clone = this.clone();
667
        TermNode<T> childClone;
668

    
669
        for(TermNode<T> childNode : this.getChildNodes()){
670
            childClone = childNode.clone();
671
            for (TermNode<T> childChild:childNode.getChildNodes()){
672
                childClone.addChild(childChild.cloneDescendants());
673
            }
674
            clone.addChild(childClone);
675

    
676
        }
677
        return clone;
678
    }
679

    
680
// ********************** TREE NODE METHODS ******************************/
681

    
682
	@Override
683
	public String treeIndex() {
684
		return this.treeIndex;
685
	}    @Override
686
    public String treeIndexLike() {
687
        return treeIndex + "%";
688
    }
689
    @Override
690
    public String treeIndexWc() {
691
        return treeIndex + "*";
692
    }
693

    
694
	@Override
695
	@Deprecated
696
	public void setTreeIndex(String newTreeIndex) {
697
		this.treeIndex = newTreeIndex;
698
	}
699

    
700
	@Override
701
	@Deprecated
702
	public int treeId() {
703
		if (this.getGraph() == null){
704
			return -1;
705
		}else{
706
			return this.getGraph().getId();
707
		}
708
	}
709

    
710
	void updateSortIndex(){
711
	 // TODO workaround (see sortIndex doc)
712
        for (int i = 0; i < children.size(); i++) {
713
            children.get(i).setSortIndex(i);
714
        }
715
	}
716

    
717
	public void removeNullValueFromChildren(){
718
	    HHH_9751_Util.removeAllNull(children);
719
        updateSortIndex();
720
    }
721
}
(25-25/33)