Project

General

Profile

Download (18 KB) Statistics
| Branch: | Tag: | Revision:
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

    
11
package eu.etaxonomy.cdm.model.description;
12

    
13
import java.io.PrintStream;
14
import java.util.HashSet;
15
import java.util.Set;
16

    
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;
32

    
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;
39

    
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;
49

    
50
/**
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.
59
 *
60
 * @author h.fradin
61
 * @created 13.08.2009
62
 *
63
 * @author a.mueller
64
 * @version 2.0 (08.11.2010)
65
 */
66
@XmlAccessorType(XmlAccessType.FIELD)
67
@XmlType(name = "PolytomousKey", propOrder = {
68
        "coveredTaxa",
69
        "taxonomicScope",
70
        "geographicalScope",
71
        "scopeRestrictions",
72
        "root",
73
        "startNumber"})
74
@XmlRootElement(name = "PolytomousKey")
75
@Entity
76
@Indexed(index = "eu.etaxonomy.cdm.model.description.PolytomousKey")
77
@Audited
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);
81

    
82
    @XmlElementWrapper(name = "CoveredTaxa")
83
    @XmlElement(name = "CoveredTaxon")
84
    @XmlIDREF
85
    @XmlSchemaType(name = "IDREF")
86
    @ManyToMany(fetch = FetchType.LAZY)
87
    @NotNull
88
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
89
    private Set<Taxon> coveredTaxa = new HashSet<Taxon>();
90

    
91
    @XmlElementWrapper(name = "TaxonomicScope")
92
    @XmlElement(name = "Taxon")
93
    @XmlIDREF
94
    @XmlSchemaType(name = "IDREF")
95
    @ManyToMany(fetch = FetchType.LAZY)
96
    @JoinTable(name = "PolytomousKey_Taxon", joinColumns = @JoinColumn(name = "polytomousKey_id"), inverseJoinColumns = @JoinColumn(name = "taxon_id"))
97
    @NotNull
98
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
99
    private Set<Taxon> taxonomicScope = new HashSet<Taxon>();
100

    
101
    @XmlElementWrapper(name = "GeographicalScope")
102
    @XmlElement(name = "Area")
103
    @XmlIDREF
104
    @XmlSchemaType(name = "IDREF")
105
    @ManyToMany(fetch = FetchType.LAZY)
106
    @JoinTable(name = "PolytomousKey_NamedArea")
107
    @NotNull
108
    @Cascade({CascadeType.MERGE})
109
    private Set<NamedArea> geographicalScope = new HashSet<NamedArea>();
110

    
111
    @XmlElementWrapper(name = "ScopeRestrictions")
112
    @XmlElement(name = "Restriction")
113
    @XmlIDREF
114
    @XmlSchemaType(name = "IDREF")
115
    @ManyToMany(fetch = FetchType.LAZY)
116
    @JoinTable(name = "PolytomousKey_Scope")
117
    @NotNull
118
    @Cascade({CascadeType.MERGE})
119
    private Set<DefinedTerm> scopeRestrictions = new HashSet<DefinedTerm>();
120

    
121
    @XmlElement(name = "Root")
122
    @OneToOne(fetch = FetchType.LAZY)
123
    @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE })
124
    private PolytomousKeyNode root;
125

    
126
    @XmlElement(name = "StartNumber")
127
    private int startNumber = 1;
128

    
129

    
130
// ***************** STATIC METHODS ********************************/
131

    
132
    /**
133
     * Creates a new empty identification multi-access key instance.
134
     */
135
    public static PolytomousKey NewInstance() {
136
        return new PolytomousKey();
137
    }
138

    
139
    /**
140
     * Creates a new empty identification polytomous key instance.
141
     */
142
    public static PolytomousKey NewTitledInstance(String title) {
143
        PolytomousKey result = new PolytomousKey();
144
        result.setTitleCache(title, true);
145
        return result;
146
    }
147

    
148
// ************************** CONSTRUCTOR ************************/
149

    
150
    /**
151
     * Class constructor: creates a new empty multi-access key instance.
152
     */
153
    protected PolytomousKey() {
154
        super();
155
        root = PolytomousKeyNode.NewInstance();
156
        root.setNodeNumber(getStartNumber());
157
        root.setKey(this);
158
        this.cacheStrategy = PolytomousKeyDefaultCacheStrategy.NewInstance();
159
    }
