root/trunk/cdmlib/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/description/PolytomousKey.java

Revision 12679, 15.0 kB (checked in by a.mueller, 10 months ago)

add PolytomousKey? cache strategy and cascading PolytomousKeyNode?-> PolytomousKey?

Line 
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
11package eu.etaxonomy.cdm.model.description;
12
13import java.io.PrintStream;
14import java.util.HashSet;
15import java.util.Set;
16
17import javax.persistence.Entity;
18import javax.persistence.FetchType;
19import javax.persistence.JoinColumn;
20import javax.persistence.JoinTable;
21import javax.persistence.ManyToMany;
22import javax.persistence.OneToOne;
23import javax.validation.constraints.NotNull;
24import javax.xml.bind.annotation.XmlAccessType;
25import javax.xml.bind.annotation.XmlAccessorType;
26import javax.xml.bind.annotation.XmlElement;
27import javax.xml.bind.annotation.XmlElementWrapper;
28import javax.xml.bind.annotation.XmlIDREF;
29import javax.xml.bind.annotation.XmlRootElement;
30import javax.xml.bind.annotation.XmlSchemaType;
31import javax.xml.bind.annotation.XmlType;
32
33import org.apache.log4j.Logger;
34import org.hibernate.annotations.Cascade;
35import org.hibernate.annotations.CascadeType;
36import org.hibernate.envers.Audited;
37import org.hibernate.search.annotations.Indexed;
38import org.hibernate.tool.hbm2x.StringUtils;
39
40import eu.etaxonomy.cdm.common.CdmUtils;
41import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
42import eu.etaxonomy.cdm.model.common.Language;
43import eu.etaxonomy.cdm.model.location.NamedArea;
44import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
45import eu.etaxonomy.cdm.model.taxon.Taxon;
46import eu.etaxonomy.cdm.strategy.cache.description.PolytomousKeyDefaultCacheStrategy;
47import eu.etaxonomy.cdm.strategy.generate.PolytomousKeyGenerator;
48
49/**
50 * The class allowing the representation of single-access fixed dichotomous or
51 * polytomous decision keys used to identify {@link SpecimenOrObservationBase
52 * specimens or observations} (this means to assign {@link Taxon taxa} to). The
53 * key may be written by an author or may be generated by the
54 * {@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()}. Please refer to {@link PolytomousKeyNode} for detailed
58 * documentation on this decision graph please
59 *
60 * @author h.fradin
61 * @created 13.08.2009
62 * @version 1.0
63 *
64 * @author a.mueller
65 * @created 08.11.2010
66 * @version 2.0
67 */
68@XmlAccessorType(XmlAccessType.FIELD)
69@XmlType(name = "PolytomousKey", propOrder = { "coveredTaxa", "taxonomicScope",
70                "geographicalScope", "scopeRestrictions", "root" })
71@XmlRootElement(name = "PolytomousKey")
72@Entity
73@Indexed(index = "eu.etaxonomy.cdm.model.media.FeatureTree")
74@Audited
75public class PolytomousKey extends IdentifiableEntity<PolytomousKeyDefaultCacheStrategy> implements IIdentificationKey {
76        private static final long serialVersionUID = -3368243754557343942L;
77        private static final Logger logger = Logger.getLogger(PolytomousKey.class);
78
79        @XmlElementWrapper(name = "CoveredTaxa")
80        @XmlElement(name = "CoveredTaxon")
81        @XmlIDREF
82        @XmlSchemaType(name = "IDREF")
83        @ManyToMany(fetch = FetchType.LAZY)
84        @NotNull
85        @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
86        private Set<Taxon> coveredTaxa = new HashSet<Taxon>();
87
88        @XmlElementWrapper(name = "TaxonomicScope")
89        @XmlElement(name = "Taxon")
90        @XmlIDREF
91        @XmlSchemaType(name = "IDREF")
92        @ManyToMany(fetch = FetchType.LAZY)
93        @JoinTable(name = "PolytomousKey_Taxon", joinColumns = @JoinColumn(name = "polytomousKey_id"), inverseJoinColumns = @JoinColumn(name = "taxon_id"))
94        @NotNull
95        @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
96        private Set<Taxon> taxonomicScope = new HashSet<Taxon>();
97
98        @XmlElementWrapper(name = "GeographicalScope")
99        @XmlElement(name = "Area")
100        @XmlIDREF
101        @XmlSchemaType(name = "IDREF")
102        @ManyToMany(fetch = FetchType.LAZY)
103        @JoinTable(name = "PolytomousKey_NamedArea")
104        @NotNull
105        private Set<NamedArea> geographicalScope = new HashSet<NamedArea>();
106
107        @XmlElementWrapper(name = "ScopeRestrictions")
108        @XmlElement(name = "Restriction")
109        @XmlIDREF
110        @XmlSchemaType(name = "IDREF")
111        @ManyToMany(fetch = FetchType.LAZY)
112        @JoinTable(name = "PolytomousKey_Scope")
113        @NotNull
114        private Set<Scope> scopeRestrictions = new HashSet<Scope>();
115
116        @XmlElement(name = "Root")
117        @OneToOne(fetch = FetchType.LAZY)
118        @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE })
119        private PolytomousKeyNode root;
120
121// ***************** STATIC METHODS ********************************/
122
123        /**
124         * Creates a new empty identification multi-access key instance.
125         */
126        public static PolytomousKey NewInstance() {
127                return new PolytomousKey();
128        }
129
130        /**
131         * Creates a new empty identification polytomous key instance.
132         */
133        public static PolytomousKey NewTitledInstance(String title) {
134                PolytomousKey result = new PolytomousKey();
135                result.setTitleCache(title, true);
136                return result;
137        }
138
139// ************************** CONSTRUCTOR ************************/
140
141        /**
142         * Class constructor: creates a new empty multi-access key instance.
143         */
144        protected PolytomousKey() {
145                super();
146                root = PolytomousKeyNode.NewRootInstance();
147                root.setKey(this);
148                this.cacheStrategy = PolytomousKeyDefaultCacheStrategy.NewInstance();
149        }
150
151        // ************************ GETTER/ SETTER
152
153        /**
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.
158         */
159        public PolytomousKeyNode getRoot() {
160                return root;
161        }
162
163        /**
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.
167         *
168         * @see #getRoot()
169         */
170        public void setRoot(PolytomousKeyNode root) {
171                this.root = root;
172        }
173
174        /**
175         * Returns the set of possible {@link Taxon taxa} corresponding to
176         * <i>this</i> identification key.
177         */
178        public Set<Taxon> getCoveredTaxa() {
179                if (coveredTaxa == null) {
180                        this.coveredTaxa = new HashSet<Taxon>();
181                }
182                return coveredTaxa;
183        }
184
185        /**
186         * @see #getCoveredTaxa()
187         */
188        protected void setCoveredTaxa(Set<Taxon> coveredTaxa) {
189                this.coveredTaxa = coveredTaxa;
190        }
191
192        /**
193         * Adds a {@link Taxon taxa} to the set of {@link #getCoveredTaxa() covered
194         * taxa} corresponding to <i>this</i> identification key.
195         *
196         * @param taxon
197         *            the taxon to be added to <i>this</i> identification key
198         * @see #getCoveredTaxa()
199         */
200        public void addCoveredTaxon(Taxon taxon) {
201                this.coveredTaxa.add(taxon);
202        }
203
204        /**
205         * Removes one element from the set of {@link #getCoveredTaxa() covered
206         * taxa} corresponding to <i>this</i> identification key.
207         *
208         * @param taxon
209         *            the taxon which should be removed
210         * @see #getCoveredTaxa()
211         * @see #addCoveredTaxon(Taxon)
212         */
213        public void removeCoveredTaxon(Taxon taxon) {
214                this.coveredTaxa.remove(taxon);
215        }
216
217        /**
218         * Returns the set of {@link NamedArea named areas} indicating the
219         * geospatial data where <i>this</i> identification key is valid.
220         */
221        public Set<NamedArea> getGeographicalScope() {
222                if (geographicalScope == null) {
223                        this.geographicalScope = new HashSet<NamedArea>();
224                }
225                return geographicalScope;
226        }
227
228        /**
229         * Adds a {@link NamedArea geoScope} to the set of {@link #getGeoScopes()
230         * geogspatial scopes} corresponding to <i>this</i> identification key.
231         *
232         * @param geoScope
233         *            the named area to be added to <i>this</i> identification key
234         * @see #getGeoScopes()
235         */
236        public void addGeographicalScope(NamedArea geoScope) {
237                this.geographicalScope.add(geoScope);
238        }
239
240        /**
241         * Removes one element from the set of {@link #getGeoScopes() geogspatial
242         * scopes} corresponding to <i>this</i> identification key.
243         *
244         * @param geoScope
245         *            the named area which should be removed
246         * @see #getGeoScopes()
247         * @see #addGeoScope(NamedArea)
248         */
249        public void removeGeographicalScope(NamedArea geoScope) {
250                this.geographicalScope.remove(geoScope);
251        }
252
253        /**
254         * Returns the set of {@link Taxon taxa} that define the taxonomic scope of
255         * <i>this</i> identification key
256         */
257        public Set<Taxon> getTaxonomicScope() {
258                if (taxonomicScope == null) {
259                        this.taxonomicScope = new HashSet<Taxon>();
260                }
261                return taxonomicScope;
262        }
263
264        /**
265         * Adds a {@link Taxon taxa} to the set of {@link #getTaxonomicScope()
266         * taxonomic scopes} corresponding to <i>this</i> identification key.
267         *
268         * @param taxon
269         *            the taxon to be added to <i>this</i> identification key
270         * @see #getTaxonomicScope()
271         */
272        public void addTaxonomicScope(Taxon taxon) {
273                this.taxonomicScope.add(taxon);
274        }
275
276        /**
277         * Removes one element from the set of {@link #getTaxonomicScope() taxonomic
278         * scopes} corresponding to <i>this</i> identification key.
279         *
280         * @param taxon
281         *            the taxon which should be removed
282         * @see #getTaxonomicScope()
283         * @see #addTaxonomicScope(Taxon)
284         */
285        public void removeTaxonomicScope(Taxon taxon) {
286                this.taxonomicScope.remove(taxon);
287        }
288
289        /**
290         * Returns the set of {@link Scope scope restrictions} corresponding to
291         * <i>this</i> identification key
292         */
293        public Set<Scope> getScopeRestrictions() {
294                if (scopeRestrictions == null) {
295                        this.scopeRestrictions = new HashSet<Scope>();
296                }
297                return scopeRestrictions;
298        }
299
300        /**
301         * Adds a {@link Scope scope restriction} to the set of
302         * {@link #getScopeRestrictions() scope restrictions} corresponding to
303         * <i>this</i> identification key.
304         *
305         * @param scopeRestriction
306         *            the scope restriction to be added to <i>this</i>
307         *            identification key
308         * @see #getScopeRestrictions()
309         */
310        public void addScopeRestriction(Scope scopeRestriction) {
311                this.scopeRestrictions.add(scopeRestriction);
312        }
313
314        /**
315         * Removes one element from the set of {@link #getScopeRestrictions() scope
316         * restrictions} corresponding to <i>this</i> identification key.
317         *
318         * @param scopeRestriction
319         *            the scope restriction which should be removed
320         * @see #getScopeRestrictions()
321         * @see #addScopeRestriction(Scope)
322         */
323        public void removeScopeRestriction(Scope scopeRestriction) {
324                this.scopeRestrictions.remove(scopeRestriction);
325        }
326
327        // ******************** toString *****************************************/
328
329        private class IntegerObject {
330                int number = 0;
331
332                int inc() {
333                        return number++;
334                };
335
336                @Override
337                public String toString() {
338                        return String.valueOf(number);
339                }
340        }
341
342        public String print(PrintStream stream) {
343                String title = this.getTitleCache() + "\n";
344                String strPrint = title;
345
346                if (stream != null) {
347                        stream.print(title);
348                }
349
350                PolytomousKeyNode root = this.getRoot();
351                strPrint += printNode(root, null, "  ", stream);
352                return strPrint;
353        }
354
355        /**
356         * TODO this is a preliminary implementation
357         *
358         * @param node
359         * @param identation
360         * @param no
361         * @param myNumber
362         * @param stream
363         * @return
364         */
365        private String printNode(PolytomousKeyNode node, PolytomousKeyNode parent2,
366                        String identation, PrintStream stream) {
367                String separator = ", ";
368
369                String result = identation + node.getNodeNumber() + ". ";
370                if (node != null) {
371                        // key choice
372                        String question = null;
373                        String feature = null;
374                        if (node.getQuestion() != null) {
375                                question = node.getQuestion().getLabelText(Language.DEFAULT());
376                        }
377                        if (node.getFeature() != null) {
378                                feature = node.getFeature().getLabel(Language.DEFAULT());
379                        }
380                        result += CdmUtils.concat(" - ", question, feature) + "\n";
381                        ;
382
383                        // Leads
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(
389                                                        Language.DEFAULT());
390                                        result += identation + "  " + leadNumber + ") "
391                                                        + (statement == null ? "" : (statement));
392                                        result += " ... ";
393                                        // child node
394                                        if (!child.isLeaf()) {
395                                                result += child.getNodeNumber() + separator;
396                                        }
397                                        // taxon
398                                        if (child.getTaxon() != null) {
399                                                String strTaxon = "";
400                                                if (child.getTaxon().getName() != null) {
401                                                        strTaxon = child.getTaxon().getName()
402                                                                        .getTitleCache();
403                                                } else {
404                                                        strTaxon = child.getTaxon().getTitleCache();
405                                                }
406                                                result += strTaxon + separator;
407                                        }
408                                        // subkey
409                                        if (child.getSubkey() != null) {
410                                                String subkey = child.getSubkey().getTitleCache();
411                                                result += subkey + separator;
412                                        }
413                                        // other node
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
419                                                                        .getNodeNumber());
420                                                } else {
421                                                        otherNodeString = otherNode.getKey() + " "
422                                                                        + otherNode.getNodeNumber();
423                                                }
424                                                result += otherNodeString + separator;
425                                        }
426
427                                        result = StringUtils.chompLast(result, separator);
428                                        result += "\n";
429                                }
430                        }
431
432                        if (stream != null) {
433                                stream.print(result);
434                        }
435                        for (PolytomousKeyNode child : node.getChildren()) {
436                                if (!child.isLeaf()) {
437                                        result += printNode(child, node, identation + "", stream);
438                                }
439                        }
440                }
441                return result;
442        }
443
444        //
445        // public List<PolytomousKeyNode> getChildren() {
446        // return getRoot().getChildren();
447        // }
448
449        // *********************** CLONE
450        // ********************************************************/
451
452        /**
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.
456         *
457         * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#clone()
458         * @see java.lang.Object#clone()
459         */
460        @Override
461        public Object clone() {
462                PolytomousKey result;
463
464                try {
465                        result = (PolytomousKey) super.clone();
466
467                        result.coveredTaxa = new HashSet<Taxon>();
468                        for (Taxon taxon : this.coveredTaxa) {
469                                result.addCoveredTaxon(taxon);
470                        }
471
472                        result.geographicalScope = new HashSet<NamedArea>();
473                        for (NamedArea area : this.geographicalScope) {
474                                result.addGeographicalScope(area);
475                        }
476
477                        result.root = (PolytomousKeyNode) this.root.clone();
478
479                        result.scopeRestrictions = new HashSet<Scope>();
480                        for (Scope scope : this.scopeRestrictions) {
481                                result.addScopeRestriction(scope);
482                        }
483
484                        result.taxonomicScope = new HashSet<Taxon>();
485                        for (Taxon taxon : this.taxonomicScope) {
486                                result.addTaxonomicScope(taxon);
487                        }
488
489                        return result;
490
491                } catch (CloneNotSupportedException e) {
492                        logger.warn("Object does not implement cloneable");
493                        e.printStackTrace();
494                        return null;
495                }
496
497        }
498
499}
Note: See TracBrowser for help on using the browser.