Project

General

Profile

Download (17.7 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2007 EDIT
3
 * European Distributed Institute of Taxonomy
4
 * http://www.e-taxonomy.eu
5
 *
6
 * The contents of this file are subject to the Mozilla Public License Version 1.1
7
 * See LICENSE.TXT at the top of this package for the full license terms.
8
 */
9

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

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

    
16
import javax.persistence.Entity;
17
import javax.persistence.FetchType;
18
import javax.persistence.JoinColumn;
19
import javax.persistence.JoinTable;
20
import javax.persistence.ManyToMany;
21
import javax.persistence.OneToOne;
22
import javax.validation.constraints.NotNull;
23
import javax.xml.bind.annotation.XmlAccessType;
24
import javax.xml.bind.annotation.XmlAccessorType;
25
import javax.xml.bind.annotation.XmlElement;
26
import javax.xml.bind.annotation.XmlElementWrapper;
27
import javax.xml.bind.annotation.XmlIDREF;
28
import javax.xml.bind.annotation.XmlRootElement;
29
import javax.xml.bind.annotation.XmlSchemaType;
30
import javax.xml.bind.annotation.XmlType;
31

    
32
import org.apache.commons.lang.StringUtils;
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

    
38
import eu.etaxonomy.cdm.common.CdmUtils;
39
import eu.etaxonomy.cdm.model.common.DefinedTerm;
40
import eu.etaxonomy.cdm.model.common.IIntextReferenceTarget;
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
79
            extends IdentifiableEntity<PolytomousKeyDefaultCacheStrategy>
80
            implements IIdentificationKey, IIntextReferenceTarget {
81

    
82
    private static final long serialVersionUID = -3368243754557343942L;
83
    private static final Logger logger = Logger.getLogger(PolytomousKey.class);
84

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

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

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

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

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

    
132
    @XmlElement(name = "StartNumber")
133
    @Audited
134
    private int startNumber = 1;
135

    
136

    
137
// ***************** STATIC METHODS ********************************/
138

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

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

    
155
// ************************** CONSTRUCTOR ************************/
156

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

    
168
    // ************************ GETTER/ SETTER
169

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
359

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

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

    
377
    // ******************** toString *****************************************/
378

    
379
    private class IntegerObject {
380
        int number = 0;
381

    
382
        int inc() {
383
            return number++;
384
        };
385

    
386
        @Override
387
        public String toString() {
388
            return String.valueOf(number);
389
        }
390
    }
391

    
392
    public String print(PrintStream stream) {
393
        String title = this.getTitleCache() + "\n";
394
        String strPrint = title;
395

    
396
        if (stream != null) {
397
            stream.print(title);
398
        }
399

    
400
        PolytomousKeyNode root = this.getRoot();
401
        strPrint += printNode(root, null, "  ", stream);
402
        return strPrint;
403
    }
404

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

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

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

    
477
                    result = StringUtils.chomp(result, separator);
478
                    result += "\n";
479
                }
480
            }
481

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

    
494
    //
495
    // public List<PolytomousKeyNode> getChildren() {
496
    // return getRoot().getChildren();
497
    // }
498

    
499
    // *********************** CLONE ************************************/
500

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

    
513
        try {
514
            result = (PolytomousKey) super.clone();
515

    
516
            result.coveredTaxa = new HashSet<Taxon>();
517
            for (Taxon taxon : this.coveredTaxa) {
518
                result.addCoveredTaxon(taxon);
519
            }
520

    
521
            result.geographicalScope = new HashSet<NamedArea>();
522
            for (NamedArea area : this.geographicalScope) {
523
                result.addGeographicalScope(area);
524
            }
525

    
526
            result.root = (PolytomousKeyNode) this.root.clone();
527

    
528
            result.scopeRestrictions = new HashSet<DefinedTerm>();
529
            for (DefinedTerm scope : this.scopeRestrictions) {
530
                result.addScopeRestriction(scope);
531
            }
532

    
533
            result.taxonomicScope = new HashSet<Taxon>();
534
            for (Taxon taxon : this.taxonomicScope) {
535
                result.addTaxonomicScope(taxon);
536
            }
537

    
538
            return result;
539

    
540
        } catch (CloneNotSupportedException e) {
541
            logger.warn("Object does not implement cloneable");
542
            e.printStackTrace();
543
            return null;
544
        }
545

    
546
    }
547

    
548
}
(21-21/37)