160

    
161
    // ************************ GETTER/ SETTER
162

    
163
    /**
164
     * Returns the topmost {@link PolytomousKeyNode polytomous key node} (root
165
     * node) of <i>this</i> polytomous key. The root node does not have any
166
     * parent. Since polytomous key nodes recursively point to their child nodes
167
     * the complete polytomous key is defined by its root node.
168
     */
169
    public PolytomousKeyNode getRoot() {
170
        return root;
171
    }
172

    
173
    /**
174
     * This method should be used by Hibernate only. If we want to make this
175
     * method public we have to think about biderionality and also what should
176
     * happen with the old root node.
177
     *
178
     * @see #getRoot()
179
     */
180
    public void setRoot(PolytomousKeyNode root) {
181
        this.root = root;
182
        if (root != null){
183
            root.setKey(this);
184
        }
185
    }
186

    
187
    /**
188
     * Returns the set of possible {@link Taxon taxa} corresponding to
189
     * <i>this</i> identification key.
190
     */
191
    @Override
192
    public Set<Taxon> getCoveredTaxa() {
193
        if (coveredTaxa == null) {
194
            this.coveredTaxa = new HashSet<Taxon>();
195
        }
196
        return coveredTaxa;
197
    }
198

    
199
    /**
200
     * @see #getCoveredTaxa()
201
     */
202
    protected void setCoveredTaxa(Set<Taxon> coveredTaxa) {
203
        this.coveredTaxa = coveredTaxa;
204
    }
205

    
206
    /**
207
     * Adds a {@link Taxon taxa} to the set of {@link #getCoveredTaxa() covered
208
     * taxa} corresponding to <i>this</i> identification key.
209
     *
210
     * @param taxon
211
     *            the taxon to be added to <i>this</i> identification key
212
     * @see #getCoveredTaxa()
213
     */
214
    @Override
215
    public void addCoveredTaxon(Taxon taxon) {
216
        this.coveredTaxa.add(taxon);
217
    }
218

    
219
    /**
220
     * Removes one element from the set of {@link #getCoveredTaxa() covered
221
     * taxa} corresponding to <i>this</i> identification key.
222
     *
223
     * @param taxon
224
     *            the taxon which should be removed
225
     * @see #getCoveredTaxa()
226
     * @see #addCoveredTaxon(Taxon)
227
     */
228
    @Override
229
    public void removeCoveredTaxon(Taxon taxon) {
230
        this.coveredTaxa.remove(taxon);
231
    }
232

    
233
    /**
234
     * Returns the set of {@link NamedArea named areas} indicating the
235
     * geospatial data where <i>this</i> identification key is valid.
236
     */
237
    @Override
238
    public Set<NamedArea> getGeographicalScope() {
239
        if (geographicalScope == null) {
240
            this.geographicalScope = new HashSet<NamedArea>();
241
        }
242
        return geographicalScope;
243
    }
244

    
245
    /**
246
     * Adds a {@link NamedArea geoScope} to the set of {@link #getGeoScopes()
247
     * geogspatial scopes} corresponding to <i>this</i> identification key.
248
     *
249
     * @param geoScope
250
     *            the named area to be added to <i>this</i> identification key
251
     * @see #getGeoScopes()
252
     */
253
    @Override
254
    public void addGeographicalScope(NamedArea geoScope) {
255
        this.geographicalScope.add(geoScope);
256
    }
257

    
258
    /**
259
     * Removes one element from the set of {@link #getGeoScopes() geogspatial
260
     * scopes} corresponding to <i>this</i> identification key.
261
     *
262
     * @param geoScope
263
     *            the named area which should be removed
264
     * @see #getGeoScopes()
265
     * @see #addGeoScope(NamedArea)
266
     */
267
    @Override
268
    public void removeGeographicalScope(NamedArea geoScope) {
269
        this.geographicalScope.remove(geoScope);
270
    }
271

    
272
    /**
273
     * Returns the set of {@link Taxon taxa} that define the taxonomic scope of
274
     * <i>this</i> identification key
275
     */
276
    @Override
277
    public Set<Taxon> getTaxonomicScope() {
278
        if (taxonomicScope == null) {
279
            this.taxonomicScope = new HashSet<Taxon>();
280
        }
281
        return taxonomicScope;
282
    }
