Project

General

Profile

Download (62.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.api.facade;
11

    
12
import java.beans.PropertyChangeEvent;
13
import java.beans.PropertyChangeListener;
14
import java.text.ParseException;
15
import java.util.ArrayList;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.List;
19
import java.util.Map;
20
import java.util.Set;
21

    
22
import javax.persistence.Transient;
23

    
24
import org.apache.log4j.Logger;
25

    
26
import eu.etaxonomy.cdm.api.service.IOccurrenceService;
27
import eu.etaxonomy.cdm.model.agent.AgentBase;
28
import eu.etaxonomy.cdm.model.agent.Person;
29
import eu.etaxonomy.cdm.model.common.Annotation;
30
import eu.etaxonomy.cdm.model.common.CdmBase;
31
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
32
import eu.etaxonomy.cdm.model.common.Language;
33
import eu.etaxonomy.cdm.model.common.LanguageString;
34
import eu.etaxonomy.cdm.model.common.TimePeriod;
35
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
36
import eu.etaxonomy.cdm.model.description.Feature;
37
import eu.etaxonomy.cdm.model.description.Sex;
38
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
39
import eu.etaxonomy.cdm.model.description.Stage;
40
import eu.etaxonomy.cdm.model.description.TextData;
41
import eu.etaxonomy.cdm.model.location.NamedArea;
42
import eu.etaxonomy.cdm.model.location.Point;
43
import eu.etaxonomy.cdm.model.location.ReferenceSystem;
44
import eu.etaxonomy.cdm.model.media.Media;
45
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
46
import eu.etaxonomy.cdm.model.occurrence.Collection;
47
import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
48
import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;
49
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
50
import eu.etaxonomy.cdm.model.occurrence.FieldObservation;
51
import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
52
import eu.etaxonomy.cdm.model.occurrence.PreservationMethod;
53
import eu.etaxonomy.cdm.model.occurrence.Specimen;
54
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
55
import eu.etaxonomy.cdm.model.reference.Reference;
56

    
57
/**
58
 * This class is a facade to the eu.etaxonomy.cdm.model.occurrence package from
59
 * a specimen based view. It does not support all functionality available in the
60
 * occurrence package.<BR>
61
 * The most significant restriction is that a specimen may derive only from
62
 * one direct derivation event and there must be only one field observation (gathering event)
63
 * it derives from.<BR> 
64
 * 
65
 * @author a.mueller
66
 * @date 14.05.2010
67
 */
68
public class DerivedUnitFacade {
69
	private static final Logger logger = Logger.getLogger(DerivedUnitFacade.class);
70
	
71
	private static final String notSupportMessage = "A specimen facade not supported exception has occurred at a place where this should not have happened. The developer should implement not support check properly during class initialization ";
72
	
73
	/**
74
	 * Enum that defines the class the "Specimen" belongs to.
75
	 * Some methods of the facade are not available for certain classes
76
	 * and will throw an Exception when invoking them.
77
	 */
78
	public enum DerivedUnitType{
79
		Specimen ("Specimen"),
80
		Observation("Observation"),
81
		LivingBeing("Living Being"),
82
		Fossil("Fossil"),
83
		DerivedUnit("Derived Unit");
84
		
85
		String representation;
86
		private DerivedUnitType(String representation){
87
			this.representation = representation;
88
		}
89
		
90
		/**
91
		 * @return the representation
92
		 */
93
		public String getRepresentation() {
94
			return representation;
95
		}
96
		
97
		private DerivedUnitBase getNewDerivedUnitInstance(){
98
			if (this == DerivedUnitType.Specimen){
99
				return eu.etaxonomy.cdm.model.occurrence.Specimen.NewInstance();
100
			}else if (this == DerivedUnitType.Observation){
101
				return eu.etaxonomy.cdm.model.occurrence.Observation.NewInstance();
102
			}else if (this == DerivedUnitType.LivingBeing){
103
				return eu.etaxonomy.cdm.model.occurrence.LivingBeing.NewInstance();
104
			}else if (this == DerivedUnitType.Fossil){
105
				return eu.etaxonomy.cdm.model.occurrence.Fossil.NewInstance();
106
			}else if (this == DerivedUnitType.DerivedUnit){
107
				return eu.etaxonomy.cdm.model.occurrence.DerivedUnit.NewInstance();
108
			}else{
109
				throw new IllegalStateException("Unknown derived unit type " +  this.getRepresentation());
110
			}
111
		}
112
		
113
	}
114
	
115
	
116
	private DerivedUnitFacadeConfigurator config;
117
	
118
	//private GatheringEvent gatheringEvent;
119
	private DerivedUnitType type;  //needed?
120
	
121
	private FieldObservation fieldObservation;
122
	
123
	private DerivedUnitBase derivedUnit;
124

    
125
	//media - the text data holding the media
126
	private TextData derivedUnitMediaTextData;
127
	private TextData fieldObjectMediaTextData;
128
	
129
	
130
	private TextData ecology;
131
	private TextData plantDescription;
132
	
133
	
134
	/**
135
	 * Creates a derived unit facade for a new derived unit of type <code>type</code>.
136
	 * @param type
137
	 * @return
138
	 */
139
	public static DerivedUnitFacade NewInstance(DerivedUnitType type){
140
		return new DerivedUnitFacade(type);
141
	}
142

    
143
	/**
144
	 * Creates a derived unit facade for a given derived unit using the default configuration.
145
	 * @param derivedUnit
146
	 * @return
147
	 * @throws DerivedUnitFacadeNotSupportedException
148
	 */
149
	public static DerivedUnitFacade NewInstance(DerivedUnitBase derivedUnit) throws DerivedUnitFacadeNotSupportedException{
150
		return new DerivedUnitFacade(derivedUnit, null);
151
	}
152
	
153
	public static DerivedUnitFacade NewInstance(DerivedUnitBase derivedUnit, DerivedUnitFacadeConfigurator config) throws DerivedUnitFacadeNotSupportedException{
154
		return new DerivedUnitFacade(derivedUnit, config);
155
	}
156

    
157

    
158
	
159
// ****************** CONSTRUCTOR ****************************************************
160
	
161
	private DerivedUnitFacade(DerivedUnitType type){
162
		this.config = DerivedUnitFacadeConfigurator.NewInstance();
163
		
164
		//derivedUnit
165
		derivedUnit = type.getNewDerivedUnitInstance();
166
		setCacheStrategy();
167
	}
168
	
169
	private DerivedUnitFacade(DerivedUnitBase derivedUnit, DerivedUnitFacadeConfigurator config) throws DerivedUnitFacadeNotSupportedException{
170
		
171
		if (config == null){
172
			config = DerivedUnitFacadeConfigurator.NewInstance();
173
		}
174
		this.config = config;
175
		
176
		//derived unit
177
		this.derivedUnit = derivedUnit;
178
		setCacheStrategy();
179
		
180
		//derivation event
181
		if (this.derivedUnit.getDerivedFrom() != null){
182
			DerivationEvent derivationEvent = getDerivationEvent(true);
183
			//fieldObservation
184
			Set<FieldObservation> fieldOriginals = getFieldObservationsOriginals(derivationEvent, null);
185
			if (fieldOriginals.size() > 1){
186
				throw new DerivedUnitFacadeNotSupportedException("Specimen must not have more than 1 derivation event");
187
			}else if (fieldOriginals.size() == 0){
188
				//fieldObservation = FieldObservation.NewInstance();
189
			}else if (fieldOriginals.size() == 1){
190
				fieldObservation = fieldOriginals.iterator().next();
191
				//###fieldObservation = getInitializedFieldObservation(fieldObservation);
192
				fieldObservation.addPropertyChangeListener(getNewEventPropagationListener());
193
			}else{
194
				throw new IllegalStateException("Illegal state");
195
			}	
196
		}
197
		// #### derivedUnit = getInitializedDerivedUnit(derivedUnit);
198

    
199
		//test if unsupported
200
		
201
		//media
202
		//specimen
203
//		String objectTypeExceptionText = "Specimen";
204
//		SpecimenDescription imageGallery = getImageGalleryWithSupportTest(derivedUnit, objectTypeExceptionText, false);
205
//		getImageTextDataWithSupportTest(imageGallery, objectTypeExceptionText);
206
		this.derivedUnitMediaTextData = inititializeTextDataWithSupportTest(Feature.IMAGE(), this.derivedUnit, false, true);
207
		
208
		//field observation
209
//		objectTypeExceptionText = "Field observation";
210
//		imageGallery = getImageGalleryWithSupportTest(fieldObservation, objectTypeExceptionText, false);
211
//		getImageTextDataWithSupportTest(imageGallery, objectTypeExceptionText);
212
		fieldObjectMediaTextData = initializeFieldObjectTextDataWithSupportTest(Feature.IMAGE(), false, true);
213
		
214
		//handle derivedUnit.getMedia()
215
		if (derivedUnit.getMedia().size() > 0){
216
			//TODO better changed model here to allow only one place for images
217
			if (this.config.isMoveDerivedUnitMediaToGallery()){
218
				Set<Media> mediaSet = derivedUnit.getMedia();
219
				for (Media media : mediaSet){
220
					this.addDerivedUnitMedia(media);
221
				}
222
				mediaSet.removeAll(getDerivedUnitMedia());
223
			}else{
224
				throw new DerivedUnitFacadeNotSupportedException("Specimen may not have direct media. Only (one) image gallery is allowed");
225
			}
226
		}
227
		
228
		//handle fieldObservation.getMedia()
229
		if (fieldObservation != null && fieldObservation.getMedia() != null && fieldObservation.getMedia().size() > 0){
230
			//TODO better changed model here to allow only one place for images
231
			if (this.config.isMoveFieldObjectMediaToGallery()){
232
				Set<Media> mediaSet = fieldObservation.getMedia();
233
				for (Media media : mediaSet){
234
					this.addFieldObjectMedia(media);
235
				}
236
				mediaSet.removeAll(getFieldObjectMedia());
237
			}else{
238
				throw new DerivedUnitFacadeNotSupportedException("Field object may not have direct media. Only (one) image gallery is allowed");
239
			}
240
		}
241
		
242
		//test if descriptions are supported
243
		ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), false, false);
244
		plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), false, false);
