start bugfixing and refactoring sortindex handling in TaxonNode
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / taxon / Classification.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.Iterator;
16 import java.util.List;
17 import java.util.Set;
18
19 import javax.persistence.Entity;
20 import javax.persistence.FetchType;
21 import javax.persistence.JoinColumn;
22 import javax.persistence.ManyToOne;
23 import javax.persistence.OneToOne;
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.XmlIDREF;
29 import javax.xml.bind.annotation.XmlRootElement;
30 import javax.xml.bind.annotation.XmlSchemaType;
31 import javax.xml.bind.annotation.XmlType;
32
33 import org.apache.log4j.Logger;
34 import org.hibernate.annotations.Cascade;
35 import org.hibernate.annotations.CascadeType;
36 import org.hibernate.envers.Audited;
37 import org.hibernate.search.annotations.Indexed;
38 import org.hibernate.search.annotations.IndexedEmbedded;
39
40 import eu.etaxonomy.cdm.model.common.IReferencedEntity;
41 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
42 import eu.etaxonomy.cdm.model.common.Language;
43 import eu.etaxonomy.cdm.model.common.LanguageString;
44 import eu.etaxonomy.cdm.model.reference.Reference;
45 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
46
47 /**
48 * @author a.mueller
49 * @created 31.03.2009
50 */
51 @XmlAccessorType(XmlAccessType.FIELD)
52 @XmlType(name = "Classification", propOrder = {
53 "name",
54 "rootNode",
55 "reference",
56 "microReference"
57
58 })
59 @XmlRootElement(name = "Classification")
60 @Entity
61 @Audited
62 @Indexed(index = "eu.etaxonomy.cdm.model.taxon.Classification")
63 public class Classification extends IdentifiableEntity<IIdentifiableEntityCacheStrategy<Classification>> implements IReferencedEntity, ITaxonTreeNode, Cloneable{
64 private static final long serialVersionUID = -753804821474209635L;
65 private static final Logger logger = Logger.getLogger(Classification.class);
66
67 @XmlElement(name = "Name")
68 @OneToOne(fetch = FetchType.LAZY)
69 @Cascade({CascadeType.SAVE_UPDATE})
70 @JoinColumn(name = "name_id", referencedColumnName = "id")
71 @IndexedEmbedded
72 private LanguageString name;
73
74
75 @XmlElement(name = "rootNode")
76 @XmlIDREF
77 @XmlSchemaType(name = "IDREF")
78 @OneToOne(fetch=FetchType.LAZY)
79 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
80 private TaxonNode rootNode;
81
82 @XmlElement(name = "reference")
83 @XmlIDREF
84 @XmlSchemaType(name = "IDREF")
85 @ManyToOne(fetch = FetchType.LAZY)
86 @Cascade({CascadeType.SAVE_UPDATE})
87 private Reference<?> reference;
88
89
90
91 @XmlElement(name = "microReference")
92 private String microReference;
93
94 // /**
95 // * If this classification is an alternative classification for a subclassification in
96 // * an other classification(parent view),
97 // * the alternativeViewRoot is the connection node from this classification to the parent classification.
98 // * It replaces another node in the parent view.
99 // */
100 // private AlternativeViewRoot alternativeViewRoot;
101
102 // ********************** FACTORY METHODS *********************************************/
103
104 public static Classification NewInstance(String name){
105 return NewInstance(name, null, Language.DEFAULT());
106 }
107
108 public static Classification NewInstance(String name, Language language){
109 return NewInstance(name, null, language);
110 }
111
112 public static Classification NewInstance(String name, Reference reference){
113 return NewInstance(name, reference, Language.DEFAULT());
114 }
115
116 public static Classification NewInstance(String name, Reference reference, Language language){
117 return new Classification(name, reference, language);
118 }
119
120 // **************************** CONSTRUCTOR *********************************/
121
122 //for hibernate use only, protected required by Javassist
123 protected Classification(){super();}
124
125 protected Classification(String name, Reference reference, Language language){
126 this();
127 LanguageString langName = LanguageString.NewInstance(name, language);
128 setName(langName);
129 setReference(reference);
130 this.rootNode = new TaxonNode();
131 rootNode.setClassification(this);
132 }
133
134 //********************** xxxxxxxxxxxxx ******************************************/
135 /**
136 * Returns the topmost {@link TaxonNode taxon node} (root node) of <i>this</i>
137 * classification. The root node does not have any parent and no taxon. Since taxon nodes
138 * recursively point to their child nodes the complete classification is
139 * defined by its root node.
140 */
141 public TaxonNode getRootNode(){
142 return rootNode;
143 }
144
145 public void setRootNode(TaxonNode root){
146 this.rootNode = root;
147 }
148
149 @Override
150 public TaxonNode addChildNode(TaxonNode childNode, Reference citation, String microCitation) {
151 return addChildNode(childNode, rootNode.getCountChildren(), citation, microCitation);
152 }
153
154 @Override
155 public TaxonNode addChildNode(TaxonNode childNode, int index, Reference citation, String microCitation) {
156
157 childNode.setParentTreeNode(this.rootNode, index);
158
159 childNode.setReference(citation);
160 childNode.setMicroReference(microCitation);
161 // childNode.setSynonymToBeUsed(synonymToBeUsed);
162
163 return childNode;
164 }
165
166 @Override
167 public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
168 return addChildTaxon(taxon, rootNode.getCountChildren(), citation, microCitation);
169 }
170
171 @Override
172 public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
173 return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
174 }
175
176 @Override
177 public boolean deleteChildNode(TaxonNode node) {
178 boolean result = removeChildNode(node);
179
180 if (node.hasTaxon()){
181 node.getTaxon().removeTaxonNode(node);
182 node.setTaxon(null);
183 }
184
185 ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
186 for (TaxonNode childNode : childNodes){
187 if (childNode != null){
188 node.deleteChildNode(childNode);
189 }
190 }
191 return result;
192 }
193
194 public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
195 boolean result = removeChildNode(node);
196
197 node.getTaxon().removeTaxonNode(node);
198 //node.setTaxon(null);
199 if (deleteChildren){
200 ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
201 for (TaxonNode childNode : childNodes){
202 node.deleteChildNode(childNode);
203 }
204 }
205 return result;
206 }
207
208 /**
209 *
210 * @param node
211 * @return
212 */
213 protected boolean removeChildNode(TaxonNode node){
214 boolean result = false;
215 if(!rootNode.getChildNodes().contains(node)){
216 throw new IllegalArgumentException("TaxonNode is a not a root node of this classification");
217 }
218
219 result = rootNode.removeChildNode(node);
220
221 node.setParent(null);
222 node.setClassification(null);
223
224 return result;
225 }
226
227 public boolean removeRootNode(){
228 boolean result = false;
229
230 if (rootNode != null){
231 this.rootNode.setChildNodes(new ArrayList<TaxonNode>());
232 this.rootNode.setParent(null);
233 rootNode = null;
234 result = true;
235 }
236 return result;
237
238 }
239
240 /**
241 * Appends an existing topmost node to another node of this tree. The existing topmost node becomes
242 * an ordinary node.
243 * @param topmostNode
244 * @param otherNode
245 * @param ref
246 * @param microReference
247 * @throws IllegalArgumentException
248 */
249 public void makeTopmostNodeChildOfOtherNode(TaxonNode topmostNode, TaxonNode otherNode, Reference ref, String microReference)
250 throws IllegalArgumentException{
251 if (otherNode == null){
252 throw new NullPointerException("other node must not be null");
253 }
254 if (! getChildNodes().contains(topmostNode)){
255 throw new IllegalArgumentException("root node to be added as child must already be root node within this tree");
256 }
257 if (otherNode.getClassification() == null || ! otherNode.getClassification().equals(this)){
258 throw new IllegalArgumentException("other node must already be node within this tree");
259 }
260 if (otherNode.equals(topmostNode)){
261 throw new IllegalArgumentException("root node and other node must not be the same");
262 }
263 otherNode.addChildNode(topmostNode, ref, microReference);
264 //getRootNodes().remove(root);
265 }
266
267
268 /**
269 * Checks if the given taxon is part of <b>this</b> tree.
270 * @param taxon
271 * @return
272 */
273 public boolean isTaxonInTree(Taxon taxon){
274 return (getNode(taxon) != null);
275 }
276
277 /**
278 * Checks if the given taxon is part of <b>this</b> tree. If so the according TaxonNode is returned.
279 * Otherwise null is returned.
280 * @param taxon
281 * @return
282 */
283 public TaxonNode getNode(Taxon taxon){
284 if (taxon == null){
285 return null;
286 }
287 for (TaxonNode taxonNode: taxon.getTaxonNodes()){
288 if (taxonNode.getClassification().equals(this)){
289 return taxonNode;
290 }
291 }
292 return null;
293 }
294
295 /**
296 * Checks if the given taxon is one of the topmost taxa in <b>this</b> tree.
297 * @param taxon
298 * @return
299 */
300 public boolean isTopmostInTree(Taxon taxon){
301 return (getTopmostNode(taxon) != null);
302 }
303
304
305 /**
306 * Checks if the taxon is a direct child of <b>this</b> tree and returns the according node if true.
307 * Returns null otherwise.
308 * @param taxon
309 * @return
310 */
311 public TaxonNode getTopmostNode(Taxon taxon){
312 if (taxon == null){
313 return null;
314 }
315 for (TaxonNode taxonNode: taxon.getTaxonNodes()){
316 if (taxonNode.getClassification().equals(this)){
317 if (this.getChildNodes().contains(taxonNode)){
318 if (taxonNode.getParent() == null){
319 logger.warn("A topmost node should always have the root node as parent but actually has no parent");
320 }else if (taxonNode.getParent().getParent() != null){
321 logger.warn("The root node should have not parent but actually has one");
322 }else if (taxonNode.getParent().getTaxon() != null){
323 logger.warn("The root node should have not taxon but actually has one");
324 }
325 return taxonNode;
326 }
327 }
328 }
329 return null;
330 }
331
332 private boolean handleCitationOverwrite(TaxonNode childNode, Reference citation, String microCitation){
333 if (citation != null){
334 if (childNode.getReference() != null && ! childNode.getReference().equals(citation)){
335 logger.warn("ReferenceForParentChildRelation will be overwritten");
336 }
337 childNode.setReference(citation);
338 }
339 if (microCitation != null){
340 if (childNode.getMicroReference() != null && ! childNode.getMicroReference().equals(microCitation)){
341 logger.warn("MicroReferenceForParentChildRelation will be overwritten");
342 }
343 childNode.setMicroReference(microCitation);
344 }
345 return true;
346 }
347
348 /**
349 * Relates two taxa as parent-child nodes within a classification. <BR>
350 * If the taxa are not yet part of the tree they are added to it.<Br>
351 * If the child taxon is a topmost node still it is added as child and deleted from the rootNode set.<Br>
352 * If the child is a child of another parent already an IllegalStateException is thrown because a child can have only
353 * one parent. <Br>
354 * If the parent-child relationship between these two taxa already exists nothing is changed. Only
355 * citation and microcitation are overwritten by the new values if these values are not null.
356 *
357 * @param parent
358 * @param child
359 * @param citation
360 * @param microCitation
361 * @return the childNode
362 * @throws IllegalStateException If the child is a child of another parent already
363 */
364 public TaxonNode addParentChild (Taxon parent, Taxon child, Reference citation, String microCitation)
365 throws IllegalStateException{
366 try {
367 if (parent == null || child == null){
368 logger.warn("Child or parent taxon is null.");
369 return null;
370 }
371 if (parent == child){
372 logger.warn("A taxon should never be its own child. Child not added");
373 return null;
374 }
375 TaxonNode parentNode = this.getNode(parent);
376 TaxonNode childNode = this.getNode(child);
377
378 //if child exists in tree and has a parent
379 //no multiple parents are allowed in the tree
380 if (childNode != null && ! childNode.isTopmostNode()){
381 //...different to the parent taxon throw exception
382 if ( !(childNode.getParent().getTaxon().equals(parent) )){
383 throw new IllegalStateException("The child taxon is already part of the tree but has an other parent taxon than the parent to be added. Child: " + child.toString() + ", new parent:" + parent.toString() + ", old parent: " + childNode.getParent().getTaxon().toString()) ;
384 //... same as the parent taxon do nothing but overwriting citation and microCitation
385 }else{
386 handleCitationOverwrite(childNode, citation, microCitation);
387 return childNode;
388 }
389 }
390
391 //add parent node if not exist
392 if (parentNode == null){
393 parentNode = this.addChildTaxon(parent, null, null);
394 }
395
396 //add child if not exists
397 if (childNode == null){
398 childNode = parentNode.addChildTaxon(child, citation, microCitation);
399 }else{
400 //child is still topmost node
401 //TODO test if child is topmostNode otherwise throw IllegalStateException
402 if (! this.isTopmostInTree(child)){
403 //throw new IllegalStateException("Child is not a topmost node but must be");
404 if (childNode.getClassification() != null){
405 logger.warn("Child has no parent and is not a topmost node, child: " + child.getId() + " classification: " + childNode.getClassification().getId());
406 }else{
407 logger.warn("ChildNode has no classification: " + childNode.getId());
408 }
409 parentNode.addChildNode(childNode, citation, microCitation);
410 if (!parentNode.isTopmostNode()){
411 this.addChildNode(parentNode, citation, microCitation);
412 logger.warn("parent is added as a topmost node");
413 }else{
414 logger.warn("parent is already a topmost node");
415 }
416 }else{
417 this.makeTopmostNodeChildOfOtherNode(childNode, parentNode, citation, microCitation);
418 }
419 }
420 return childNode;
421 } catch (IllegalStateException e) {
422 throw e;
423 } catch (RuntimeException e){
424 throw e;
425 }
426 }
427
428
429 @Override
430 @Transient
431 public Reference getCitation() {
432 return reference;
433 }
434
435 public LanguageString getName() {
436 return name;
437 }
438
439 public void setName(LanguageString name) {
440 this.name = name;
441 }
442
443 /**
444 * Returns a set containing all nodes in this classification.
445 *
446 * Caution: Use this method with care. It can be very time and resource consuming and might
447 * run into OutOfMemoryExceptions for big trees.
448 *
449 * @return
450 */
451 @Transient
452 public Set<TaxonNode> getAllNodes() {
453 Set<TaxonNode> allNodes = new HashSet<TaxonNode>();
454
455 for(TaxonNode rootNode : getChildNodes()){
456 allNodes.addAll(rootNode.getDescendants());
457 }
458
459 return allNodes;
460 }
461
462 @Override
463 @Transient
464 public List<TaxonNode> getChildNodes() {
465 return rootNode.getChildNodes();
466 }
467
468 @Override
469 public Reference getReference() {
470 return reference;
471 }
472
473 public void setReference(Reference reference) {
474 this.reference = reference;
475 }
476
477
478 @Override
479 public String getMicroReference() {
480 return microReference;
481 }
482
483 /**
484 * @param microReference the microReference to set
485 */
486 public void setMicroReference(String microReference) {
487 this.microReference = microReference;
488 }
489
490 @Override
491 public String generateTitle() {
492 return name.getText();
493 }
494
495 public int compareTo(Object o) {
496 return 0;
497 }
498
499
500 @Override
501 public boolean hasChildNodes() {
502 return getChildNodes().size() > 0;
503 }
504
505 //*********************** CLONE ********************************************************/
506 /**
507 * Clones <i>this</i> classification. This is a shortcut that enables to create
508 * a new instance that differs only slightly from <i>this</i> classification by
509 * modifying only some of the attributes.<BR><BR>
510
511 * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
512 * @see java.lang.Object#clone()
513 */
514 @Override
515 public Object clone() {
516 Classification result;
517 try{
518 result = (Classification)super.clone();
519 //result.rootNode.childNodes = new ArrayList<TaxonNode>();
520 List<TaxonNode> rootNodes = new ArrayList<TaxonNode>();
521 TaxonNode rootNodeClone;
522
523
524 rootNodes.addAll(rootNode.getChildNodes());
525 TaxonNode rootNode;
526 Iterator<TaxonNode> iterator = rootNodes.iterator();
527
528 while (iterator.hasNext()){
529 rootNode = iterator.next();
530 rootNodeClone = rootNode.cloneDescendants();
531 rootNodeClone.setClassification(result);
532 result.addChildNode(rootNodeClone, rootNode.getReference(), rootNode.getMicroReference());
533 rootNodeClone.setSynonymToBeUsed(rootNode.getSynonymToBeUsed());
534 }
535
536 return result;
537
538 }catch (CloneNotSupportedException e) {
539 logger.warn("Object does not implement cloneable");
540 e.printStackTrace();
541 return null;
542 }
543
544
545
546
547
548 }
549
550
551 }