283

    
284
    /**
285
     * Adds a {@link Taxon taxa} to the set of {@link #getTaxonomicScope()
286
     * taxonomic scopes} corresponding to <i>this</i> identification key.
287
     *
288
     * @param taxon
289
     *            the taxon to be added to <i>this</i> identification key
290
     * @see #getTaxonomicScope()
291
     */
292
    @Override
293
    public void addTaxonomicScope(Taxon taxon) {
294
        this.taxonomicScope.add(taxon);
295
    }
296

    
297
    /**
298
     * Removes one element from the set of {@link #getTaxonomicScope() taxonomic
299
     * scopes} corresponding to <i>this</i> identification key.
300
     *
301
     * @param taxon
302
     *            the taxon which should be removed
303
     * @see #getTaxonomicScope()
304
     * @see #addTaxonomicScope(Taxon)
305
     */
306
    @Override
307
    public void removeTaxonomicScope(Taxon taxon) {
308
        this.taxonomicScope.remove(taxon);
309
    }
310

    
311
    /**
312
     * Returns the set of {@link Scope scope restrictions} corresponding to
313
     * <i>this</i> identification key
314
     */
315
    @Override
316
    public Set<DefinedTerm> getScopeRestrictions() {
317
        if (scopeRestrictions == null) {
318
            this.scopeRestrictions = new HashSet<DefinedTerm>();
319
        }
320
        return scopeRestrictions;
321
    }
322

    
323
    /**
324
     * Adds a {@link Scope scope restriction} to the set of
325
     * {@link #getScopeRestrictions() scope restrictions} corresponding to
326
     * <i>this</i> identification key.
327
     *
328
     * @param scopeRestriction
329
     *            the scope restriction to be added to <i>this</i>
330
     *            identification key
331
     * @see #getScopeRestrictions()
332
     */
333
    @Override
334
    public void addScopeRestriction(DefinedTerm scopeRestriction) {
335
        this.scopeRestrictions.add(scopeRestriction);
336
    }
337

    
338
    /**
339
     * Removes one element from the set of {@link #getScopeRestrictions() scope
340
     * restrictions} corresponding to <i>this</i> identification key.
341
     *
342
     * @param scopeRestriction
343
     *            the scope restriction which should be removed
344
     * @see #getScopeRestrictions()
345
     * @see #addScopeRestriction(Scope)
346
     */
347
    @Override
348
    public void removeScopeRestriction(DefinedTerm scopeRestriction) {
349
        this.scopeRestrictions.remove(scopeRestriction);
350
    }
351

    
352

    
353
    /**
354
     * The first number for the automated numbering of {@link PolytomousKeyNode key nodes}.
355
     * Default value is 1.
356
     * @return
357
     */
358
    public int getStartNumber() {
359
        return startNumber;
360
    }
361

    
362
    /**
363
     * @see #getStartNumber()
364
     * @param startNumber
365
     */
366
    public void setStartNumber(int startNumber) {
367
        this.startNumber = startNumber;
368
    }
369

    
370
    // ******************** toString *****************************************/
371

    
372
    private class IntegerObject {
373
        int number = 0;
374

    
375
        int inc() {
376
            return number++;
377
        };
378

    
379
        @Override
380
        public String toString() {
381
            return String.valueOf(number);
382
        }
383
    }
384

    
385
    public String print(PrintStream stream) {
386
        String title = this.getTitleCache() + "\n";
387
        String strPrint = title;
388

    
389
        if (stream != null) {
390
            stream.print(title);
391
        }
392

    
393
        PolytomousKeyNode root = this.getRoot();
394
        strPrint += printNode(root, null, "  ", stream);
395
        return strPrint;
396
    }
397

    
398
    /**
399
     * TODO this is a preliminary implementation
400
     *
401
     * @param node
402
     * @param identation
403
     * @param no
404
     * @param myNumber
405
     * @param stream
406
     * @return
407
     */
