Project

General

Profile

Download (23.9 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
package eu.etaxonomy.cdm.model.occurrence;
10

    
11
import java.util.ArrayList;
12
import java.util.Collection;
13
import java.util.HashMap;
14
import java.util.HashSet;
15
import java.util.Map;
16
import java.util.Set;
17

    
18
import javax.persistence.Column;
19
import javax.persistence.Entity;
20
import javax.persistence.FetchType;
21
import javax.persistence.Index;
22
import javax.persistence.Inheritance;
23
import javax.persistence.InheritanceType;
24
import javax.persistence.ManyToMany;
25
import javax.persistence.ManyToOne;
26
import javax.persistence.MapKeyJoinColumn;
27
import javax.persistence.OneToMany;
28
import javax.persistence.Table;
29
import javax.persistence.Transient;
30
import javax.validation.constraints.NotNull;
31
import javax.xml.bind.annotation.XmlAccessType;
32
import javax.xml.bind.annotation.XmlAccessorType;
33
import javax.xml.bind.annotation.XmlAttribute;
34
import javax.xml.bind.annotation.XmlElement;
35
import javax.xml.bind.annotation.XmlElementWrapper;
36
import javax.xml.bind.annotation.XmlIDREF;
37
import javax.xml.bind.annotation.XmlRootElement;
38
import javax.xml.bind.annotation.XmlSchemaType;
39
import javax.xml.bind.annotation.XmlType;
40
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
41

    
42
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
43
import org.hibernate.annotations.Cascade;
44
import org.hibernate.annotations.CascadeType;
45
import org.hibernate.annotations.Type;
46
import org.hibernate.envers.Audited;
47
import org.hibernate.search.annotations.Analyze;
48
import org.hibernate.search.annotations.Field;
49
import org.hibernate.search.annotations.FieldBridge;
50
import org.hibernate.search.annotations.Fields;
51
import org.hibernate.search.annotations.IndexedEmbedded;
52
import org.hibernate.search.annotations.SortableField;
53
import org.hibernate.search.annotations.Store;
54
import org.hibernate.search.bridge.builtin.BooleanBridge;
55

    
56
import eu.etaxonomy.cdm.common.URI;
57
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
58
import eu.etaxonomy.cdm.hibernate.search.StripHtmlBridge;
59
import eu.etaxonomy.cdm.hibernate.search.UriBridge;
60
import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
61
import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
62
import eu.etaxonomy.cdm.model.common.CdmBase;
63
import eu.etaxonomy.cdm.model.common.IIntextReferenceTarget;
64
import eu.etaxonomy.cdm.model.common.IMultiLanguageTextHolder;
65
import eu.etaxonomy.cdm.model.common.IPublishable;
66
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
67
import eu.etaxonomy.cdm.model.common.Language;
68
import eu.etaxonomy.cdm.model.common.LanguageString;
69
import eu.etaxonomy.cdm.model.common.MultilanguageText;
70
import eu.etaxonomy.cdm.model.description.DescriptionBase;
71
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
72
import eu.etaxonomy.cdm.model.description.IDescribable;
73
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
74
import eu.etaxonomy.cdm.model.description.TaxonDescription;
75
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
76
import eu.etaxonomy.cdm.model.term.DefinedTerm;
77
import eu.etaxonomy.cdm.model.term.TermType;
78
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
79
import eu.etaxonomy.cdm.strategy.match.Match;
80
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
81
import eu.etaxonomy.cdm.strategy.match.MatchMode;
82

    
83
/**
84
 * type figures are observations with at least a figure object in media
85
 * @author m.doering
86
 * @since 08-Nov-2007 13:06:41
87
 */
88
@XmlAccessorType(XmlAccessType.FIELD)
89
@XmlType(name = "SpecimenOrObservationBase", propOrder = {
90
    "recordBasis",
91
    "identityCache",
92
    "protectedIdentityCache",
93
    "publish",
94
    "preferredStableUri",
95
    "sex",
96
    "lifeStage",
97
    "kindOfUnit",
98
    "individualCount",
99
    "definition",
100
    "descriptions",
101
    "determinations",
102
    "derivationEvents"
103
})
104
@XmlRootElement(name = "SpecimenOrObservationBase")
105
@Entity
106
@Audited
107
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
108
@Table(name="SpecimenOrObservationBase", indexes = { @Index(name = "specimenOrObservationBaseTitleCacheIndex", columnList = "titleCache"),
109
        @Index(name = "specimenOrObservationBaseIdentityCacheIndex", columnList = "identityCache") })
110
public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCacheStrategy<?>>
111
                extends IdentifiableEntity<S>
112
                implements IMultiLanguageTextHolder, IIntextReferenceTarget, IDescribable<DescriptionBase<S>>, IPublishable  {
113

    
114
    private static final long serialVersionUID = 6932680139334408031L;
115
    private static final Logger logger = LogManager.getLogger(SpecimenOrObservationBase.class);
116

    
117
    /**
118
     * An indication of what the unit record describes.
119
     *
120
     * NOTE: The name of the attribute was chosen against the common naming conventions of the CDM
121
     * as it is well known in common standards like ABCD and DarwinCore. According to CDM naming
122
     * conventions it would be specimenOrObservationType.
123
     *
124
     * @see ABCD: DataSets/DataSet/Units/Unit/RecordBasis
125
     * @see Darwin Core: http://wiki.tdwg.org/twiki/bin/view/DarwinCore/BasisOfRecord
126
     */
127
    @XmlAttribute(name ="RecordBasis")
128
    @Column(name="recordBasis")
129
    @NotNull
130
    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
131
        parameters = {@org.hibernate.annotations.Parameter(name = "enumClass", value = "eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType")}
132
    )
133
    @Audited
134
    private SpecimenOrObservationType recordBasis;
135

    
136

    
137
    @XmlElementWrapper(name = "Descriptions")
138
    @XmlElement(name = "Description")
139
    @OneToMany(mappedBy="describedSpecimenOrObservation", fetch = FetchType.LAZY)
140
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
141
    @NotNull
142
    private Set<DescriptionBase<S>> descriptions = new HashSet<>();
143

    
144

    
145
    @XmlElementWrapper(name = "Determinations")
146
    @XmlElement(name = "Determination")
147
    @OneToMany(mappedBy="identifiedUnit", orphanRemoval=true)
148
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
149
    @IndexedEmbedded(depth = 2)
150
    @NotNull
151
    private Set<DeterminationEvent> determinations = new HashSet<>();
152

    
153
    @XmlElement(name = "Sex")
154
    @XmlIDREF
155
    @XmlSchemaType(name = "IDREF")
156
    @ManyToOne(fetch = FetchType.LAZY)
157
    private DefinedTerm sex;
158

    
159
    @XmlElement(name = "LifeStage")
160
    @XmlIDREF
161
    @XmlSchemaType(name = "IDREF")
162
    @ManyToOne(fetch = FetchType.LAZY)
163
    private DefinedTerm lifeStage;
164

    
165
    /**
166
     * Part(s) of organism or class of materials represented by this unit.
167
     * Example: fruits, seeds, tissue, gDNA, leaves
168
     *
169
     * @see ABCD: DataSets/DataSet/Units/Unit/KindOfUnit
170
     * @see TermType#KindOfUnit
171
     */
172
    @XmlElement(name = "KindOfUnit")
173
    @XmlIDREF
174
    @XmlSchemaType(name = "IDREF")
175
    @ManyToOne(fetch = FetchType.LAZY)
176
//    @IndexedEmbedded(depth=1)
177
    private DefinedTerm kindOfUnit;
178

    
179
    @XmlElement(name = "IndividualCount")
180
    @Field(analyze = Analyze.NO)
181
    private String individualCount;
182

    
183
    /**
184
     * The preferred stable identifier (URI) as discussed in
185
     * {@link  https://dev.e-taxonomy.eu/redmine/issues/5606}
186
     */
187
    @XmlElement(name = "PreferredStableUri")
188
    @Field(analyze = Analyze.NO)
189
    @FieldBridge(impl = UriBridge.class)
190
    @Type(type="uriUserType")
191
    private URI preferredStableUri;
192

    
193
    // the verbatim description of this occurrence. Free text usable when no atomised data is available.
194
    // in conjunction with titleCache which serves as the "citation" string for this object
195
    @XmlElement(name = "Description")
196
    @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
197
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
198
    @MapKeyJoinColumn(name="definition_mapkey_id")
199
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
200
    @IndexedEmbedded
201
    @NotNull
202
    protected Map<Language,LanguageString> definition = new HashMap<>();
203

    
204
    // events that created derivedUnits from this unit
205
    @XmlElementWrapper(name = "DerivationEvents")
206
    @XmlElement(name = "DerivationEvent")
207
    @XmlIDREF
208
    @XmlSchemaType(name = "IDREF")
209
    @ManyToMany(fetch=FetchType.LAZY)
210
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
211
    @NotNull
212
    protected Set<DerivationEvent> derivationEvents = new HashSet<>();
213

    
214
    @XmlAttribute(name = "publish")
215
    @Field(analyze = Analyze.NO)
216
    @FieldBridge(impl=BooleanBridge.class)
217
    private boolean publish = true;
218

    
219
    @XmlElement(name = "IdentityCache", required = false)
220
    @XmlJavaTypeAdapter(FormattedTextAdapter.class)
221
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
222
//    @NotEmpty(groups = Level2.class) // implicitly NotNull
223
    @Fields({
224
        @Field(store=Store.YES),
225
        //  If the field is only needed for sorting and nothing else, you may configure it as
226
        //  un-indexed and un-stored, thus avoid unnecessary index growth.
227
        @Field(name = "identityCache__sort", analyze = Analyze.NO, store=Store.NO, index = org.hibernate.search.annotations.Index.NO)
228
    })
229
    @SortableField(forField = "identityCache__sort")
230
    @FieldBridge(impl=StripHtmlBridge.class)
231
    private String identityCache;
232

    
233

    
234
    //if true identityCache will not be automatically generated/updated
235
    @XmlElement(name = "ProtectedIdentityCache")
236
    private boolean protectedIdentityCache;
237

    
238

    
239
//********************************** CONSTRUCTOR *********************************/
240

    
241
    //for hibernate use only
242
    @Deprecated
243
    protected SpecimenOrObservationBase(){
244
        super();
245
    }
246

    
247
    protected SpecimenOrObservationBase(SpecimenOrObservationType recordBasis) {
248
        super();
249
        if (recordBasis == null){ throw new IllegalArgumentException("RecordBasis must not be null");}
250
        this.recordBasis = recordBasis;
251
    }
252

    
253
//************************* GETTER / SETTER ***********************/
254

    
255
    /**@see #recordBasis */
256
    public SpecimenOrObservationType getRecordBasis() {
257
        return recordBasis;
258
    }
259
    /**@see #recordBasis */
260
    public void setRecordBasis(SpecimenOrObservationType recordBasis) {
261
        this.recordBasis = recordBasis;
262
    }
263

    
264

    
265
    /**
266
     * @return the identityCache
267
     */
268
    public String getIdentityCache() {
269
        return identityCache;
270
    }
271
    /**
272
     * @Deprecated For special use only.
273
     * Use {@link #setIdentityCache(String, boolean)} instead
274
     */
275
    @Deprecated
276
    public void setIdentityCache(String identityCache) {
277
        this.identityCache = identityCache;
278
    }
279

    
280
    /**
281
     * Sets the identity (short) cache. For how to use see {@link #setTitleCache(String, boolean)}
282
     */
283
    public void setIdentityCache(String identityCache, boolean isProtected) {
284
        setIdentityCache(identityCache);
285
        this.protectedIdentityCache = isProtected;
286
    }
287

    
288
    /**
289
     * @return the protectedIdentityCache
290
     */
291
    public boolean isProtectedIdentityCache() {
292
        return protectedIdentityCache;
293
    }
294
    /**
295
     * @param protectedIdentityCache the protectedIdentityCache to set
296
     */
297
    public void setProtectedIdentityCache(boolean protectedIdentityCache) {
298
        this.protectedIdentityCache = protectedIdentityCache;
299
    }
300

    
301
    /**@see #preferredStableUri */
302
    public URI getPreferredStableUri() {
303
        return preferredStableUri;
304
    }
305
    /**@see #preferredStableUri */
306
    public void setPreferredStableUri(URI preferredStableUri) {
307
        this.preferredStableUri = preferredStableUri;
308
    }
309

    
310

    
311
    /**
312
     * Returns the boolean value indicating if this specimen or observation should be withheld
313
     * (<code>publish=false</code>) or not (<code>publish=true</code>) during any publication
314
     * process to the general public.
315
     * This publish flag implementation is preliminary and may be replaced by a more general
316
     * implementation of READ rights in future.<BR>
317
     * The default value is <code>true</code>.
318
     */
319
    @Override
320
    public boolean isPublish() {
321
        return publish;
322
    }
323

    
324
    /**
325
     * @see #isPublish()
326
     * @param publish
327
     */
328
    @Override
329
    public void setPublish(boolean publish) {
330
        this.publish = publish;
331
    }
332

    
333
    /**
334
     * The descriptions this specimen or observation is part of.<BR>
335
     * A specimen can not only have it's own {@link SpecimenDescription specimen description }
336
     * but can also be part of a {@link TaxonDescription taxon description} or a
337
     * {@link TaxonNameDescription taxon name description}.<BR>
338
     * @see #getSpecimenDescriptions()
339
     * @return
340
     */
341
    @Override
342
    public Set<DescriptionBase<S>> getDescriptions() {
343
        if(descriptions == null) {
344
            this.descriptions = new HashSet<>();
345
        }
346
        return this.descriptions;
347
    }
348

    
349
    /**
350
     * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
351
     * @see #getDescriptions()
352
     * @return
353
     */
354
    @Transient
355
    public Set<SpecimenDescription> getSpecimenDescriptions() {
356
        return getSpecimenDescriptions(true);
357
    }
358

    
359
    /**
360
     * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
361
     * @see #getDescriptions()
362
     * @return
363
     */
364
    @Transient
365
    public Set<SpecimenDescription> getSpecimenDescriptions(boolean includeImageGallery) {
366
        Set<SpecimenDescription> specimenDescriptions = new HashSet<>();
367
        for (DescriptionBase<?> descriptionBase : getDescriptions()){
368
            if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
369
                if (includeImageGallery || descriptionBase.isImageGallery() == false){
370
                    specimenDescriptions.add(CdmBase.deproxy(descriptionBase, SpecimenDescription.class));
371
                }
372

    
373
            }
374
        }
375
        return specimenDescriptions;
376
    }
