Project

General

Profile

Download (28.6 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.OrderColumn;
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.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
36
import org.hibernate.annotations.Cascade;
37
import org.hibernate.annotations.CascadeType;
38
import org.hibernate.envers.Audited;
39

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

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

    
159
	// This is the main key a node belongs to. Although other keys may also
160
	// reference <code>this</code> node, a node usually belongs to a given key.
161
	@XmlElement(name = "PolytomousKey")
162
	@XmlIDREF
163
	@XmlSchemaType(name = "IDREF")
164
//	@NotNull
165
	@ManyToOne(fetch = FetchType.LAZY)
166
	@Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE })
167
	private PolytomousKey key;
168

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

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

    
195
	//see comment on children @IndexColumn
196
	//see also https://dev.e-taxonomy.eu/redmine/issues/3722, #4278
197
	@Transient
198
	private Integer sortIndex = -1;
199

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

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

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

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

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

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

    
245
	private Integer nodeNumber = null;
246

    
247
	// TODO should be available for each taxon/result
248
	@XmlElement(name = "ModifyingText")
249
	@XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
250
	@OneToMany(fetch = FetchType.LAZY)
251
	@MapKeyJoinColumn(name="modifyingtext_mapkey_id")
252
    @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE })
253
	private Map<Language, LanguageString> modifyingText = new HashMap<>();
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
//      sortIndex = sortIndex;  old #3722
303
        //do nothing
304
	}
305

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

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

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

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

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

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

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

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

    
379
	/**
380
	 * A link to another sub-node, which has not *this*
381
	 * node as primary parent. The sub-node may be within
382
	 * this key or within another key.
383
	 * @see #setOtherNode(PolytomousKeyNode)
384
	 * @see #getTaxon()
385
	 * @see #getChildren()
386
	 * @see #getSubkey()
387
	 */
388
	public PolytomousKeyNode getOtherNode() {
389
		return otherNode;
390
	}
391

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

    
400
	// TODO
401
	public void setFeature(Feature feature) {
402
		this.feature = feature;
403
	}
404
	public Feature getFeature() {
405
		return feature;
406
	}
407

    
408
	/**
409
	 * Returns the parent node of <code>this</code> child.
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
		return children;
437
	}
438

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

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

    
483

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

    
487
		updateSortIndex();
488
		child.setSortIndex(index);
489
		child.setParent(this);
490
		//this.removeNullValueFromChildren();
491
	}
492

    
493

    
494

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

    
513
	/**
514
	 * Removes the feature node placed at the given (index + 1) position from
515
	 * the list of {@link #getChildren() children} of <i>this</i> feature node.
516
	 * If the given index is out of bounds no child will be removed.
517
	 *
518
	 * @param index
519
	 *            the integer indicating the position of the feature node to be
520
	 *            removed
521
	 * @see #getChildren()
522
	 * @see #addChild(PolytomousKeyNode, int)
523
	 * @see #addChild(PolytomousKeyNode)
524
	 * @see #removeChild(PolytomousKeyNode)
525
	 */
526
	public void removeChild(int index) {
527
		PolytomousKeyNode child = children.get(index);
528
		if (child != null) {
529
			children.remove(index);
530
			child.setParent(null);
531
			// TODO workaround (see sortIndex doc)
532
			for (int i = 0; i < children.size(); i++) {
533
				PolytomousKeyNode childAt = children.get(i);
534
				childAt.setSortIndex(i);
535
			}
536
			child.setSortIndex(null);
537
			child.setNodeNumber(null);
538
		}
539
		refreshNodeNumbering();
540
	}
541

    
542
// **************************** METHODS ************************************/
543

    
544
	/**
545
	 * Returns the current maximum value of the node number in the entire key
546
	 * starting from the root.
547
	 *
548
	 * @return
549
	 */
550
	private int getMaxNodeNumberFromRoot() {
551
		PolytomousKeyNode rootKeyNode = this.getKey().getRoot();
552
		int rootNumber = this.getKey().getStartNumber();
553

    
554
		rootKeyNode.updateSortIndex();
555
		return getMaxNodeNumber(rootNumber, rootKeyNode);
556
	}
