Project

General

Profile

Download (28.4 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * European Distributed Institute of Taxonomy
3
 * http://www.e-taxonomy.eu
4
 *
5
 * The contents of this file are subject to the Mozilla Public License Version 1.1
6
 * See LICENSE.TXT at the top of this package for the full license terms.
7
 */
8

    
9
package eu.etaxonomy.cdm.model.description;
10

    
11
import java.util.ArrayList;
12
import java.util.HashMap;
13
import java.util.List;
14
import java.util.Map;
15
import java.util.Map.Entry;
16

    
17
import javax.persistence.Entity;
18
import javax.persistence.FetchType;
19
import javax.persistence.JoinColumn;
20
import javax.persistence.ManyToOne;
21
import javax.persistence.MapKeyJoinColumn;
22
import javax.persistence.OneToMany;
23
import javax.persistence.OrderBy;
24
import javax.persistence.Transient;
25
import javax.xml.bind.annotation.XmlAccessType;
26
import javax.xml.bind.annotation.XmlAccessorType;
27
import javax.xml.bind.annotation.XmlElement;
28
import javax.xml.bind.annotation.XmlElementWrapper;
29
import javax.xml.bind.annotation.XmlIDREF;
30
import javax.xml.bind.annotation.XmlRootElement;
31
import javax.xml.bind.annotation.XmlSchemaType;
32
import javax.xml.bind.annotation.XmlType;
33
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
34

    
35
import org.apache.log4j.Logger;
36
import org.hibernate.LazyInitializationException;
37
import org.hibernate.annotations.Cascade;
38
import org.hibernate.annotations.CascadeType;
39
import org.hibernate.annotations.IndexColumn;
40
import org.hibernate.envers.Audited;
41

    
42
import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
43
import eu.etaxonomy.cdm.model.common.IMultiLanguageTextHolder;
44
import eu.etaxonomy.cdm.model.common.Language;
45
import eu.etaxonomy.cdm.model.common.LanguageString;
46
import eu.etaxonomy.cdm.model.common.MultilanguageText;
47
import eu.etaxonomy.cdm.model.common.VersionableEntity;
48
import eu.etaxonomy.cdm.model.taxon.Taxon;
49

    
50
/**
51
 * This class represents a node within a {@link PolytomousKey polytomous key}
52
 * structure. The structure of such a key is a directed tree like acyclic graph
53
 * of <code>PolytomousKeyNode</code>s.
54
 * A <code>PolytomousKeyNode</code> represents both the node and the edges that lead
55
 * to <code>this</code> node, therefore an extra class representing the edges
56
 * does not exist.
57
 * <BR>
58
 * The attribute representing the edge leading from its parent node to <code>this</code>
59
 * node is the {@link #getStatement() statement}, attributes leading to the child nodes
60
 * are either the {@link #getQuestion() question} or the {@link #getFeature() feature}.
61
 * While {@link #getStatement() statements} are required, {@link #getQuestion() questions} and
62
 * {@link #getFeature() features} are optional and do typically not exist in classical keys.
63
 * Both, {@link #getQuestion() questions} and {@link #getFeature() features}, will be "answered" by the
64
 * {@link #getStatement() statements} of the child nodes, where {@link #getQuestion() questions}
65
 * are usually free text used in manually created keys while {@link #getFeature() features} are
66
 * typically used in automatically created keys based on structured descriptive data.
67
 * Only one of them should be defined in a node. However, if both exist the {@link #getQuestion() question}
68
 * should always be given <b>priority</b> over the {@link #getFeature() feature}.<br>
69
 *
70
 * Typically a node either links to its child nodes (subnodes) or represents a link
71
 * to a {@link Taxon taxon}. The later, if taken as part of the tree,  are usually
72
 * the leaves of the represented tree like structure (taxonomically they are the
73
 * end point of the decision process).<br>
74
 *
75
 * However, there are exceptions to this simple structure:
76
 *
77
 * <li>Subnodes and taxon link<br>
78
 *
79
 * In rare cases a node can have both, subnodes and a {@link #getTaxon() link to a taxon}.
80
 * In this case the taxonomic determination process may be either terminated
81
 * at the given {@link Taxon taxon} or can proceed with the children if a more accurate
82
 * determination is wanted. This may be the case e.g. in a key that generally
83
 * covers all taxa of rank species and at the same time allows identification of
84
 * subspecies or varieties of these taxa.</li>
85
 *
86
 * <li>{@link #getOtherNode() Other nodes}: <br>
87
 *
88
 * A node may not only link to its subnodes or to a taxon but it may
89
 * also link to {@link #getOtherNode() another node} (with a different parent) of either the same key
90
 * or another key.
91
 * <br>
92
 * <b>NOTE: </b>
93
 * If an {@link #getOtherNode() otherNode} represents a node
94
 * of the same tree the key does not represent a strict tree structure
95
 * anymore. However, as this is a rare case we will still use this term
96
 * at some places.</li>
97
 *
98
 * <li>{@link #getSubkey() Subkey}:<br>
99
 *
100
 * A node may also link to another key ({@link #getSubkey() subkey}) as a whole, which is
101
 * equal to an {@link #getOtherNode() otherNode} link to the root node of the other key.
102
 * In this case the path in the decision graph spans over multiple keys.</li>
103
 * This structure is typically used when a key covers taxa down to a certain rank, whereas
104
 * taxa below this rank are covered by extra keys (e.g. a parent key may cover all taxa
105
 * of rank species while subspecies and varieties are covered by a subkeys for each of these
106
 * species.
107
 * Another usecase for subkeys is the existence of an alternative key for a certain part
108
 * of the decision tree.
109
 *
110
 * <li>Multiple taxa<br>
111
 *
112
 * Some nodes in legacy keys do link to multiple taxa, meaning that the key ambigous at
113
 * this point. To represent such nodes one must use child nodes with empty
114
 * {@link #getStatement() statements} for each such taxon (in all other cases - except for
115
 * root nodes - the <code>statement</code> is required).
116
 * Applications that do visualize the key should handle such a node-subnode structure as one
117
 * node with multiple taxon links. This complicated data structure has been chosen for
118
 * this rare to avoid a more complicated <code>List<Taxon></code> structure for the standard
119
 * case.</li>
120
 *
121
 * The {@link PolytomousKey#getRoot() root node of the key} may represent the entry point
122
 * question or feature but does naturally neither have a statement nor a linked taxon as
123
 * there is no prior decision yet.
124
 *
125
 * <h4>Notes</h4>
126
 * <p>
127
 * A polytomous key node can be referenced from multiple other nodes via the
128
 * {@link #getOtherNode() otherNode} attribute of the other nodes. Therefore, though
129
 * we speek about a "decision tree" structure a node does not necessarily have only
130
 * one parent.
131
 * However, nodes are mainly represented in a tree structure and therefore do have
132
 * a defined {@link #getParent() parent} which is the "main" parent. But when implementing
133
 * visualizing or editing tools one should keep in mind that this parent may not be
134
 * the only node linking the child node.
135
 *
136
 * @author a.mueller
137
 * @created 13-Oct-2010
138
 *
139
 */
140
@SuppressWarnings("serial")
141
@XmlAccessorType(XmlAccessType.FIELD)
142
@XmlType(name = "PolytomousKeyNode", propOrder = { "key", "parent", "children",
143
		"sortIndex", "nodeNumber", "statement", "question", "feature", "taxon",
144
		"subkey", "otherNode", "modifyingText" })
145
@XmlRootElement(name = "FeatureNode")
146
@Entity
147
@Audited
148
public class PolytomousKeyNode extends VersionableEntity implements IMultiLanguageTextHolder {
149
	private static final Logger logger = Logger.getLogger(PolytomousKeyNode.class);
150

    
151
	// This is the main key a node belongs to. Although other keys may also
152
	// reference
153
	// <code>this</code> node, a node usually belongs to a given key.
154
	@XmlElement(name = "PolytomousKey")
155
	@XmlIDREF
156
	@XmlSchemaType(name = "IDREF")
157
//	@ManyToOne(fetch = FetchType.LAZY, optional=false)
158
//	@JoinColumn(nullable=false)
159
//	@NotNull
160
//	@Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE /*, CascadeType.DELETE_ORPHAN */})
161
	@ManyToOne(fetch = FetchType.LAZY)
162
	@Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE })
163
	private PolytomousKey key;
164

    
165
	@XmlElementWrapper(name = "Children")
166
	@XmlElement(name = "Child")
167
	// @OrderColumn("sortIndex") //JPA 2.0 same as @IndexColumn
168
	// @IndexColumn does not work because not every FeatureNode has a parent.
169
	// But only NotNull will solve the problem (otherwise
170
	// we will need a join table
171
	// http://stackoverflow.com/questions/2956171/jpa-2-0-ordercolumn-annotation-in-hibernate-3-5
172
	// http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-collection-extratype-indexbidir
173
	// see also https://forum.hibernate.org/viewtopic.php?p=2392563
174
	// http://opensource.atlassian.com/projects/hibernate/browse/HHH-4390
175
	// reading works, but writing doesn't
176
	//
177
	@IndexColumn(name = "sortIndex", base = 0)
178
	@OrderBy("sortIndex")
179
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "parent")
180
	@Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE })
181
	private List<PolytomousKeyNode> children = new ArrayList<PolytomousKeyNode>();
182

    
183

    
184

    
185
	@XmlElement(name = "Parent")
186
	@XmlIDREF
187
	@XmlSchemaType(name = "IDREF")
188
	@Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE })
189
	@ManyToOne(fetch = FetchType.LAZY, targetEntity = PolytomousKeyNode.class)