377
    /**
378
     * Returns the {@link SpecimenDescription specimen descriptions} which act as an image gallery
379
     * and which this specimen is part of.
380
     * @see #getDescriptions()
381
     */
382
    @Transient
383
    public Set<SpecimenDescription> getSpecimenDescriptionImageGallery() {
384
        Set<SpecimenDescription> specimenDescriptions = new HashSet<>();
385
        for (DescriptionBase<?> descriptionBase : getDescriptions()){
386
            if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
387
                if (descriptionBase.isImageGallery() == true){
388
                    specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
389
                }
390
            }
391
        }
392
        return specimenDescriptions;
393
    }
394

    
395
    /**
396
     * Adds a new description to this specimen or observation
397
     */
398
    @Override
399
    public void addDescription(DescriptionBase description) {
400
        if (description.getDescribedSpecimenOrObservation() != null){
401
            description.getDescribedSpecimenOrObservation().removeDescription(description);
402
        }
403
        descriptions.add(description);
404
        description.setDescribedSpecimenOrObservation(this);
405
    }
406

    
407
    /**
408
     * Removes a specimen from a description (removes a description from this specimen)
409
     */
410
    @Override
411
    public void removeDescription(DescriptionBase description) {
412
        boolean existed = descriptions.remove(description);
413
        if (existed){
414
            description.setDescribedSpecimenOrObservation(null);
415
        }
416
    }