557

    
558
	/**
559
	 * Returns the current maximum value of the node number in the entire key
560
	 * starting from the given key node, comparing with a given max value as input.
561
	 *
562
	 * @return
563
	 */
564
	private int getMaxNodeNumber(int maxNumber, PolytomousKeyNode parent) {
565
		if (parent.getNodeNumber() != null) {
566
			maxNumber = (maxNumber < parent.getNodeNumber()) ? parent.getNodeNumber() : maxNumber;
567
			parent.removeNullValueFromChildren();
568
			for (PolytomousKeyNode child : parent.getChildren()) {
569
			    if (parent == child){
570
					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.");
571
				}else{
572
					maxNumber = getMaxNodeNumber(maxNumber, child);
573
				}
574
			}
575
		}
576
		return maxNumber;
577
	}
578

    
579
	/**
580
	 * Refresh numbering of key nodes starting from root.
581
	 *
582
	 */
583
	public void refreshNodeNumbering() {
584
		updateNodeNumbering(getKey().getRoot(), getKey().getStartNumber());
585
	}
586

    
587
	/**
588
	 * Recursively (depth-first) refresh numbering of key nodes starting from the given key node,
589
	 * starting with a given node number.
590
	 *
591
	 * @return new starting node number value
592
	 */
593
	private int updateNodeNumbering(PolytomousKeyNode node,int nodeN) {
594
		int newNodeN = nodeN;
595
		if (node.isLeaf()) {
596
			node.setNodeNumber(null);
597
		} else {
598
			node.setNodeNumber(nodeN);
599
			newNodeN++;
600
			List<PolytomousKeyNode> children = node.getChildren();
601
			HHH_9751_Util.removeAllNull(children);
602
			updateSortIndex();
603
			for (PolytomousKeyNode child : children) {
604
				if (node == child){
605
					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.");
606
				}else{
607
					newNodeN = updateNodeNumbering(child, newNodeN);
608
				}
609
			}
610
		}
611
		return newNodeN;
612
	}
613

    
614
	/**
615
	 * Returns the feature node placed at the given (childIndex + 1) position
616
	 * within the list of {@link #getChildren() children} of <i>this</i> feature
617
	 * node. If the given index is out of bounds no child will be returned.
618
	 *
619
	 * @param childIndex
620
	 *            the integer indicating the position of the feature node
621
	 * @see #getChildren()
622
	 * @see #addChild(PolytomousKeyNode, int)
623
	 * @see #removeChild(int)
624
	 */
625
	public PolytomousKeyNode getChildAt(int childIndex) {
626
		return children.get(childIndex);
627
	}
628

    
629
	/**
630
	 * Returns the number of children nodes of <i>this</i> feature node.
631
	 *
632
	 * @see #getChildren()
633
	 */
634
	@Transient
635
	public int childCount() {
636
		return children.size();
637
	}
638

    
639
	/**
640
	 * Returns the integer indicating the position of the given feature node
641
	 * within the list of {@link #getChildren() children} of <i>this</i> feature
642
	 * node. If the list does not contain this node then -1 will be returned.
643
	 *
644
	 * @param node
645
	 *            the feature node the position of which is being searched
646
	 * @see #addChild(PolytomousKeyNode, int)
647
	 * @see #removeChild(int)
648
	 */
649
	public int getIndex(PolytomousKeyNode node) {
650
		if (!children.contains(node)) {
651
			return -1;
652
		} else {
653
			return children.indexOf(node);
654
		}
655
	}
656

    
657
	/**
658
	 * Returns the boolean value indicating if <i>this</i> feature node has
659
	 * children (false) or not (true). A node without children is at the
660
	 * bottommost level of a tree and is called a leaf.
661
	 *
662
	 * @see #getChildren()
663
	 * @see #getChildCount()
664
	 */
665
	@Transient
666
	public boolean isLeaf() {
667
		return children.size() < 1;
668
	}
669

    
670
	// ** ********************** QUESTIONS AND STATEMENTS ************************/