190
	@JoinColumn(name = "parent_id" /*
191
									 * , insertable=false, updatable=false,
192
									 * nullable=false
193
									 */)
194
	private PolytomousKeyNode parent;
195

    
196
	// see comment on children @IndexColumn
197
	private Integer sortIndex;
198

    
199
	@XmlElement(name = "Statement")
200
	@XmlIDREF
201
	@XmlSchemaType(name = "IDREF")
202
	@ManyToOne(fetch = FetchType.LAZY)
203
	@Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE })
204
	private KeyStatement statement;
205

    
206
	@XmlElement(name = "Question")
207
	@XmlIDREF
208
	@XmlSchemaType(name = "IDREF")
209
	@ManyToOne(fetch = FetchType.LAZY)
210
	@Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
211
	private KeyStatement question;
212

    
213
	@XmlElement(name = "Feature")
214
	@XmlIDREF
215
	@XmlSchemaType(name = "IDREF")
216
	@ManyToOne(fetch = FetchType.LAZY)
217
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
218
	private Feature feature;
219

    
220
	@XmlElement(name = "Taxon")
221
	@XmlIDREF
222
	@XmlSchemaType(name = "IDREF")
223
	@ManyToOne(fetch = FetchType.LAZY)
224
	@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
225
	private Taxon taxon;
226

    
227
	// Refers to an entire key
228
	// <code>this</code> node, a node usually belongs to a given key.
229
	@XmlElement(name = "SubKey")
230
	@XmlIDREF
231
	@XmlSchemaType(name = "IDREF")
232
	@ManyToOne(fetch = FetchType.LAZY)
233
	@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
234
	private PolytomousKey subkey;
235

    
236
	// Refers to an other node within this key or an other key
237
	@XmlElement(name = "PolytomousKey")
238
	@XmlIDREF
239
	@XmlSchemaType(name = "IDREF")
240
	@ManyToOne(fetch = FetchType.LAZY)
241
    @Cascade({CascadeType.MERGE})
242
	private PolytomousKeyNode otherNode;
243

    
244
	private Integer nodeNumber = null;
245

    
246
	// TODO should be available for each taxon/result
247
	@XmlElement(name = "ModifyingText")
248
	@XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
249
	@OneToMany(fetch = FetchType.LAZY)
250
	// @JoinTable(name = "DescriptionElementBase_ModifyingText")
251
	@MapKeyJoinColumn(name="modifyingtext_mapkey_id")
252
    @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE })
253
	private Map<Language, LanguageString> modifyingText = new HashMap<Language, LanguageString>();
254

    
255
// ************************** FACTORY ********************************/
256

    
257
	/**
258
	 * Creates a new empty polytomous key node instance.
259
	 */
260
	public static PolytomousKeyNode NewInstance() {
261
		return new PolytomousKeyNode();
262
	}
263

    
264
	/**
265
	 * Creates a new polytomous key node instance.
266
	 *
267
	 */
268
	public static PolytomousKeyNode NewInstance(String statement) {
269
		PolytomousKeyNode result = new PolytomousKeyNode();
270
		result.setStatement(KeyStatement.NewInstance(statement));
271
		return result;
272
	}