417

    
418
    public Set<DerivationEvent> getDerivationEvents() {
419
        if(derivationEvents == null) {
420
            this.derivationEvents = new HashSet<>();
421
        }
422
        return this.derivationEvents;
423
    }
424

    
425
    public void addDerivationEvent(DerivationEvent derivationEvent) {
426
        if (! this.derivationEvents.contains(derivationEvent)){
427
            this.derivationEvents.add(derivationEvent);
428
            derivationEvent.addOriginal(this);
429
        }
430
    }
431

    
432
    public void removeDerivationEvent(DerivationEvent derivationEvent) {
433
        if (this.derivationEvents.contains(derivationEvent)){
434
            this.derivationEvents.remove(derivationEvent);
435
            derivationEvent.removeOriginal(this);
436
        }
437
    }
438

    
439
    public DerivationEvent addDerivedUnit(DerivedUnit derivedUnit, DerivationEventType derivationType) {
440
        return DerivationEvent.NewSimpleInstance(this, derivedUnit, derivationType);
441
    }
442

    
443
    //determination
444

    
445
    public Set<DeterminationEvent> getDeterminations() {
446
        if(determinations == null) {
447
            this.determinations = new HashSet<>();
448
        }
449
        return this.determinations;
450
    }
451

    
452
    /**
453
     * This method returns the preferred determination.
454
     * @see #getOtherDeterminations()
455
     * @see #getDeterminations()
456
     */