245
	}
246
	
247

    
248
	private DerivedUnitBase getInitializedDerivedUnit(DerivedUnitBase derivedUnit) {
249
		IOccurrenceService occurrenceService = this.config.getOccurrenceService();
250
		if (occurrenceService == null){
251
			return derivedUnit;
252
		}
253
		List<String> propertyPaths = this.config.getPropertyPaths();
254
		if (propertyPaths == null){
255
			return derivedUnit;
256
		}
257
		propertyPaths = getDerivedUnitPropertyPaths(propertyPaths);
258
		DerivedUnitBase result = (DerivedUnitBase)occurrenceService.load(derivedUnit.getUuid(), propertyPaths);
259
		return result;
260
	}
261

    
262
	/**
263
	 * Initializes the derived unit according to the configuartions property path.
264
	 * If the property path is <code>null</code> or no occurrence service is given the
265
	 * returned object is the same as the input parameter.
266
	 * @param fieldObservation2
267
	 * @return
268
	 */
269
	private FieldObservation getInitializedFieldObservation(FieldObservation fieldObservation) {
270
		IOccurrenceService occurrenceService = this.config.getOccurrenceService();
271
		if (occurrenceService == null){
272
			return fieldObservation;
273
		}
274
		List<String> propertyPaths = this.config.getPropertyPaths();
275
		if (propertyPaths == null){
276
			return fieldObservation;
277
		}
278
		propertyPaths = getFieldObjectPropertyPaths(propertyPaths);
279
		FieldObservation result = (FieldObservation)occurrenceService.load(fieldObservation.getUuid(), propertyPaths);
280
		return result;
281
	}
282

    
283
	/**
284
	 * Transforms the property paths in a way that the facade is handled just like an 
285
	 * ordinary CdmBase object.<BR>
286
	 * E.g. a property path "collectinAreas" will be translated into gatheringEvent.collectingAreas
287
	 * @param propertyPaths
288
	 * @return
289
	 */
290
	private List<String> getFieldObjectPropertyPaths(List<String> propertyPaths) {
291
		List<String> result = new ArrayList<String>();
292
		for (String facadePath : propertyPaths){
293
			// collecting areas (named area)
294
			if (facadePath.startsWith("collectingAreas")){
295
				facadePath = "gatheringEvent." + facadePath;
296
				result.add(facadePath);
297
			}
298
			// collector (agentBase)
299
			else if (facadePath.startsWith("collector")){
300
				facadePath = facadePath.replace("collector", "gatheringEvent.actor");
301
				result.add(facadePath);
302
			}
303
			// exactLocation (agentBase)
304
			else if (facadePath.startsWith("exactLocation")){
305
				facadePath = "gatheringEvent." + facadePath;
306
				result.add(facadePath);
307
			}
308
			// gatheringPeriod (TimePeriod)
309
			else if (facadePath.startsWith("gatheringPeriod")){
310
				facadePath = facadePath.replace("gatheringPeriod", "gatheringEvent.timeperiod");
311
				result.add(facadePath);
312
			}
313
			// (locality/ localityLanguage , LanguageString)
314
			else if (facadePath.startsWith("locality")){
315
				facadePath = "gatheringEvent." + facadePath;
316
				result.add(facadePath);
317
			}
318
			
319
			//*********** FIELD OBJECT ************
320
			// fieldObjectDefinitions (Map<language, languageString)
321
			else if (facadePath.startsWith("fieldObjectDefinitions")){
322
				// TODO or definition ???
323
				facadePath = facadePath.replace("fieldObjectDefinitions", "description");
324
				result.add(facadePath);
325
			}
326
			// fieldObjectMedia  (Media)
327
			else if (facadePath.startsWith("fieldObjectMedia")){
328
				// TODO ??? 
329
				facadePath = facadePath.replace("fieldObjectMedia", "descriptions.elements.media");
330
				result.add(facadePath);
331
			}
332
			
333
			//Gathering Event will always be added
334
			result.add("gatheringEvent");
335
			
336
		}
337
		
338
/*
339
		Gathering Event
340
		====================
341
		- gatheringEvent (GatheringEvent)
342

    
343
		Field Object
344
		=================
345
		- ecology/ ecologyAll (String)  ???
346
		- plant description (like ecology)
347
		
348
		- fieldObjectImageGallery (SpecimenDescription)  - is automatically initialized via fieldObjectMedia
349

    
350
*/
351
		
352
		return result;
353
	}
354
	
355
	/**
356
	 * Transforms the property paths in a way that the facade is handled just like an 
357
	 * ordinary CdmBase object.<BR>
358
	 * E.g. a property path "collectinAreas" will be translated into gatheringEvent.collectingAreas
359
	 * 
360
	 * Not needed (?) as the facade works with REST service property paths without using this method.
361
	 * @param propertyPaths
362
	 * @return
363
	 */
364
	private List<String> getDerivedUnitPropertyPaths(List<String> propertyPaths) {
365
		List<String> result = new ArrayList<String>();
366
		for (String facadePath : propertyPaths){
367
			// determinations (DeterminationEvent)
368
			if (facadePath.startsWith("determinations")){
369
				facadePath = "" + facadePath;  //no change
370
				result.add(facadePath);
371
			}
372
			// storedUnder (TaxonNameBase)
373
			else if (facadePath.startsWith("storedUnder")){
374
				facadePath = "" + facadePath;  //no change
375
				result.add(facadePath);
376
			}
377
			// sources (IdentifiableSource)
378
			else if (facadePath.startsWith("sources")){
379
				facadePath = "" + facadePath;  //no change
380
				result.add(facadePath);
381
			}
382
			// collection (Collection)
383
			else if (facadePath.startsWith("collection")){
384
				facadePath = "" + facadePath;  //no change
385
				result.add(facadePath);
386
			}
387
			// (locality/ localityLanguage , LanguageString)
388
			else if (facadePath.startsWith("locality")){
389
				facadePath = "gatheringEvent." + facadePath;
390
				result.add(facadePath);
391
			}
392
		
393
			//*********** FIELD OBJECT ************
394
			// derivedUnitDefinitions (Map<language, languageString)
395
			else if (facadePath.startsWith("derivedUnitDefinitions")){
396
				// TODO or definition ???
397
				facadePath = facadePath.replace("derivedUnitDefinitions", "description");
398
				result.add(facadePath);
399
			}
400
			
401
			// derivedUnitMedia  (Media)
402
			else if (facadePath.startsWith("derivedUnitMedia")){
403
				// TODO ??? 
404
				facadePath = facadePath.replace("derivedUnitMedia", "descriptions.elements.media");
405
				result.add(facadePath);
406
			}
407
			
408
		}
409
		
410
/*
411
		//TODO
412
		Derived Unit
413
		=====================
414
		
415
		- derivedUnitImageGallery (SpecimenDescription)  - is automatically initialized via derivedUnitMedia
416
		
417
		- derivationEvent (DerivationEvent)  - will always be initialized
418
		- duplicates (??? Specimen???) ???
419
*/
420
		
421
		return result;
422
	}
423

    
424
	/**
425
	 * 
426
	 */
427
	private void setCacheStrategy() {
428
		if (derivedUnit == null){
429
			throw new NullPointerException("Facade's derviedUnit must not be null to set cache strategy");
430
		}
431
		derivedUnit.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
432
	}
433

    
434

    
435
	/**
436
	 * @param feature
437
	 * @param createIfNotExists
438
	 * @param isImageGallery
439
	 * @return
440
	 * @throws DerivedUnitFacadeNotSupportedException
441
	 */
442
	private TextData initializeFieldObjectTextDataWithSupportTest(Feature feature, boolean createIfNotExists, boolean isImageGallery) throws DerivedUnitFacadeNotSupportedException {
443
		//field object
444
		FieldObservation fieldObject = getFieldObservation(createIfNotExists) ;
445
		if (fieldObject == null){
446
			return null;
447
		}
448
		return inititializeTextDataWithSupportTest(feature, fieldObject, createIfNotExists, isImageGallery);
449
	}