273

    
274
	/**
275
	 * Creates a new polytomous key node instance.
276
	 *
277
	 */
278
	public static PolytomousKeyNode NewInstance(String statement,
279
			String question, Taxon taxon, Feature feature) {
280
		PolytomousKeyNode result = new PolytomousKeyNode();
281
		result.setTaxon(taxon);
282
		result.setStatement(KeyStatement.NewInstance(statement));
283
		result.setQuestion(KeyStatement.NewInstance(question));
284
		result.setFeature(feature);
285
		return result;
286
	}
287

    
288
// ************************** CONSTRUCTOR *****************************/
289

    
290
	/**
291
	 * Class constructor: creates a new empty feature node instance.
292
	 */
293
	protected PolytomousKeyNode() {
294
		super();
295
	}
296

    
297
// ** ********************** GETTER / SETTER  ******************************/
298

    
299

    
300
	//see #4278 and #4200, alternatively can be private and use deproxy(this, PolytomousKeyNode.class)
301
	protected void setSortIndex(Integer sortIndex) {
302
		this.sortIndex = sortIndex;
303
	}
304

    
305
	/**
306
	 * @return
307
	 */
308
	public PolytomousKey getKey() {
309
		return key;
310
	}
311

    
312
	/**
313
	 * @param key
314
	 */
315
	public void setKey(PolytomousKey key) {
316
		this.key = key;
317
	}
318

    
319
	/**
320
	 * The node number is the number of the node within the key. This
321
	 * corresponds to the number for key choices in written keys.
322
	 */
323
	public Integer getNodeNumber() {
324
		return nodeNumber;
325
	}
326

    
327
	/**
328
	 * Is computed automatically and therefore should not be set by the user.
329
	 */
330
	public void setNodeNumber(Integer nodeNumber) {
331
		this.nodeNumber = nodeNumber;
332
	}
333

    
334
	/**
335
	 * Returns the taxon this node links to. This is usually the case when this
336
	 * node is a leaf.
337
	 *
338
	 * @return
339
	 * @see #setTaxon(Taxon)
340
	 * @see #getSubkey()
341
	 * @see #getChildren()
342
	 * @see #getOtherNode()
343
	 */
344
	public Taxon getTaxon() {
345
		return taxon;
346
	}
347

    
348
	/**
349
	 * Sets the taxon this node links to. <BR>
350
	 * If a tax
351
	 *
352
	 * @param taxon
353
	 * @see #getTaxon()
354
	 */
355
	public void setTaxon(Taxon taxon) {
356
		this.taxon = taxon;
357
	}
358

    
359
	/**
360
	 * @return
361
	 * @see #setSubkey(PolytomousKey)
362
	 * @see #getTaxon()
363
	 * @see #getChildren()
364
	 * @see #getOtherNode()
365
	 */
366
	public PolytomousKey getSubkey() {
367
		return subkey;
368
	}
369

    
370
	/**
371
	 * @param subkey
372
	 * @see #getSubkey()
373
	 */
374
	public void setSubkey(PolytomousKey subkey) {
375
		this.subkey = subkey;
376
	}
377

    
378
	/**
379
	 * @return
380
	 * @see #setOtherNode(PolytomousKeyNode)
381
	 * @see #getTaxon()
382
	 * @see #getChildren()
383
	 * @see #getSubkey()
384
	 */
385
	public PolytomousKeyNode getOtherNode() {
386
		return otherNode;
387
	}
388

    
389
	/**
390
	 * @param otherNode
391
	 * @see #getOtherNode()
392
	 */
393
	public void setOtherNode(PolytomousKeyNode otherNode) {
394
		this.otherNode = otherNode;
395
	}
396

    
397
	// TODO
398
	public void setFeature(Feature feature) {
399
		this.feature = feature;
400
	}
401

    
402
	public Feature getFeature() {
403
		return feature;
404
	}
405

    
406
	/**
407
	 * Returns the parent node of <code>this</code> child.
408
	 *
409
	 * @return
410
	 */
411
	public PolytomousKeyNode getParent() {
412
		return parent;
413
	}
414

    
415
	/**
416
	 * For bidirectional use only !
417
	 *
418
	 * @param parent
419
	 */