457
    @Transient
458
    public DeterminationEvent getPreferredDetermination() {
459
        for (DeterminationEvent event : getDeterminations()){
460
            if (event.getPreferredFlag() == true){
461
                return event;
462
            }
463
        }
464
        return null;
465
    }
466

    
467
    /**
468
     * This method returns the preferred determination.
469
     * @see #getOtherDeterminations()
470
     * @see #getDeterminations()
471
     */
472
    @Transient
473
    public void setPreferredDetermination(DeterminationEvent newEvent) {
474
        for (DeterminationEvent event : getDeterminations()){
475
            if (event.getPreferredFlag() == true){
476
                event.setPreferredFlag(false);
477
            }
478
        }
479
        newEvent.setPreferredFlag(true);
480
        getDeterminations().add(newEvent);
481
    }
482

    
483
    /**
484
     * This method returns all determinations except for the preferred one.
485
     * @see #getPreferredDetermination()
486
     * @see #getDeterminations()
487
     */
488
    @Transient
489
    public Set<DeterminationEvent> getOtherDeterminations(){
490
        Set<DeterminationEvent> result = new HashSet<>();
491
        for (DeterminationEvent event : getDeterminations()){
492
            if (event.getPreferredFlag() == false){
493
                result.add(event);
494
            }
495
        }
496
        return result;
497
    }