450

    
451

    
452
	/**
453
	 * @param feature
454
	 * @param specimen
455
	 * @param createIfNotExists
456
	 * @param isImageGallery
457
	 * @return
458
	 * @throws DerivedUnitFacadeNotSupportedException
459
	 */
460
	private TextData inititializeTextDataWithSupportTest(Feature feature, SpecimenOrObservationBase specimen, boolean createIfNotExists, 
461
				boolean isImageGallery) throws DerivedUnitFacadeNotSupportedException {
462
		if (feature == null ){
463
			return null;
464
		}
465
		TextData textData = null;
466
		if (createIfNotExists){
467
			textData = TextData.NewInstance(feature);
468
		}
469
		
470
		Set<SpecimenDescription> descriptions;
471
		if (isImageGallery){
472
			descriptions = specimen.getSpecimenDescriptionImageGallery();
473
		}else{
474
			descriptions = specimen.getSpecimenDescriptions(false);
475
		}
476
		if (descriptions.size() == 0){
477
			if (createIfNotExists){
478
				SpecimenDescription newSpecimenDescription = SpecimenDescription.NewInstance(specimen);
479
				newSpecimenDescription.addElement(textData);
480
				newSpecimenDescription.setImageGallery(isImageGallery);
481
				return textData;
482
			}else{
483
				return null;
484
			}
485
		}
486
		Set<DescriptionElementBase> existingTextData = new HashSet<DescriptionElementBase>();
487
		for (SpecimenDescription description : descriptions){
488
			for (DescriptionElementBase element: description.getElements()){
489
				if (element.isInstanceOf(TextData.class) && ( feature.equals(element.getFeature() )|| isImageGallery ) ){
490
					existingTextData.add(element);
491
				}
492
			}
493
		}
494
		if (existingTextData.size() > 1){
495
			throw new DerivedUnitFacadeNotSupportedException("Specimen facade does not support more than one description text data of type " + feature.getLabel());
496
			
497
		}else if (existingTextData.size() == 1){
498
			return CdmBase.deproxy(existingTextData.iterator().next(), TextData.class);
499
		}else{
500
			SpecimenDescription description = descriptions.iterator().next();
501
			description.addElement(textData);
502
			return textData;
503
		}
504
	}
505
	
506

    
507
	/**
508
	 * Tests if a given image gallery is supported by the derived unit facade.
509
	 * It returns the only text data attached to the given image gallery.
510
	 * If the given image gallery does not have text data attached, it is created and attached.
511
	 * @param imageGallery
512
	 * @return
513
	 * @throws DerivedUnitFacadeNotSupportedException
514
	 */
515
	private TextData testImageGallery(SpecimenDescription imageGallery) throws DerivedUnitFacadeNotSupportedException {
516
		if (imageGallery.isImageGallery() == false){
517
			throw new DerivedUnitFacadeNotSupportedException("Image gallery needs to have image gallery flag set");
518
		}
519
		if (imageGallery.getElements().size() > 1){
520
			throw new DerivedUnitFacadeNotSupportedException("Image gallery must not have more then one description element");
521
		}
522
		TextData textData;
523
		if (imageGallery.getElements().size() == 0){
524
			textData = TextData.NewInstance(Feature.IMAGE());
525
			imageGallery.addElement(textData);
526
		}else{
527
			if (! imageGallery.getElements().iterator().next().isInstanceOf(TextData.class)){
528
				throw new DerivedUnitFacadeNotSupportedException("Image gallery must only have TextData as element");
529
			}else{
530
				textData = CdmBase.deproxy(imageGallery.getElements().iterator().next(), TextData.class);
531
			}
532
		}
533
		return textData;
534
	}