408
    private String printNode(PolytomousKeyNode node, PolytomousKeyNode parent2,
409
            String identation, PrintStream stream) {
410
        String separator = ", ";
411

    
412
        String result = identation + node.getNodeNumber() + ". ";
413
        if (node != null) {
414
            // key choice
415
            String question = null;
416
            String feature = null;
417
            if (node.getQuestion() != null) {
418
                question = node.getQuestion().getLabelText(Language.DEFAULT());
419
            }
420
            if (node.getFeature() != null) {
421
                feature = node.getFeature().getLabel(Language.DEFAULT());
422
            }
423
            result += CdmUtils.concat(" - ", question, feature) + "\n";
424
            ;
425

    
426
            // Leads
427
            char nextCounter = 'a';
428
            for (PolytomousKeyNode child : node.getChildren()) {
429
                String leadNumber = String.valueOf(nextCounter++);
430
                if (child.getStatement() != null) {
431
                    String statement = child.getStatement().getLabelText(
432
                            Language.DEFAULT());
433
                    result += identation + "  " + leadNumber + ") "
434
                            + (statement == null ? "" : (statement));
435
                    result += " ... ";
436
                    // child node
437
                    if (!child.isLeaf()) {
438
                        result += child.getNodeNumber() + separator;
439
                    }
440
                    // taxon
441
                    if (child.getTaxon() != null) {
442
                        String strTaxon = "";
443
                        if (child.getTaxon().getName() != null) {
444
                            strTaxon = child.getTaxon().getName()
445
                                    .getTitleCache();
446
                        } else {
447
                            strTaxon = child.getTaxon().getTitleCache();
448
                        }
449
                        result += strTaxon + separator;
450
                    }
451
                    // subkey
452
                    if (child.getSubkey() != null) {
453
                        String subkey = child.getSubkey().getTitleCache();
454
                        result += subkey + separator;
455
                    }
456
                    // other node
457
                    if (child.getOtherNode() != null) {
458
                        PolytomousKeyNode otherNode = child.getOtherNode();
459
                        String otherNodeString = null;
460
                        if (child.getKey().equals(otherNode.getKey())) {
461
                            otherNodeString = String.valueOf(otherNode
462
                                    .getNodeNumber());
463
                        } else {
464
                            otherNodeString = otherNode.getKey() + " "
465
                                    + otherNode.getNodeNumber();
466
                        }
467
                        result += otherNodeString + separator;
468
                    }
469

    
470
                    result = StringUtils.chomp(result, separator);
471
                    result += "\n";
472
                }
473
            }
474

    
475
            if (stream != null) {
476
                stream.print(result);
477
            }
478
            for (PolytomousKeyNode child : node.getChildren()) {
479
                if (!child.isLeaf()) {
480
                    result += printNode(child, node, identation + "", stream);
481
                }
482
            }
483
        }
484
        return result;
485
    }
486

    
487
    //
488
    // public List<PolytomousKeyNode> getChildren() {
489
    // return getRoot().getChildren();
490
    // }
491

    
492
    // *********************** CLONE ************************************/
493

    
494
    /**
495
     * Clones <i>this</i> PolytomousKey. This is a shortcut that enables to
496
     * create a new instance that differs only slightly from <i>this</i>
497
     * PolytomousKey by modifying only some of the attributes.
498
     *
499
     * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#clone()
500
     * @see java.lang.Object#clone()
501
     */
502
    @Override
503
    public Object clone() {
504
        PolytomousKey result;
505

    
506
        try {
507
            result = (PolytomousKey) super.clone();
508

    
509
            result.coveredTaxa = new HashSet<Taxon>();
510
            for (Taxon taxon : this.coveredTaxa) {
511
                result.addCoveredTaxon(taxon);
512
            }
513

    
514
            result.geographicalScope = new HashSet<NamedArea>();
515
            for (NamedArea area : this.geographicalScope) {
516
                result.addGeographicalScope(area);
517
            }
518

    
519
            result.root = (PolytomousKeyNode) this.root.clone();
520

    
521
            result.scopeRestrictions = new HashSet<DefinedTerm>();
522
            for (DefinedTerm scope : this.scopeRestrictions) {
523
                result.addScopeRestriction(scope);
524
            }
525

    
526
            result.taxonomicScope = new HashSet<Taxon>();
527
            for (Taxon taxon : this.taxonomicScope) {
528
                result.addTaxonomicScope(taxon);
529
            }
530

    
531
            return result;
532

    
533
        } catch (CloneNotSupportedException e) {
534
            logger.warn("Object does not implement cloneable");
535
            e.printStackTrace();
536
            return null;
537
        }
538

    
539
    }
540

    
541
}
(20-20/36)