3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
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.
11 package eu
.etaxonomy
.cdm
.model
.taxon
;
13 import java
.util
.ArrayList
;
14 import java
.util
.HashSet
;
15 import java
.util
.Iterator
;
16 import java
.util
.List
;
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
;
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
;
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
;
51 @XmlAccessorType(XmlAccessType
.FIELD
)
52 @XmlType(name
= "Classification", propOrder
= {
59 @XmlRootElement(name
= "Classification")
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);
67 @XmlElement(name
= "Name")
68 @OneToOne(fetch
= FetchType
.LAZY
)
69 @Cascade({CascadeType
.SAVE_UPDATE
})
70 @JoinColumn(name
= "name_id", referencedColumnName
= "id")
72 private LanguageString name
;
75 @XmlElement(name
= "rootNode")
77 @XmlSchemaType(name
= "IDREF")
78 @OneToOne(fetch
=FetchType
.LAZY
)
79 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
80 private TaxonNode rootNode
;
82 @XmlElement(name
= "reference")
84 @XmlSchemaType(name
= "IDREF")
85 @ManyToOne(fetch
= FetchType
.LAZY
)
86 @Cascade({CascadeType
.SAVE_UPDATE
})
87 private Reference
<?
> reference
;
91 @XmlElement(name
= "microReference")
92 private String microReference
;
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.
100 // private AlternativeViewRoot alternativeViewRoot;
102 // ********************** FACTORY METHODS *********************************************/
104 public static Classification
NewInstance(String name
){
105 return NewInstance(name
, null, Language
.DEFAULT());
108 public static Classification
NewInstance(String name
, Language language
){
109 return NewInstance(name
, null, language
);
112 public static Classification
NewInstance(String name
, Reference reference
){
113 return NewInstance(name
, reference
, Language
.DEFAULT());
116 public static Classification
NewInstance(String name
, Reference reference
, Language language
){
117 return new Classification(name
, reference
, language
);
120 // **************************** CONSTRUCTOR *********************************/
122 //for hibernate use only, protected required by Javassist
123 protected Classification(){super();}
125 protected Classification(String name
, Reference reference
, Language language
){
127 LanguageString langName
= LanguageString
.NewInstance(name
, language
);
129 setReference(reference
);
130 this.rootNode
= new TaxonNode();
131 rootNode
.setClassification(this);
134 //********************** xxxxxxxxxxxxx ******************************************/
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.
141 public TaxonNode
getRootNode(){
145 public void setRootNode(TaxonNode root
){
146 this.rootNode
= root
;
150 public TaxonNode
addChildNode(TaxonNode childNode
, Reference citation
, String microCitation
) {
151 return addChildNode(childNode
, rootNode
.getCountChildren(), citation
, microCitation
);
155 public TaxonNode
addChildNode(TaxonNode childNode
, int index
, Reference citation
, String microCitation
) {
157 childNode
.setParentTreeNode(this.rootNode
, index
);
159 childNode
.setReference(citation
);
160 childNode
.setMicroReference(microCitation
);
161 // childNode.setSynonymToBeUsed(synonymToBeUsed);
167 public TaxonNode
addChildTaxon(Taxon taxon
, Reference citation
, String microCitation
) {
168 return addChildTaxon(taxon
, rootNode
.getCountChildren(), citation
, microCitation
);
172 public TaxonNode
addChildTaxon(Taxon taxon
, int index
, Reference citation
, String microCitation
) {
173 return addChildNode(new TaxonNode(taxon
), index
, citation
, microCitation
);
177 public boolean deleteChildNode(TaxonNode node
) {
178 boolean result
= removeChildNode(node
);
180 if (node
.hasTaxon()){
181 node
.getTaxon().removeTaxonNode(node
);
185 ArrayList
<TaxonNode
> childNodes
= new ArrayList
<TaxonNode
>(node
.getChildNodes());
186 for (TaxonNode childNode
: childNodes
){
187 if (childNode
!= null){
188 node
.deleteChildNode(childNode
);
194 public boolean deleteChildNode(TaxonNode node
, boolean deleteChildren
) {
195 boolean result
= removeChildNode(node
);
197 node
.getTaxon().removeTaxonNode(node
);
198 //node.setTaxon(null);
200 ArrayList
<TaxonNode
> childNodes
= new ArrayList
<TaxonNode
>(node
.getChildNodes());
201 for (TaxonNode childNode
: childNodes
){
202 node
.deleteChildNode(childNode
);
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");
219 result
= rootNode
.removeChildNode(node
);
221 node
.setParent(null);
222 node
.setClassification(null);
227 public boolean removeRootNode(){
228 boolean result
= false;
230 if (rootNode
!= null){
231 this.rootNode
.setChildNodes(new ArrayList
<TaxonNode
>());
232 this.rootNode
.setParent(null);
241 * Appends an existing topmost node to another node of this tree. The existing topmost node becomes
246 * @param microReference
247 * @throws IllegalArgumentException
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");
254 if (! getChildNodes().contains(topmostNode
)){
255 throw new IllegalArgumentException("root node to be added as child must already be root node within this tree");
257 if (otherNode
.getClassification() == null || ! otherNode
.getClassification().equals(this)){
258 throw new IllegalArgumentException("other node must already be node within this tree");
260 if (otherNode
.equals(topmostNode
)){
261 throw new IllegalArgumentException("root node and other node must not be the same");
263 otherNode
.addChildNode(topmostNode
, ref
, microReference
);
264 //getRootNodes().remove(root);
269 * Checks if the given taxon is part of <b>this</b> tree.
273 public boolean isTaxonInTree(Taxon taxon
){
274 return (getNode(taxon
) != null);
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.
283 public TaxonNode
getNode(Taxon taxon
){
287 for (TaxonNode taxonNode
: taxon
.getTaxonNodes()){
288 if (taxonNode
.getClassification().equals(this)){
296 * Checks if the given taxon is one of the topmost taxa in <b>this</b> tree.
300 public boolean isTopmostInTree(Taxon taxon
){
301 return (getTopmostNode(taxon
) != null);
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.
311 public TaxonNode
getTopmostNode(Taxon taxon
){
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");
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");
337 childNode
.setReference(citation
);
339 if (microCitation
!= null){
340 if (childNode
.getMicroReference() != null && ! childNode
.getMicroReference().equals(microCitation
)){
341 logger
.warn("MicroReferenceForParentChildRelation will be overwritten");
343 childNode
.setMicroReference(microCitation
);
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
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.
360 * @param microCitation
361 * @return the childNode
362 * @throws IllegalStateException If the child is a child of another parent already
364 public TaxonNode
addParentChild (Taxon parent
, Taxon child
, Reference citation
, String microCitation
)
365 throws IllegalStateException
{
367 if (parent
== null || child
== null){
368 logger
.warn("Child or parent taxon is null.");
371 if (parent
== child
){
372 logger
.warn("A taxon should never be its own child. Child not added");
375 TaxonNode parentNode
= this.getNode(parent
);
376 TaxonNode childNode
= this.getNode(child
);
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
386 handleCitationOverwrite(childNode
, citation
, microCitation
);
391 //add parent node if not exist
392 if (parentNode
== null){
393 parentNode
= this.addChildTaxon(parent
, null, null);
396 //add child if not exists
397 if (childNode
== null){
398 childNode
= parentNode
.addChildTaxon(child
, citation
, microCitation
);
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());
407 logger
.warn("ChildNode has no classification: " + childNode
.getId());
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");
414 logger
.warn("parent is already a topmost node");
417 this.makeTopmostNodeChildOfOtherNode(childNode
, parentNode
, citation
, microCitation
);
421 } catch (IllegalStateException e
) {
423 } catch (RuntimeException e
){
431 public Reference
getCitation() {
435 public LanguageString
getName() {
439 public void setName(LanguageString name
) {
444 * Returns a set containing all nodes in this classification.
446 * Caution: Use this method with care. It can be very time and resource consuming and might
447 * run into OutOfMemoryExceptions for big trees.
452 public Set
<TaxonNode
> getAllNodes() {
453 Set
<TaxonNode
> allNodes
= new HashSet
<TaxonNode
>();
455 for(TaxonNode rootNode
: getChildNodes()){
456 allNodes
.addAll(rootNode
.getDescendants());
464 public List
<TaxonNode
> getChildNodes() {
465 return rootNode
.getChildNodes();
469 public Reference
getReference() {
473 public void setReference(Reference reference
) {
474 this.reference
= reference
;
479 public String
getMicroReference() {
480 return microReference
;
484 * @param microReference the microReference to set
486 public void setMicroReference(String microReference
) {
487 this.microReference
= microReference
;
491 public String
generateTitle() {
492 return name
.getText();
495 public int compareTo(Object o
) {
501 public boolean hasChildNodes() {
502 return getChildNodes().size() > 0;
505 //*********************** CLONE ********************************************************/
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>
511 * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
512 * @see java.lang.Object#clone()
515 public Object
clone() {
516 Classification result
;
518 result
= (Classification
)super.clone();
519 //result.rootNode.childNodes = new ArrayList<TaxonNode>();
520 List
<TaxonNode
> rootNodes
= new ArrayList
<TaxonNode
>();
521 TaxonNode rootNodeClone
;
524 rootNodes
.addAll(rootNode
.getChildNodes());
526 Iterator
<TaxonNode
> iterator
= rootNodes
.iterator();
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());
538 }catch (CloneNotSupportedException e
) {
539 logger
.warn("Object does not implement cloneable");