535

    
536
//************************** METHODS *****************************************	
537

    
538
	private TextData getDerivedUnitImageGalleryTextData(boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException{
539
		if (this.derivedUnitMediaTextData == null && createIfNotExists){
540
			this.derivedUnitMediaTextData = getImageGalleryTextData(derivedUnit, "Specimen");
541
		}
542
		return this.derivedUnitMediaTextData;
543
	}
544
	
545
	private TextData getObservationImageGalleryTextData(boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException{
546
		if (this.fieldObjectMediaTextData == null && createIfNotExists){
547
			this.fieldObjectMediaTextData = getImageGalleryTextData(fieldObservation, "Field observation");
548
		}
549
		return this.fieldObjectMediaTextData;
550
	}
551

    
552
	
553
	
554
	/**
555
	 * @param derivationEvent2
556
	 * @return
557
	 * @throws DerivedUnitFacadeNotSupportedException 
558
	 */
559
	private Set<FieldObservation> getFieldObservationsOriginals(DerivationEvent derivationEvent, Set<SpecimenOrObservationBase> recursionAvoidSet) throws DerivedUnitFacadeNotSupportedException {
560
		if (recursionAvoidSet == null){
561
			recursionAvoidSet = new HashSet<SpecimenOrObservationBase>();
562
		}
563
		Set<FieldObservation> result = new HashSet<FieldObservation>();
564
		Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
565
		for (SpecimenOrObservationBase original : originals){
566
			if (original.isInstanceOf(FieldObservation.class)){
567
				result.add(CdmBase.deproxy(original, FieldObservation.class));
568
			}else if (original.isInstanceOf(DerivedUnitBase.class)){
569
				//if specimen has already been tested exclude it from further recursion
570
				if (recursionAvoidSet.contains(original)){
571
					continue;
572
				}
573
				DerivedUnitBase derivedUnit = CdmBase.deproxy(original, DerivedUnitBase.class);
574
				DerivationEvent originalDerivation = derivedUnit.getDerivedFrom();
575
//				Set<DerivationEvent> derivationEvents = original.getDerivationEvents(); 
576
//				for (DerivationEvent originalDerivation : derivationEvents){
577
					Set<FieldObservation> fieldObservations = getFieldObservationsOriginals(originalDerivation, recursionAvoidSet);
578
					result.addAll(fieldObservations);
579
//				}
580
			}else{
581
				throw new DerivedUnitFacadeNotSupportedException("Unhandled specimen or observation base type: " + original.getClass().getName() );
582
			}
583
			
584
		}
585
		return result;
586
	}
587
	
588
	//*********** MEDIA METHODS ******************************
589
	
590
//	/**
591
//	 * Returns the media list for a specimen. Throws an exception if the existing specimen descriptions
592
//	 * are not supported by this facade.
593
//	 * @param specimen the specimen the media belongs to
594
//	 * @param specimenExceptionText text describing the specimen for exception messages
595
//	 * @return
596
//	 * @throws DerivedUnitFacadeNotSupportedException
597
//	 */
598
//	private List<Media> getImageGalleryMedia(SpecimenOrObservationBase specimen, String specimenExceptionText) throws DerivedUnitFacadeNotSupportedException{
599
//		List<Media> result;
600
//		SpecimenDescription imageGallery = getImageGalleryWithSupportTest(specimen, specimenExceptionText, true);
601
//		TextData textData = getImageTextDataWithSupportTest(imageGallery, specimenExceptionText);
602
//		result = textData.getMedia();
603
//		return result;
604
//	}
605
	
606
	/**
607
	 * Returns the media list for a specimen. Throws an exception if the existing specimen descriptions
608
	 * are not supported by this facade.
609
	 * @param specimen the specimen the media belongs to
610
	 * @param specimenExceptionText text describing the specimen for exception messages
611
	 * @return
612
	 * @throws DerivedUnitFacadeNotSupportedException
613
	 */
614
	private TextData getImageGalleryTextData(SpecimenOrObservationBase specimen, String specimenExceptionText) throws DerivedUnitFacadeNotSupportedException{
615
		TextData result;
616
		SpecimenDescription imageGallery = getImageGalleryWithSupportTest(specimen, specimenExceptionText, true);
617
		result = getImageTextDataWithSupportTest(imageGallery, specimenExceptionText);
618
		return result;
619
	}
620
	
621
	
622
	/**
623
	 * Returns the image gallery of the according specimen. Throws an exception if the attached
624
	 * image gallerie(s) are not supported by this facade.
625
	 * If no image gallery exists a new one is created if <code>createNewIfNotExists</code> is true and
626
	 * if specimen is not <code>null</code>.
627
	 * @param specimen
628
	 * @param specimenText
629
	 * @param createNewIfNotExists
630
	 * @return
631
	 * @throws DerivedUnitFacadeNotSupportedException
632
	 */
633
	private SpecimenDescription getImageGalleryWithSupportTest(SpecimenOrObservationBase<?> specimen, String specimenText, boolean createNewIfNotExists) throws DerivedUnitFacadeNotSupportedException{
634
		if (specimen == null){
635
			return null;
636
		}
637
		SpecimenDescription imageGallery;
638
		if (hasMultipleImageGalleries(specimen)){
639
			throw new DerivedUnitFacadeNotSupportedException( specimenText + " must not have more than 1 image gallery");
640
		}else{
641
			imageGallery = getImageGallery(specimen, createNewIfNotExists);
642
			getImageTextDataWithSupportTest(imageGallery, specimenText);
643
		}
644
		return imageGallery;
645
	}
646
	
647
	/**
648
	 * Returns the media holding text data element of the image gallery. Throws an exception if multiple
649
	 * such text data already exist.
650
	 * Creates a new text data if none exists and adds it to the image gallery.
651
	 * If image gallery is <code>null</code> nothing happens.
652
	 * @param imageGallery
653
	 * @param textData
654
	 * @return
655
	 * @throws DerivedUnitFacadeNotSupportedException 
656
	 */
657
	private TextData getImageTextDataWithSupportTest(SpecimenDescription imageGallery, String specimenText) throws DerivedUnitFacadeNotSupportedException {
658
		if (imageGallery == null){
659
			return null;
660
		}
661
		TextData textData = null;
662
		for (DescriptionElementBase element: imageGallery.getElements()){
663
			if (element.isInstanceOf(TextData.class) && element.getFeature().equals(Feature.IMAGE())){
664
				if (textData != null){
665
					throw new DerivedUnitFacadeNotSupportedException( specimenText + " must not have more than 1 image text data element in image gallery");
666
				}
667
				textData = CdmBase.deproxy(element, TextData.class);
668
			}
669
		}
670
		if (textData == null){
671
			textData = TextData.NewInstance(Feature.IMAGE());
672
			imageGallery.addElement(textData);
673
		}
674
		return textData;
675
	}
676

    
677
	/**
678
	 * Checks, if a specimen belongs to more than one description that is an image gallery
679
	 * @param derivedUnit
680
	 * @return
681
	 */
682
	private boolean hasMultipleImageGalleries(SpecimenOrObservationBase<?> derivedUnit){
683
		int count = 0;
684
		Set<SpecimenDescription> descriptions= derivedUnit.getSpecimenDescriptions();
685
		for (SpecimenDescription description : descriptions){
686
			if (description.isImageGallery()){
687
				count++;
688
			}
689
		}
690
		return (count > 1);
691
	}
692

    
693
	
694
	/**
695
	 * Returns the image gallery for a specimen. If there are multiple specimen descriptions
696
	 * marked as image galleries an arbitrary one is chosen.
697
	 * If no image gallery exists, a new one is created if <code>createNewIfNotExists</code>
698
	 * is <code>true</code>.<Br>
699
	 * If specimen is <code>null</code> a null pointer exception is thrown.
700
	 * @param createNewIfNotExists
701
	 * @return
702
	 */
703
	private SpecimenDescription getImageGallery(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) {
704
		SpecimenDescription result = null;
705
		Set<SpecimenDescription> descriptions= specimen.getSpecimenDescriptions();
706
		for (SpecimenDescription description : descriptions){
707
			if (description.isImageGallery()){
708
				result = description;
709
				break;
710
			}
711
		}
712
		if (result == null && createIfNotExists){
713
			result = SpecimenDescription.NewInstance(specimen);
714
			result.setImageGallery(true);
715
		}
716
		return result;
717
	}
718
	
719
	/**
720
	 * Adds a media to the specimens image gallery. If media is <code>null</code> nothing happens.
721
	 * @param media
722
	 * @param specimen
723
	 * @return true if media is not null (as specified by {@link java.util.Collection#add(Object) Collection.add(E e)} 
724
	 * @throws DerivedUnitFacadeNotSupportedException
725
	 */
726
	private boolean addMedia(Media media, SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
727
		if (media != null){
728
			List<Media> mediaList = getMedia(specimen, true);
729
			return mediaList.add(media);
730
		}else{
731
			return false;
732
		}
733
	}
734

    
735
	/**
736
	 * Removes a media from the specimens image gallery.
737
	 * @param media
738
	 * @param specimen
739
	 * @return true if an element was removed as a result of this call (as specified by {@link java.util.Collection#remove(Object) Collection.remove(E e)} 
740
	 * @throws DerivedUnitFacadeNotSupportedException
741
	 */
742
	private boolean removeMedia(Media media, SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
743
		List<Media> mediaList = getMedia(specimen, true);
744
		return mediaList == null ? null : mediaList.remove(media);
745
	}
746

    
747
	private List<Media> getMedia(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException {
748
		TextData textData = getMediaTextData(specimen, createIfNotExists);
749
		return textData == null ? null : textData.getMedia();
750
	}
751
	
752
	/**
753
	 * Returns the one media list of a specimen which is part of the only image gallery that 
754
	 * this specimen is part of.<BR>
755
	 * If these conditions are not hold an exception is thrwon.
756
	 * @param specimen
757
	 * @return
758
	 * @throws DerivedUnitFacadeNotSupportedException
759
	 */
760
//	private List<Media> getMedia(SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
761
//		if (specimen == null){
762
//			return null;
763
//		}
764
//		if (specimen == this.derivedUnit){
765
//			return getDerivedUnitImageGalleryMedia();
766
//		}else if (specimen == this.fieldObservation){
767
//			return getObservationImageGalleryTextData();
768
//		}else{
769
//			return getImageGalleryMedia(specimen, "Undefined specimen ");
770
//		}
771
//	}
772
	
773
	/**
774
	 * Returns the one media list of a specimen which is part of the only image gallery that 
775
	 * this specimen is part of.<BR>
776
	 * If these conditions are not hold an exception is thrwon.
777
	 * @param specimen
778
	 * @return
779
	 * @throws DerivedUnitFacadeNotSupportedException
780
	 */
781
	private TextData getMediaTextData(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException {
782
		if (specimen == null){
783
			return null;
784
		}
785
		if (specimen == this.derivedUnit){
786
			return getDerivedUnitImageGalleryTextData(createIfNotExists);
787
		}else if (specimen == this.fieldObservation){
788
			return getObservationImageGalleryTextData(createIfNotExists);
789
		}else{
790
			return getImageGalleryTextData(specimen, "Undefined specimen ");
791
		}
792
	}
793
	
794
	
795
//****************** GETTER / SETTER / ADDER / REMOVER ***********************/	
796
	
797
// ****************** Gathering Event *********************************/
798
	
799
	//country
800
	@Transient
801
	public NamedArea getCountry(){
802
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCountry() : null);
803
	}
804
	
805
	public void setCountry(NamedArea country){
806
		getGatheringEvent(true).setCountry(country);
807
	}
808
	
809
	
810
	//Collecting area
811
	public void addCollectingArea(NamedArea area) {
812
		getGatheringEvent(true).addCollectingArea(area);
813
	}
814
	public void addCollectingAreas(java.util.Collection<NamedArea> areas) {
815
		for (NamedArea area : areas){
816
			getGatheringEvent(true).addCollectingArea(area);
817
		}
818
	}
819
	@Transient
820
	public Set<NamedArea> getCollectingAreas() {
821
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCollectingAreas() : null);
822
	}
823
	public void removeCollectingArea(NamedArea area) {
824
		if (hasGatheringEvent()){
825
			getGatheringEvent(true).removeCollectingArea(area);
826
		}
827
	}
828

    
829
	//absolute elevation  
830
	/** meter above/below sea level of the surface 
831
	 * @see #getAbsoluteElevationError()
832
	 * @see #getAbsoluteElevationRange()
833
	 **/
834
	@Transient
835
	public Integer getAbsoluteElevation() {
836
		return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevation() : null);
837
	}
838
	public void setAbsoluteElevation(Integer absoluteElevation) {
839
		getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
840
	}
841

    
842
	//absolute elevation error
843
	@Transient
844
	public Integer getAbsoluteElevationError() {
845
		return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevationError() : null);
846
	}
847
	public void setAbsoluteElevationError(Integer absoluteElevationError) {
848
		getGatheringEvent(true).setAbsoluteElevationError(absoluteElevationError);
849
	}
850
	
851
	/**
852
	 * @see #getAbsoluteElevation()
853
	 * @see #getAbsoluteElevationError()
854
	 * @see #setAbsoluteElevationRange(Integer, Integer)
855
	 * @see #getAbsoluteElevationMaximum()
856
	 */
857
	@Transient
858
	public Integer getAbsoluteElevationMinimum(){
859
		if ( ! hasGatheringEvent() ){
860
			return null;
861
		}
862
		Integer minimum = getGatheringEvent(true).getAbsoluteElevation();
863
		if (getGatheringEvent(true).getAbsoluteElevationError() != null){
864
			minimum = minimum -  getGatheringEvent(true).getAbsoluteElevationError();
865
		}
866
		return minimum;
867
	}
868
	/**
869
	 * @see #getAbsoluteElevation()
870
	 * @see #getAbsoluteElevationError()
871
	 * @see #setAbsoluteElevationRange(Integer, Integer)
872
	 * @see #getAbsoluteElevationMinimum()
873
	 */
874
	@Transient
875
	public Integer getAbsoluteElevationMaximum(){
876
		if ( ! hasGatheringEvent() ){
877
			return null;
878
		}
879
		Integer maximum = getGatheringEvent(true).getAbsoluteElevation();
880
		if (getGatheringEvent(true).getAbsoluteElevationError() != null){
881
			maximum = maximum +  getGatheringEvent(true).getAbsoluteElevationError();
882
		}
883
		return maximum;
884
	}
885

    
886
	
