Project

General

Profile

Download (34.1 KB) Statistics
| Branch: | Tag: | Revision:
1
package eu.etaxonomy.cdm.api.service;
2

    
3
import java.util.ArrayList;
4
import java.util.Arrays;
5
import java.util.Collection;
6
import java.util.HashMap;
7
import java.util.HashSet;
8
import java.util.List;
9
import java.util.Map;
10
import java.util.Optional;
11
import java.util.Set;
12
import java.util.UUID;
13

    
14
import org.apache.log4j.Logger;
15
import org.springframework.beans.factory.annotation.Autowired;
16
import org.springframework.stereotype.Service;
17
import org.springframework.transaction.annotation.Transactional;
18

    
19
import eu.etaxonomy.cdm.api.service.UpdateResult.Status;
20
import eu.etaxonomy.cdm.api.service.dto.RowWrapperDTO;
21
import eu.etaxonomy.cdm.api.service.dto.SpecimenRowWrapperDTO;
22
import eu.etaxonomy.cdm.api.service.dto.TaxonRowWrapperDTO;
23
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
24
import eu.etaxonomy.cdm.common.monitor.IRemotingProgressMonitor;
25
import eu.etaxonomy.cdm.filter.TaxonNodeFilter;
26
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
27
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
28
import eu.etaxonomy.cdm.model.common.Language;
29
import eu.etaxonomy.cdm.model.common.Marker;
30
import eu.etaxonomy.cdm.model.common.MarkerType;
31
import eu.etaxonomy.cdm.model.common.OriginalSourceType;
32
import eu.etaxonomy.cdm.model.description.CategoricalData;
33
import eu.etaxonomy.cdm.model.description.Character;
34
import eu.etaxonomy.cdm.model.description.DescriptionBase;
35
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
36
import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
37
import eu.etaxonomy.cdm.model.description.DescriptiveSystemRole;
38
import eu.etaxonomy.cdm.model.description.Feature;
39
import eu.etaxonomy.cdm.model.description.QuantitativeData;
40
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
41
import eu.etaxonomy.cdm.model.description.StateData;
42
import eu.etaxonomy.cdm.model.description.StatisticalMeasure;
43
import eu.etaxonomy.cdm.model.description.StatisticalMeasurementValue;
44
import eu.etaxonomy.cdm.model.description.TaxonDescription;
45
import eu.etaxonomy.cdm.model.description.TextData;
46
import eu.etaxonomy.cdm.model.location.NamedArea;
47
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
48
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
49
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
50
import eu.etaxonomy.cdm.model.taxon.Classification;
51
import eu.etaxonomy.cdm.model.taxon.Taxon;
52
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
53
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
54
import eu.etaxonomy.cdm.persistence.dao.description.IDescriptiveDataSetDao;
55
import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
56
import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
57
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
58
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
59

    
60
@Service
61
@Transactional(readOnly=true)
62
public class DescriptiveDataSetService
63
        extends IdentifiableServiceBase<DescriptiveDataSet, IDescriptiveDataSetDao>