420
	   protected void setParent(PolytomousKeyNode parent) {
421
	        PolytomousKeyNode oldParent = this.parent;
422
	        if (oldParent != null){
423
	            if (oldParent.getChildren().contains(this)){
424
	                    oldParent.removeChild(this);
425
	            }
426
	        }
427
	        this.parent = parent;
428

    
429
	    }
430

    
431
	/**
432
	 * Returns the (ordered) list of feature nodes which are children nodes of
433
	 * <i>this</i> feature node.
434
	 */
435
	public List<PolytomousKeyNode> getChildren() {
436
	    removeNullValueFromChildren();
437
		return children;
438
	}
439

    
440
	/**
441
	 * Adds the given polytomous key node at the end of the list of children of
442
	 * <i>this</i> polytomous key node.
443
	 *
444
	 * @param child
445
	 *            the feature node to be added
446
	 * @see #getChildren()
447
	 * @see #setChildren(List)
448
	 * @see #addChild(PolytomousKeyNode, int)
449
	 * @see #removeChild(PolytomousKeyNode)
450
	 * @see #removeChild(int)
451
	 */
452
	public void addChild(PolytomousKeyNode child) {
453
		addChild(child, children.size());
454
	}
455

    
456
	/**
457
	 * Inserts the given child node in the list of children of <i>this</i>
458
	 * polytomous key node at the given (index + 1) position. If the given index
459
	 * is out of bounds an exception will be thrown.<BR>
460
	 *
461
	 * @param child
462
	 *            the polytomous key node to be added
463
	 * @param index
464
	 *            the integer indicating the position at which the child should
465
	 *            be added
466
	 * @see #getChildren()
467
	 * @see #setChildren(List)
468
	 * @see #addChild(PolytomousKeyNode)
469
	 * @see #removeChild(PolytomousKeyNode)
470
	 * @see #removeChild(int)
471
	 */
472
	public void addChild(PolytomousKeyNode child, int index) {
473
		if (index < 0 || index > children.size() + 1) {
474
			throw new IndexOutOfBoundsException("Wrong index: " + index);
475
		}
476
		removeNullValueFromChildren();
477

    
478
		if(nodeNumber == null) {
479
            	nodeNumber = getMaxNodeNumberFromRoot() + 1;
480
        }
481

    
482

    
483
		children.add(index, child);
484
		child.setKey(this.getKey());
485

    
486
		// TODO workaround (see sortIndex doc)
487
		for (int i = 0; i < children.size(); i++) {
488
			children.get(i).setSortIndex(i);
489
		}
490
		child.setSortIndex(index);
491
		child.setParent(this);
492
	}
493

    
494

    
495

    
496
	/**
497
	 * Removes the given polytomous key node from the list of
498
	 * {@link #getChildren() children} of <i>this</i> polytomous key node.
499
	 *
500
	 * @param child
501
	 *            the feature node which should be removed
502
	 * @see #getChildren()
503
	 * @see #addChild(PolytomousKeyNode, int)
504
	 * @see #addChild(PolytomousKeyNode)
505
	 * @see #removeChild(int)
506
	 */
507
	public void removeChild(PolytomousKeyNode child) {
508
		int index = children.indexOf(child);
509
		removeNullValueFromChildren();
510
		if (index >= 0) {
511
			removeChild(index);
512
		}
513
	}
514

    
515

    
516
	private void removeNullValueFromChildren(){
517
	    try {
518
    	    if (children.contains(null)){
519
                while(children.contains(null)){
520
                    children.remove(null);
521
                }
522
            }
523
	    } catch (LazyInitializationException e) {
524
	        logger.info("Cannot clean up uninitialized children without a session, skipping.");
525
	    }
526
	}
527
	/**
528
	 * Removes the feature node placed at the given (index + 1) position from
529
	 * the list of {@link #getChildren() children} of <i>this</i> feature node.
530
	 * If the given index is out of bounds no child will be removed.
531
	 *
532
	 * @param index
533
	 *            the integer indicating the position of the feature node to be
534
	 *            removed
535
	 * @see #getChildren()
536
	 * @see #addChild(PolytomousKeyNode, int)
537
	 * @see #addChild(PolytomousKeyNode)
538
	 * @see #removeChild(PolytomousKeyNode)
539
	 */