887
	/**
888
	 * This method replaces absoluteElevation and absoulteElevationError by
889
	 * internally translating minimum and maximum values into
890
	 * average and error values. As all these values are integer based
891
	 * it is necessary that the distance is between minimum and maximum is <b>even</b>,
892
	 * otherwise we will get a rounding error resulting in a maximum that is increased
893
	 * by 1.
894
	 * @see #setAbsoluteElevation(Integer)
895
	 * @see #setAbsoluteElevationError(Integer)
896
	 * @param minimumElevation minimum of the range
897
	 * @param maximumElevation maximum of the range
898
	 */
899
	public void setAbsoluteElevationRange(Integer minimumElevation, Integer maximumElevation){
900
		if (minimumElevation == null || maximumElevation == null){
901
			Integer elevation = minimumElevation;
902
			Integer error = 0;
903
			if (minimumElevation == null){
904
				elevation = maximumElevation;
905
				if (elevation == null){
906
					error = null;
907
				}
908
			}
909
			getGatheringEvent(true).setAbsoluteElevation(elevation);
910
			getGatheringEvent(true).setAbsoluteElevationError(error);
911
		}else{
912
			if (! isEvenDistance(minimumElevation, maximumElevation) ){
913
				throw new IllegalArgumentException("Distance between minimum and maximum elevation must be even but was " + Math.abs(minimumElevation - maximumElevation));
914
			}
915
			Integer absoluteElevationError = Math.abs(maximumElevation - minimumElevation);
916
			absoluteElevationError = absoluteElevationError / 2;
917
			Integer absoluteElevation = minimumElevation + absoluteElevationError;
918
			getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
919
			getGatheringEvent(true).setAbsoluteElevationError(absoluteElevationError);
920
		}
921
	}
922

    
923
	/**
924
	 * @param minimumElevation
925
	 * @param maximumElevation
926
	 * @return
927
	 */
928
	private boolean isEvenDistance(Integer minimumElevation, Integer maximumElevation) {
929
		Integer diff = ( maximumElevation - minimumElevation);
930
		Integer testDiff = (diff /2) *2 ;
931
		return (testDiff == diff);
932
	}
933

    
934
	//collector
935
	@Transient
936
	public AgentBase getCollector() {
937
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCollector() : null);
938
	}
939
	public void setCollector(AgentBase collector){
940
		getGatheringEvent(true).setCollector(collector);
941
	}
942

    
943
	//collecting method
944
	@Transient
945
	public String getCollectingMethod() {
946
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCollectingMethod() : null);
947
	}
948
	public void setCollectingMethod(String collectingMethod) {
949
		getGatheringEvent(true).setCollectingMethod(collectingMethod);
950
	}
951

    
952
	//distance to ground
953
	@Transient
954
	public Integer getDistanceToGround() {
955
		return  (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToGround() : null);
956
	}
957
	public void setDistanceToGround(Integer distanceToGround) {
958
		getGatheringEvent(true).setDistanceToGround(distanceToGround);
959
	}
960

    
961
	//distance to water surface
962
	@Transient
963
	public Integer getDistanceToWaterSurface() {
964
		return  (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToWaterSurface() : null);
965
	}
966
	public void setDistanceToWaterSurface(Integer distanceToWaterSurface) {
967
		getGatheringEvent(true).setDistanceToWaterSurface(distanceToWaterSurface);
968
	}
969

    
970
	//exact location
971
	@Transient
972
	public Point getExactLocation() {
973
		return  (hasGatheringEvent() ? getGatheringEvent(true).getExactLocation() : null );
974
	}
975
	
976
	/**
977
	 * Returns a sexagesimal representation of the exact location (e.g. 12°59'N, 35°23E).
978
	 * If the exact location is <code>null</code> the empty string is returned.
979
	 * @param includeEmptySeconds
980
	 * @param includeReferenceSystem
981
	 * @return
982
	 */
983
	public String getExactLocationText(boolean includeEmptySeconds, boolean includeReferenceSystem){
984
		return (this.getExactLocation() == null ? "" : this.getExactLocation().toSexagesimalString(includeEmptySeconds, includeReferenceSystem));
985
	}
986
	public void setExactLocation(Point exactLocation) {
987
		getGatheringEvent(true).setExactLocation(exactLocation);
988
	}
989
	public void setExactLocationByParsing(String longitudeToParse, String latitudeToParse, ReferenceSystem referenceSystem, Integer errorRadius) throws ParseException{
990
		Point point = Point.NewInstance(null, null, referenceSystem, errorRadius);
991
		point.setLongitudeByParsing(longitudeToParse);
992
		point.setLatitudeByParsing(latitudeToParse);
993
		setExactLocation(point);
994
	}
995
	
996
	//gathering event description
997
	@Transient
998
	public String getGatheringEventDescription() {
999
		return  (hasGatheringEvent() ? getGatheringEvent(true).getDescription() : null);
1000
	}
1001
	public void setGatheringEventDescription(String description) {
1002
		getGatheringEvent(true).setDescription(description);
1003
	}
1004

    
1005
	//gatering period
1006
	@Transient
1007
	public TimePeriod getGatheringPeriod() {
1008
		return (hasGatheringEvent() ? getGatheringEvent(true).getTimeperiod() : null);
1009
	}
1010
	public void setGatheringPeriod(TimePeriod timeperiod) {
1011
		getGatheringEvent(true).setTimeperiod(timeperiod);
1012
	}
1013

    
1014
	//locality
1015
	@Transient
1016
	public LanguageString getLocality(){
1017
		return (hasGatheringEvent() ? getGatheringEvent(true).getLocality() : null);
1018
	}
1019
	/**
1020
	 * convienience method for {@link #getLocality()}.{@link LanguageString#getText() getText()}
1021
	 * @return
1022
	 */
1023
	@Transient
1024
	public String getLocalityText(){
1025
		LanguageString locality = getLocality();
1026
		if(locality != null){
1027
			return locality.getText();
1028
		}
1029
		return null;
1030
	}
1031
	/**
1032
	 * convienience method for {@link #getLocality()}.{@link LanguageString#getLanguage() getLanguage()}
1033
	 * @return
1034
	 */
1035
	@Transient
1036
	public Language getLocalityLanguage(){
1037
		LanguageString locality = getLocality();
1038
		if(locality != null){
1039
			return locality.getLanguage();
1040
		}
1041
		return null;
1042
	}
1043
	
1044
	/**
1045
	 * Sets the locality string in the default language
1046
	 * @param locality
1047
	 */
1048
	public void setLocality(String locality){
1049
		Language language = Language.DEFAULT();
1050
		setLocality(locality, language);
1051
	}
1052
	public void setLocality(String locality, Language language){
1053
		LanguageString langString = LanguageString.NewInstance(locality, language);
1054
		setLocality(langString);
1055
	}
1056
	public void setLocality(LanguageString locality){
1057
		getGatheringEvent(true).setLocality(locality);
1058
	}
1059
	
1060
	/**
1061
	 * The gathering event will be used for the field object instead of the old gathering event.<BR>
1062
	 * <B>This method will override all gathering values (see below).</B>
1063
	 * @see #getAbsoluteElevation()
1064
	 * @see #getAbsoluteElevationError()
1065
	 * @see #getDistanceToGround()
1066
	 * @see #getDistanceToWaterSurface()
1067
	 * @see #getExactLocation()
1068
	 * @see #getGatheringEventDescription()
1069
	 * @see #getGatheringPeriod()
1070
	 * @see #getCollectingAreas()
1071
	 * @see #getCollectingMethod()
1072
	 * @see #getLocality()
1073
	 * @see #getCollector()	
1074
	 * @param gatheringEvent
1075
	 */
1076
	public void setGatheringEvent(GatheringEvent gatheringEvent) {
1077
		getFieldObservation(true).setGatheringEvent(gatheringEvent);
1078
	}
1079
	public boolean hasGatheringEvent(){
1080
		return (getGatheringEvent(false) != null);
1081
	}
1082
	
1083
	public GatheringEvent innerGatheringEvent() {
1084
		return getGatheringEvent(false);
1085
	}
1086
	
1087
	public GatheringEvent getGatheringEvent(boolean createIfNotExists) {
1088
		if (! hasFieldObservation() && ! createIfNotExists){
1089
			return null;
1090
		}
1091
		if (createIfNotExists && getFieldObservation(true).getGatheringEvent() == null ){
1092
			GatheringEvent gatheringEvent = GatheringEvent.NewInstance();
1093
			getFieldObservation(true).setGatheringEvent(gatheringEvent);
1094
		}
1095
		return getFieldObservation(true).getGatheringEvent();
1096
	}
1097
	
1098
// ****************** Field Object ************************************/
1099
	
1100
	/**
1101
	 * Returns true if a field observation exists (even if all attributes are empty or <code>null<code>.
1102
	 * @return
1103
	 */
1104
	public boolean hasFieldObject(){
1105
		return this.fieldObservation != null;
1106
	}
1107
	
1108
	//ecology
1109
	@Transient
1110
	public String getEcology(){
1111
		return getEcology(Language.DEFAULT());
1112
	}
1113
	public String getEcology(Language language){
1114
		LanguageString languageString = getEcologyAll().get(language);
1115
		return (languageString == null ? null : languageString.getText());
1116
	}