64
        implements IDescriptiveDataSetService {
65

    
66
    private static Logger logger = Logger.getLogger(DescriptiveDataSetService.class);
67

    
68
    @Autowired
69
    private IOccurrenceService occurrenceService;
70

    
71
    @Autowired
72
    private ITaxonService taxonService;
73

    
74
    @Autowired
75
    private IDescriptionService descriptionService;
76

    
77
    @Autowired
78
    private ITaxonNodeService taxonNodeService;
79

    
80
    @Autowired
81
    private IProgressMonitorService progressMonitorService;
82

    
83
    //FIXME: Use actual MarkerType for default descriptions when implemented #7957
84
    private MarkerType MARKER_DEFAULT = MarkerType.TO_BE_CHECKED();
85

    
86
	@Override
87
	@Autowired
88
	protected void setDao(IDescriptiveDataSetDao dao) {
89
		this.dao = dao;
90
	}
91

    
92
	@Override
93
    public Map<DescriptionBase, Set<DescriptionElementBase>> getDescriptionElements(DescriptiveDataSet descriptiveDataSet, Set<Feature> features, Integer pageSize,	Integer pageNumber,
94
			List<String> propertyPaths) {
95
		return dao.getDescriptionElements(descriptiveDataSet, features, pageSize, pageNumber, propertyPaths);
96
	}
97

    
98
	@Override
99
	public <T extends DescriptionElementBase> Map<UuidAndTitleCache, Map<UUID, Set<T>>> getTaxonFeatureDescriptionElementMap(
100
			Class<T> clazz, UUID descriptiveDataSetUuid, DescriptiveSystemRole role) {
101
		return dao.getTaxonFeatureDescriptionElementMap(clazz, descriptiveDataSetUuid, role);
102
	}
103

    
104
	@Override
105
    public List<UuidAndTitleCache<DescriptiveDataSet>> getDescriptiveDataSetUuidAndTitleCache(Integer limitOfInitialElements, String pattern) {
106
        return dao.getDescriptiveDataSetUuidAndTitleCache( limitOfInitialElements, pattern);
107
    }
108

    
109
	@Override
110
	public ArrayList<RowWrapperDTO> getRowWrapper(DescriptiveDataSet descriptiveDataSet, IProgressMonitor monitor) {
111
	    monitor.beginTask("Load row wrapper", descriptiveDataSet.getDescriptions().size());
112
	    ArrayList<RowWrapperDTO> wrappers = new ArrayList<>();
113
	    Set<DescriptionBase> descriptions = descriptiveDataSet.getDescriptions();
114
	    for (DescriptionBase description : descriptions) {
115
            if(monitor.isCanceled()){
116
                return new ArrayList<>();
117
            }
118
            RowWrapperDTO rowWrapper = null;
119
            if(HibernateProxyHelper.isInstanceOf(description, TaxonDescription.class)){
120
                rowWrapper = createTaxonRowWrapper(description.getUuid(), descriptiveDataSet.getUuid());
121
            }
122
            else if (HibernateProxyHelper.isInstanceOf(description, SpecimenDescription.class)){
123
                rowWrapper = createSpecimenRowWrapper(HibernateProxyHelper.deproxy(description, SpecimenDescription.class), descriptiveDataSet);
124
            }
125
            if(rowWrapper!=null){
126
                wrappers.add(rowWrapper);
127
            }
128
            monitor.worked(1);
129
        }
130
	    return wrappers;
131
	}
132

    
133
    @Override
134
    public Collection<SpecimenNodeWrapper> loadSpecimens(DescriptiveDataSet descriptiveDataSet){
135
        List<UUID> filteredNodes = findFilteredTaxonNodes(descriptiveDataSet);
136
        return occurrenceService.listUuidAndTitleCacheByAssociatedTaxon(filteredNodes, null, null);
137
    }
138

    
139
    @Override
140
    public List<UUID> findFilteredTaxonNodes(DescriptiveDataSet descriptiveDataSet){
141
        TaxonNodeFilter filter = TaxonNodeFilter.NewRankInstance(descriptiveDataSet.getMinRank(), descriptiveDataSet.getMaxRank());
142
        descriptiveDataSet.getGeoFilter().forEach(area -> filter.orArea(area.getUuid()));
143
        descriptiveDataSet.getTaxonSubtreeFilter().forEach(node -> filter.orSubtree(node));
144
        filter.setIncludeUnpublished(true);
145

    
146
        return taxonNodeService.uuidList(filter);
147
    }
148

    
149
    @Override
150
    public List<TaxonNode> loadFilteredTaxonNodes(DescriptiveDataSet descriptiveDataSet, List<String> propertyPaths){
151
        return taxonNodeService.load(findFilteredTaxonNodes(descriptiveDataSet), propertyPaths);
152
    }
153

    
154
    private TaxonNode findTaxonNodeForDescription(TaxonNode taxonNode, SpecimenOrObservationBase specimen){
155
        Collection<SpecimenNodeWrapper> nodeWrapper = occurrenceService.listUuidAndTitleCacheByAssociatedTaxon(Arrays.asList(taxonNode.getUuid()), null, null);
156
        for (SpecimenNodeWrapper specimenNodeWrapper : nodeWrapper) {
157
            if(specimenNodeWrapper.getUuidAndTitleCache().getId().equals(specimen.getId())){
158
                return taxonNode;
159
            }
160
        }
161
        return null;
162
    }
163

    
164
    @Override
165
    public TaxonRowWrapperDTO createTaxonRowWrapper(UUID taxonDescriptionUuid, UUID descriptiveDataSetUuid) {
166
        TaxonNode taxonNode = null;
167
        Classification classification = null;
168
        TaxonDescription description = (TaxonDescription) descriptionService.load(taxonDescriptionUuid,
169
                Arrays.asList("taxon", "descriptionElements", "descriptionElements.feature"));
170
        DescriptiveDataSet descriptiveDataSet = dao.load(descriptiveDataSetUuid, null);
171
        Optional<TaxonNode> first = descriptiveDataSet.getTaxonSubtreeFilter().stream()
172
                .filter(node->node.getClassification()!=null).findFirst();
173
        Optional<Classification> classificationOptional = first.map(node->node.getClassification());
174
        if(classificationOptional.isPresent()){
175
            classification = classificationOptional.get();
176
            Taxon taxon = (Taxon) taxonService.load(description.getTaxon().getId(), Arrays.asList("taxonNodes", "taxonNodes.classification"));
177
            taxonNode = taxon.getTaxonNode(classification);
178
        }
179
        return new TaxonRowWrapperDTO(description, new TaxonNodeDto(taxonNode));
180
    }
181

    
182
    @Override
183
    public SpecimenRowWrapperDTO createSpecimenRowWrapper(SpecimenDescription description, DescriptiveDataSet descriptiveDataSet){
184
	    SpecimenOrObservationBase specimen = description.getDescribedSpecimenOrObservation();
185
	    TaxonNode taxonNode = null;
186
        FieldUnit fieldUnit = null;
187
        String identifier = null;
188
        NamedArea country = null;
189
        //supplemental information
190
        //get taxon node
191
        Set<TaxonNode> taxonSubtreeFilter = descriptiveDataSet.getTaxonSubtreeFilter();
192
        for (TaxonNode node : taxonSubtreeFilter) {
193
            //check for node
194
            List<String> taxonNodePropertyPath = Arrays.asList("taxon", "taxon.descriptions", "taxon.descriptions.markers");
195
            node = taxonNodeService.load(node.getId(), taxonNodePropertyPath);
196
            taxonNode = findTaxonNodeForDescription(node, specimen);
197
            if(taxonNode!=null){
198
                break;
199
            }
200
            else{
201
                //check for child nodes
202
                List<TaxonNode> allChildren = taxonNodeService.loadChildNodesOfTaxonNode(node, taxonNodePropertyPath, true, true, null);
203
                for (TaxonNode child : allChildren) {
204
                    taxonNode = findTaxonNodeForDescription(child, specimen);
205
                    if(taxonNode!=null){
206
                        break;
207
                    }
208
                }
209
            }
210
        }
211
        if(taxonNode==null){
212
            return null;
213
        }
214
        //taxon node was found
215

    
216
        //get field unit
217
        Collection<FieldUnit> fieldUnits = occurrenceService.findFieldUnits(specimen.getUuid(),
218
                Arrays.asList(new String[]{
219
                        "gatheringEvent",
220
                        "gatheringEvent.country"
221
                }));
222
        if(fieldUnits.size()!=1){
223
            logger.error("More than one or no field unit found for specimen"); //$NON-NLS-1$
224
            return null;
225
        }
226
        else{
227
            fieldUnit = fieldUnits.iterator().next();
228
        }
229
        //get identifier
230
        if(HibernateProxyHelper.isInstanceOf(specimen, DerivedUnit.class)){
231
            identifier = occurrenceService.getMostSignificantIdentifier(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
232
        }
233
        //get country
234
        if(fieldUnit!=null && fieldUnit.getGatheringEvent()!=null){
235
            country = fieldUnit.getGatheringEvent().getCountry();
236
        }
237
        //get default taxon description
238
        TaxonDescription defaultTaxonDescription = findTaxonDescriptionByMarkerType(descriptiveDataSet.getUuid(),
239
                taxonNode.getUuid(), MARKER_DEFAULT);
240
        TaxonRowWrapperDTO taxonRowWrapper = defaultTaxonDescription != null
241
                ? createTaxonRowWrapper(defaultTaxonDescription.getUuid(), descriptiveDataSet.getUuid()) : null;
242
        return new SpecimenRowWrapperDTO(description, new TaxonNodeDto(taxonNode), fieldUnit, identifier, country);
243
	}
244

    
245
    @Override
246
    @Transactional(readOnly = false)
247
    public void updateTitleCache(Class<? extends DescriptiveDataSet> clazz, Integer stepSize,
248
            IIdentifiableEntityCacheStrategy<DescriptiveDataSet> cacheStrategy, IProgressMonitor monitor) {
249
        if (clazz == null) {
250
            clazz = DescriptiveDataSet.class;
251
        }
252
        super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
253
    }
254

    
255
    private TaxonDescription findTaxonDescriptionByMarkerType(DescriptiveDataSet dataSet, Taxon taxon, MarkerType markerType){
256
        Set<DescriptionBase> dataSetDescriptions = dataSet.getDescriptions();
257
        //filter by DEFAULT descriptions
258
        Optional<TaxonDescription> first = taxon.getDescriptions().stream()
259
                .filter(desc -> desc.getMarkers().stream().anyMatch(marker -> marker.getMarkerType().equals(markerType)))
260
                .filter(defaultDescription->dataSetDescriptions.contains(defaultDescription))
261
                .findFirst();
262
        if(first.isPresent()){
263
            return HibernateProxyHelper.deproxy(descriptionService.load(first.get().getUuid(),
264
                  Arrays.asList("taxon", "descriptionElements", "descriptionElements.feature")), TaxonDescription.class);
265
        }
266
        return null;
267
    }
268

    
269
    @Override
270
    public TaxonDescription findTaxonDescriptionByMarkerType(UUID dataSetUuid, UUID taxonNodeUuid, MarkerType markerType){
271
        DescriptiveDataSet dataSet = load(dataSetUuid);
272
        TaxonNode taxonNode = taxonNodeService.load(taxonNodeUuid);
273
        return findTaxonDescriptionByMarkerType(dataSet, taxonNode.getTaxon(), markerType);
274
    }
275

    
276
    @Override
277
    @Transactional(readOnly=false)
278
    public UpdateResult aggregateTaxonDescription(UUID taxonNodeUuid, UUID descriptiveDataSetUuid,
279
            IRemotingProgressMonitor monitor){
280
        UpdateResult result = new UpdateResult();
281

    
282
        TaxonNode node = taxonNodeService.load(taxonNodeUuid);
283
        Taxon taxon = HibernateProxyHelper.deproxy(taxonService.load(node.getTaxon().getUuid()), Taxon.class);
284
        result.setCdmEntity(taxon);
285

    
286
        //get all "computed" descriptions from all sub nodes
287
        List<TaxonNode> childNodes = taxonNodeService.listChildrenOf(node, null, null, true, false, null);
288
        List<TaxonDescription> computedDescriptions = new ArrayList<>();
289

    
290
        childNodes.stream().map(childNode -> childNode.getTaxon())
291
                .forEach(childTaxon -> childTaxon.getDescriptions().stream()
292
                        // filter out non-computed descriptions
293
                        .filter(description -> description.getMarkers().stream()
294
                                .anyMatch(marker -> marker.getMarkerType().equals(MarkerType.COMPUTED())))
295
                        // add them to the list
296
                        .forEach(computedDescription -> computedDescriptions.add(computedDescription)));
297

    
298
        UpdateResult aggregateDescription = aggregateDescription(taxon, computedDescriptions,
299
                "[Taxon Descriptions]"+taxon.getTitleCache(), descriptiveDataSetUuid);
300
        result.includeResult(aggregateDescription);
301
        result.setCdmEntity(aggregateDescription.getCdmEntity());
302
        aggregateDescription.setCdmEntity(null);
303
        return result;
304
    }
305

    
306
    @Override
307
    @Transactional(readOnly=false)
308
    public UpdateResult aggregateDescription(UUID taxonUuid, List<UUID> descriptionUuids, String descriptionTitle
309
            , UUID descriptiveDataSetUuid) {
310
        UpdateResult result = new UpdateResult();
311

    
312
        TaxonBase taxonBase = taxonService.load(taxonUuid);
313
        if(!(taxonBase instanceof Taxon)){
314
            result.addException(new ClassCastException("The given taxonUUID does not belong to a taxon"));
315
            result.setError();
316
            return result;
317
        }
318
        Taxon taxon = (Taxon)taxonBase;
319

    
320
        List<DescriptionBase> descriptions = descriptionService.load(descriptionUuids, null);
321

    
322
        UpdateResult aggregateDescriptionResult = aggregateDescription(taxon, descriptions, descriptionTitle, descriptiveDataSetUuid);
323
        result.setCdmEntity(aggregateDescriptionResult.getCdmEntity());
324
        aggregateDescriptionResult.setCdmEntity(null);
325
        result.includeResult(aggregateDescriptionResult);
326
        return result;
327
    }
328

    
329
    @SuppressWarnings("unchecked")
330
    private UpdateResult aggregateDescription(Taxon taxon, List<? extends DescriptionBase> descriptions, String descriptionTitle
331
            , UUID descriptiveDataSetUuid) {
332
        UpdateResult result = new UpdateResult();
333
        Map<Character, List<DescriptionElementBase>> featureToElementMap = new HashMap<>();
334

    
335
        DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
336
        if(dataSet==null){
337
            result.addException(new IllegalArgumentException("Could not find data set for uuid "+descriptiveDataSetUuid));
338
            result.setAbort();
339
            return result;
340
        }
341

    
342
        //extract all character description elements
343
        descriptions.forEach(description->{
344
            description.getElements()
345
            .stream()
346
            //filter out elements that do not have a Characters as Feature
347
            .filter(element->HibernateProxyHelper.isInstanceOf(((DescriptionElementBase)element).getFeature(), Character.class))
348
            .forEach(ele->{
349
                DescriptionElementBase descriptionElement = (DescriptionElementBase)ele;
350
                List<DescriptionElementBase> list = featureToElementMap.get(descriptionElement.getFeature());
351
                if(list==null){
352
                    list = new ArrayList<>();
353
                }
354
                list.add(descriptionElement);
355
                featureToElementMap.put(HibernateProxyHelper.deproxy(descriptionElement.getFeature(), Character.class), list);
356
            });
357
        });
358

    
359
        // delete existing aggregation description, if present
360
        TaxonDescription aggregation = findTaxonDescriptionByMarkerType(dataSet, taxon, MarkerType.COMPUTED());
361
        removeDescription(aggregation.getUuid(), descriptiveDataSetUuid);
362

    
363
        // create new aggregation
364
        TaxonDescription description = TaxonDescription.NewInstance(taxon);
365
        description.setTitleCache("[Aggregation] "+descriptionTitle, true);
366
        description.addMarker(Marker.NewInstance(MarkerType.COMPUTED(), true));
367
        IdentifiableSource source = IdentifiableSource.NewInstance(OriginalSourceType.Aggregation);
368
        description.addSource(source);
369
        description.addDescriptiveDataSet(dataSet);
370

    
371
        featureToElementMap.forEach((feature, elements)->{
372
            //aggregate categorical data
373
            if(feature.isSupportsCategoricalData()){
374
                CategoricalData aggregate = CategoricalData.NewInstance(feature);
375
                elements.stream()
376
                .filter(element->element instanceof CategoricalData)
377
                .forEach(categoricalData->((CategoricalData)categoricalData).getStateData()
378
                        .forEach(stateData->aggregate.addStateData((StateData) stateData.clone())));
379
                description.addElement(aggregate);
380
            }
381
            //aggregate quantitative data
382
            else if(feature.isSupportsQuantitativeData()){
383
                QuantitativeData aggregate = QuantitativeData.NewInstance(feature);
384
                elements.stream()
385
                .filter(element->element instanceof QuantitativeData)
386
                .forEach(categoricalData->((QuantitativeData)categoricalData).getStatisticalValues()
387
                        .forEach(statisticalValue->aggregate.addStatisticalValue((StatisticalMeasurementValue) statisticalValue.clone())));
388
                description.addElement(aggregate);
389
            }
390
        });
391
        result.addUpdatedObject(taxon);
392
        result.setCdmEntity(description);
393
        return result;
394
    }
395

    
396
    @Override
397
    @Transactional(readOnly=false)
398
    public DeleteResult removeDescription(UUID descriptionUuid, UUID descriptiveDataSetUuid) {
399
        DeleteResult result = new DeleteResult();
400
        DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
401
        DescriptionBase descriptionBase = descriptionService.load(descriptionUuid);
402
        if(dataSet==null || descriptionBase==null){
403
            result.setError();
404
        }
405
        else{
406
            boolean success = dataSet.removeDescription(descriptionBase);
407
            result.addDeletedObject(descriptionBase);
408
            result.addUpdatedObject(dataSet);
409
            result.setStatus(success?Status.OK:Status.ERROR);
410
        }
411
        return result;
412
    }
413

    
414
    @Override
415
    @Transactional(readOnly=false)
416
    public TaxonRowWrapperDTO createTaxonDescription(UUID dataSetUuid, UUID taxonNodeUuid, MarkerType markerType, boolean markerFlag){
417
        DescriptiveDataSet dataSet = load(dataSetUuid);
418
        TaxonNode taxonNode = taxonNodeService.load(taxonNodeUuid, Arrays.asList("taxon"));
419
        TaxonDescription newTaxonDescription = TaxonDescription.NewInstance(taxonNode.getTaxon());
420
        String tag = "";
421
        if(markerFlag){
422
            //FIXME: Add specific MarkerTypes to enum (see #7957)
423
            if(markerType.equals(MARKER_DEFAULT)){
424
                tag = "[Default]";
425
            }
426
            else if(markerType.equals(MarkerType.IN_BIBLIOGRAPHY())){
427
                tag = "[Literature]";
428
            }
429
        }
430
        newTaxonDescription.setTitleCache(tag+" "+dataSet.getLabel()+": "+newTaxonDescription.generateTitle(), true); //$NON-NLS-2$
431
        if(markerType!=null){
432
            newTaxonDescription.addMarker(Marker.NewInstance(markerType, markerFlag));
433
        }
434
        dataSet.getDescriptiveSystem().getDistinctFeatures().forEach(wsFeature->{
435
            if(wsFeature.isSupportsCategoricalData()){
436
                newTaxonDescription.addElement(CategoricalData.NewInstance(wsFeature));
437
            }
438
            else if(wsFeature.isSupportsQuantitativeData()){
439
                newTaxonDescription.addElement(QuantitativeData.NewInstance(wsFeature));
440
            }
441
        });
442
        dataSet.addDescription(newTaxonDescription);
443

    
444
        return createTaxonRowWrapper(newTaxonDescription.getUuid(), dataSet.getUuid());
445
    }
446

    
447
    @Override
448
    @Transactional(readOnly=false)
449
    public SpecimenDescription findSpecimenDescription(UUID descriptiveDataSetUuid, UUID specimenUuid, boolean addDatasetSource){
450
        DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
451
        SpecimenOrObservationBase specimen = occurrenceService.load(specimenUuid);
452

    
453
        Set<Feature> datasetFeatures = dataSet.getDescriptiveSystem().getDistinctFeatures();
454
        List<DescriptionElementBase> matchingDescriptionElements = new ArrayList<>();
455

    
456
        for (SpecimenDescription specimenDescription : (Set<SpecimenDescription>) specimen.getDescriptions()) {
457
            specimenDescription = (SpecimenDescription) descriptionService.load(specimenDescription.getUuid());
458

    
459
            //check if description is already added to data set
460
            if(dataSet.getDescriptions().contains(specimenDescription)){
461
                return specimenDescription;
462
            }
463

    
464
            //gather specimen description features and check for match with dataset features
465
            Set<Feature> specimenDescriptionFeatures = new HashSet<>();
466
            for (DescriptionElementBase specimenDescriptionElement : specimenDescription.getElements()) {
467
                Feature feature = specimenDescriptionElement.getFeature();
468
                specimenDescriptionFeatures.add(feature);
469
                if(datasetFeatures.contains(feature)){
470
                    matchingDescriptionElements.add(specimenDescriptionElement);
471
                }
472
            }
473
        }
474
        //Create new specimen description if description has not already been added to the dataset
475
        SpecimenDescription newDesription = SpecimenDescription.NewInstance(specimen);
476
        newDesription.setTitleCache("Dataset "+dataSet.getLabel()+": "+newDesription.generateTitle(), true); //$NON-NLS-2$
477

    
478
        //check for equals description element (same feature and same values)
479
        Map<Feature, List<DescriptionElementBase>> featureToElementMap = new HashMap<>();
480
        for(DescriptionElementBase element:matchingDescriptionElements){
481
            List<DescriptionElementBase> list = featureToElementMap.get(element.getFeature());
482
            if(list==null){
483
                list = new ArrayList<>();
484
            }
485
            list.add(element);
486
            featureToElementMap.put(element.getFeature(), list);
487
        }
488
        Set<DescriptionElementBase> descriptionElementsToClone = new HashSet<>();
489
        for(Feature feature:featureToElementMap.keySet()){
490
            List<DescriptionElementBase> elements = featureToElementMap.get(feature);
491
            //no duplicate description elements found for this feature
492
            if(elements.size()==1){
493
                descriptionElementsToClone.add(elements.get(0));
494
            }
495
            //duplicates found -> check if all are equal
496
            else{
497
                DescriptionElementBase match = null;
498
                for (DescriptionElementBase descriptionElementBase : elements) {
499
                    if(match==null){
500
                        match = descriptionElementBase;
501
                    }
502
                    else if(!new DescriptionElementCompareWrapper(match).equals(new DescriptionElementCompareWrapper(descriptionElementBase))){
503
                        match = null;
504
                        //TODO: propagate message
505
//                        MessagingUtils.informationDialog(Messages.CharacterMatrix_MULTIPLE_DATA,
506
//                                String.format(Messages.CharacterMatrix_MULTIPLE_DATA_MESSAGE, feature.getLabel()));
507
                        break;
508
                    }
509
                }
510
                if(match!=null){
511
                    descriptionElementsToClone.add(match);
512
                }
513
            }
514
        }
515
        //clone matching descriptionElements
516
        for (DescriptionElementBase descriptionElementBase : descriptionElementsToClone) {
517
            DescriptionElementBase clone;
518
            try {
519
                clone = descriptionElementBase.clone(newDesription);
520
                clone.getSources().forEach(source -> {
521
                    if(descriptionElementBase instanceof CategoricalData){
522
                        TextData label = new DefaultCategoricalDescriptionBuilder().build((CategoricalData) descriptionElementBase, null);
523
                        source.setOriginalNameString(label.getText(Language.DEFAULT()));
524
                    }
525
                    else if(descriptionElementBase instanceof QuantitativeData){
526
                        TextData label = new DefaultQuantitativeDescriptionBuilder().build((QuantitativeData) descriptionElementBase, null);
527
                        source.setOriginalNameString(label.getText(Language.DEFAULT()));
528
                    }
529
                });
530
            } catch (CloneNotSupportedException e) {
531
                //nothing
532
            }
533
        }
534

    
535
        //add all remaining description elements to the new description
536
        for(Feature wsFeature:datasetFeatures){
537
            boolean featureFound = false;
538
            for(DescriptionElementBase element:newDesription.getElements()){
539
                if(element.getFeature().equals(wsFeature)){
540
                    featureFound = true;
541
                    break;
542
                }
543
            }
544
            if(!featureFound){
545
                if(wsFeature.isSupportsCategoricalData()){
546
                    newDesription.addElement(CategoricalData.NewInstance(wsFeature));
547
                }
548
                else if(wsFeature.isSupportsQuantitativeData()){
549
                    newDesription.addElement(QuantitativeData.NewInstance(wsFeature));
550
                }
551
            }
552
        }
553
        //add sources of data set
554
        if(addDatasetSource){
555
            dataSet.getSources().forEach(source->{
556
                try {
557
                    newDesription.addSource((IdentifiableSource) source.clone());
558
                } catch (CloneNotSupportedException e) {
559
                    //nothing
560
                }
561
            });
562
        }
563
        return newDesription;
564

    
565
    }
566

    
567
    //TODO: this should either be solved in the model class itself
568
    //OR this should cover all possibilities including modifiers for example
569
    private class DescriptionElementCompareWrapper {
570

    
571
        private DescriptionElementBase element;
572
        private Set<UUID> stateUuids = new HashSet<>();
573
        private Set<Float> avgs = new HashSet<>();
574
        private Set<Float> exacts = new HashSet<>();
575
        private Set<Float> maxs = new HashSet<>();
576
        private Set<Float> mins = new HashSet<>();
577
        private Set<Float> sampleSizes = new HashSet<>();
578
        private Set<Float> standardDevs = new HashSet<>();
579
        private Set<Float> lowerBounds = new HashSet<>();
580
        private Set<Float> upperBounds = new HashSet<>();
581
        private Set<Float> variances = new HashSet<>();
582

    
583
        public DescriptionElementCompareWrapper(DescriptionElementBase element) {
584
            this.element = element;
585
            if(element.isInstanceOf(CategoricalData.class)){
586
                CategoricalData elementData = (CategoricalData)element;
587
                elementData.getStatesOnly().forEach(state->stateUuids.add(state.getUuid()));
588
            }
589
            else if(element.isInstanceOf(QuantitativeData.class)){
590
                QuantitativeData elementData = (QuantitativeData)element;
591
                elementData.getStatisticalValues().forEach(value->{
592
                    if(value.getType().equals(StatisticalMeasure.AVERAGE())){
593
                        avgs.add(value.getValue());
594
                    }
595
                    else if(value.getType().equals(StatisticalMeasure.EXACT_VALUE())){
596
                        exacts.add(value.getValue());
597

    
598
                    }
599
                    else if(value.getType().equals(StatisticalMeasure.MAX())){
600
                        maxs.add(value.getValue());
601
                    }
602
                    else if(value.getType().equals(StatisticalMeasure.MIN())){
603
                        mins.add(value.getValue());
604
                    }
605
                    else if(value.getType().equals(StatisticalMeasure.SAMPLE_SIZE())){
606
                        sampleSizes.add(value.getValue());
607

    
608
                    }
609
                    else if(value.getType().equals(StatisticalMeasure.STANDARD_DEVIATION())){
610
                        standardDevs.add(value.getValue());
611
                    }
612
                    else if(value.getType().equals(StatisticalMeasure.TYPICAL_LOWER_BOUNDARY())){
613
                        lowerBounds.add(value.getValue());
614

    
615
                    }
616
                    else if(value.getType().equals(StatisticalMeasure.TYPICAL_UPPER_BOUNDARY())){
617
                        upperBounds.add(value.getValue());
618
                    }
619
                    else if(value.getType().equals(StatisticalMeasure.VARIANCE())){
620
                        variances.add(value.getValue());
621
                    }
622
                });
623
            }
624
        }
625

    
626
        @Override
627
        public int hashCode() {
628
            final int prime = 31;
629
            int result = 1;
630
            result = prime * result + getOuterType().hashCode();
631
            result = prime * result + ((avgs == null) ? 0 : avgs.hashCode());
632
            result = prime * result + ((element == null) ? 0 : element.hashCode());
633
            result = prime * result + ((exacts == null) ? 0 : exacts.hashCode());
634
            result = prime * result + ((lowerBounds == null) ? 0 : lowerBounds.hashCode());
635
            result = prime * result + ((maxs == null) ? 0 : maxs.hashCode());
636
            result = prime * result + ((mins == null) ? 0 : mins.hashCode());
637
            result = prime * result + ((sampleSizes == null) ? 0 : sampleSizes.hashCode());
638
            result = prime * result + ((standardDevs == null) ? 0 : standardDevs.hashCode());
639
            result = prime * result + ((stateUuids == null) ? 0 : stateUuids.hashCode());
640
            result = prime * result + ((upperBounds == null) ? 0 : upperBounds.hashCode());
641
            result = prime * result + ((variances == null) ? 0 : variances.hashCode());
642
            return result;
643
        }
644

    
645
        @Override
646
        public boolean equals(Object obj) {
647
            if (this == obj) {
648
                return true;
649
            }
650
            if (obj == null) {
651
                return false;
652
            }
653
            if (getClass() != obj.getClass()) {
654
                return false;
655
            }
656
            DescriptionElementCompareWrapper other = (DescriptionElementCompareWrapper) obj;
657
            if (!getOuterType().equals(other.getOuterType())) {
658
                return false;
659
            }
660
            if (avgs == null) {
661
                if (other.avgs != null) {
662
                    return false;
663
                }
664
            } else if (!avgs.equals(other.avgs)) {
665
                return false;
666
            }
667
            if (element == null) {
668
                if (other.element != null) {
669
                    return false;
670
                }
671
            } else if (!element.equals(other.element)) {
672
                return false;
673
            }
674
            if (exacts == null) {
675
                if (other.exacts != null) {
676
                    return false;
677
                }
678
            } else if (!exacts.equals(other.exacts)) {
679
                return false;
680
            }
681
            if (lowerBounds == null) {
682
                if (other.lowerBounds != null) {
683
                    return false;
684
                }
685
            } else if (!lowerBounds.equals(other.lowerBounds)) {
686
                return false;
687
            }
688
            if (maxs == null) {
689
                if (other.maxs != null) {
690
                    return false;
691
                }
692
            } else if (!maxs.equals(other.maxs)) {
693
                return false;
694
            }
695
            if (mins == null) {
696
                if (other.mins != null) {
697
                    return false;
698
                }
699
            } else if (!mins.equals(other.mins)) {
700
                return false;
701
            }
702
            if (sampleSizes == null) {
703
                if (other.sampleSizes != null) {
704
                    return false;
705
                }
706
            } else if (!sampleSizes.equals(other.sampleSizes)) {
707
                return false;
708
            }
709
            if (standardDevs == null) {
710
                if (other.standardDevs != null) {
711
                    return false;
712
                }
713
            } else if (!standardDevs.equals(other.standardDevs)) {
714
                return false;
715
            }
716
            if (stateUuids == null) {
717
                if (other.stateUuids != null) {
718
                    return false;
719
                }
720
            } else if (!stateUuids.equals(other.stateUuids)) {
721
                return false;
722
            }
723
            if (upperBounds == null) {
724
                if (other.upperBounds != null) {
725
                    return false;
726
                }
727
            } else if (!upperBounds.equals(other.upperBounds)) {
728
                return false;
729
            }
730
            if (variances == null) {
731
                if (other.variances != null) {
732
                    return false;
733
                }
734
            } else if (!variances.equals(other.variances)) {
735
                return false;
736
            }
737
            return true;
738
        }
739

    
740
        private DescriptiveDataSetService getOuterType() {
741
            return DescriptiveDataSetService.this;
742
        }
743

    
744
    }
745

    
746
}
(17-17/103)