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
.description
;
13 import java
.io
.PrintStream
;
14 import java
.util
.HashSet
;
17 import javax
.persistence
.Entity
;
18 import javax
.persistence
.FetchType
;
19 import javax
.persistence
.JoinColumn
;
20 import javax
.persistence
.JoinTable
;
21 import javax
.persistence
.ManyToMany
;
22 import javax
.persistence
.OneToOne
;
23 import javax
.validation
.constraints
.NotNull
;
24 import javax
.xml
.bind
.annotation
.XmlAccessType
;
25 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
26 import javax
.xml
.bind
.annotation
.XmlElement
;
27 import javax
.xml
.bind
.annotation
.XmlElementWrapper
;
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
.commons
.lang
.StringUtils
;
34 import org
.apache
.log4j
.Logger
;
35 import org
.hibernate
.annotations
.Cascade
;
36 import org
.hibernate
.annotations
.CascadeType
;
37 import org
.hibernate
.envers
.Audited
;
38 import org
.hibernate
.search
.annotations
.Indexed
;
40 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
41 import eu
.etaxonomy
.cdm
.model
.common
.DefinedTerm
;
42 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
43 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
44 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
45 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
46 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
47 import eu
.etaxonomy
.cdm
.strategy
.cache
.description
.PolytomousKeyDefaultCacheStrategy
;
48 import eu
.etaxonomy
.cdm
.strategy
.generate
.PolytomousKeyGenerator
;
51 * This class represents a fixed single-access key (dichotomous or
52 * polytomous) used to identify (assign a {@link Taxon taxon} to) a {@link SpecimenOrObservationBase
53 * specimen or observation}. The key may be written manually or may be generated automatically
54 * e.g. by the {@link PolytomousKeyGenerator}. The different paths to the taxa are expressed
55 * by a decision graph consisting of {@link PolytomousKeyNode
56 * PolytomousKeyNodes}. The root node of such graph is accessible by
57 * {@link #getRoot()}. Refer to {@link PolytomousKeyNode} for detailed
58 * documentation on the decision graph structure.
64 * @version 2.0 (08.11.2010)
66 @XmlAccessorType(XmlAccessType
.FIELD
)
67 @XmlType(name
= "PolytomousKey", propOrder
= {
74 @XmlRootElement(name
= "PolytomousKey")
76 @Indexed(index
= "eu.etaxonomy.cdm.model.media.FeatureTree")
78 public class PolytomousKey
extends IdentifiableEntity
<PolytomousKeyDefaultCacheStrategy
> implements IIdentificationKey
{
79 private static final long serialVersionUID
= -3368243754557343942L;
80 private static final Logger logger
= Logger
.getLogger(PolytomousKey
.class);
82 @XmlElementWrapper(name
= "CoveredTaxa")
83 @XmlElement(name
= "CoveredTaxon")
85 @XmlSchemaType(name
= "IDREF")
86 @ManyToMany(fetch
= FetchType
.LAZY
)
88 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
89 private Set
<Taxon
> coveredTaxa
= new HashSet
<Taxon
>();
91 @XmlElementWrapper(name
= "TaxonomicScope")
92 @XmlElement(name
= "Taxon")
94 @XmlSchemaType(name
= "IDREF")
95 @ManyToMany(fetch
= FetchType
.LAZY
)
96 @JoinTable(name
= "PolytomousKey_Taxon", joinColumns
= @JoinColumn(name
= "polytomousKey_id"), inverseJoinColumns
= @JoinColumn(name
= "taxon_id"))
98 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
99 private Set
<Taxon
> taxonomicScope
= new HashSet
<Taxon
>();
101 @XmlElementWrapper(name
= "GeographicalScope")
102 @XmlElement(name
= "Area")
104 @XmlSchemaType(name
= "IDREF")
105 @ManyToMany(fetch
= FetchType
.LAZY
)
106 @JoinTable(name
= "PolytomousKey_NamedArea")
108 private Set
<NamedArea
> geographicalScope
= new HashSet
<NamedArea
>();
110 @XmlElementWrapper(name
= "ScopeRestrictions")
111 @XmlElement(name
= "Restriction")
113 @XmlSchemaType(name
= "IDREF")
114 @ManyToMany(fetch
= FetchType
.LAZY
)
115 @JoinTable(name
= "PolytomousKey_Scope")
117 private Set
<DefinedTerm
> scopeRestrictions
= new HashSet
<DefinedTerm
>();
119 @XmlElement(name
= "Root")
120 @OneToOne(fetch
= FetchType
.LAZY
)
121 @Cascade({ CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
122 private PolytomousKeyNode root
;
124 @XmlElement(name
= "StartNumber")
125 private int startNumber
= 1;
128 // ***************** STATIC METHODS ********************************/
131 * Creates a new empty identification multi-access key instance.
133 public static PolytomousKey
NewInstance() {
134 return new PolytomousKey();
138 * Creates a new empty identification polytomous key instance.
140 public static PolytomousKey
NewTitledInstance(String title
) {
141 PolytomousKey result
= new PolytomousKey();
142 result
.setTitleCache(title
, true);
146 // ************************** CONSTRUCTOR ************************/
149 * Class constructor: creates a new empty multi-access key instance.
151 protected PolytomousKey() {
153 root
= PolytomousKeyNode
.NewInstance();
154 root
.setNodeNumber(getStartNumber());
156 this.cacheStrategy
= PolytomousKeyDefaultCacheStrategy
.NewInstance();
159 // ************************ GETTER/ SETTER
162 * Returns the topmost {@link PolytomousKeyNode polytomous key node} (root
163 * node) of <i>this</i> polytomous key. The root node does not have any
164 * parent. Since polytomous key nodes recursively point to their child nodes
165 * the complete polytomous key is defined by its root node.
167 public PolytomousKeyNode
getRoot() {
172 * This method should be used by Hibernate only. If we want to make this
173 * method public we have to think about biderionality and also what should
174 * happen with the old root node.
178 public void setRoot(PolytomousKeyNode root
) {
183 * Returns the set of possible {@link Taxon taxa} corresponding to
184 * <i>this</i> identification key.
186 public Set
<Taxon
> getCoveredTaxa() {
187 if (coveredTaxa
== null) {
188 this.coveredTaxa
= new HashSet
<Taxon
>();
194 * @see #getCoveredTaxa()
196 protected void setCoveredTaxa(Set
<Taxon
> coveredTaxa
) {
197 this.coveredTaxa
= coveredTaxa
;
201 * Adds a {@link Taxon taxa} to the set of {@link #getCoveredTaxa() covered
202 * taxa} corresponding to <i>this</i> identification key.
205 * the taxon to be added to <i>this</i> identification key
206 * @see #getCoveredTaxa()
208 public void addCoveredTaxon(Taxon taxon
) {
209 this.coveredTaxa
.add(taxon
);
213 * Removes one element from the set of {@link #getCoveredTaxa() covered
214 * taxa} corresponding to <i>this</i> identification key.
217 * the taxon which should be removed
218 * @see #getCoveredTaxa()
219 * @see #addCoveredTaxon(Taxon)
221 public void removeCoveredTaxon(Taxon taxon
) {
222 this.coveredTaxa
.remove(taxon
);
226 * Returns the set of {@link NamedArea named areas} indicating the
227 * geospatial data where <i>this</i> identification key is valid.
229 public Set
<NamedArea
> getGeographicalScope() {
230 if (geographicalScope
== null) {
231 this.geographicalScope
= new HashSet
<NamedArea
>();
233 return geographicalScope
;
237 * Adds a {@link NamedArea geoScope} to the set of {@link #getGeoScopes()
238 * geogspatial scopes} corresponding to <i>this</i> identification key.
241 * the named area to be added to <i>this</i> identification key
242 * @see #getGeoScopes()
244 public void addGeographicalScope(NamedArea geoScope
) {
245 this.geographicalScope
.add(geoScope
);
249 * Removes one element from the set of {@link #getGeoScopes() geogspatial
250 * scopes} corresponding to <i>this</i> identification key.
253 * the named area which should be removed
254 * @see #getGeoScopes()
255 * @see #addGeoScope(NamedArea)
257 public void removeGeographicalScope(NamedArea geoScope
) {
258 this.geographicalScope
.remove(geoScope
);
262 * Returns the set of {@link Taxon taxa} that define the taxonomic scope of
263 * <i>this</i> identification key
265 public Set
<Taxon
> getTaxonomicScope() {
266 if (taxonomicScope
== null) {
267 this.taxonomicScope
= new HashSet
<Taxon
>();
269 return taxonomicScope
;
273 * Adds a {@link Taxon taxa} to the set of {@link #getTaxonomicScope()
274 * taxonomic scopes} corresponding to <i>this</i> identification key.
277 * the taxon to be added to <i>this</i> identification key
278 * @see #getTaxonomicScope()
280 public void addTaxonomicScope(Taxon taxon
) {
281 this.taxonomicScope
.add(taxon
);
285 * Removes one element from the set of {@link #getTaxonomicScope() taxonomic
286 * scopes} corresponding to <i>this</i> identification key.
289 * the taxon which should be removed
290 * @see #getTaxonomicScope()
291 * @see #addTaxonomicScope(Taxon)
293 public void removeTaxonomicScope(Taxon taxon
) {
294 this.taxonomicScope
.remove(taxon
);
298 * Returns the set of {@link Scope scope restrictions} corresponding to
299 * <i>this</i> identification key
301 public Set
<DefinedTerm
> getScopeRestrictions() {
302 if (scopeRestrictions
== null) {
303 this.scopeRestrictions
= new HashSet
<DefinedTerm
>();
305 return scopeRestrictions
;
309 * Adds a {@link Scope scope restriction} to the set of
310 * {@link #getScopeRestrictions() scope restrictions} corresponding to
311 * <i>this</i> identification key.
313 * @param scopeRestriction
314 * the scope restriction to be added to <i>this</i>
316 * @see #getScopeRestrictions()
318 public void addScopeRestriction(DefinedTerm scopeRestriction
) {
319 this.scopeRestrictions
.add(scopeRestriction
);
323 * Removes one element from the set of {@link #getScopeRestrictions() scope
324 * restrictions} corresponding to <i>this</i> identification key.
326 * @param scopeRestriction
327 * the scope restriction which should be removed
328 * @see #getScopeRestrictions()
329 * @see #addScopeRestriction(Scope)
331 public void removeScopeRestriction(DefinedTerm scopeRestriction
) {
332 this.scopeRestrictions
.remove(scopeRestriction
);
337 * The first number for the automated numbering of {@link PolytomousKeyNode key nodes}.
338 * Default value is 1.
341 public int getStartNumber() {
346 * @see #getStartNumber()
349 public void setStartNumber(int startNumber
) {
350 this.startNumber
= startNumber
;
353 // ******************** toString *****************************************/
355 private class IntegerObject
{
363 public String
toString() {
364 return String
.valueOf(number
);
368 public String
print(PrintStream stream
) {
369 String title
= this.getTitleCache() + "\n";
370 String strPrint
= title
;
372 if (stream
!= null) {
376 PolytomousKeyNode root
= this.getRoot();
377 strPrint
+= printNode(root
, null, " ", stream
);
382 * TODO this is a preliminary implementation
391 private String
printNode(PolytomousKeyNode node
, PolytomousKeyNode parent2
,
392 String identation
, PrintStream stream
) {
393 String separator
= ", ";
395 String result
= identation
+ node
.getNodeNumber() + ". ";
398 String question
= null;
399 String feature
= null;
400 if (node
.getQuestion() != null) {
401 question
= node
.getQuestion().getLabelText(Language
.DEFAULT());
403 if (node
.getFeature() != null) {
404 feature
= node
.getFeature().getLabel(Language
.DEFAULT());
406 result
+= CdmUtils
.concat(" - ", question
, feature
) + "\n";
410 char nextCounter
= 'a';
411 for (PolytomousKeyNode child
: node
.getChildren()) {
412 String leadNumber
= String
.valueOf(nextCounter
++);
413 if (child
.getStatement() != null) {
414 String statement
= child
.getStatement().getLabelText(
416 result
+= identation
+ " " + leadNumber
+ ") "
417 + (statement
== null ?
"" : (statement
));
420 if (!child
.isLeaf()) {
421 result
+= child
.getNodeNumber() + separator
;
424 if (child
.getTaxon() != null) {
425 String strTaxon
= "";
426 if (child
.getTaxon().getName() != null) {
427 strTaxon
= child
.getTaxon().getName()
430 strTaxon
= child
.getTaxon().getTitleCache();
432 result
+= strTaxon
+ separator
;
435 if (child
.getSubkey() != null) {
436 String subkey
= child
.getSubkey().getTitleCache();
437 result
+= subkey
+ separator
;
440 if (child
.getOtherNode() != null) {
441 PolytomousKeyNode otherNode
= child
.getOtherNode();
442 String otherNodeString
= null;
443 if (child
.getKey().equals(otherNode
.getKey())) {
444 otherNodeString
= String
.valueOf(otherNode
447 otherNodeString
= otherNode
.getKey() + " "
448 + otherNode
.getNodeNumber();
450 result
+= otherNodeString
+ separator
;
453 result
= StringUtils
.chomp(result
, separator
);
458 if (stream
!= null) {
459 stream
.print(result
);
461 for (PolytomousKeyNode child
: node
.getChildren()) {
462 if (!child
.isLeaf()) {
463 result
+= printNode(child
, node
, identation
+ "", stream
);
471 // public List<PolytomousKeyNode> getChildren() {
472 // return getRoot().getChildren();
475 // *********************** CLONE ************************************/
478 * Clones <i>this</i> PolytomousKey. This is a shortcut that enables to
479 * create a new instance that differs only slightly from <i>this</i>
480 * PolytomousKey by modifying only some of the attributes.
482 * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#clone()
483 * @see java.lang.Object#clone()
486 public Object
clone() {
487 PolytomousKey result
;
490 result
= (PolytomousKey
) super.clone();
492 result
.coveredTaxa
= new HashSet
<Taxon
>();
493 for (Taxon taxon
: this.coveredTaxa
) {
494 result
.addCoveredTaxon(taxon
);
497 result
.geographicalScope
= new HashSet
<NamedArea
>();
498 for (NamedArea area
: this.geographicalScope
) {
499 result
.addGeographicalScope(area
);
502 result
.root
= (PolytomousKeyNode
) this.root
.clone();
504 result
.scopeRestrictions
= new HashSet
<DefinedTerm
>();
505 for (DefinedTerm scope
: this.scopeRestrictions
) {
506 result
.addScopeRestriction(scope
);
509 result
.taxonomicScope
= new HashSet
<Taxon
>();
510 for (Taxon taxon
: this.taxonomicScope
) {
511 result
.addTaxonomicScope(taxon
);
516 } catch (CloneNotSupportedException e
) {
517 logger
.warn("Object does not implement cloneable");