Project

General

Profile

Download (17.6 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

    
39
import eu.etaxonomy.cdm.common.CdmUtils;
40
import eu.etaxonomy.cdm.model.common.DefinedTerm;
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.cache.description.PolytomousKeyDefaultCacheStrategy;
47
import eu.etaxonomy.cdm.strategy.generate.PolytomousKeyGenerator;
48

    
49
/**
50
 * This class represents a fixed single-access key (dichotomous or
51
 * polytomous) used to identify (assign a {@link Taxon taxon} to) a {@link SpecimenOrObservationBase
52
 * specimen or observation}. The key may be written manually or may be generated automatically
53
 * e.g. by the {@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()}. Refer to {@link PolytomousKeyNode} for detailed
57
 * documentation on the decision graph structure.
58
 *
59
 * @author h.fradin
60
 * @created 13.08.2009
61
 *
62
 * @author a.mueller
63
 * @version 2.0 (08.11.2010)
64
 */
65
@XmlAccessorType(XmlAccessType.FIELD)
66
@XmlType(name = "PolytomousKey", propOrder = {
67
        "coveredTaxa",
68
        "taxonomicScope",
69
        "geographicalScope",
70
        "scopeRestrictions",
71
        "root",
72
        "startNumber"})
73
@XmlRootElement(name = "PolytomousKey")
74
@Entity
75
//@Indexed disabled to reduce clutter in indexes, since this type is not used by any search
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",
97
        joinColumns = @JoinColumn(name = "polytomousKey_id"),
98
        inverseJoinColumns = @JoinColumn(name = "taxon_id")
99
    )
100
    @NotNull
101
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
102
    private Set<Taxon> taxonomicScope = new HashSet<Taxon>();
103

    
104
    @XmlElementWrapper(name = "GeographicalScope")
105
    @XmlElement(name = "Area")
106
    @XmlIDREF
107
    @XmlSchemaType(name = "IDREF")
108
    @ManyToMany(fetch = FetchType.LAZY)
109
    @JoinTable(name = "PolytomousKey_NamedArea")
110
    @NotNull
111
//    @Cascade({CascadeType.MERGE})   remove cascade #5755
112
    private Set<NamedArea> geographicalScope = new HashSet<NamedArea>();
113

    
114
    @XmlElementWrapper(name = "ScopeRestrictions")
115
    @XmlElement(name = "Restriction")
116
    @XmlIDREF
117
    @XmlSchemaType(name = "IDREF")
118
    @ManyToMany(fetch = FetchType.LAZY)
119
    @JoinTable(name = "PolytomousKey_Scope")
120
    @NotNull
121
//    @Cascade({CascadeType.MERGE}) remove cascade #5755
122
    private Set<DefinedTerm> scopeRestrictions = new HashSet<DefinedTerm>();
123

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

    
129
    @XmlElement(name = "StartNumber")
130
    @Audited
131
    private int startNumber = 1;
132

    
133

    
134
// ***************** STATIC METHODS ********************************/
135

    
136
    /**
137
     * Creates a new empty identification multi-access key instance.
138
     */
139
    public static PolytomousKey NewInstance() {
140
        return new PolytomousKey();
141
    }
142

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

    
152
// ************************** CONSTRUCTOR ************************/
153

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

    
165
    // ************************ GETTER/ SETTER
166

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

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

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

    
203
    /**
204
     * @see #getCoveredTaxa()
205
     */
206
    protected void setCoveredTaxa(Set<Taxon> coveredTaxa) {
207
        this.coveredTaxa = coveredTaxa;
208
    }
209

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

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

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

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

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

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

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

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

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

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

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

    
356

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

    
366
    /**
367
     * @see #getStartNumber()
368
     * @param startNumber
369
     */
370
    public void setStartNumber(int startNumber) {
371
        this.startNumber = startNumber;
372
    }
373

    
374
    // ******************** toString *****************************************/
375

    
376
    private class IntegerObject {
377
        int number = 0;
378

    
379
        int inc() {
380
            return number++;
381
        };
382

    
383
        @Override
384
        public String toString() {
385
            return String.valueOf(number);
386
        }
387
    }
388

    
389
    public String print(PrintStream stream) {
390
        String title = this.getTitleCache() + "\n";
391
        String strPrint = title;
392

    
393
        if (stream != null) {
394
            stream.print(title);
395
        }
396

    
397
        PolytomousKeyNode root = this.getRoot();
398
        strPrint += printNode(root, null, "  ", stream);
399
        return strPrint;
400
    }
401

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

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

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

    
474
                    result = StringUtils.chomp(result, separator);
475
                    result += "\n";
476
                }
477
            }
478

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

    
491
    //
492
    // public List<PolytomousKeyNode> getChildren() {
493
    // return getRoot().getChildren();
494
    // }
495

    
496
    // *********************** CLONE ************************************/
497

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

    
510
        try {
511
            result = (PolytomousKey) super.clone();
512

    
513
            result.coveredTaxa = new HashSet<Taxon>();
514
            for (Taxon taxon : this.coveredTaxa) {
515
                result.addCoveredTaxon(taxon);
516
            }
517

    
518
            result.geographicalScope = new HashSet<NamedArea>();
519
            for (NamedArea area : this.geographicalScope) {
520
                result.addGeographicalScope(area);
521
            }
522

    
523
            result.root = (PolytomousKeyNode) this.root.clone();
524

    
525
            result.scopeRestrictions = new HashSet<DefinedTerm>();
526
            for (DefinedTerm scope : this.scopeRestrictions) {
527
                result.addScopeRestriction(scope);
528
            }
529

    
530
            result.taxonomicScope = new HashSet<Taxon>();
531
            for (Taxon taxon : this.taxonomicScope) {
532
                result.addTaxonomicScope(taxon);
533
            }
534

    
535
            return result;
536

    
537
        } catch (CloneNotSupportedException e) {
538
            logger.warn("Object does not implement cloneable");
539
            e.printStackTrace();
540
            return null;
541
        }
542

    
543
    }
544

    
545
}
(20-20/36)