671

    
672
	/**
673
	 * Returns the statement for <code>this</code> PolytomousKeyNode. When coming
674
	 * from the parent node the user needs to agree with the statement (and disagree
675
	 * with all statements of sibling nodes) to follow <code>this</code> node.<BR>
676
	 * The statement may stand alone (standard in classical keys) or it may be
677
	 * either the answer to the {@link #getQuestion() question} or the
678
	 * value for the {@link #getFeature() feature} of the parent node.
679
	 *
680
	 * @return the statement
681
	 * @see #getQuestion()
682
	 */
683
	public KeyStatement getStatement() {
684
		return statement;
685
	}
686

    
687
	/**
688
	 * This is a convenience method to set the statement text for this node in
689
	 * the given language. <BR>
690
	 * If no statement exists yet a new statement is created. <BR>
691
	 * If a statement text in the given language exists already it is
692
	 * overwritten and the old text is returned. If language is
693
	 * <code>null</code> the default language is used instead.
694
	 *
695
	 * @param text
696
	 *            the statement text
697
	 * @param language
698
	 *            the language of the statement text
699
	 * @return the old statement text in the given language as LanguageString
700
	 */
701
	public LanguageString addStatementText(String text, Language language) {
702
		if (language == null) {
703
			language = Language.DEFAULT();
704
		}
705
		if (this.statement == null) {
706
			setStatement(KeyStatement.NewInstance());
707
		}
708
		return getStatement().putLabel(language, text);
709
	}
710

    
711
	/**
712
	 * @param statement
713
	 * @see #getStatement()
714
	 */
715
	public void setStatement(KeyStatement statement) {
716
		this.statement = statement;
717
	}
718

    
719
	/**
720
	 * Returns the question for <code>this</code> PolytomousKeyNode. <BR>
721
	 * A question is answered by statements in leads below this tree node.
722
	 * Questions are optional and are usually empty in traditional keys.
723
	 *
724
	 * @return the question
725
	 * @see #getStatement()
726
	 */
727
	public KeyStatement getQuestion() {
728
		return question;
729
	}
730

    
731
	/**
732
	 * This is a convenience method to sets the question text for this node in
733
	 * the given language. <BR>
734
	 * If no question exists yet a new question is created. <BR>
735
	 * If a question text in the given language exists already it is overwritten
736
	 * and the old text is returned. If language is <code>null</code> the
737
	 * default language is used instead.
738
	 *
739
	 * @param text
740
	 * @param language
741
	 * @return
742
	 */
743
	public LanguageString addQuestionText(String text, Language language) {
744
		if (language == null) {
745
			language = Language.DEFAULT();
746
		}
747
		if (this.question == null) {
748
			setQuestion(KeyStatement.NewInstance());
749
		}
750
		return getQuestion().putLabel(language, text);
751
	}
752

    
753
	/**
754
	 * @param question
755
	 * @see #getQuestion()
756
	 */
757
	public void setQuestion(KeyStatement question) {
758
		this.question = question;
759
	}
760

    
761
	// **************** modifying text ***************************************
762

    
763
	/**
764
	 * Returns the {@link MultilanguageText} like "an unusual form of",
765
	 * commenting the determined taxon. That is a modifyingText may by used to
766
	 * comment or to constraint the decision step represented by the edge
767
	 * leading to <i>this</i> node
768
	 * <p>
769
	 * All {@link LanguageString language strings} contained in the
770
	 * multilanguage texts should all have the same meaning.<BR>
771
	 */
772
	public Map<Language, LanguageString> getModifyingText() {
773
		return this.modifyingText;
774
	}
775

    
776
	/**
777
	 * See {@link #getModifyingText}
778
	 *
779
	 * @param description
780
	 *            the language string describing the validity in a particular
781
	 *            language
782
	 * @see #getModifyingText()
783
	 * @see #putModifyingText(Language, String)
784
	 */
785
	public LanguageString putModifyingText(LanguageString description) {
786
		return this.modifyingText.put(description.getLanguage(), description);
787
	}
788

    
789
	/**
790
	 * See {@link #getModifyingText}
791
	 *
792
	 * @param text
793
	 *            the string describing the validity in a particular language
794
	 * @param language
795
	 *            the language in which the text string is formulated
796
	 * @see #getModifyingText()
797
	 * @see #putModifyingText(LanguageString)
798
	 */