540
	public void removeChild(int index) {
541
		PolytomousKeyNode child = children.get(index);
542
		if (child != null) {
543
			children.remove(index);
544
			child.setParent(null);
545
			// TODO workaround (see sortIndex doc)
546
			for (int i = 0; i < children.size(); i++) {
547
				PolytomousKeyNode childAt = children.get(i);
548
				childAt.setSortIndex(i);
549
			}
550
			child.setSortIndex(null);
551
			child.setNodeNumber(null);
552
		}
553
		refreshNodeNumbering();
554
	}
555

    
556
// **************************** METHODS ************************************/
557

    
558
	/**
559
	 * Returns the current maximum value of the node number in the entire key
560
	 * starting from the root.
561
	 *
562
	 * @return
563
	 */
564
	private int getMaxNodeNumberFromRoot() {
565
		PolytomousKeyNode rootKeyNode = this.getKey().getRoot();
566
		int rootNumber = this.getKey().getStartNumber();
567
		return getMaxNodeNumber(rootNumber, rootKeyNode);
568
	}
569

    
570
	/**
571
	 * Returns the current maximum value of the node number in the entire key
572
	 * starting from the given key node, comparing with a given max value as input.
573
	 *
574
	 * @return
575
	 */
576
	private int getMaxNodeNumber(int maxNumber, PolytomousKeyNode parent) {
577
		if (parent.getNodeNumber() != null) {
578
			maxNumber = (maxNumber < parent.getNodeNumber()) ? parent.getNodeNumber() : maxNumber;
579
			for (PolytomousKeyNode child : parent.getChildren()) {
580
				if (parent == child){
581
					throw new RuntimeException("Parent and child are the same for the given key node. This will lead to an infinite loop when updating the max node number.");
582
				}else{
583
					maxNumber = getMaxNodeNumber(maxNumber, child);
584
				}
585
			}
586
		}
587
		return maxNumber;
588
	}
589

    
590
	/**
591
	 * Refresh numbering of key nodes starting from root.
592
	 *
593
	 */
594
	public void refreshNodeNumbering() {
595
		updateNodeNumbering(getKey().getRoot(), getKey().getStartNumber());
596
	}
597

    
598
	/**
599
	 * Recursively (depth-first) refresh numbering of key nodes starting from the given key node,
600
	 * starting with a given node number.
601
	 *
602
	 * @return new starting node number value
603
	 */
604
	private int updateNodeNumbering(PolytomousKeyNode node,int nodeN) {
605
		int newNodeN = nodeN;
606
		if (node.isLeaf()) {
607
			node.setNodeNumber(null);
608
		} else {
609
			node.setNodeNumber(nodeN);
610
			newNodeN++;
611
			List<PolytomousKeyNode> children = node.getChildren();
612
			 while (children.contains(null)){
613
			     children.remove(null);
614
		       }
615

    
616
			for (PolytomousKeyNode child : children) {
617
				if (node == child){
618
					throw new RuntimeException("Parent and child are the same for the given key node. This will lead to an infinite loop when updating node numbers.");
619
				}else{
620
					newNodeN = updateNodeNumbering(child, newNodeN);
621
				}
622
			}
623
		}
624
		return newNodeN;
625
	}
626

    
627

    
628

    
629

    
630
	/**
631
	 * Returns the feature node placed at the given (childIndex + 1) position
632
	 * within the list of {@link #getChildren() children} of <i>this</i> feature
633
	 * node. If the given index is out of bounds no child will be returned.
634
	 *
635
	 * @param childIndex
636
	 *            the integer indicating the position of the feature node
637
	 * @see #getChildren()
638
	 * @see #addChild(PolytomousKeyNode, int)
639
	 * @see #removeChild(int)
640
	 */
641
	public PolytomousKeyNode getChildAt(int childIndex) {
642
		return children.get(childIndex);
643
	}
644

    
645
	/**
646
	 * Returns the number of children nodes of <i>this</i> feature node.
647
	 *
648
	 * @see #getChildren()
649
	 */
650
	@Transient
651
	public int childCount() {
652
		return children.size();
653
	}
