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
.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
.tool
.hbm2x
.StringUtils
;
40 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
41 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
42 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
43 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
44 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
45 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
46 import eu
.etaxonomy
.cdm
.strategy
.generate
.PolytomousKeyGenerator
;
49 * The class allowing the representation of single-access fixed dichotomous or
50 * polytomous decision keys used to identify {@link SpecimenOrObservationBase
51 * specimens or observations} (this means to assign {@link Taxon taxa} to). The
52 * key may be written by an author or may be generated by the
53 * {@link PolytomousKeyGenerator}. The different paths to the taxa are expressed
54 * by a decision graph consisting of {@link PolytomousKeyNode
55 * PolytomousKeyNodes}. The root node of such graph is accessible by
56 * {@link #getRoot()}. Please refer to {@link PolytomousKeyNode} for detailed
57 * documentation on this decision graph please
67 @XmlAccessorType(XmlAccessType
.FIELD
)
68 @XmlType(name
= "PolytomousKey", propOrder
= { "coveredTaxa", "taxonomicScope",
69 "geographicalScope", "scopeRestrictions", "root" })
70 @XmlRootElement(name
= "PolytomousKey")
72 @Indexed(index
= "eu.etaxonomy.cdm.model.media.FeatureTree")
74 public class PolytomousKey
extends IdentifiableEntity
implements
76 private static final long serialVersionUID
= -3368243754557343942L;
77 @SuppressWarnings("unused")
78 private static final Logger logger
= Logger
.getLogger(PolytomousKey
.class);
80 @XmlElementWrapper(name
= "CoveredTaxa")
81 @XmlElement(name
= "CoveredTaxon")
83 @XmlSchemaType(name
= "IDREF")
84 @ManyToMany(fetch
= FetchType
.LAZY
)
86 private Set
<Taxon
> coveredTaxa
= new HashSet
<Taxon
>();
88 @XmlElementWrapper(name
= "TaxonomicScope")
89 @XmlElement(name
= "Taxon")
91 @XmlSchemaType(name
= "IDREF")
92 @ManyToMany(fetch
= FetchType
.LAZY
)
93 @JoinTable(name
= "PolytomousKey_Taxon", joinColumns
= @JoinColumn(name
= "polytomousKey_id"), inverseJoinColumns
= @JoinColumn(name
= "taxon_id"))
95 private Set
<Taxon
> taxonomicScope
= new HashSet
<Taxon
>();
97 @XmlElementWrapper(name
= "GeographicalScope")
98 @XmlElement(name
= "Area")
100 @XmlSchemaType(name
= "IDREF")
101 @ManyToMany(fetch
= FetchType
.LAZY
)
102 @JoinTable(name
= "PolytomousKey_NamedArea")
104 private Set
<NamedArea
> geographicalScope
= new HashSet
<NamedArea
>();
106 @XmlElementWrapper(name
= "ScopeRestrictions")
107 @XmlElement(name
= "Restriction")
109 @XmlSchemaType(name
= "IDREF")
110 @ManyToMany(fetch
= FetchType
.LAZY
)
111 @JoinTable(name
= "PolytomousKey_Scope")
113 private Set
<Scope
> scopeRestrictions
= new HashSet
<Scope
>();
115 @XmlElement(name
= "Root")
116 @OneToOne(fetch
= FetchType
.LAZY
)
117 @Cascade({ CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
118 private PolytomousKeyNode root
;
120 // ******************************** STATIC METHODS
121 // ********************************/
124 * Creates a new empty identification multi-access key instance.
126 public static PolytomousKey
NewInstance() {
127 return new PolytomousKey();
131 * Creates a new empty identification polytomous key instance.
133 public static PolytomousKey
NewTitledInstance(String title
) {
134 PolytomousKey result
= new PolytomousKey();
135 result
.setTitleCache(title
, true);
139 // ************************** CONSTRUCTOR
140 // *******************************************/
143 * Class constructor: creates a new empty multi-access key instance.
145 protected PolytomousKey() {
147 root
= PolytomousKeyNode
.NewRootInstance();
151 // ************************ GETTER/ SETTER
154 * Returns the topmost {@link PolytomousKeyNode polytomous key node} (root
155 * node) of <i>this</i> polytomous key. The root node does not have any
156 * parent. Since polytomous key nodes recursively point to their child nodes
157 * the complete polytomous key is defined by its root node.
159 public PolytomousKeyNode
getRoot() {
164 * This method should be used by Hibernate only. If we want to make this
165 * method public we have to think about biderionality and also what should
166 * happen with the old root node.
170 public void setRoot(PolytomousKeyNode root
) {
175 * Returns the set of possible {@link Taxon taxa} corresponding to
176 * <i>this</i> identification key.
178 public Set
<Taxon
> getCoveredTaxa() {
179 if (coveredTaxa
== null) {
180 this.coveredTaxa
= new HashSet
<Taxon
>();
186 * @see #getCoveredTaxa()
188 protected void setCoveredTaxa(Set
<Taxon
> coveredTaxa
) {
189 this.coveredTaxa
= coveredTaxa
;
193 * Adds a {@link Taxon taxa} to the set of {@link #getCoveredTaxa() covered
194 * taxa} corresponding to <i>this</i> identification key.
197 * the taxon to be added to <i>this</i> identification key
198 * @see #getCoveredTaxa()
200 public void addCoveredTaxon(Taxon taxon
) {
201 this.coveredTaxa
.add(taxon
);
205 * Removes one element from the set of {@link #getCoveredTaxa() covered
206 * taxa} corresponding to <i>this</i> identification key.
209 * the taxon which should be removed
210 * @see #getCoveredTaxa()
211 * @see #addCoveredTaxon(Taxon)
213 public void removeCoveredTaxon(Taxon taxon
) {
214 this.coveredTaxa
.remove(taxon
);
218 * Returns the set of {@link NamedArea named areas} indicating the
219 * geospatial data where <i>this</i> identification key is valid.
221 public Set
<NamedArea
> getGeographicalScope() {
222 if (geographicalScope
== null) {
223 this.geographicalScope
= new HashSet
<NamedArea
>();
225 return geographicalScope
;
229 * Adds a {@link NamedArea geoScope} to the set of {@link #getGeoScopes()
230 * geogspatial scopes} corresponding to <i>this</i> identification key.
233 * the named area to be added to <i>this</i> identification key
234 * @see #getGeoScopes()
236 public void addGeographicalScope(NamedArea geoScope
) {
237 this.geographicalScope
.add(geoScope
);
241 * Removes one element from the set of {@link #getGeoScopes() geogspatial
242 * scopes} corresponding to <i>this</i> identification key.
245 * the named area which should be removed
246 * @see #getGeoScopes()
247 * @see #addGeoScope(NamedArea)
249 public void removeGeographicalScope(NamedArea geoScope
) {
250 this.geographicalScope
.remove(geoScope
);
254 * Returns the set of {@link Taxon taxa} that define the taxonomic scope of
255 * <i>this</i> identification key
257 public Set
<Taxon
> getTaxonomicScope() {
258 if (taxonomicScope
== null) {
259 this.taxonomicScope
= new HashSet
<Taxon
>();
261 return taxonomicScope
;
265 * Adds a {@link Taxon taxa} to the set of {@link #getTaxonomicScope()
266 * taxonomic scopes} corresponding to <i>this</i> identification key.
269 * the taxon to be added to <i>this</i> identification key
270 * @see #getTaxonomicScope()
272 public void addTaxonomicScope(Taxon taxon
) {
273 this.taxonomicScope
.add(taxon
);
277 * Removes one element from the set of {@link #getTaxonomicScope() taxonomic
278 * scopes} corresponding to <i>this</i> identification key.
281 * the taxon which should be removed
282 * @see #getTaxonomicScope()
283 * @see #addTaxonomicScope(Taxon)
285 public void removeTaxonomicScope(Taxon taxon
) {
286 this.taxonomicScope
.remove(taxon
);
290 * Returns the set of {@link Scope scope restrictions} corresponding to
291 * <i>this</i> identification key
293 public Set
<Scope
> getScopeRestrictions() {
294 if (scopeRestrictions
== null) {
295 this.scopeRestrictions
= new HashSet
<Scope
>();
297 return scopeRestrictions
;
301 * Adds a {@link Scope scope restriction} to the set of
302 * {@link #getScopeRestrictions() scope restrictions} corresponding to
303 * <i>this</i> identification key.
305 * @param scopeRestriction
306 * the scope restriction to be added to <i>this</i>
308 * @see #getScopeRestrictions()
310 public void addScopeRestriction(Scope scopeRestriction
) {
311 this.scopeRestrictions
.add(scopeRestriction
);
315 * Removes one element from the set of {@link #getScopeRestrictions() scope
316 * restrictions} corresponding to <i>this</i> identification key.
318 * @param scopeRestriction
319 * the scope restriction which should be removed
320 * @see #getScopeRestrictions()
321 * @see #addScopeRestriction(Scope)
323 public void removeScopeRestriction(Scope scopeRestriction
) {
324 this.scopeRestrictions
.remove(scopeRestriction
);
327 // ******************** toString *****************************************/
329 private class IntegerObject
{
337 public String
toString() {
338 return String
.valueOf(number
);
342 public String
print(PrintStream stream
) {
343 String title
= this.getTitleCache() + "\n";
344 String strPrint
= title
;
346 if (stream
!= null) {
350 PolytomousKeyNode root
= this.getRoot();
351 strPrint
+= printNode(root
, null, " ", stream
);
356 * TODO this is a preliminary implementation
365 private String
printNode(PolytomousKeyNode node
, PolytomousKeyNode parent2
,
366 String identation
, PrintStream stream
) {
367 String separator
= ", ";
369 String result
= identation
+ node
.getNodeNumber() + ". ";
372 String question
= null;
373 String feature
= null;
374 if (node
.getQuestion() != null) {
375 question
= node
.getQuestion().getLabelText(Language
.DEFAULT());
377 if (node
.getFeature() != null) {
378 feature
= node
.getFeature().getLabel(Language
.DEFAULT());
380 result
+= CdmUtils
.concat(" - ", question
, feature
) + "\n";
384 char nextCounter
= 'a';
385 for (PolytomousKeyNode child
: node
.getChildren()) {
386 String leadNumber
= String
.valueOf(nextCounter
++);
387 if (child
.getStatement() != null) {
388 String statement
= child
.getStatement().getLabelText(
390 result
+= identation
+ " " + leadNumber
+ ") "
391 + (statement
== null ?
"" : (statement
));
394 if (!child
.isLeaf()) {
395 result
+= child
.getNodeNumber() + separator
;
398 if (child
.getTaxon() != null) {
399 String strTaxon
= "";
400 if (child
.getTaxon().getName() != null) {
401 strTaxon
= child
.getTaxon().getName()
404 strTaxon
= child
.getTaxon().getTitleCache();
406 result
+= strTaxon
+ separator
;
409 if (child
.getSubkey() != null) {
410 String subkey
= child
.getSubkey().getTitleCache();
411 result
+= subkey
+ separator
;
414 if (child
.getOtherNode() != null) {
415 PolytomousKeyNode otherNode
= child
.getOtherNode();
416 String otherNodeString
= null;
417 if (child
.getKey().equals(otherNode
.getKey())) {
418 otherNodeString
= String
.valueOf(otherNode
421 otherNodeString
= otherNode
.getKey() + " "
422 + otherNode
.getNodeNumber();
424 result
+= otherNodeString
+ separator
;
427 result
= StringUtils
.chompLast(result
, separator
);
432 if (stream
!= null) {
433 stream
.print(result
);
435 for (PolytomousKeyNode child
: node
.getChildren()) {
436 if (!child
.isLeaf()) {
437 result
+= printNode(child
, node
, identation
+ "", stream
);
445 // public List<PolytomousKeyNode> getChildren() {
446 // return getRoot().getChildren();
449 // *********************** CLONE
450 // ********************************************************/
453 * Clones <i>this</i> PolytomousKey. This is a shortcut that enables to
454 * create a new instance that differs only slightly from <i>this</i>
455 * PolytomousKey by modifying only some of the attributes.
457 * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#clone()
458 * @see java.lang.Object#clone()
461 public Object
clone() {
462 PolytomousKey result
;
465 result
= (PolytomousKey
) super.clone();
467 result
.coveredTaxa
= new HashSet
<Taxon
>();
468 for (Taxon taxon
: this.coveredTaxa
) {
469 result
.addCoveredTaxon(taxon
);
472 result
.geographicalScope
= new HashSet
<NamedArea
>();
473 for (NamedArea area
: this.geographicalScope
) {
474 result
.addGeographicalScope(area
);
477 result
.root
= (PolytomousKeyNode
) this.root
.clone();
479 result
.scopeRestrictions
= new HashSet
<Scope
>();
480 for (Scope scope
: this.scopeRestrictions
) {
481 result
.addScopeRestriction(scope
);
484 result
.taxonomicScope
= new HashSet
<Taxon
>();
485 for (Taxon taxon
: this.taxonomicScope
) {
486 result
.addTaxonomicScope(taxon
);
491 } catch (CloneNotSupportedException e
) {
492 logger
.warn("Object does not implement cloneable");