1117
//	public String getEcologyPreferred(List<Language> languages){
1118
//		LanguageString languageString = getEcologyAll().getPreferredLanguageString(languages);
1119
//		return languageString.getText();
1120
//	}
1121
	/**
1122
	 * Returns a copy of the multilanguage text holding the ecology data.
1123
	 * @see {@link TextData#getMultilanguageText()}
1124
	 * @return
1125
	 */
1126
	@Transient
1127
	public Map<Language, LanguageString> getEcologyAll(){
1128
		if (ecology == null){
1129
			try {
1130
				ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), false, false);
1131
			} catch (DerivedUnitFacadeNotSupportedException e) {
1132
				throw new IllegalStateException(notSupportMessage, e);
1133
			}
1134
			if (ecology == null){
1135
				return new HashMap<Language, LanguageString>();
1136
			}
1137
		}
1138
		return ecology.getMultilanguageText();
1139
	}
1140
	
1141
	public void setEcology(String ecology){
1142
		setEcology(ecology, null);
1143
	}
1144
	public void setEcology(String ecologyText, Language language){
1145
		if (language == null){
1146
			language = Language.DEFAULT();
1147
		}
1148
		if (ecology == null){
1149
			try {
1150
				ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), true, false);
1151
			} catch (DerivedUnitFacadeNotSupportedException e) {
1152
				throw new IllegalStateException(notSupportMessage, e);
1153
			}
1154
		}
1155
		if (ecologyText == null){
1156
			ecology.removeText(language);
1157
		}else{
1158
			ecology.putText(language, ecologyText);
1159
		}
1160
	}
1161
	public void removeEcology(Language language){
1162
		setEcology(null, language);
1163
	}
1164
	/**
1165
	 * Removes ecology for the default language
1166
	 */
1167
	public void removeEcology(){
1168
		setEcology(null, null);
1169
	}
1170
	public void removeEcologyAll(){
1171
		
1172
	}
1173

    
1174
	
1175
	//plant description
1176
	@Transient
1177
	public String getPlantDescription(){
1178
		return getPlantDescription(null);
1179
	}
1180
	public String getPlantDescription(Language language){
1181
		if (language == null){
1182
			language = Language.DEFAULT();
1183
		}
1184
		LanguageString languageString = getPlantDescriptionAll().get(language);
1185
		return (languageString == null ? null : languageString.getText());
1186
	}
1187
//	public String getPlantDescriptionPreferred(List<Language> languages){
1188
//		LanguageString languageString = getPlantDescriptionAll().getPreferredLanguageString(languages);
1189
//		return languageString.getText();
1190
//	}
1191
	/**
1192
	 * Returns a copy of the multilanguage text holding the description data.
1193
	 * @see {@link TextData#getMultilanguageText()}
1194
	 * @return
1195
	 */
1196
	@Transient
1197
	public Map<Language, LanguageString> getPlantDescriptionAll(){
1198
		if (plantDescription == null){
1199
			try {
1200
				plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), false, false);
1201
			} catch (DerivedUnitFacadeNotSupportedException e) {
1202
				throw new IllegalStateException(notSupportMessage, e);
1203
			}
1204
			if (plantDescription == null){
1205
				return new HashMap<Language, LanguageString>();
1206
			}
1207
		}
1208
		return plantDescription.getMultilanguageText();
1209
	}
1210
	public void setPlantDescription(String plantDescription){
1211
		setPlantDescription(plantDescription, null);
1212
	}
1213
	public void setPlantDescription(String plantDescriptionText, Language language){
1214
		if (language == null){
1215
			language = Language.DEFAULT();
1216
		}
1217
		if (plantDescription == null){
1218
			try {
1219
				plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), true, false);
1220
			} catch (DerivedUnitFacadeNotSupportedException e) {
1221
				throw new IllegalStateException(notSupportMessage, e);
1222
			}
1223
		}
1224
		if (plantDescriptionText == null){
1225
			plantDescription.removeText(language);
1226
		}else{
1227
			plantDescription.putText(language, plantDescriptionText);
1228
		}
1229
	}
1230
	public void removePlantDescription(Language language){
1231
		setPlantDescription(null, language);
1232
	}
1233
	
1234
	//field object definition
1235
	public void addFieldObjectDefinition(String text, Language language) {
1236
		getFieldObservation(true).addDefinition(text, language);
1237
	}
1238
	@Transient
1239
	public Map<Language, LanguageString> getFieldObjectDefinition() {
1240
		if (! hasFieldObservation()){
1241
			return new HashMap<Language, LanguageString>();
1242
		}else{
1243
			return getFieldObservation(true).getDefinition();
1244
		}
1245
	}
1246
	public String getFieldObjectDefinition(Language language) {
1247
		Map<Language, LanguageString> map = getFieldObjectDefinition();
1248
		LanguageString languageString = (map == null? null : map.get(language));
1249
		if (languageString != null){
1250
			return languageString.getText();
1251
		}else {
1252
			return null;
1253
		}
1254
	}
1255
	public void removeFieldObjectDefinition(Language lang) {
1256
		if (hasFieldObservation()){
1257
			getFieldObservation(true).removeDefinition(lang);
1258
		}
1259
	}
1260
	
1261

    
1262
	//media
1263
	public boolean addFieldObjectMedia(Media media)  {
1264
		try {
1265
			return addMedia(media, getFieldObservation(true));
1266
		} catch (DerivedUnitFacadeNotSupportedException e) {
1267
			throw new IllegalStateException(notSupportMessage, e);
1268
		}
1269
	}
1270
	/**
1271
	 * Returns true, if an image gallery for the field object exists.<BR>
1272
	 * Returns also <code>true</code> if the image gallery is empty. 
1273
	 * @return
1274
	 */
1275
	public boolean hasFieldObjectImageGallery(){
1276
		if (! hasFieldObject()){
1277
			return false;
1278
		}else{
1279
			return (getImageGallery(fieldObservation, false) != null);
1280
		}
1281
	}
1282
	
1283
	public void setFieldObjectImageGallery(SpecimenDescription imageGallery) throws DerivedUnitFacadeNotSupportedException{
1284
		SpecimenDescription existingGallery = getFieldObjectImageGallery(false);
1285
		
1286
		//test attached specimens contain this.derivedUnit
1287
		SpecimenOrObservationBase<?> facadeFieldObservation = innerFieldObservation();
1288
		testSpecimenInImageGallery(imageGallery, facadeFieldObservation);
1289
		
1290
		if (existingGallery != null){
1291
			if (existingGallery != imageGallery){
1292
				throw new DerivedUnitFacadeNotSupportedException("DerivedUnitFacade does not allow more than one image gallery");
1293
			}else{
1294
				//do nothing
1295
			}
1296
		}else {
1297
			TextData textData = testImageGallery(imageGallery);
1298
			this.fieldObjectMediaTextData = textData;
1299
		}
1300
	}
1301

    
1302
	
1303
	/**
1304
	 * Returns the field object image gallery. If no such image gallery exists and
1305
	 * createIfNotExists is true an new one is created. Otherwise null is returned.
1306
	 * @param createIfNotExists
1307
	 * @return
1308
	 */
1309
	public SpecimenDescription getFieldObjectImageGallery(boolean createIfNotExists){
1310
		TextData textData;
1311
		try {
1312
			textData = initializeFieldObjectTextDataWithSupportTest(Feature.IMAGE(), createIfNotExists, true);
1313
		} catch (DerivedUnitFacadeNotSupportedException e) {
1314
			throw new IllegalStateException(notSupportMessage, e);
1315
		}
1316
		if (textData != null){
1317
			return CdmBase.deproxy(textData.getInDescription(), SpecimenDescription.class);
1318
		}else{
1319
			return null;
1320
		}
1321
	}
1322
	/**
1323
	 * Returns the media for the field object.<BR>
1324
	 * @return
1325
	 */
1326
	@Transient
1327
	public List<Media> getFieldObjectMedia() {
1328
		try {
1329
			List<Media> result = getMedia(getFieldObservation(false), false);
1330
			return result == null ? new ArrayList<Media>() : result;
1331
		} catch (DerivedUnitFacadeNotSupportedException e) {
1332
			throw new IllegalStateException(notSupportMessage, e);
1333
		}
1334
	}
1335
	public boolean removeFieldObjectMedia(Media media) {
1336
		try {
1337
			return removeMedia(media, getFieldObservation(false));
1338
		} catch (DerivedUnitFacadeNotSupportedException e) {
1339
			throw new IllegalStateException(notSupportMessage, e);
1340
		}
1341
	}
1342

    
1343
	//field number
1344
	@Transient
1345
	public String getFieldNumber() {
1346
		if (! hasFieldObservation()){
1347
			return null;
1348
		}else{
1349
			return getFieldObservation(true).getFieldNumber();
1350
		}
1351
	}
1352
	public void setFieldNumber(String fieldNumber) {
1353
		getFieldObservation(true).setFieldNumber(fieldNumber);
1354
	}
1355

    
1356
	//primary collector
1357
	@Transient
1358
	public Person getPrimaryCollector() {
1359
		if (! hasFieldObservation()){
1360
			return null;
1361
		}else{
1362
			return getFieldObservation(true).getPrimaryCollector();
1363
		}
1364
	}