654

    
655
	/**
656
	 * Returns the integer indicating the position of the given feature node
657
	 * within the list of {@link #getChildren() children} of <i>this</i> feature
658
	 * node. If the list does not contain this node then -1 will be returned.
659
	 *
660
	 * @param node
661
	 *            the feature node the position of which is being searched
662
	 * @see #addChild(PolytomousKeyNode, int)
663
	 * @see #removeChild(int)
664
	 */
665
	public int getIndex(PolytomousKeyNode node) {
666
		if (!children.contains(node)) {
667
			return -1;
668
		} else {
669
			return children.indexOf(node);
670
		}
671
	}
672

    
673
	/**
674
	 * Returns the boolean value indicating if <i>this</i> feature node has
675
	 * children (false) or not (true). A node without children is at the
676
	 * bottommost level of a tree and is called a leaf.
677
	 *
678
	 * @see #getChildren()
679
	 * @see #getChildCount()
680
	 */
681
	@Transient
682
	public boolean isLeaf() {
683
		return children.size() < 1;
684
	}
685

    
686
	// ** ********************** QUESTIONS AND STATEMENTS ************************/
687

    
688
	/**
689
	 * Returns the statement for <code>this</code> PolytomousKeyNode. When coming
690
	 * from the parent node the user needs to agree with the statement (and disagree
691
	 * with all statements of sibling nodes) to follow <code>this</code> node.<BR>
692
	 * The statement may stand alone (standard in classical keys) or it may be
693
	 * either the answer to the {@link #getQuestion() question} or the
694
	 * value for the {@link #getFeature() feature} of the parent node.
695
	 *
696
	 * @return the statement
697
	 * @see #getQuestion()
698
	 */
699
	public KeyStatement getStatement() {
700
		return statement;
701
	}
702

    
703
	/**
704
	 * This is a convenience method to set the statement text for this node in
705
	 * the given language. <BR>
706
	 * If no statement exists yet a new statement is created. <BR>
707
	 * If a statement text in the given language exists already it is
708
	 * overwritten and the old text is returned. If language is
709
	 * <code>null</code> the default language is used instead.
710
	 *
711
	 * @param text
712
	 *            the statement text
713
	 * @param language
714
	 *            the language of the statement text
715
	 * @return the old statement text in the given language as LanguageString
716
	 */
717
	public LanguageString addStatementText(String text, Language language) {
718
		if (language == null) {
719
			language = Language.DEFAULT();
720
		}
721
		if (this.statement == null) {
722
			setStatement(KeyStatement.NewInstance());
723
		}
724
		return getStatement().putLabel(language, text);
725
	}
726

    
727
	/**
728
	 * @param statement
729
	 * @see #getStatement()
730
	 */
731
	public void setStatement(KeyStatement statement) {
732
		this.statement = statement;
733
	}
734

    
735
	/**
736
	 * Returns the question for <code>this</code> PolytomousKeyNode. <BR>
737
	 * A question is answered by statements in leads below this tree node.
738
	 * Questions are optional and are usually empty in traditional keys.
739
	 *
740
	 * @return the question
741
	 * @see #getStatement()
742
	 */
743
	public KeyStatement getQuestion() {
744
		return question;
745
	}
746

    
747
	/**
748
	 * This is a convenience method to sets the question text for this node in
749
	 * the given language. <BR>
750
	 * If no question exists yet a new question is created. <BR>
751
	 * If a question text in the given language exists already it is overwritten
752
	 * and the old text is returned. If language is <code>null</code> the
753
	 * default language is used instead.
754
	 *
755
	 * @param text
756
	 * @param language
757
	 * @return
758
	 */
759
	public LanguageString addQuestionText(String text, Language language) {
760
		if (language == null) {
761
			language = Language.DEFAULT();
762
		}
763
		if (this.question == null) {
764
			setQuestion(KeyStatement.NewInstance());
765
		}
766
		return getQuestion().putLabel(language, text);
767
	}
768

    
769
	/**
770
	 * @param question
771
	 * @see #getQuestion()
772
	 */
773
	public void setQuestion(KeyStatement question) {
774
		this.question = question;
775
	}
776

    
777
	// **************** modifying text ***************************************
778

    
779
	/**
780
	 * Returns the {@link MultilanguageText} like "an unusual form of",
781
	 * commenting the determined taxon. That is a modifyingText may by used to
782
	 * comment or to constraint the decision step represented by the edge
783
	 * leading to <i>this</i> node
784
	 * <p>
785
	 * All {@link LanguageString language strings} contained in the
786
	 * multilanguage texts should all have the same meaning.<BR>
787
	 */