799
	public LanguageString putModifyingText(Language language, String text) {
800
		return this.modifyingText.put(language,
801
				LanguageString.NewInstance(text, language));
802
	}
803

    
804
	/**
805
	 * See {@link #getModifyingText}
806
	 *
807
	 * @param language
808
	 *            the language in which the language string to be removed has
809
	 *            been formulated
810
	 * @see #getModifyingText()
811
	 */
812
	public LanguageString removeModifyingText(Language language) {
813
		return this.modifyingText.remove(language);
814
	}
815

    
816
	/**
817
	 * Sets the taxon of this {@link PolytomousKeyNode}.
818
	 * If this node already has a taxon removes the taxon
819
	 * and puts it to a new empty child node. <BR>
820
	 * If no taxon exists, but empty child nodes exist
821
	 * adds a new empty child node and puts the taxon.
822
	 * Note: Handle with care, if
823
	 * @param taxon
824
	 * @return the number of taxa belong to the node
825
	 * after the taxon was added
826
	 */
827
	public PolytomousKeyNode setOrAddTaxon(Taxon taxon){
828
	    if (taxon == null){
829
	        throw new NullPointerException("Taxon must not be null");
830
	    }
831
	    if(this.taxon != null){
832
            //rearrange first taxon  //TODO code to PKNode class
833
            Taxon firstTaxon = this.removeTaxon();
834
            PolytomousKeyNode firstChildNode = PolytomousKeyNode.NewInstance();
835
            firstChildNode.setTaxon(firstTaxon);
836
            this.addChild(firstChildNode);
837
        }
838
	    if (!emptyChildNodeExists()){
839
	        setTaxon(taxon);
840
	        return this;
841
	    }else{
842
	        PolytomousKeyNode childNode = PolytomousKeyNode.NewInstance();
843
	        childNode.setTaxon(taxon);
844
	        this.addChild(childNode);
845
	        return childNode;
846
	    }
847
	}
848

    
849
    private boolean emptyChildNodeExists() {
850
        for (PolytomousKeyNode child : this.children){
851
            if (child.getStatement() == null && child.getQuestion() == null && child.getChildren().isEmpty()
852
                    && child.getSubkey() == null && child.getOtherNode() == null){
853
                return true;
854
            }
855
        }
856
        return false;
857
    }
858

    
859
	// *********************** CLONE ********************************************************/
860

    
861
    /**
862
	 * Clones <i>this</i> PolytomousKeyNode. This is a shortcut that enables to
863
	 * create a new instance that differs only slightly from <i>this</i>
864
	 * PolytomousKeyNode by modifying only some of the attributes. The parent,
865
	 * the feature and the key are the are the same as for the original feature
866
	 * node the children are removed.
867
	 *
868
	 * @see eu.etaxonomy.cdm.model.common.VersionableEntity#clone()
869
	 * @see java.lang.Object#clone()
870
	 */
871
	@Override
872
	public PolytomousKeyNode clone() {
873
		PolytomousKeyNode result;
874
		try {
875
			result = (PolytomousKeyNode) super.clone();
876
			result.children = new ArrayList<PolytomousKeyNode>();
877

    
878
			result.modifyingText = new HashMap<Language, LanguageString>();
879
			for (Entry<Language, LanguageString> entry : this.modifyingText
880
					.entrySet()) {
881
				result.putModifyingText(entry.getValue());
882
			}
883

    
884
			return result;
885
		} catch (CloneNotSupportedException e) {
886
			logger.warn("Object does not implement cloneable");
887
			e.printStackTrace();
888
			return null;
889
		}
890
	}
891

    
892
    public Taxon removeTaxon() {
893
        Taxon result = taxon;
894
        this.taxon = null;
895
        return result;
896
    }
897

    
898
    private void updateSortIndex(){
899
        HHH_9751_Util.removeAllNull(children);
900
        for (int i = 0; i < children.size(); i++) {
901
            children.get(i).setSortIndex(i);
902
        }
903
    }
904

    
905
    public void removeNullValueFromChildren(){
906
        updateSortIndex();
907
    }
908
}
(23-23/38)