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
.HashMap
;
15 import java
.util
.HashSet
;
16 import java
.util
.Iterator
;
17 import java
.util
.List
;
21 import javax
.persistence
.Entity
;
22 import javax
.persistence
.FetchType
;
23 import javax
.persistence
.JoinColumn
;
24 import javax
.persistence
.JoinTable
;
25 import javax
.persistence
.ManyToMany
;
26 import javax
.persistence
.ManyToOne
;
27 import javax
.persistence
.MapKeyJoinColumn
;
28 import javax
.persistence
.OneToMany
;
29 import javax
.persistence
.OneToOne
;
30 import javax
.persistence
.Transient
;
31 import javax
.validation
.constraints
.NotNull
;
32 import javax
.xml
.bind
.annotation
.XmlAccessType
;
33 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
34 import javax
.xml
.bind
.annotation
.XmlElement
;
35 import javax
.xml
.bind
.annotation
.XmlElementWrapper
;
36 import javax
.xml
.bind
.annotation
.XmlIDREF
;
37 import javax
.xml
.bind
.annotation
.XmlRootElement
;
38 import javax
.xml
.bind
.annotation
.XmlSchemaType
;
39 import javax
.xml
.bind
.annotation
.XmlType
;
40 import javax
.xml
.bind
.annotation
.adapters
.XmlJavaTypeAdapter
;
42 import org
.apache
.log4j
.Logger
;
43 import org
.hibernate
.annotations
.Cascade
;
44 import org
.hibernate
.annotations
.CascadeType
;
45 import org
.hibernate
.envers
.Audited
;
46 import org
.hibernate
.search
.annotations
.Field
;
47 import org
.hibernate
.search
.annotations
.FieldBridge
;
48 import org
.hibernate
.search
.annotations
.Indexed
;
49 import org
.hibernate
.search
.annotations
.IndexedEmbedded
;
50 import org
.hibernate
.search
.annotations
.Store
;
52 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
53 import eu
.etaxonomy
.cdm
.hibernate
.search
.MultilanguageTextFieldBridge
;
54 import eu
.etaxonomy
.cdm
.jaxb
.MultilanguageTextAdapter
;
55 import eu
.etaxonomy
.cdm
.model
.common
.IReferencedEntity
;
56 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
57 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
58 import eu
.etaxonomy
.cdm
.model
.common
.LanguageString
;
59 import eu
.etaxonomy
.cdm
.model
.common
.MultilanguageText
;
60 import eu
.etaxonomy
.cdm
.model
.common
.TimePeriod
;
61 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
62 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
63 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
69 @XmlAccessorType(XmlAccessType
.FIELD
)
70 @XmlType(name
= "Classification", propOrder
= {
79 @XmlRootElement(name
= "Classification")
82 @Indexed(index
= "eu.etaxonomy.cdm.model.taxon.Classification")
83 public class Classification
extends IdentifiableEntity
<IIdentifiableEntityCacheStrategy
<Classification
>> implements IReferencedEntity
, ITaxonTreeNode
, Cloneable
{
84 private static final long serialVersionUID
= -753804821474209635L;
85 private static final Logger logger
= Logger
.getLogger(Classification
.class);
87 @XmlElement(name
= "Name")
88 @OneToOne(fetch
= FetchType
.LAZY
)
89 @Cascade({CascadeType
.SAVE_UPDATE
})
90 @JoinColumn(name
= "name_id", referencedColumnName
= "id")
92 private LanguageString name
;
95 @XmlElement(name
= "rootNode")
97 @XmlSchemaType(name
= "IDREF")
98 @OneToOne(fetch
=FetchType
.LAZY
)
99 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
100 private TaxonNode rootNode
;
102 @XmlElement(name
= "reference")
104 @XmlSchemaType(name
= "IDREF")
105 @ManyToOne(fetch
= FetchType
.LAZY
)
106 @Cascade({CascadeType
.SAVE_UPDATE
})
107 private Reference
<?
> reference
;
109 @XmlElement(name
= "microReference")
110 private String microReference
;
112 @XmlElement(name
= "TimePeriod")
113 private TimePeriod timeperiod
= TimePeriod
.NewInstance();
115 @XmlElementWrapper( name
= "GeoScopes")
116 @XmlElement( name
= "GeoScope")
118 @XmlSchemaType(name
="IDREF")
119 @ManyToMany(fetch
= FetchType
.LAZY
)
120 @JoinTable(name
="Classification_GeoScope")
121 @Cascade({CascadeType
.SAVE_UPDATE
})
122 private Set
<NamedArea
> geoScopes
= new HashSet
<NamedArea
>();
124 @XmlElement(name
= "Description")
125 @XmlJavaTypeAdapter(MultilanguageTextAdapter
.class)
126 @OneToMany(fetch
= FetchType
.LAZY
, orphanRemoval
=true)
127 @MapKeyJoinColumn(name
="description_mapkey_id")
128 @Cascade({CascadeType
.SAVE_UPDATE
,CascadeType
.MERGE
, CascadeType
.DELETE
})
129 @JoinTable(name
= "Classification_Description")
130 // @Field(name="text", store=Store.YES)
131 // @FieldBridge(impl=MultilanguageTextFieldBridge.class)
132 private Map
<Language
,LanguageString
> description
= new HashMap
<Language
,LanguageString
>();
137 // * If this classification is an alternative classification for a subclassification in
138 // * an other classification(parent view),
139 // * the alternativeViewRoot is the connection node from this classification to the parent classification.
140 // * It replaces another node in the parent view.
142 // private AlternativeViewRoot alternativeViewRoot;
144 // ********************** FACTORY METHODS *********************************************/
146 public static Classification
NewInstance(String name
){
147 return NewInstance(name
, null, Language
.DEFAULT());
150 public static Classification
NewInstance(String name
, Language language
){
151 return NewInstance(name
, null, language
);
154 public static Classification
NewInstance(String name
, Reference reference
){
155 return NewInstance(name
, reference
, Language
.DEFAULT());
158 public static Classification
NewInstance(String name
, Reference reference
, Language language
){
159 return new Classification(name
, reference
, language
);
162 // **************************** CONSTRUCTOR *********************************/
164 //for hibernate use only, protected required by Javassist
165 protected Classification(){super();}
167 protected Classification(String name
, Reference reference
, Language language
){
169 LanguageString langName
= LanguageString
.NewInstance(name
, language
);
171 setReference(reference
);
172 this.rootNode
= new TaxonNode();
173 rootNode
.setClassification(this);
176 //********************** xxxxxxxxxxxxx ******************************************/
178 * Returns the topmost {@link TaxonNode taxon node} (root node) of <i>this</i>
179 * classification. The root node does not have any parent and no taxon. Since taxon nodes
180 * recursively point to their child nodes the complete classification is
181 * defined by its root node.
183 public TaxonNode
getRootNode(){
187 public void setRootNode(TaxonNode root
){
188 this.rootNode
= root
;
192 public TaxonNode
addChildNode(TaxonNode childNode
, Reference citation
, String microCitation
) {
193 return addChildNode(childNode
, rootNode
.getCountChildren(), citation
, microCitation
);
197 public TaxonNode
addChildNode(TaxonNode childNode
, int index
, Reference citation
, String microCitation
) {
199 childNode
.setParentTreeNode(this.rootNode
, index
);
201 childNode
.setReference(citation
);
202 childNode
.setMicroReference(microCitation
);
203 // childNode.setSynonymToBeUsed(synonymToBeUsed);
209 public TaxonNode
addChildTaxon(Taxon taxon
, Reference citation
, String microCitation
) {
210 return addChildTaxon(taxon
, rootNode
.getCountChildren(), citation
, microCitation
);
214 public TaxonNode
addChildTaxon(Taxon taxon
, int index
, Reference citation
, String microCitation
) {
215 return addChildNode(new TaxonNode(taxon
), index
, citation
, microCitation
);
219 public boolean deleteChildNode(TaxonNode node
) {
220 boolean result
= removeChildNode(node
);
222 if (node
.hasTaxon()){
223 node
.getTaxon().removeTaxonNode(node
);
227 ArrayList
<TaxonNode
> childNodes
= new ArrayList
<TaxonNode
>(node
.getChildNodes());
228 for (TaxonNode childNode
: childNodes
){
229 if (childNode
!= null){
230 node
.deleteChildNode(childNode
);
236 public boolean deleteChildNode(TaxonNode node
, boolean deleteChildren
) {
237 boolean result
= removeChildNode(node
);
239 node
.getTaxon().removeTaxonNode(node
);
240 //node.setTaxon(null);
242 ArrayList
<TaxonNode
> childNodes
= new ArrayList
<TaxonNode
>(node
.getChildNodes());
243 for (TaxonNode childNode
: childNodes
){
244 node
.deleteChildNode(childNode
);
255 protected boolean removeChildNode(TaxonNode node
){
256 boolean result
= false;
257 if(!rootNode
.getChildNodes().contains(node
)){
258 throw new IllegalArgumentException("TaxonNode is a not a root node of this classification");
261 result
= rootNode
.removeChildNode(node
);
263 node
.setParent(null);
264 node
.setClassification(null);
269 public boolean removeRootNode(){
270 boolean result
= false;
272 if (rootNode
!= null){
273 this.rootNode
.setChildNodes(new ArrayList
<TaxonNode
>());
274 this.rootNode
.setParent(null);
283 * Appends an existing topmost node to another node of this tree. The existing topmost node becomes
288 * @param microReference
289 * @throws IllegalArgumentException
291 public void makeTopmostNodeChildOfOtherNode(TaxonNode topmostNode
, TaxonNode otherNode
, Reference ref
, String microReference
)
292 throws IllegalArgumentException
{
293 if (otherNode
== null){
294 throw new NullPointerException("other node must not be null");
296 if (! getChildNodes().contains(topmostNode
)){
297 throw new IllegalArgumentException("root node to be added as child must already be root node within this tree");
299 if (otherNode
.getClassification() == null || ! otherNode
.getClassification().equals(this)){
300 throw new IllegalArgumentException("other node must already be node within this tree");
302 if (otherNode
.equals(topmostNode
)){
303 throw new IllegalArgumentException("root node and other node must not be the same");
305 otherNode
.addChildNode(topmostNode
, ref
, microReference
);
310 * Checks if the given taxon is part of <b>this</b> tree.
314 public boolean isTaxonInTree(Taxon taxon
){
315 return (getNode(taxon
) != null);
319 * Checks if the given taxon is part of <b>this</b> tree. If so the according TaxonNode is returned.
320 * Otherwise null is returned.
324 public TaxonNode
getNode(Taxon taxon
){
329 for (TaxonNode taxonNode
: taxon
.getTaxonNodes()){
330 Classification classification
= HibernateProxyHelper
.deproxy(taxonNode
.getClassification(), Classification
.class);
331 if (classification
.equals(this)){
339 * Checks if the given taxon is one of the topmost taxa in <b>this</b> tree.
343 public boolean isTopmostInTree(Taxon taxon
){
344 return (getTopmostNode(taxon
) != null);
349 * Checks if the taxon is a direct child of <b>this</b> tree and returns the according node if true.
350 * Returns null otherwise.
354 public TaxonNode
getTopmostNode(Taxon taxon
){
358 for (TaxonNode taxonNode
: taxon
.getTaxonNodes()){
359 if (taxonNode
.getClassification().equals(this)){
360 if (this.getChildNodes().contains(taxonNode
)){
361 if (taxonNode
.getParent() == null){
362 logger
.warn("A topmost node should always have the root node as parent but actually has no parent");
363 }else if (taxonNode
.getParent().getParent() != null){
364 logger
.warn("The root node should have not parent but actually has one");
365 }else if (taxonNode
.getParent().getTaxon() != null){
366 logger
.warn("The root node should have not taxon but actually has one");
375 private boolean handleCitationOverwrite(TaxonNode childNode
, Reference citation
, String microCitation
){
376 if (citation
!= null){
377 if (childNode
.getReference() != null && ! childNode
.getReference().equals(citation
)){
378 logger
.warn("ReferenceForParentChildRelation will be overwritten");
380 childNode
.setReference(citation
);
382 if (microCitation
!= null){
383 if (childNode
.getMicroReference() != null && ! childNode
.getMicroReference().equals(microCitation
)){
384 logger
.warn("MicroReferenceForParentChildRelation will be overwritten");
386 childNode
.setMicroReference(microCitation
);
392 * Relates two taxa as parent-child nodes within a classification. <BR>
393 * If the taxa are not yet part of the tree they are added to it.<Br>
394 * If the child taxon is a topmost node still it is added as child and deleted from the rootNode set.<Br>
395 * If the child is a child of another parent already an IllegalStateException is thrown because a child can have only
397 * If the parent-child relationship between these two taxa already exists nothing is changed. Only
398 * citation and microcitation are overwritten by the new values if these values are not null.
403 * @param microCitation
404 * @return the childNode
405 * @throws IllegalStateException If the child is a child of another parent already
407 public TaxonNode
addParentChild (Taxon parent
, Taxon child
, Reference citation
, String microCitation
)
408 throws IllegalStateException
{
410 if (parent
== null || child
== null){
411 logger
.warn("Child or parent taxon is null.");
414 if (parent
== child
){
415 logger
.warn("A taxon should never be its own child. Child not added");
418 TaxonNode parentNode
= this.getNode(parent
);
419 TaxonNode childNode
= this.getNode(child
);
421 //if child exists in tree and has a parent
422 //no multiple parents are allowed in the tree
423 if (childNode
!= null && ! childNode
.isTopmostNode()){
424 //...different to the parent taxon throw exception
425 if ( !(childNode
.getParent().getTaxon().equals(parent
) )){
426 if (childNode
.getParent().getTaxon().getId() != 0 && childNode
.getParent().getTaxon().getName().equals(parent
.getName())){
427 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()) ;
429 //... same as the parent taxon do nothing but overwriting citation and microCitation
431 handleCitationOverwrite(childNode
, citation
, microCitation
);
436 //add parent node if not exist
437 if (parentNode
== null){
438 parentNode
= this.addChildTaxon(parent
, null, null);
441 //add child if not exists
442 if (childNode
== null){
443 childNode
= parentNode
.addChildTaxon(child
, citation
, microCitation
);
445 //child is still topmost node
446 //TODO test if child is topmostNode otherwise throw IllegalStateException
447 if (! this.isTopmostInTree(child
)){
448 //throw new IllegalStateException("Child is not a topmost node but must be");
449 if (childNode
.getClassification() != null && childNode
.getParent() == null){
450 logger
.warn("Child has no parent and is not a topmost node, child: " + child
.getId() + " classification: " + childNode
.getClassification().getId());
452 logger
.warn("ChildNode has no classification: " + childNode
.getId());
454 parentNode
.addChildNode(childNode
, citation
, microCitation
);
455 if (!parentNode
.isTopmostNode()){
456 this.addChildNode(parentNode
, citation
, microCitation
);
457 logger
.warn("parent is added as a topmost node");
459 logger
.warn("parent is already a topmost node");
462 this.makeTopmostNodeChildOfOtherNode(childNode
, parentNode
, citation
, microCitation
);
466 } catch (IllegalStateException e
) {
468 } catch (RuntimeException e
){
476 public Reference
getCitation() {
480 public LanguageString
getName() {
484 public void setName(LanguageString name
) {
489 * Returns a set containing all nodes in this classification.
491 * Caution: Use this method with care. It can be very time and resource consuming and might
492 * run into OutOfMemoryExceptions for big trees.
497 public Set
<TaxonNode
> getAllNodes() {
498 Set
<TaxonNode
> allNodes
= new HashSet
<TaxonNode
>();
500 for(TaxonNode rootNode
: getChildNodes()){
501 allNodes
.addAll(rootNode
.getDescendants());
509 public List
<TaxonNode
> getChildNodes() {
510 return rootNode
.getChildNodes();
514 public Reference
getReference() {
518 public void setReference(Reference reference
) {
519 this.reference
= reference
;
524 public String
getMicroReference() {
525 return microReference
;
529 * @param microReference the microReference to set
531 public void setMicroReference(String microReference
) {
532 this.microReference
= microReference
;
537 * The point in time, the time period or the season for which this description element
538 * is valid. A season may be expressed by not filling the year part(s) of the time period.
540 public TimePeriod
getTimeperiod() {
545 * @see #getTimeperiod()
547 public void setTimeperiod(TimePeriod timeperiod
) {
548 if (timeperiod
== null){
549 timeperiod
= TimePeriod
.NewInstance();
551 this.timeperiod
= timeperiod
;
556 * Returns the set of {@link NamedArea named areas} indicating the geospatial
557 * data where <i>this</i> {@link Classification} is valid.
559 public Set
<NamedArea
> getGeoScopes(){
560 return this.geoScopes
;
564 * Adds a {@link NamedArea named area} to the set of {@link #getGeoScopes() named areas}
565 * delimiting the geospatial area where <i>this</i> {@link Classification} is valid.
567 * @param geoScope the named area to be additionally assigned to <i>this</i> taxon description
568 * @see #getGeoScopes()
570 public void addGeoScope(NamedArea geoScope
){
571 this.geoScopes
.add(geoScope
);
575 * Removes one element from the set of {@link #getGeoScopes() named areas} delimiting
576 * the geospatial area where <i>this</i> {@link Classification} is valid.
578 * @param geoScope the named area which should be removed
579 * @see #getGeoScopes()
580 * @see #addGeoScope(NamedArea)
582 public void removeGeoScope(NamedArea geoScope
){
583 this.geoScopes
.remove(geoScope
);
588 * Returns the i18n description used to describe
589 * <i>this</i> {@link Classification}. The different {@link LanguageString language strings}
590 * contained in the multilanguage text should all have the same meaning.
592 public Map
<Language
,LanguageString
> getDescription(){
593 return this.description
;
597 * Adds a translated {@link LanguageString text in a particular language}
598 * to the {@link MultilanguageText description} used to describe
599 * <i>this</i> {@link Classification}.
601 * @param description the language string describing the individuals association
602 * in a particular language
603 * @see #getDescription()
604 * @see #putDescription(Language, String)
607 public void putDescription(LanguageString description
){
608 this.description
.put(description
.getLanguage(),description
);
611 * Creates a {@link LanguageString language string} based on the given text string
612 * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
613 * used to describe <i>this</i> {@link Classification}.
615 * @param text the string describing the individuals association
616 * in a particular language
617 * @param language the language in which the text string is formulated
618 * @see #getDescription()
619 * @see #putDescription(LanguageString)
621 public void putDescription(Language language
, String text
){
622 this.description
.put(language
, LanguageString
.NewInstance(text
, language
));
625 * Removes from the {@link MultilanguageText description} used to describe
626 * <i>this</i> {@link Classification} the one {@link LanguageString language string}
627 * with the given {@link Language language}.
629 * @param language the language in which the language string to be removed
630 * has been formulated
631 * @see #getDescription()
633 public void removeDescription(Language language
){
634 this.description
.remove(language
);
639 public String
generateTitle() {
640 return name
.getText();
643 public int compareTo(Object o
) {
649 public boolean hasChildNodes() {
650 return getChildNodes().size() > 0;
653 //*********************** CLONE ********************************************************/
655 * Clones <i>this</i> classification. This is a shortcut that enables to create
656 * a new instance that differs only slightly from <i>this</i> classification by
657 * modifying only some of the attributes.<BR><BR>
659 * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
660 * @see java.lang.Object#clone()
663 public Object
clone() {
664 Classification result
;
666 result
= (Classification
)super.clone();
667 //result.rootNode.childNodes = new ArrayList<TaxonNode>();
668 List
<TaxonNode
> rootNodes
= new ArrayList
<TaxonNode
>();
669 TaxonNode rootNodeClone
;
672 rootNodes
.addAll(rootNode
.getChildNodes());
674 Iterator
<TaxonNode
> iterator
= rootNodes
.iterator();
676 while (iterator
.hasNext()){
677 rootNode
= iterator
.next();
678 rootNodeClone
= rootNode
.cloneDescendants();
679 rootNodeClone
.setClassification(result
);
680 result
.addChildNode(rootNodeClone
, rootNode
.getReference(), rootNode
.getMicroReference());
681 rootNodeClone
.setSynonymToBeUsed(rootNode
.getSynonymToBeUsed());
685 result
.geoScopes
= new HashSet
<NamedArea
>();
686 for (NamedArea namedArea
: getGeoScopes()){
687 result
.geoScopes
.add(namedArea
);
692 }catch (CloneNotSupportedException e
) {
693 logger
.warn("Object does not implement cloneable");