Project

General

Profile

Download (17.3 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.lang3.StringUtils;
33
import org.apache.logging.log4j.LogManager;
34
import org.apache.logging.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.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.model.term.DefinedTerm;
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
 * @since 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 disabled to reduce clutter in indexes, since this type is not used by any search
77
//@Indexed(index = "eu.etaxonomy.cdm.model.description.PolytomousKey")
78
@Audited
79
public class PolytomousKey
80
            extends IdentifiableEntity<PolytomousKeyDefaultCacheStrategy>
81
            implements IIdentificationKey, IIntextReferenceTarget {
82

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

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

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

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

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

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

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

    
137

    
138
// ***************** STATIC METHODS ********************************/
139

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

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

    
156
// ************************** CONSTRUCTOR ************************/
157

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

    
168
    @Override
169
    protected void initDefaultCacheStrategy() {
170
        this.cacheStrategy = PolytomousKeyDefaultCacheStrategy.NewInstance();
171
    }
172

    
173
// ************************ GETTER/ SETTER
174

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

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

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

    
211
    /**
212
     * @see #getCoveredTaxa()
213
     */
214
    protected void setCoveredTaxa(Set<Taxon> coveredTaxa) {
215
        this.coveredTaxa = coveredTaxa;
216
    }
217

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

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

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

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

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

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

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

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

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

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

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

    
364

    
365
    /**
366
     * The first number for the automated numbering of {@link PolytomousKeyNode key nodes}.
367
     * Default value is 1.
368
     * @return
369
     */
370
    public int getStartNumber() {
371
        return startNumber;
372
    }
373

    
374
    /**
375
     * @see #getStartNumber()
376
     * @param startNumber
377
     */
378
    public void setStartNumber(int startNumber) {
379
        this.startNumber = startNumber;
380
    }
381

    
382
    // ******************** toString *****************************************/
383

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

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

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

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

    
413
        String separator = ", ";
414

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

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

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

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

    
488

    
489
// *********************** CLONE ************************************/
490

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

    
503
        try {
504
            result = (PolytomousKey) super.clone();
505

    
506
            result.coveredTaxa = new HashSet<>();
507
            for (Taxon taxon : this.coveredTaxa) {
508
                result.addCoveredTaxon(taxon);
509
            }
510

    
511
            result.geographicalScope = new HashSet<>();
512
            for (NamedArea area : this.geographicalScope) {
513
                result.addGeographicalScope(area);
514
            }
515

    
516
            result.root = this.root.clone();
517

    
518
            result.scopeRestrictions = new HashSet<>();
519
            for (DefinedTerm scope : this.scopeRestrictions) {
520
                result.addScopeRestriction(scope);
521
            }
522

    
523
            result.taxonomicScope = new HashSet<>();
524
            for (Taxon taxon : this.taxonomicScope) {
525
                result.addTaxonomicScope(taxon);
526
            }
527

    
528
            return result;
529

    
530
        } catch (CloneNotSupportedException e) {
531
            logger.warn("Object does not implement cloneable");
532
            e.printStackTrace();
533
            return null;
534
        }
535
    }
536
}
(22-22/38)