1365
	public void setPrimaryCollector(Person primaryCollector) {
1366
		getFieldObservation(true).setPrimaryCollector(primaryCollector);
1367
	}
1368
	
1369
	
1370
	
1371
	//field notes
1372
	@Transient
1373
	public String getFieldNotes() {
1374
		if (! hasFieldObservation()){
1375
			return null;
1376
		}else{
1377
			return getFieldObservation(true).getFieldNotes();
1378
		}
1379
	}
1380
	public void setFieldNotes(String fieldNotes) {
1381
		getFieldObservation(true).setFieldNotes(fieldNotes);
1382
	}
1383

    
1384

    
1385
	//individual counts
1386
	@Transient
1387
	public Integer getIndividualCount() {
1388
		return (hasFieldObservation()? getFieldObservation(true).getIndividualCount() : null );
1389
	}
1390
	public void setIndividualCount(Integer individualCount) {
1391
		getFieldObservation(true).setIndividualCount(individualCount);
1392
	}
1393

    
1394
	//life stage
1395
	@Transient
1396
	public Stage getLifeStage() {
1397
		return (hasFieldObservation()? getFieldObservation(true).getLifeStage() : null );
1398
	}
1399
	public void setLifeStage(Stage lifeStage) {
1400
		getFieldObservation(true).setLifeStage(lifeStage);
1401
	}
1402

    
1403
	//sex
1404
	@Transient
1405
	public Sex getSex() {
1406
		return (hasFieldObservation()? getFieldObservation(true).getSex() : null );
1407
	}
1408
	public void setSex(Sex sex) {
1409
		getFieldObservation(true).setSex(sex);
1410
	}
1411
	
1412
	
1413
	//field observation
1414
	public boolean hasFieldObservation(){
1415
		return (getFieldObservation(false) != null);
1416
	}
1417
	
1418
	/**
1419
	 * Returns the field observation as an object.
1420
	 * @return
1421
	 */
1422
	public FieldObservation innerFieldObservation(){
1423
		return getFieldObservation(false);
1424
	}
1425

    
1426
	/**
1427
	 * Returns the field observation as an object.
1428
	 * @return
1429
	 */
1430
	public FieldObservation getFieldObservation(boolean createIfNotExists){
1431
		if (fieldObservation == null && createIfNotExists){
1432
			fieldObservation = FieldObservation.NewInstance();
1433
			fieldObservation.addPropertyChangeListener(getNewEventPropagationListener());
1434
			DerivationEvent derivationEvent = getDerivationEvent(true);
1435
			derivationEvent.addOriginal(fieldObservation);
1436
		}
1437
		return this.fieldObservation;
1438
	}
1439

    
1440
	
1441

    
1442

    
1443
	
1444
//****************** Specimen **************************************************	
1445
	
1446
	//Definition
1447
	public void addDerivedUnitDefinition(String text, Language language) {
1448
		derivedUnit.addDefinition(text, language);
1449
	}
1450
	@Transient
1451
	public Map<Language, LanguageString> getDerivedUnitDefinitions(){
1452
		return this.derivedUnit.getDefinition();
1453
	}
1454
	public String getDerivedUnitDefinition(Language language) {
1455
		Map<Language,LanguageString> languageMap = derivedUnit.getDefinition();
1456
		LanguageString languageString = languageMap.get(language);
1457
		if (languageString != null){
1458
			return languageString.getText();
1459
		}else {
1460
			return null;
1461
		}
1462
	}
1463
	public void removeDerivedUnitDefinition(Language lang) {
1464
		derivedUnit.removeDefinition(lang);
1465
	}
1466

    
1467
	//Determination
1468
	public void addDetermination(DeterminationEvent determination) {
1469
		derivedUnit.addDetermination(determination);
1470
	}
1471
	@Transient
1472
	public Set<DeterminationEvent> getDeterminations() {
1473
		return derivedUnit.getDeterminations();
1474
	}
1475
	public void removeDetermination(DeterminationEvent determination) {
1476
		derivedUnit.removeDetermination(determination);
1477
	}
1478
	
1479
	//Media
1480
	public boolean addDerivedUnitMedia(Media media)  {
1481
		try {
1482
			return addMedia(media, derivedUnit);
1483
		} catch (DerivedUnitFacadeNotSupportedException e) {
1484
			throw new IllegalStateException(notSupportMessage, e);
1485
		}
1486
	}
1487
	/**
1488
	 * Returns true, if an image gallery exists for the specimen.<BR>
1489
	 * Returns also <code>true</code> if the image gallery is empty. 
1490
	 */
1491
	public boolean hasDerivedUnitImageGallery(){
1492
		return (getImageGallery(derivedUnit, false) != null);
1493
	}
1494
	
1495
	public SpecimenDescription getDerivedUnitImageGallery(boolean createIfNotExists){
1496
		TextData textData;
1497
		try {
1498
			textData = inititializeTextDataWithSupportTest(Feature.IMAGE(), derivedUnit, createIfNotExists, true);
1499
		} catch (DerivedUnitFacadeNotSupportedException e) {
1500
			throw new IllegalStateException(notSupportMessage, e);
1501
		}
1502
		if (textData != null){
1503
			return CdmBase.deproxy(textData.getInDescription(), SpecimenDescription.class);
1504
		}else{
1505
			return null;
1506
		}
1507
	}
1508
	public void setDerivedUnitImageGallery(SpecimenDescription imageGallery) throws DerivedUnitFacadeNotSupportedException{
1509
		SpecimenDescription existingGallery = getDerivedUnitImageGallery(false);
1510
		
1511
		//test attached specimens contain this.derivedUnit
1512
		SpecimenOrObservationBase facadeDerivedUnit = innerDerivedUnit();
1513
		testSpecimenInImageGallery(imageGallery, facadeDerivedUnit);
1514
		
1515
		if (existingGallery != null){
1516
			if (existingGallery != imageGallery){
1517
				throw new DerivedUnitFacadeNotSupportedException("DerivedUnitFacade does not allow more than one image gallery");
1518
			}else{
1519
				//do nothing
1520
			}
1521
		}else {
1522
			TextData textData = testImageGallery(imageGallery);
1523
			this.derivedUnitMediaTextData = textData;
1524
		}
1525
	}
1526

    
1527
	/**
1528
	 * @param imageGallery
1529
	 * @throws DerivedUnitFacadeNotSupportedException
1530
	 */
1531
	private void testSpecimenInImageGallery(SpecimenDescription imageGallery, SpecimenOrObservationBase specimen) throws DerivedUnitFacadeNotSupportedException {
1532
		Set<SpecimenOrObservationBase> imageGallerySpecimens = imageGallery.getDescribedSpecimenOrObservations();
1533
		if (imageGallerySpecimens.size() < 1){
1534
			throw new DerivedUnitFacadeNotSupportedException("Image Gallery has no Specimen attached. Please attache according specimen or field observation.");
1535
		}
1536
		if (! imageGallerySpecimens.contains(specimen)){
1537
			throw new DerivedUnitFacadeNotSupportedException("Image Gallery has not the facade's field object attached. Please add field object first to image gallery specimenOrObservation list.");
1538
		}
1539
	}
1540
	
1541
	/**
1542
	 * Returns the media for the specimen.<BR>
1543
	 * @return
1544
	 */
1545
	@Transient
1546
	public List<Media> getDerivedUnitMedia() {
1547
		try {
1548
			List<Media> result = getMedia(derivedUnit, false);
1549
			return result == null ? new ArrayList<Media>() : result;
1550
		} catch (DerivedUnitFacadeNotSupportedException e) {
1551
			throw new IllegalStateException(notSupportMessage, e);
1552
		}
1553
	}
1554
	public boolean removeDerivedUnitMedia(Media media) {
1555
		try {
1556
			return removeMedia(media, derivedUnit);
1557
		} catch (DerivedUnitFacadeNotSupportedException e) {
1558
			throw new IllegalStateException(notSupportMessage, e);
1559
		}
1560
	}
1561

    
1562
	
1563
	//Accession Number
1564
	@Transient
1565
	public String getAccessionNumber() {
1566
		return derivedUnit.getAccessionNumber();
1567
	}
1568
	public void setAccessionNumber(String accessionNumber) {
1569
		derivedUnit.setAccessionNumber(accessionNumber);
1570
	}
1571

    
1572
	@Transient
1573
	public String getCatalogNumber() {
1574
		return derivedUnit.getCatalogNumber();
1575
	}
1576
	public void setCatalogNumber(String catalogNumber) {
1577
		derivedUnit.setCatalogNumber(catalogNumber);
1578
	}
1579

    
1580
	@Transient
1581
	public String getBarcode() {
1582
		return derivedUnit.getBarcode();
1583
	}
1584
	public void setBarcode(String barcode) {
1585
		derivedUnit.setBarcode(barcode);
1586
	}
1587

    
1588
	
1589
	//Preservation Method
1590
	
1591
	/**
1592
	 * Only supported by specimen and fossils
1593
	 * @see #DerivedUnitType
1594
	 * @return
1595
	 */
1596
	@Transient