498

    
499
    public void addDetermination(DeterminationEvent determination) {
500
        // FIXME bidirectional integrity. Use protected Determination setter
501
        this.determinations.add(determination);
502
    }
503

    
504
    public void removeDetermination(DeterminationEvent determination) {
505
        // FIXME bidirectional integrity. Use protected Determination setter
506
        this.determinations.remove(determination);
507
    }
508

    
509
    public DefinedTerm getSex() {
510
        return sex;
511
    }
512

    
513
    public void setSex(DefinedTerm sex) {
514
        this.sex = sex;
515
    }
516

    
517
    public DefinedTerm getLifeStage() {
518
        return lifeStage;
519
    }
520

    
521
    public void setLifeStage(DefinedTerm lifeStage) {
522
        this.lifeStage = lifeStage;
523
    }
524

    
525

    
526
    /**
527
     * @see #kindOfUnit
528
     * @return
529
     */
530
    public DefinedTerm getKindOfUnit() {
531
        return kindOfUnit;
532
    }
533

    
534
    /**
535
     * @see #kindOfUnit
536
     * @param kindOfUnit
537
     */
538
    public void setKindOfUnit(DefinedTerm kindOfUnit) {
539
        this.kindOfUnit = kindOfUnit;
540
    }
541

    
542
    public String getIndividualCount() {
543
        return individualCount;
544
    }
545

    
546
    public void setIndividualCount(String individualCount) {
547
        this.individualCount = individualCount;
548
    }
549

    
550
    public Map<Language,LanguageString> getDefinition(){
551
        return this.definition;
552
    }
553

    
554
    /**
555
     * adds the {@link LanguageString description} to the {@link MultilanguageText multilanguage text}
556
     * used to define <i>this</i> specimen or observation.
557
     *
558
     * @param description	the languageString in with the title string and the given language
559
     *
560
     * @see    	   		#getDefinition()
561
     * @see    	   		#putDefinition(Language, String)
562
     */
563
    public void putDefinition(LanguageString description){
564
        this.definition.put(description.getLanguage(),description);
565
    }