788
	public Map<Language, LanguageString> getModifyingText() {
789
		return this.modifyingText;
790
	}
791

    
792
	/**
793
	 * See {@link #getModifyingText}
794
	 *
795
	 * @param description
796
	 *            the language string describing the validity in a particular
797
	 *            language
798
	 * @see #getModifyingText()
799
	 * @see #putModifyingText(Language, String)
800
	 * @deprecated should follow the put semantic of maps, this method will be
801
	 *             removed in v4.0 Use the
802
	 *             {@link #putModifyingText(LanguageString) putModifyingText}
803
	 *             method instead
804
	 */
805
	@Deprecated
806
	public LanguageString addModifyingText(LanguageString description) {
807
		return this.putModifyingText(description);
808
	}
809

    
810
	/**
811
	 * See {@link #getModifyingText}
812
	 *
813
	 * @param description
814
	 *            the language string describing the validity in a particular
815
	 *            language
816
	 * @see #getModifyingText()
817
	 * @see #putModifyingText(Language, String)
818
	 */
819
	public LanguageString putModifyingText(LanguageString description) {
820
		return this.modifyingText.put(description.getLanguage(), description);
821
	}
822

    
823
	/**
824
	 * See {@link #getModifyingText}
825
	 *
826
	 * @param text
827
	 *            the string describing the validity in a particular language
828
	 * @param language
829
	 *            the language in which the text string is formulated
830
	 * @see #getModifyingText()
831
	 * @see #putModifyingText(LanguageString)
832
	 * @deprecated should follow the put semantic of maps, this method will be
833
	 *             removed in v4.0 Use the
834
	 *             {@link #putModifyingText(Language, String) putModifyingText}
835
	 *             method instead
836
	 */
837
	@Deprecated
838
	public LanguageString addModifyingText(String text, Language language) {
839
		return this.putModifyingText(language, text);
840
	}
841

    
842
	/**
843
	 * See {@link #getModifyingText}
844
	 *
845
	 * @param text
846
	 *            the string describing the validity in a particular language
847
	 * @param language
848
	 *            the language in which the text string is formulated
849
	 * @see #getModifyingText()
850
	 * @see #putModifyingText(LanguageString)
851
	 */
852
	public LanguageString putModifyingText(Language language, String text) {
853
		return this.modifyingText.put(language,
854
				LanguageString.NewInstance(text, language));
855
	}
856

    
857
	/**
858
	 * See {@link #getModifyingText}
859
	 *
860
	 * @param language
861
	 *            the language in which the language string to be removed has
862
	 *            been formulated
863
	 * @see #getModifyingText()
864
	 */
865
	public LanguageString removeModifyingText(Language language) {
866
		return this.modifyingText.remove(language);
867
	}
868

    
869

    
870
	// *********************** CLONE ********************************************************/
871

    
872
	/**
873
	 * Clones <i>this</i> PolytomousKeyNode. This is a shortcut that enables to
874
	 * create a new instance that differs only slightly from <i>this</i>
875
	 * PolytomousKeyNode by modifying only some of the attributes. The parent,
876
	 * the feature and the key are the are the same as for the original feature
877
	 * node the children are removed.
878
	 *
879
	 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
880
	 * @see java.lang.Object#clone()
881
	 */
882
	@Override
883
	public Object clone() {
884
		PolytomousKeyNode result;
885
		try {
886
			result = (PolytomousKeyNode) super.clone();
887
			result.children = new ArrayList<PolytomousKeyNode>();
888

    
889
			result.modifyingText = new HashMap<Language, LanguageString>();
890
			for (Entry<Language, LanguageString> entry : this.modifyingText
891
					.entrySet()) {
892
				result.putModifyingText(entry.getValue());
893
			}
894

    
895
			return result;
896
		} catch (CloneNotSupportedException e) {
897
			logger.warn("Object does not implement cloneable");
898
			e.printStackTrace();
899
			return null;
900
		}
901
	}
902

    
903
    /**
904
     *
905
     */
906
    public void removeTaxon() {
907
        this.taxon = null;
908

    
909
    }
910

    
911

    
912
}
(21-21/36)