Project

General

Profile

Download (22 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.net.URI;
12
import java.util.ArrayList;
13
import java.util.Collection;
14
import java.util.HashMap;
15
import java.util.HashSet;
16
import java.util.Map;
17
import java.util.Set;
18

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

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

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

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

    
112
    private static final long serialVersionUID = 6932680139334408031L;
113
    private static final Logger logger = Logger.getLogger(SpecimenOrObservationBase.class);
114

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

    
134

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

    
142

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

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

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

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

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

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

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

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

    
211
    @XmlAttribute(name = "publish")
212
    @Field(analyze = Analyze.NO)
213
    @FieldBridge(impl=BooleanBridge.class)
214
    private boolean publish = true;
215

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

    
230

    
231
    //if true identityCache will not be automatically generated/updated
232
    @XmlElement(name = "ProtectedIdentityCache")
233
    private boolean protectedIdentityCache;
234

    
235

    
236
//********************************** CONSTRUCTOR *********************************/
237

    
238
    //for hibernate use only
239
    @Deprecated
240
    protected SpecimenOrObservationBase(){
241
        super();
242
    }
243

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

    
250

    
251
    /**
252
     * Subclasses should implement setting the default cache strate
253
     */
254
    protected abstract void initDefaultCacheStrategy();
255

    
256

    
257
//************************* GETTER / SETTER ***********************/
258

    
259

    
260
    /**@see #recordBasis */
261
    public SpecimenOrObservationType getRecordBasis() {
262
        return recordBasis;
263
    }
264
    /**@see #recordBasis */
265
    public void setRecordBasis(SpecimenOrObservationType recordBasis) {
266
        this.recordBasis = recordBasis;
267
    }
268

    
269

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

    
285
    public void setIdentityCache(String identityCache, boolean isProtected) {
286
        this.protectedIdentityCache = isProtected;
287
        setIdentityCache(identityCache);
288
    }
289

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

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

    
312

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

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

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

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

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

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

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

    
411
    /**
412
     * Removes a specimen from a description (removes a description from this specimen)
413
     * @param description
414
     */
415
    @Override
416
    public void removeDescription(DescriptionBase description) {
417
        boolean existed = descriptions.remove(description);
418
        if (existed){
419
            description.setDescribedSpecimenOrObservation(null);
420
        }
421
    }
422

    
423

    
424
    public Set<DerivationEvent> getDerivationEvents() {
425
        if(derivationEvents == null) {
426
            this.derivationEvents = new HashSet<>();
427
        }
428
        return this.derivationEvents;
429
    }
430

    
431
    public void addDerivationEvent(DerivationEvent derivationEvent) {
432
        if (! this.derivationEvents.contains(derivationEvent)){
433
            this.derivationEvents.add(derivationEvent);
434
            derivationEvent.addOriginal(this);
435
        }
436
    }
437

    
438
    public void removeDerivationEvent(DerivationEvent derivationEvent) {
439
        if (this.derivationEvents.contains(derivationEvent)){
440
            this.derivationEvents.remove(derivationEvent);
441
            derivationEvent.removeOriginal(this);
442
        }
443
    }
444

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

    
452
    public void addDetermination(DeterminationEvent determination) {
453
        // FIXME bidirectional integrity. Use protected Determination setter
454
        this.determinations.add(determination);
455
    }
456

    
457
    public void removeDetermination(DeterminationEvent determination) {
458
        // FIXME bidirectional integrity. Use protected Determination setter
459
        this.determinations.remove(determination);
460
    }
461

    
462
    public DefinedTerm getSex() {
463
        return sex;
464
    }
465

    
466
    public void setSex(DefinedTerm sex) {
467
        this.sex = sex;
468
    }
469

    
470
    public DefinedTerm getLifeStage() {
471
        return lifeStage;
472
    }
473

    
474
    public void setLifeStage(DefinedTerm lifeStage) {
475
        this.lifeStage = lifeStage;
476
    }
477

    
478

    
479
    /**
480
     * @see #kindOfUnit
481
     * @return
482
     */
483
    public DefinedTerm getKindOfUnit() {
484
        return kindOfUnit;
485
    }
486

    
487
    /**
488
     * @see #kindOfUnit
489
     * @param kindOfUnit
490
     */
491
    public void setKindOfUnit(DefinedTerm kindOfUnit) {
492
        this.kindOfUnit = kindOfUnit;
493
    }
494

    
495
    public String getIndividualCount() {
496
        return individualCount;
497
    }
498

    
499
    public void setIndividualCount(String individualCount) {
500
        this.individualCount = individualCount;
501
    }
502

    
503
    public Map<Language,LanguageString> getDefinition(){
504
        return this.definition;
505
    }
506

    
507
    /**
508
     * adds the {@link LanguageString description} to the {@link MultilanguageText multilanguage text}
509
     * used to define <i>this</i> specimen or observation.
510
     *
511
     * @param description	the languageString in with the title string and the given language
512
     *
513
     * @see    	   		#getDefinition()
514
     * @see    	   		#putDefinition(Language, String)
515
     */
516
    public void putDefinition(LanguageString description){
517
        this.definition.put(description.getLanguage(),description);
518
    }
519

    
520
    /**
521
     * Creates a {@link LanguageString language string} based on the given text string
522
     * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
523
     * used to define <i>this</i> specimen or observation.
524
     *
525
     * @param language	the language in which the title string is formulated
526
     * @param text		the definition in a particular language
527
     *
528
     * @see    	   		#getDefinition()
529
     * @see    	   		#putDefinition(LanguageString)
530
     */
531
    public void putDefinition(Language language, String text){
532
        this.definition.put(language, LanguageString.NewInstance(text, language));
533
    }
534

    
535

    
536
    public void removeDefinition(Language lang){
537
        this.definition.remove(lang);
538
    }
539

    
540
    /**
541
     * for derived units get the single next higher parental/original unit.
542
     * If multiple original units exist throw error
543
     * @return
544
     */
545
    @Transient
546
    public SpecimenOrObservationBase getOriginalUnit(){
547
        logger.warn("GetOriginalUnit not yet implemented");
548
        return null;
549
    }
550

    
551

    
552
    public boolean hasCharacterData() {
553
        Set<DescriptionBase<S>> descriptions = this.getDescriptions();
554
        for (DescriptionBase<?> descriptionBase : descriptions) {
555
            if (descriptionBase.isInstanceOf(SpecimenDescription.class)) {
556
                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(descriptionBase, SpecimenDescription.class);
557
                Set<DescriptionElementBase> elements = specimenDescription.getElements();
558
                for (DescriptionElementBase descriptionElementBase : elements) {
559
                    if (descriptionElementBase.isCharacterData()){
560
                        return true;
561
                    }
562
                }
563
            }
564
        }
565
        return false;
566
    }
567

    
568
    /**
569
     * Returns a list of all description items which
570
     * @return
571
     */
572
    @Transient
573
    public Collection<DescriptionElementBase> characterData() {
574
        Collection<DescriptionElementBase> states = new ArrayList<>();
575
        Set<DescriptionBase<S>> descriptions = this.getDescriptions();
576
        for (DescriptionBase<?> descriptionBase : descriptions) {
577
            if (descriptionBase.isInstanceOf(SpecimenDescription.class)) {
578
                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(descriptionBase, SpecimenDescription.class);
579
                Set<DescriptionElementBase> elements = specimenDescription.getElements();
580
                for (DescriptionElementBase descriptionElementBase : elements) {
581
                    if(descriptionElementBase.isCharacterData()){
582
                        states.add(descriptionElementBase);
583
                    }
584
                }
585
            }
586
        }
587
        return states;
588
    }
589

    
590
    public Collection<DerivedUnit> collectDerivedUnits() {
591
        Collection<DerivedUnit> derivedUnits = new ArrayList<>();
592
        for (DerivationEvent derivationEvent : getDerivationEvents()) {
593
            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
594
                derivedUnits.add(derivative);
595
                derivedUnits.addAll(derivative.collectDerivedUnits());
596
            }
597
        }
598
        return derivedUnits;
599
    }
600

    
601
//******************** CLONE **********************************************/
602

    
603
    @Override
604
    public SpecimenOrObservationBase<S> clone() throws CloneNotSupportedException {
605
        SpecimenOrObservationBase<S> result = (SpecimenOrObservationBase<S>)super.clone();
606

    
607
        //defininion (description, languageString)
608
        result.definition = cloneLanguageString(this.definition);
609

    
610
        //sex
611
        result.setSex(this.sex);
612
        //life stage
613
        result.setLifeStage(this.lifeStage);
614

    
615
        result.descriptions = new HashSet<>();
616
        //Descriptions
617
        for(DescriptionBase<S> description : this.descriptions) {
618
            result.addDescription(description.clone());
619
        }
620

    
621
        result.determinations = new HashSet<>();
622
        for(DeterminationEvent determination : this.determinations) {
623
            result.addDetermination(determination.clone());
624
        }
625

    
626
        result.derivationEvents = new HashSet<>();
627
        //DerivationEvent
628
        for(DerivationEvent derivationEvent : this.derivationEvents) {
629
            //TODO should we clone this?
630
            result.addDerivationEvent(derivationEvent);
631
        }
632

    
633
        //no changes to: individualCount
634
        return result;
635
    }
636
}
(11-11/14)