increased storage capacity of mock cdm cachers
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / taxon / TaxonNode.java
1 // $Id$
2 /**
3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10
11 package eu.etaxonomy.cdm.model.taxon;
12
13 import java.util.ArrayList;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Set;
17
18 import javax.persistence.Entity;
19 import javax.persistence.FetchType;
20 import javax.persistence.ManyToOne;
21 import javax.persistence.OneToMany;
22 import javax.persistence.OrderBy;
23 import javax.persistence.OrderColumn;
24 import javax.persistence.Transient;
25 import javax.validation.constraints.Size;
26 import javax.xml.bind.annotation.XmlAccessType;
27 import javax.xml.bind.annotation.XmlAccessorType;
28 import javax.xml.bind.annotation.XmlElement;
29 import javax.xml.bind.annotation.XmlElementWrapper;
30 import javax.xml.bind.annotation.XmlIDREF;
31 import javax.xml.bind.annotation.XmlRootElement;
32 import javax.xml.bind.annotation.XmlSchemaType;
33 import javax.xml.bind.annotation.XmlType;
34
35 import org.apache.log4j.Logger;
36 import org.hibernate.annotations.Cascade;
37 import org.hibernate.annotations.CascadeType;
38 import org.hibernate.annotations.Index;
39 import org.hibernate.annotations.Table;
40 import org.hibernate.envers.Audited;
41 import org.hibernate.search.annotations.ContainedIn;
42 import org.hibernate.search.annotations.Indexed;
43 import org.hibernate.search.annotations.IndexedEmbedded;
44
45 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
46 import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
47 import eu.etaxonomy.cdm.model.common.CdmBase;
48 import eu.etaxonomy.cdm.model.common.ITreeNode;
49 import eu.etaxonomy.cdm.model.reference.Reference;
50
51 /**
52 * @author a.mueller
53 * @created 31.03.2009
54 */
55 @XmlAccessorType(XmlAccessType.FIELD)
56 @XmlType(name = "TaxonNode", propOrder = {
57 "classification",
58 "taxon",
59 "parent",
60 "treeIndex",
61 "sortIndex",
62 "childNodes",
63 "referenceForParentChildRelation",
64 "microReferenceForParentChildRelation",
65 "countChildren",
66 "synonymToBeUsed"
67 })
68 @XmlRootElement(name = "TaxonNode")
69 @Entity
70 @Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonNode")
71 @Audited
72 @Table(appliesTo="TaxonNode", indexes = { @Index(name = "taxonNodeTreeIndex", columnNames = { "treeIndex" }) })
73 public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITreeNode<TaxonNode>, Cloneable{
74 private static final long serialVersionUID = -4743289894926587693L;
75 private static final Logger logger = Logger.getLogger(TaxonNode.class);
76
77 @XmlElement(name = "taxon")
78 @XmlIDREF
79 @XmlSchemaType(name = "IDREF")
80 @ManyToOne(fetch = FetchType.LAZY)
81 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
82 @ContainedIn
83 private Taxon taxon;
84
85
86 @XmlElement(name = "parent")
87 @XmlIDREF
88 @XmlSchemaType(name = "IDREF")
89 @ManyToOne(fetch = FetchType.LAZY)
90 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
91 private TaxonNode parent;
92
93
94 @XmlElement(name = "treeIndex")
95 @Size(max=255)
96 private String treeIndex;
97
98
99 @XmlElement(name = "classification")
100 @XmlIDREF
101 @XmlSchemaType(name = "IDREF")
102 @ManyToOne(fetch = FetchType.LAZY)
103 @Cascade({CascadeType.SAVE_UPDATE})
104 // TODO @NotNull // avoids creating a UNIQUE key for this field
105 @IndexedEmbedded
106 private Classification classification;
107
108 @XmlElementWrapper(name = "childNodes")
109 @XmlElement(name = "childNode")
110 @XmlIDREF
111 @XmlSchemaType(name = "IDREF")
112 //see https://dev.e-taxonomy.eu/trac/ticket/3722
113 @OrderColumn(name="sortIndex")
114 @OrderBy("sortIndex")
115 @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
116 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
117 private List<TaxonNode> childNodes = new ArrayList<TaxonNode>();
118
119 //see https://dev.e-taxonomy.eu/trac/ticket/3722
120 //see https://dev.e-taxonomy.eu/trac/ticket/4200
121 private Integer sortIndex = -1;
122
123 @XmlElement(name = "reference")
124 @XmlIDREF
125 @XmlSchemaType(name = "IDREF")
126 @ManyToOne(fetch = FetchType.LAZY)
127 @Cascade({CascadeType.SAVE_UPDATE})
128 private Reference<?> referenceForParentChildRelation;
129
130 @XmlElement(name = "microReference")
131 private String microReferenceForParentChildRelation;
132
133 @XmlElement(name = "countChildren")
134 private int countChildren;
135
136 // private Taxon originalConcept;
137 // //or
138 @XmlElement(name = "synonymToBeUsed")
139 @XmlIDREF
140 @XmlSchemaType(name = "IDREF")
141 @ManyToOne(fetch = FetchType.LAZY)
142 @Cascade({CascadeType.SAVE_UPDATE})
143 private Synonym synonymToBeUsed;
144
145 // ******************** CONSTRUCTOR **********************************************/
146
147 protected TaxonNode(){super();}
148
149 /**
150 * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
151 * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
152 * @param taxon
153 * @param classification
154 * @deprecated setting of classification is handled in the addTaxonNode() method,
155 * use TaxonNode(taxon) instead
156 */
157 @Deprecated
158 protected TaxonNode (Taxon taxon, Classification classification){
159 this(taxon);
160 setClassification(classification);
161 }
162
163 /**
164 * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
165 * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
166 *
167 * @param taxon
168 */
169 protected TaxonNode(Taxon taxon){
170 setTaxon(taxon);
171 }
172
173 // ************************* GETTER / SETTER *******************************/
174
175 public Integer getSortIndex() {
176 return sortIndex;
177 }
178 /**
179 * SortIndex shall be handled only internally, therefore not public.
180 * However, as javaassist only supports protected methods it needs to be protected, not private.
181 * Alternatively we could use deproxy on every call of this method (see commented code)
182 * @param i
183 * @return
184 * @deprecated for internal use only
185 */
186 protected void setSortIndex(Integer i) {
187 // CdmBase.deproxy(this, TaxonNode.class).sortIndex = i; //alternative solution for private, DON'T remove
188 sortIndex = i;
189 }
190
191
192 public Taxon getTaxon() {
193 return taxon;
194 }
195 protected void setTaxon(Taxon taxon) {
196 this.taxon = taxon;
197 if (taxon != null){
198 taxon.addTaxonNode(this);
199 }
200 }
201
202
203 @Override
204 public List<TaxonNode> getChildNodes() {
205 return childNodes;
206 }
207 protected void setChildNodes(List<TaxonNode> childNodes) {
208 this.childNodes = childNodes;
209 }
210
211
212 public Classification getClassification() {
213 return classification;
214 }
215 /**
216 * THIS METHOD SHOULD NOT BE CALLED!
217 * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
218 * @param classification
219 * @deprecated for internal use only
220 */
221 protected void setClassification(Classification classification) {
222 this.classification = classification;
223 }
224
225
226 @Override
227 public String getMicroReference() {
228 return microReferenceForParentChildRelation;
229 }
230 public void setMicroReference(String microReference) {
231 this.microReferenceForParentChildRelation = microReference;
232 }
233
234
235 @Override
236 public Reference getReference() {
237 return referenceForParentChildRelation;
238 }
239 public void setReference(Reference reference) {
240 this.referenceForParentChildRelation = reference;
241 }
242
243 //countChildren
244 public int getCountChildren() {
245 return countChildren;
246 }
247 /**
248 * @deprecated for internal use only
249 * @param countChildren
250 */
251 protected void setCountChildren(int countChildren) {
252 this.countChildren = countChildren;
253 }
254
255
256 //parent
257 @Override
258 public TaxonNode getParent(){
259 return parent;
260 }
261 /**
262 * Sets the parent of this taxon node.<BR>
263 *
264 * In most cases you would want to call setParentTreeNode(ITreeNode) which
265 * handles updating of the bidirectional relationship
266 *
267 * @see setParentTreeNode(ITreeNode)
268 * @param parent
269 *
270 */
271 protected void setParent(TaxonNode parent) {
272 this.parent = parent;
273 }
274
275
276 //synonymToBeused
277 public Synonym getSynonymToBeUsed() {
278 return synonymToBeUsed;
279 }
280 public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
281 this.synonymToBeUsed = synonymToBeUsed;
282 }
283
284
285 //treeindex
286 @Override
287 public String treeIndex() {
288 return treeIndex;
289 }
290 @Override
291 @Deprecated //for CDM lib internal use only, may be removed in future versions
292 public void setTreeIndex(String treeIndex) {
293 this.treeIndex = treeIndex;
294 }
295
296
297
298 //************************ METHODS **************************/
299
300 @Override
301 public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
302 return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
303 }
304
305
306 @Override
307 public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
308 if (this.getClassification().isTaxonInTree(taxon)){
309 throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));
310 }
311 return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
312 }
313
314 /**
315 * Moves a taxon node to a new parent. Descendents of the node are moved as well
316 *
317 * @param childNode the taxon node to be moved to the new parent
318 * @return the child node in the state of having a new parent
319 */
320 @Override
321 public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference){
322 addChildNode(childNode, childNodes.size(), reference, microReference);
323 return childNode;
324 }
325
326 /**
327 * Inserts the given taxon node in the list of children of <i>this</i> taxon node
328 * at the given (index + 1) position. If the given index is out of bounds
329 * an exception will arise.<BR>
330 * Due to bidirectionality this method must also assign <i>this</i> taxon node
331 * as the parent of the given child.
332 *
333 * @param child the taxon node to be added
334 * @param index the integer indicating the position at which the child
335 * should be added
336 * @see #getChildNodes()
337 * @see #addChildNode(TaxonNode, Reference, String, Synonym)
338 * @see #deleteChildNode(TaxonNode)
339 * @see #deleteChildNode(int)
340 */
341 @Override
342 public TaxonNode addChildNode(TaxonNode child, int index, Reference reference, String microReference){
343 if (index < 0 || index > childNodes.size() + 1){
344 throw new IndexOutOfBoundsException("Wrong index: " + index);
345 }
346 // check if this node is a descendant of the childNode
347 if(child.getParent() != this && child.isAncestor(this)){
348 throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
349 }
350
351 child.setParentTreeNode(this, index);
352
353 child.setReference(reference);
354 child.setMicroReference(microReference);
355
356 return child;
357 }
358
359 /**
360 * Sets this nodes classification. Updates classification of child nodes recursively.
361 *
362 * If the former and the actual tree are equal() this method does nothing.
363 *
364 * @throws IllegalArgumentException if newClassifciation is null
365 *
366 * @param newClassification
367 */
368 @Transient
369 private void setClassificationRecursively(Classification newClassification) {
370 if (newClassification == null){
371 throw new IllegalArgumentException("New Classification must not be 'null' when setting new classification.");
372 }
373 if(! newClassification.equals(this.getClassification())){
374 this.setClassification(newClassification);
375 for(TaxonNode childNode : this.getChildNodes()){
376 childNode.setClassificationRecursively(newClassification);
377 }
378 }
379 }
380
381 @Override
382 public boolean deleteChildNode(TaxonNode node) {
383 boolean result = removeChildNode(node);
384 Taxon taxon = node.getTaxon();
385 node.setTaxon(null);
386 taxon.removeTaxonNode(node);
387
388 ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
389 for(TaxonNode childNode : childNodes){
390 node.deleteChildNode(childNode);
391 }
392
393 return result;
394 }
395
396 /**
397 * Deletes the child node and also removes children of childnode
398 * recursively if delete children is <code>true</code>
399 * @param node
400 * @param deleteChildren
401 * @return
402 */
403 public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
404 boolean result = removeChildNode(node);
405 Taxon taxon = node.getTaxon();
406 node.setTaxon(null);
407 taxon.removeTaxonNode(node);
408 if (deleteChildren){
409 ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
410 for(TaxonNode childNode : childNodes){
411 node.deleteChildNode(childNode, deleteChildren);
412 }
413 } else{
414 ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
415 for(TaxonNode childNode : childNodes){
416 this.addChildNode(childNode, null, null);
417 }
418 }
419
420 return result;
421 }
422
423 /**
424 * Removes the child node from this node. Sets the parent and the classification of the child
425 * node to null
426 *
427 * @param childNode
428 * @return
429 */
430 protected boolean removeChildNode(TaxonNode childNode){
431 boolean result = true;
432
433 if(childNode == null){
434 throw new IllegalArgumentException("TaxonNode may not be null");
435 }
436 int index = childNodes.indexOf(childNode);
437 if (index >= 0){
438 removeChild(index);
439 } else {
440 result = false;
441 }
442 return result;
443 }
444
445 /**
446 * Removes the child node placed at the given (index + 1) position
447 * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
448 * Sets the parent and the classification of the child
449 * node to null.
450 * If the given index is out of bounds no child will be removed.
451 *
452 * @param index the integer indicating the position of the taxon node to
453 * be removed
454 * @see #getChildNodes()
455 * @see #addChildNode(TaxonNode, Reference, String)
456 * @see #addChildNode(TaxonNode, int, Reference, String)
457 * @see #deleteChildNode(TaxonNode)
458 */
459 public void removeChild(int index){
460
461 TaxonNode child = childNodes.get(index);
462 child = HibernateProxyHelper.deproxy(child, TaxonNode.class); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
463
464 if (child != null){
465
466 TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent(), TaxonNode.class);
467 TaxonNode thisNode = HibernateProxyHelper.deproxy(this, TaxonNode.class);
468 if(parent != null && parent != thisNode){
469 throw new IllegalArgumentException("Child TaxonNode (id:" + child.getId() +") must be a child of this (id:" + thisNode.getId() + " node. Sortindex is: " + index + ", parent-id:" + parent.getId());
470 }else if (parent == null){
471 throw new IllegalStateException("Parent of child is null in TaxonNode.removeChild(int). This should not happen.");
472 }
473 childNodes.remove(index);
474 child.setClassification(null);
475
476 //update sortindex
477 //TODO workaround (see sortIndex doc)
478 this.countChildren = childNodes.size();
479 child.setParent(null);
480
481 updateSortIndex(childNodes, index);
482 child.setSortIndex(null);
483 }
484 }
485
486
487 /**
488 * Remove this taxonNode From its taxonomic parent
489 *
490 * @return true on success
491 */
492 public boolean delete(){
493 if(isTopmostNode()){
494 return classification.deleteChildNode(this);
495 }else{
496 return getParent().deleteChildNode(this);
497 }
498 }
499
500 /**
501 * Remove this taxonNode From its taxonomic parent
502 *
503 * @return true on success
504 */
505 public boolean delete(boolean deleteChildren){
506 if(isTopmostNode()){
507 return classification.deleteChildNode(this, deleteChildren);
508 }else{
509 return getParent().deleteChildNode(this, deleteChildren);
510 }
511 }
512
513 @Override
514 @Deprecated //for CDM lib internal use only, may be removed in future versions
515 public int treeId() {
516 if (this.classification == null){
517 logger.warn("TaxonNode has no classification. This should not happen."); //#3840
518 return -1;
519 }else{
520 return this.classification.getId();
521 }
522 }
523
524
525 /**
526 * Sets the parent of this taxon node to the given parent. Cleans up references to
527 * old parents and sets the classification to the new parents classification
528 *
529 * @param parent
530 */
531 @Transient
532 protected void setParentTreeNode(TaxonNode parent, int index){
533 // remove ourselves from the old parent
534 TaxonNode formerParent = this.getParent();
535
536 if (formerParent != null){
537 //special case, child already exists for same parent
538 //FIXME document / check for correctness
539 if (formerParent.equals(parent)){
540 int currentIndex = formerParent.getChildNodes().indexOf(this);
541 if (currentIndex != -1 && currentIndex < index){
542 index--;
543 }
544 }
545
546 //remove from old parent
547 formerParent.removeChildNode(this);
548 }
549
550 // set the new parent
551 setParent(parent);
552
553 // set the classification to the parents classification
554 Classification classification = parent.getClassification();
555 //FIXME also set the tree index here for performance reasons
556 setClassificationRecursively(classification);
557
558 // add this node to the parent child nodes
559 List<TaxonNode> parentChildren = parent.getChildNodes();
560 if (parentChildren.contains(this)){
561 //avoid duplicates
562 if (parentChildren.indexOf(this) < index){
563 index = index -1;
564 }
565 parentChildren.remove(this);
566 parentChildren.add(index, this);
567 }else{
568 parentChildren.add(index, this);
569 }
570
571
572 //sortIndex
573 //TODO workaround (see sortIndex doc)
574 updateSortIndex(parentChildren, index);
575 //only for debugging
576 if (! this.getSortIndex().equals(index)){
577 logger.warn("index and sortindex are not equal");
578 }
579
580 // update the children count
581 parent.setCountChildren(parent.getChildNodes().size());
582 }
583
584 /**
585 * As long as the sort index is not correctly handled through hibernate this is a workaround method
586 * to update the sort index manually
587 * @param parentChildren
588 * @param index
589 */
590 private void updateSortIndex(List<TaxonNode> children, int index) {
591 for(int i = index; i < children.size(); i++){
592 TaxonNode child = children.get(i);
593 if (child != null){
594 // child = CdmBase.deproxy(child, TaxonNode.class); //deproxy not needed as long as setSortIndex is protected or public #4200
595 child.setSortIndex(i);
596 }else{
597 String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";
598 throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));
599 }
600 }
601 }
602
603
604
605
606 /**
607 * Returns a set containing this node and all nodes that are descendants of this node
608 *
609 * @return
610 */
611
612 protected Set<TaxonNode> getDescendants(){
613 Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
614
615 nodeSet.add(this);
616
617 for(TaxonNode childNode : getChildNodes()){
618 nodeSet.addAll(childNode.getDescendants());
619 }
620
621 return nodeSet;
622 }
623
624 /**
625 * Returns a set containing a clone of this node and of all nodes that are descendants of this node
626 *
627 * @return
628 */
629 protected TaxonNode cloneDescendants(){
630
631 TaxonNode clone = (TaxonNode)this.clone();
632 TaxonNode childClone;
633
634 for(TaxonNode childNode : getChildNodes()){
635 childClone = (TaxonNode) childNode.clone();
636 for (TaxonNode childChild:childNode.getChildNodes()){
637 childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());
638 }
639 clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference());
640 //childClone.addChildNode(childNode.cloneDescendants());
641 }
642 return clone;
643 }
644
645 /**
646 * Returns a
647 *
648 * @return
649 */
650 protected Set<TaxonNode> getAncestors(){
651 Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
652 nodeSet.add(this);
653 if(this.getParent() != null){
654 TaxonNode parent = CdmBase.deproxy(this.getParent(), TaxonNode.class);
655 nodeSet.addAll(parent.getAncestors());
656 }
657 return nodeSet;
658 }
659
660
661 /**
662 * Whether this TaxonNode is a direct child of the classification TreeNode
663 * @return
664 */
665 @Transient
666 public boolean isTopmostNode(){
667 boolean parentCheck = false;
668 boolean classificationCheck = false;
669
670 if(getParent() != null) {
671 if(getParent().getTaxon() == null) {
672 parentCheck = true;
673 }
674 }
675
676 //TODO remove
677 // FIXME This should work but doesn't, due to missing sort indexes, can be removed after fixing #4200, #4098
678 if (classification != null){
679 classificationCheck = classification.getRootNode().getChildNodes().contains(this);
680 }else{
681 classificationCheck = false;
682 }
683
684 // The following is just for logging purposes for the missing sort indexes problem
685 // ticket #4098
686 if(parentCheck != classificationCheck) {
687 logger.warn("isTopmost node check " + parentCheck + " not same as classificationCheck : " + classificationCheck + " for taxon node ");
688 if(this.getParent() != null) {
689 logger.warn("-- with parent uuid " + this.getParent().getUuid().toString());
690 logger.warn("-- with parent id " + this.getParent().getId());
691 for(TaxonNode node : this.getParent().getChildNodes()) {
692 if(node == null) {
693 logger.warn("-- child node is null");
694 } else if (node.getTaxon() == null) {
695 logger.warn("-- child node taxon is null");
696 }
697 }
698 logger.warn("-- parent child count" + this.getParent().getChildNodes().size());
699 }
700 }
701
702 return parentCheck;
703 }
704
705 /**
706 * Whether this TaxonNode is a descendant of the given TaxonNode
707 *
708 * Caution: use this method with care on big branches. -> performance and memory hungry
709 *
710 * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
711 * other direction (up). It will always result in a rather small set of consecutive parents beeing
712 * generated.
713 *
714 * TODO implement more efficiently without generating the set of descendants first
715 *
716 * @param possibleParent
717 * @return true if this is a descendant
718 */
719 @Transient
720 public boolean isDescendant(TaxonNode possibleParent){
721 return possibleParent == null ? false : possibleParent.isAncestor(this);
722 }
723
724 /**
725 * Whether this TaxonNode is an ascendant of the given TaxonNode
726 *
727 * @param possibleChild
728 * @return true if there are ascendants
729 */
730 @Transient
731 public boolean isAncestor(TaxonNode possibleChild){
732 return possibleChild == null ? false : possibleChild.getAncestors().contains(this);
733 }
734
735 /**
736 * Whether this taxon has child nodes
737 *
738 * @return true if the taxonNode has childNodes
739 */
740 @Transient
741 @Override
742 public boolean hasChildNodes(){
743 return childNodes.size() > 0;
744 }
745
746 //*********************** CLONE ********************************************************/
747 /**
748 * Clones <i>this</i> taxon node. This is a shortcut that enables to create
749 * a new instance that differs only slightly from <i>this</i> taxon node by
750 * modifying only some of the attributes.<BR><BR>
751 * The child nodes are not copied.<BR>
752 * The taxon and parent are the same as for the original taxon node. <BR>
753 *
754 * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
755 * @see java.lang.Object#clone()
756 */
757 @Override
758 public Object clone() {
759 TaxonNode result;
760 try{
761 result = (TaxonNode)super.clone();
762 result.getTaxon().addTaxonNode(result);
763 result.childNodes = new ArrayList<TaxonNode>();
764 result.countChildren = 0;
765
766 return result;
767 }catch (CloneNotSupportedException e) {
768 logger.warn("Object does not implement cloneable");
769 e.printStackTrace();
770 return null;
771 }
772 }
773
774 public boolean hasTaxon() {
775 return (taxon!= null);
776 }
777
778
779
780 }