566

    
567
    /**
568
     * Creates a {@link LanguageString language string} based on the given text string
569
     * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
570
     * used to define <i>this</i> specimen or observation.
571
     *
572
     * @param language	the language in which the title string is formulated
573
     * @param text		the definition in a particular language
574
     *
575
     * @see    	   		#getDefinition()
576
     * @see    	   		#putDefinition(LanguageString)
577
     */
578
    public void putDefinition(Language language, String text){
579
        this.definition.put(language, LanguageString.NewInstance(text, language));
580
    }
581

    
582

    
583
    public void removeDefinition(Language lang){
584
        this.definition.remove(lang);
585
    }
586

    
587
    /**
588
     * for derived units get the single next higher parental/original unit.
589
     * If multiple original units exist throw error
590
     * @return
591
     */
592
    @Transient
593
    public SpecimenOrObservationBase getOriginalUnit(){
594
        logger.warn("GetOriginalUnit not yet implemented");
595
        return null;
596
    }
597

    
598

    
599
    public boolean hasCharacterData() {
600
        Set<DescriptionBase<S>> descriptions = this.getDescriptions();
601
        for (DescriptionBase<?> descriptionBase : descriptions) {
602
            if (descriptionBase.isInstanceOf(SpecimenDescription.class)) {
603
                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(descriptionBase, SpecimenDescription.class);
604
                Set<DescriptionElementBase> elements = specimenDescription.getElements();
605
                for (DescriptionElementBase descriptionElementBase : elements) {
606
                    if (descriptionElementBase.isCharacterData()){
607
                        return true;
608
                    }
609
                }
610
            }
611
        }
612
        return false;
613
    }
614

    
615
    /**
616
     * Returns a list of all description items which
617
     * @return
618
     */
619
    @Transient
620
    public Collection<DescriptionElementBase> characterData() {
621
        Collection<DescriptionElementBase> states = new ArrayList<>();
622
        Set<DescriptionBase<S>> descriptions = this.getDescriptions();
623
        for (DescriptionBase<?> descriptionBase : descriptions) {
624
            if (descriptionBase.isInstanceOf(SpecimenDescription.class)) {
625
                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(descriptionBase, SpecimenDescription.class);
626
                Set<DescriptionElementBase> elements = specimenDescription.getElements();
627
                for (DescriptionElementBase descriptionElementBase : elements) {
628
                    if(descriptionElementBase.isCharacterData()){
629
                        states.add(descriptionElementBase);
630
                    }
631
                }
632
            }
633
        }
634
        return states;
635
    }
636

    
637
    public Collection<DerivedUnit> collectDerivedUnits(Integer maxDepth) {
638
        if(maxDepth == null) {
639
            maxDepth = Integer.MAX_VALUE;
640
        }
641
        Collection<DerivedUnit> derivedUnits = new ArrayList<>();
642
        for (DerivationEvent derivationEvent : getDerivationEvents()) {
643
            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
644
                derivedUnits.add(derivative);
645
                if(maxDepth > 0) {
646
                    derivedUnits.addAll(derivative.collectDerivedUnits(maxDepth - 1));
647
                }
648
            }
649
        }
650
        return derivedUnits;
651
    }
652

    
653
//******************** CLONE **********************************************/
654

    
655
    @Override
656
    public SpecimenOrObservationBase<S> clone() throws CloneNotSupportedException {
657
        SpecimenOrObservationBase<S> result = (SpecimenOrObservationBase<S>)super.clone();
658

    
659
        //defininion (description, languageString)
660
        result.definition = cloneLanguageString(this.definition);
661

    
662
        //sex
663
        result.setSex(this.sex);
664
        //life stage
665
        result.setLifeStage(this.lifeStage);
666

    
667
        result.descriptions = new HashSet<>();
668
        //Descriptions
669
        for(DescriptionBase<S> description : this.descriptions) {
670
            result.addDescription(description.clone());
671
        }
672

    
673
        result.determinations = new HashSet<>();
674
        for(DeterminationEvent determination : this.determinations) {
675
            result.addDetermination(determination.clone());
676
        }
677

    
678
        result.derivationEvents = new HashSet<>();
679
        //DerivationEvent
680
        for(DerivationEvent derivationEvent : this.derivationEvents) {
681
            //TODO should we clone this?
682
            result.addDerivationEvent(derivationEvent);
683
        }
684

    
685
        //no changes to: individualCount
686
        return result;
687
    }
688
}
(12-12/15)