1597
	public PreservationMethod getPreservationMethod() throws MethodNotSupportedByDerivedUnitTypeException {
1598
		if (derivedUnit.isInstanceOf(Specimen.class)){
1599
			return CdmBase.deproxy(derivedUnit, Specimen.class).getPreservation();
1600
		}else{
1601
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1602
				throw new MethodNotSupportedByDerivedUnitTypeException("A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
1603
			}else{
1604
				return null;
1605
			}
1606
		}
1607
	}
1608
	/**
1609
	 * Only supported by specimen and fossils
1610
	 * @see #DerivedUnitType
1611
	 * @return
1612
	 */
1613
	public void setPreservationMethod(PreservationMethod preservation)throws MethodNotSupportedByDerivedUnitTypeException  {
1614
		if (derivedUnit.isInstanceOf(Specimen.class)){
1615
			CdmBase.deproxy(derivedUnit, Specimen.class).setPreservation(preservation);
1616
		}else{
1617
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1618
				throw new MethodNotSupportedByDerivedUnitTypeException("A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
1619
			}else{
1620
				return;
1621
			}
1622
		}
1623
	}
1624

    
1625
	//Stored under name
1626
	@Transient
1627
	public TaxonNameBase getStoredUnder() {
1628
		return derivedUnit.getStoredUnder();
1629
	}
1630
	public void setStoredUnder(TaxonNameBase storedUnder) {
1631
		derivedUnit.setStoredUnder(storedUnder);
1632
	}
1633

    
1634
	//colletors number
1635
	@Transient
1636
	public String getCollectorsNumber() {
1637
		return derivedUnit.getCollectorsNumber();
1638
	}
1639
	public void setCollectorsNumber(String collectorsNumber) {
1640
		this.derivedUnit.setCollectorsNumber(collectorsNumber);
1641
	}
1642

    
1643
	//title cache
1644
	public String getTitleCache() {
1645
		if (! derivedUnit.isProtectedTitleCache()){
1646
			//always compute title cache anew as long as there are no property change listeners on 
1647
			//field observation, gathering event etc 
1648
			derivedUnit.setTitleCache(null, false);
1649
		}
1650
		return this.derivedUnit.getTitleCache();
1651
	}
1652
	public boolean isProtectedTitleCache(){
1653
		return derivedUnit.isProtectedTitleCache();
1654
	}
1655
	public void setTitleCache(String titleCache, boolean isProtected) {
1656
		this.derivedUnit.setTitleCache(titleCache, isProtected);
1657
	}
1658

    
1659

    
1660
	/**
1661
	 * Returns the derived unit itself.
1662
	 * @return the derived unit
1663
	 */
1664
	public DerivedUnitBase innerDerivedUnit() {
1665
		return this.derivedUnit;
1666
	}
1667
	
1668
	private boolean hasDerivationEvent(){
1669
		return getDerivationEvent() == null ? false : true;
1670
	}
1671
	private DerivationEvent getDerivationEvent(){
1672
		return getDerivationEvent(false);
1673
	}
1674
	private DerivationEvent getDerivationEvent(boolean createIfNotExists){
1675
		DerivationEvent result = derivedUnit.getDerivedFrom();
1676
		if (result == null){
1677
			result = DerivationEvent.NewInstance();
1678
			derivedUnit.setDerivedFrom(result);
1679
		}
1680
		return result;
1681
	}
1682
	@Transient
1683
	public String getExsiccatum() throws MethodNotSupportedByDerivedUnitTypeException {
1684
		if (derivedUnit.isInstanceOf(Specimen.class)){
1685
			return CdmBase.deproxy(derivedUnit, Specimen.class).getExsiccatum();
1686
		}else{
1687
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1688
				throw new MethodNotSupportedByDerivedUnitTypeException("An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
1689
			}else{
1690
				return null;
1691
			}
1692
		}
1693
	}
1694
	
1695
	public void setExsiccatum(String exsiccatum) throws Exception{
1696
		if (derivedUnit.isInstanceOf(Specimen.class)){
1697
			CdmBase.deproxy(derivedUnit, Specimen.class).setExsiccatum(exsiccatum);
1698
		}else{
1699
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1700
				throw new MethodNotSupportedByDerivedUnitTypeException("An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
1701
			}else{
1702
				return;
1703
			}
1704
		}
1705
	}
1706
	
1707
	
1708
	// **** sources **/
1709
	public void addSource(IdentifiableSource source){
1710
		this.derivedUnit.addSource(source);
1711
	}
1712
	/**
1713
	 * Creates an orignal source, adds it to the specimen and returns it.
1714
	 * @param reference
1715
	 * @param microReference
1716
	 * @param originalNameString
1717
	 * @return
1718
	 */
1719
	public IdentifiableSource addSource(Reference reference, String microReference, String originalNameString){
1720
		IdentifiableSource source = IdentifiableSource.NewInstance(reference, microReference);
1721
		source.setOriginalNameString(originalNameString);
1722
		derivedUnit.addSource(source);
1723
		return source;
1724
	}
1725

    
1726
	@Transient
1727
	public Set<IdentifiableSource> getSources(){
1728
		return derivedUnit.getSources();
1729
	}
1730

    
1731
	public void removeSource(IdentifiableSource source){
1732
		this.derivedUnit.removeSource(source);
1733
	}
1734
	
1735
	
1736
	/**
1737
	 * @return the collection
1738
	 */
1739
	@Transient
1740
	public Collection getCollection() {
1741
		return derivedUnit.getCollection();
1742
	}
1743

    
1744

    
1745
	/**
1746
	 * @param collection the collection to set
1747
	 */
1748
	public void setCollection(Collection collection) {
1749
		derivedUnit.setCollection(collection);
1750
	}
1751
	
1752
	//annotation	
1753
	public void addAnnotation(Annotation annotation){
1754
		this.derivedUnit.addAnnotation(annotation);
1755
	}
1756

    
1757
	@Transient
1758
	public void getAnnotations(){
1759
		this.derivedUnit.getAnnotations();
1760
	}
1761
	
1762
	public void removeAnnotation(Annotation annotation){
1763
		this.derivedUnit.removeAnnotation(annotation);
1764
	}
1765
	
1766
	
1767
// ******************************* Events *********************************************
1768
	
1769
	/**
1770
	 * @return
1771
	 */
1772
	private PropertyChangeListener getNewEventPropagationListener() {
1773
		PropertyChangeListener listener = new PropertyChangeListener(){
1774
			@Override
1775
			public void propertyChange(PropertyChangeEvent event) {
1776
				derivedUnit.firePropertyChange(event);
1777
			}
1778
			
1779
		};
1780
		return listener;
1781
	}
1782

    
1783
		
1784
	
1785

    
1786
//**************** Other Collections ***************************************************	
1787
	
1788
	/**
1789
	 * Creates a duplicate specimen which derives from the same derivation event
1790
	 * as the facade specimen and adds collection data to it (all data available in
1791
	 * DerivedUnitBase and Specimen. Data from SpecimenOrObservationBase and above
1792
	 * are not yet shared at the moment. 
1793
	 * @param collection
1794
	 * @param catalogNumber
1795
	 * @param accessionNumber
1796
	 * @param collectorsNumber
1797
	 * @param storedUnder
1798
	 * @param preservation
1799
	 * @return
1800
	 */
1801
	public Specimen addDuplicate(Collection collection, String catalogNumber, String accessionNumber, 
1802
				String collectorsNumber, TaxonNameBase storedUnder, PreservationMethod preservation){
1803
		Specimen duplicate = Specimen.NewInstance();
1804
		duplicate.setDerivedFrom(getDerivationEvent(true));
1805
		duplicate.setCollection(collection);
1806
		duplicate.setCatalogNumber(catalogNumber);
1807
		duplicate.setAccessionNumber(accessionNumber);
1808
		duplicate.setCollectorsNumber(collectorsNumber);
1809
		duplicate.setStoredUnder(storedUnder);
1810
		duplicate.setPreservation(preservation);
1811
		return duplicate;
1812
	}
1813
	
1814
	public void addDuplicate(DerivedUnitBase duplicateSpecimen){
1815
		//TODO check derivedUnitType
1816
		getDerivationEvent(true).addDerivative(duplicateSpecimen);  
1817
	}
1818
	
1819
	@Transient
1820
	public Set<Specimen> getDuplicates(){
1821
		Set<Specimen> result = new HashSet<Specimen>();
1822
		if (hasDerivationEvent()){
1823
			for (DerivedUnitBase derivedUnit: getDerivationEvent(true).getDerivatives()){
1824
				if (derivedUnit.isInstanceOf(Specimen.class) && ! derivedUnit.equals(this.derivedUnit)){
1825
					result.add(CdmBase.deproxy(derivedUnit, Specimen.class));
1826
				}
1827
			}
1828
		}
1829
		return result;
1830
	}
1831
	public void removeDuplicate(Specimen duplicateSpecimen){
1832
		if (hasDerivationEvent()){
1833
			getDerivationEvent(true).removeDerivative(duplicateSpecimen);
1834
		}
1835
	}
1836
	
1837
	
1838
	
1839
	
1840
}
(1-1/5)