minor
[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.Set;
16
17 import javax.persistence.Entity;
18 import javax.persistence.FetchType;
19 import javax.persistence.ManyToOne;
20 import javax.persistence.OneToMany;
21 import javax.persistence.Transient;
22 import javax.xml.bind.annotation.XmlAccessType;
23 import javax.xml.bind.annotation.XmlAccessorType;
24 import javax.xml.bind.annotation.XmlElement;
25 import javax.xml.bind.annotation.XmlElementWrapper;
26 import javax.xml.bind.annotation.XmlIDREF;
27 import javax.xml.bind.annotation.XmlRootElement;
28 import javax.xml.bind.annotation.XmlSchemaType;
29 import javax.xml.bind.annotation.XmlType;
30
31 import org.apache.log4j.Logger;
32 import org.hibernate.annotations.Cascade;
33 import org.hibernate.annotations.CascadeType;
34 import org.hibernate.envers.Audited;
35
36 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
37 import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
38 import eu.etaxonomy.cdm.model.reference.ReferenceBase;
39
40 /**
41 * @author a.mueller
42 * @created 31.03.2009
43 * @version 1.0
44 */
45 @XmlAccessorType(XmlAccessType.FIELD)
46 @XmlType(name = "TaxonNode", propOrder = {
47 "taxon",
48 "parent",
49 "taxonomicTree",
50 "childNodes",
51 "referenceForParentChildRelation",
52 "microReferenceForParentChildRelation",
53 "countChildren",
54 "synonymToBeUsed"
55 })
56 @XmlRootElement(name = "TaxonNode")
57 @Entity
58 @Audited
59 public class TaxonNode extends AnnotatableEntity implements ITreeNode{
60 private static final long serialVersionUID = -4743289894926587693L;
61 @SuppressWarnings("unused")
62 private static final Logger logger = Logger.getLogger(TaxonNode.class);
63
64 @XmlElement(name = "taxon")
65 @XmlIDREF
66 @XmlSchemaType(name = "IDREF")
67 @ManyToOne(fetch = FetchType.LAZY)
68 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
69 private Taxon taxon;
70
71
72 @XmlElement(name = "parent")
73 @XmlIDREF
74 @XmlSchemaType(name = "IDREF")
75 @ManyToOne(fetch = FetchType.LAZY)
76 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
77 private TaxonNode parent;
78
79
80 @XmlElement(name = "taxonomicTree")
81 @XmlIDREF
82 @XmlSchemaType(name = "IDREF")
83 @ManyToOne(fetch = FetchType.LAZY)
84 @Cascade({CascadeType.SAVE_UPDATE})
85 private TaxonomicTree taxonomicTree;
86
87 @XmlElementWrapper(name = "childNodes")
88 @XmlElement(name = "childNode")
89 @XmlIDREF
90 @XmlSchemaType(name = "IDREF")
91 @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
92 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
93 private Set<TaxonNode> childNodes = new HashSet<TaxonNode>();
94
95 @XmlElement(name = "reference")
96 @XmlIDREF
97 @XmlSchemaType(name = "IDREF")
98 @ManyToOne(fetch = FetchType.LAZY)
99 @Cascade({CascadeType.SAVE_UPDATE})
100 private ReferenceBase referenceForParentChildRelation;
101
102 @XmlElement(name = "microReference")
103 private String microReferenceForParentChildRelation;
104
105 @XmlElement(name = "countChildren")
106 private int countChildren;
107
108 // private Taxon originalConcept;
109 // //or
110 @XmlElement(name = "synonymToBeUsed")
111 @XmlIDREF
112 @XmlSchemaType(name = "IDREF")
113 @ManyToOne(fetch = FetchType.LAZY)
114 @Cascade({CascadeType.SAVE_UPDATE})
115 private Synonym synonymToBeUsed;
116
117
118 protected TaxonNode(){
119 super();
120 }
121
122 /**
123 * to create nodes either use TaxonomicView.addRoot() or TaxonNode.addChild();
124 * @param taxon
125 * @param taxonomicTree
126 * @deprecated setting of taxonomic tree is handled in the addTaxonNode() method,
127 * use TaxonNode(taxon) instead
128 */
129 protected TaxonNode (Taxon taxon, TaxonomicTree taxonomicTree){
130 this(taxon);
131 setTaxonomicTree(taxonomicTree);
132 }
133
134 /**
135 * to create nodes either use TaxonomicView.addChildTaxon() or TaxonNode.addChildTaxon();
136 *
137 * @param taxon
138 */
139 protected TaxonNode(Taxon taxon){
140 setTaxon(taxon);
141 }
142
143
144
145 //************************ METHODS **************************/
146 /**
147 * @deprecated developers should be forced to pass in null values if they choose so.
148 */
149 @Deprecated
150 public TaxonNode addChild(Taxon taxon){
151 return addChild(taxon, null, null, null);
152 }
153
154 /**
155 * @deprecated developers should be forced to pass in null values if they choose so.
156 */
157 @Deprecated
158 public TaxonNode addChild(Taxon taxon, ReferenceBase ref, String microReference){
159 return addChild(taxon, ref, microReference, null);
160 }
161
162 /**
163 *
164 * @param taxon
165 * @param ref
166 * @param microReference
167 * @param synonymUsed
168 * @return
169 * @deprecated use addChildTaxon() instead
170 */
171 @Deprecated
172 public TaxonNode addChild(Taxon taxon, ReferenceBase ref, String microReference, Synonym synonymUsed){
173 return addChildTaxon(taxon, ref, microReference, synonymUsed);
174 }
175
176
177 /* (non-Javadoc)
178 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#addChildTaxon(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.model.reference.ReferenceBase, java.lang.String, eu.etaxonomy.cdm.model.taxon.Synonym)
179 */
180 public TaxonNode addChildTaxon(Taxon taxon, ReferenceBase citation,
181 String microCitation, Synonym synonymToBeUsed) {
182 if (this.getTaxonomicTree().isTaxonInTree(taxon)){
183 throw new IllegalArgumentException("Taxon may not be in a taxonomic view twice");
184 }
185
186 return addChildNode(new TaxonNode(taxon), citation, microCitation, synonymToBeUsed);
187 }
188
189 /**
190 *
191 * @param childNode
192 * @param ref
193 * @param microReference
194 * @param synonymUsed
195 *
196 * @deprecated use addChildNode instead
197 */
198 @Deprecated
199 protected void addChildNote(TaxonNode childNode, ReferenceBase ref, String microReference, Synonym synonymUsed){
200 if (! childNode.getTaxonomicTree().equals(this.getTaxonomicTree())){
201 throw new IllegalArgumentException("addChildNote(): both nodes must be part of the same view");
202 }
203 childNode.setParent(this);
204 childNodes.add(childNode);
205 this.countChildren++;
206 childNode.setReferenceForParentChildRelation(ref);
207 childNode.setMicroReferenceForParentChildRelation(microReference);
208 childNode.setSynonymToBeUsed(synonymUsed);
209 }
210
211 /**
212 * Moves a taxon node to a new parent. Descendents of the node are moved as well
213 *
214 * @param childNode the taxon node to be moved to the new parent
215 * @return the child node in the state of having a new parent
216 */
217 public TaxonNode addChildNode(TaxonNode childNode, ReferenceBase reference, String microReference, Synonym synonymToBeUsed){
218
219 // check if this node is a descendant of the childNode
220 if(childNode.getParentTreeNode() != this && childNode.isAncestor(this)){
221 throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
222 }
223
224 childNode.setParentTreeNode(this);
225
226 childNode.setReference(reference);
227 childNode.setMicroReference(microReference);
228 childNode.setSynonymToBeUsed(synonymToBeUsed);
229
230 return childNode;
231 }
232
233 /**
234 * Sets this nodes taxonomic tree. Updates taxonomic tree of child nodes recursively
235 *
236 * If the former and the actual tree are equal() this method does nothing
237 *
238 * @param newTree
239 */
240 @Transient
241 private void setTaxonomicTreeRecursively(TaxonomicTree newTree) {
242 if(! newTree.equals(this.getTaxonomicTree())){
243 this.setTaxonomicTree(newTree);
244 for(TaxonNode childNode : this.getChildNodes()){
245 childNode.setTaxonomicTreeRecursively(newTree);
246 }
247 }
248 }
249
250 /**
251 * This removes recursively all child nodes from this node and from this taxonomic view.
252 * TODO remove orphan nodes completely
253 *
254 * @param node
255 * @return
256 * @deprecated use deleteChildNode() instead
257 */
258 @Deprecated
259 public boolean removeChild(TaxonNode node){
260 return deleteChildNode(node);
261 }
262
263
264 /* (non-Javadoc)
265 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#removeChildNode(eu.etaxonomy.cdm.model.taxon.TaxonNode)
266 */
267 public boolean deleteChildNode(TaxonNode node) {
268 boolean result = removeChildNode(node);
269
270 node.getTaxon().removeTaxonNode(node);
271 node.setTaxon(null);
272
273 ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
274 for(TaxonNode childNode : childNodes){
275 node.deleteChildNode(childNode);
276 }
277
278 // // two iterations because of ConcurrentModificationErrors
279 // Set<TaxonNode> removeNodes = new HashSet<TaxonNode>();
280 // for (TaxonNode grandChildNode : node.getChildNodes()) {
281 // removeNodes.add(grandChildNode);
282 // }
283 // for (TaxonNode childNode : removeNodes) {
284 // childNode.deleteChildNode(node);
285 // }
286
287 return result;
288 }
289
290 /**
291 * Removes the child node from this node. Sets the parent and the taxonomic tree of the child
292 * node to null
293 *
294 * @param childNode
295 * @return
296 */
297 protected boolean removeChildNode(TaxonNode childNode){
298 boolean result = false;
299
300 if(childNode == null){
301 throw new IllegalArgumentException("TaxonNode may not be null");
302 }
303 if(HibernateProxyHelper.deproxy(childNode.getParent(), TaxonNode.class) != this){
304 throw new IllegalArgumentException("TaxonNode must be a child of this node");
305 }
306
307 result = childNodes.remove(childNode);
308 this.countChildren--;
309 if (this.countChildren < 0){
310 throw new IllegalStateException("children count must not be negative ");
311 }
312 childNode.setParent(null);
313 childNode.setTaxonomicTree(null);
314
315 return result;
316 }
317
318
319 /**
320 * Remove this taxonNode From its taxonomic parent
321 *
322 * @return true on success
323 */
324 public boolean delete(){
325 if(isTopmostNode()){
326 return taxonomicTree.deleteChildNode(this);
327 }else{
328 return getParent().deleteChildNode(this);
329 }
330 }
331
332 //*********** GETTER / SETTER ***********************************/
333
334 public Taxon getTaxon() {
335 return taxon;
336 }
337 protected void setTaxon(Taxon taxon) {
338 this.taxon = taxon;
339 if (taxon != null){
340 taxon.addTaxonNode(this);
341 }
342 }
343 @Transient
344 public ITreeNode getParentTreeNode() {
345 if(isTopmostNode())
346 return getTaxonomicTree();
347 return parent;
348 }
349
350 public TaxonNode getParent(){
351 return parent;
352 }
353
354 /**
355 * Sets the parent of this taxon node.
356 *
357 * In most cases you would want to call setParentTreeNode(ITreeNode) which
358 * handles updating of the bidirectional relationship
359 *
360 * @param parent
361 *
362 * @see setParentTreeNode(ITreeNode)
363 */
364 protected void setParent(ITreeNode parent) {
365 if(parent instanceof TaxonomicTree){
366 this.parent = null;
367 return;
368 }
369 this.parent = (TaxonNode) parent;
370 }
371
372 /**
373 * Sets the parent of this taxon node to the given parent. Cleans up references to
374 * old parents and sets the taxonomic tree to the new parents taxonomic tree
375 *
376 * @param parent
377 */
378 @Transient
379 protected void setParentTreeNode(ITreeNode parent){
380 // remove ourselves from the old parent
381 ITreeNode formerParent = this.getParentTreeNode();
382 if(formerParent instanceof TaxonNode){ //child was a child itself
383 ((TaxonNode) formerParent).removeChildNode(this);
384 }
385 else if((formerParent instanceof TaxonomicTree) && ! formerParent.equals(parent)){ //child was root in old tree
386 ((TaxonomicTree) formerParent).removeChildNode(this);
387 }
388
389 // set the new parent
390 setParent(parent);
391
392 // set the taxonomic tree to the parents taxonomic tree
393 TaxonomicTree classification = (parent instanceof TaxonomicTree) ? (TaxonomicTree) parent : ((TaxonNode) parent).getTaxonomicTree();
394 setTaxonomicTreeRecursively(classification);
395
396 // add this node to the parent child nodes
397 parent.getChildNodes().add(this);
398
399 // update the children count
400 if(parent instanceof TaxonNode){
401 TaxonNode parentTaxonNode = (TaxonNode) parent;
402 parentTaxonNode.setCountChildren(parentTaxonNode.getCountChildren() + 1);
403 }
404 }
405
406 public TaxonomicTree getTaxonomicTree() {
407 return taxonomicTree;
408 }
409 /**
410 * THIS METHOD SHOULD NOT BE CALLED!
411 * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
412 * @param taxonomicTree
413 */
414 protected void setTaxonomicTree(TaxonomicTree taxonomicTree) {
415 this.taxonomicTree = taxonomicTree;
416 }
417
418 public Set<TaxonNode> getChildNodes() {
419 return childNodes;
420 }
421
422 /**
423 * Returns a set containing this node and all nodes that are descendants of this node
424 *
425 * @return
426 */
427 protected Set<TaxonNode> getDescendants(){
428 Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
429
430 nodeSet.add(this);
431
432 for(TaxonNode childNode : getChildNodes()){
433 nodeSet.addAll(childNode.getDescendants());
434 }
435
436 return nodeSet;
437 }
438
439 /**
440 * Returns a
441 *
442 * @return
443 */
444 protected Set<TaxonNode> getAncestors(){
445 Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
446
447
448 nodeSet.add(this);
449
450 if(this.getParent() != null){
451 nodeSet.addAll(((TaxonNode) this.getParent()).getAncestors());
452 }
453
454 return nodeSet;
455 }
456
457 // protected void setChildNodes(List<TaxonNode> childNodes) {
458 // this.childNodes = childNodes;
459 // }
460 /**
461 * The reference for the parent child relationship
462 *
463 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#getReference()
464 */
465 public ReferenceBase getReference() {
466 return referenceForParentChildRelation;
467 }
468
469 /* (non-Javadoc)
470 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setReference(eu.etaxonomy.cdm.model.reference.ReferenceBase)
471 */
472 public void setReference(ReferenceBase reference) {
473 this.referenceForParentChildRelation = reference;
474 }
475
476 /**
477 * @return
478 * @deprecated use getReference instead
479 */
480 @Deprecated
481 public ReferenceBase getReferenceForParentChildRelation() {
482 return getReference();
483 }
484 @Deprecated
485 public void setReferenceForParentChildRelation(ReferenceBase referenceForParentChildRelation) {
486 setReference(referenceForParentChildRelation);
487 }
488
489 /**
490 *
491 *
492 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#getMicroReference()
493 */
494 public String getMicroReference() {
495 return microReferenceForParentChildRelation;
496 }
497
498 /* (non-Javadoc)
499 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setMicroReference(java.lang.String)
500 */
501 public void setMicroReference(String microReference) {
502 this.microReferenceForParentChildRelation = microReference;
503 }
504
505 @Deprecated
506 public String getMicroReferenceForParentChildRelation() {
507 return getMicroReference();
508 }
509
510 @Deprecated
511 public void setMicroReferenceForParentChildRelation(
512 String microReferenceForParentChildRelation) {
513 setMicroReference(microReferenceForParentChildRelation);
514 }
515
516
517 /**
518 * @return the count of children this taxon node has
519 */
520 public int getCountChildren() {
521 return countChildren;
522 }
523
524 /**
525 * @param countChildren
526 */
527 protected void setCountChildren(int countChildren) {
528 this.countChildren = countChildren;
529 }
530 // public Taxon getOriginalConcept() {
531 // return originalConcept;
532 // }
533 // public void setOriginalConcept(Taxon originalConcept) {
534 // this.originalConcept = originalConcept;
535 // }
536 public Synonym getSynonymToBeUsed() {
537 return synonymToBeUsed;
538 }
539 public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
540 this.synonymToBeUsed = synonymToBeUsed;
541 }
542
543 /**
544 * Whether this TaxonNode is a root node
545 * @return
546 * @deprecated use isTopmostNode() instead
547 */
548 @Transient
549 public boolean isRootNode(){
550 return parent == null;
551 }
552
553 /**
554 * Whether this TaxonNode is a direct child of the taxonomic tree TreeNode
555 * @return
556 */
557 @Transient
558 public boolean isTopmostNode(){
559 return parent == null;
560 }
561
562 /**
563 * Whether this TaxonNode is a descendant of the given TaxonNode
564 *
565 * Caution: use this method with care on big branches. -> performance and memory hungry
566 *
567 * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
568 * other direction (up). It will always result in a rather small set of consecutive parents beeing
569 * generated.
570 *
571 * TODO implement more efficiently without generating the set of descendants first
572 *
573 * @param possibleParent
574 * @return true if this is a descendant
575 */
576 @Transient
577 public boolean isDescendant(TaxonNode possibleParent){
578 return possibleParent.getDescendants().contains(this);
579 }
580
581 /**
582 * Whether this TaxonNode is an ascendant of the given TaxonNode
583 *
584 *
585 * @param possibleChild
586 * @return true if there are ascendants
587 */
588 @Transient
589 public boolean isAncestor(TaxonNode possibleChild){
590 return possibleChild.getAncestors().contains(this);
591 }
592
593 /**
594 * Whether this taxon has child nodes
595 *
596 * @return true if the taxonNode has childNodes
597 */
598 @Transient
599 public boolean hasChildNodes(){
600 return childNodes.size() > 0;
601 }
602 }