Project

General

Profile

Download (18.2 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2017 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.api.service.name;
10

    
11
import java.util.ArrayList;
12
import java.util.Collection;
13
import java.util.Collections;
14
import java.util.Comparator;
15
import java.util.HashMap;
16
import java.util.LinkedHashMap;
17
import java.util.LinkedList;
18
import java.util.List;
19
import java.util.Map;
20
import java.util.Map.Entry;
21
import java.util.Optional;
22
import java.util.Set;
23
import java.util.UUID;
24

    
25
import eu.etaxonomy.cdm.api.service.exception.TypeDesignationSetException;
26
import eu.etaxonomy.cdm.api.service.name.TypeDesignationSet.TypeDesignationSetType;
27
import eu.etaxonomy.cdm.compare.name.NullTypeDesignationStatus;
28
import eu.etaxonomy.cdm.compare.name.TypeDesignationStatusComparator;
29
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
30
import eu.etaxonomy.cdm.model.common.CdmBase;
31
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
32
import eu.etaxonomy.cdm.model.common.VersionableEntity;
33
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
34
import eu.etaxonomy.cdm.model.name.NameTypeDesignation;
35
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
36
import eu.etaxonomy.cdm.model.name.TaxonName;
37
import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
38
import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;
39
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
40
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
41
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
42
import eu.etaxonomy.cdm.ref.EntityReference;
43
import eu.etaxonomy.cdm.ref.TypedEntityReference;
44
import eu.etaxonomy.cdm.strategy.cache.HTMLTagRules;
45
import eu.etaxonomy.cdm.strategy.cache.TaggedTextBuilder;
46

    
47
/**
48
 * Container for of collection of {@link TypeDesignationBase type designations} for the same typified name.
49
 *
50
 * Type designations are ordered by the base type which is a {@link TaxonName} for {@link NameTypeDesignation name type designations} or
51
 * a {@link FieldUnit} in case of {@link SpecimenTypeDesignation specimen type designations}. The type designations per base type are furthermore ordered by the {@link TypeDesignationStatusBase}
52
 * (or a {@link DerivedUnit} if the field unit is missing).
53
 * <BR>
54
 * All type designations belonging to one base type are handled in a {@link TypeDesignationSet}.
55
 * <BR>
56
 * The {@link TypeDesignationSetContainer} can be formatted by using the {@link TypeDesignationSetFormatter}
57
 *
58
 * @author a.kohlbecker
59
 * @since Mar 10, 2017
60
 */
61
public class TypeDesignationSetContainer {
62

    
63
    //currently not really in use
64
    enum NameTypeBaseEntityType{
65
        NAME_TYPE_DESIGNATION,
66
        TYPE_NAME;
67
    }
68

    
69
    //not yet used
70
    enum ORDER_BY{
71
        TYPE_STATUS,
72
        BASE_ENTITY;
73
    }
74

    
75
    private NameTypeBaseEntityType nameTypeBaseEntityType = NameTypeBaseEntityType.NAME_TYPE_DESIGNATION;
76

    
77
    private Map<UUID,TypeDesignationBase<?>> typeDesignations = new HashMap<>();
78

    
79
    private TaxonName typifiedName;
80

    
81
    private ORDER_BY orderBy = ORDER_BY.TYPE_STATUS;
82

    
83
    /**
84
     * Sorts the base entities (TypedEntityReference) in the following order:
85
     *
86
     * 1. FieldUnits
87
     * 2. DerivedUnit (in case of missing FieldUnit we expect the base type to be DerivedUnit)
88
     * 3. NameType
89
     *
90
     * {@inheritDoc}
91
     */
92
    private Comparator<Entry<VersionableEntity,TypeDesignationSet>> entryComparator = (o1,o2)->{
93

    
94
         TypeDesignationSet ws1 = o1.getValue();
95
         TypeDesignationSet ws2 = o2.getValue();
96

    
97
         if (ws1.getWorkingsetType() != ws2.getWorkingsetType()){
98
             //first specimen types, then name types (very rare case anyway)
99
             return ws1.getWorkingsetType() == TypeDesignationSetType.NAME_TYPE_DESIGNATION_SET? 1:-1;
100
         }
101

    
102
         boolean hasStatus1 = !ws1.keySet().contains(null) && !ws1.keySet().contains(NullTypeDesignationStatus.SINGLETON());
103
         boolean hasStatus2 = !ws2.keySet().contains(null) && !ws2.keySet().contains(NullTypeDesignationStatus.SINGLETON());
104
         if (hasStatus1 != hasStatus2){
105
             //first without status as it is difficult to distinguish a non status from a "same" status record if the first record has a status and second has no status
106
             return hasStatus1? 1:-1;
107
         }
108

    
109
         //boolean hasStatus1 = ws1.getTypeDesignations(); //.stream().filter(td -> td.getSt);
110

    
111
         Class<?> type1 = o1.getKey().getClass();
112
         Class<?> type2 = o2.getKey().getClass();
113

    
114
         if(!type1.equals(type2)) {
115
             if(type1.equals(FieldUnit.class) || type2.equals(FieldUnit.class)){
116
                 // FieldUnits first
117
                 return type1.equals(FieldUnit.class) ? -1 : 1;
118
             } else {
119
                 // name types last (in case of missing FieldUnit we expect the base type to be DerivedUnit which comes into the middle)
120
                 return type2.equals(TaxonName.class) || type2.equals(NameTypeDesignation.class) ? -1 : 1;
121
             }
122
         } else {
123
             if (orderBy == ORDER_BY.TYPE_STATUS) {
124
                 @SuppressWarnings({ "unchecked", "rawtypes" })
125
                 Comparator<TypeDesignationStatusBase<?>> statusComparator = (Comparator)new TypeDesignationStatusComparator<>();
126
                 TypeDesignationStatusBase<?> status1 = ws1.highestTypeStatus(statusComparator);
127
                 TypeDesignationStatusBase<?> status2 = ws2.highestTypeStatus(statusComparator);
128
                 int comp = statusComparator.compare(status1, status2);
129
                 if (comp != 0) {
130
                     return comp;
131
                 }
132
             }
133

    
134
             String label1 = TypeDesignationSetFormatter.entityLabel(o1.getKey());
135
             String label2 = TypeDesignationSetFormatter.entityLabel(o2.getKey());
136
             return label1.compareTo(label2);
137
         }
138
     };
139

    
140
    /**
141
     * Groups the EntityReferences for each of the TypeDesignations by the according TypeDesignationStatus.
142
     * The TypeDesignationStatusBase keys are already ordered by the term order defined in the vocabulary.
143
     */
144
    private LinkedHashMap<VersionableEntity,TypeDesignationSet> orderedByTypesByBaseEntity;
145

    
146
    private List<String> problems = new ArrayList<>();
147

    
148
// **************************** CONSTRUCTOR ***********************************/
149

    
150
    public TypeDesignationSetContainer(@SuppressWarnings("rawtypes") Collection<TypeDesignationBase> typeDesignations)
151
            throws TypeDesignationSetException{
152
    	this(typeDesignations, null);
153
    }
154

    
155
    public TypeDesignationSetContainer(@SuppressWarnings("rawtypes") Collection<TypeDesignationBase> typeDesignations,
156
            TaxonName typifiedName)
157
            throws TypeDesignationSetException  {
158
        for (TypeDesignationBase<?> typeDes:typeDesignations){
159
            this.typeDesignations.put(typeDes.getUuid(), typeDes);
160
        }
161
        try {
162
        	findTypifiedName();
163
        }catch (TypeDesignationSetException e) {
164
        	if (typifiedName == null) {
165
        		throw e;
166
        	}
167
        	this.typifiedName = typifiedName;
168
        }
169

    
170
        mapAndSort();
171
    }
172

    
173
    public TypeDesignationSetContainer(HomotypicalGroup group) {
174
        for (TypeDesignationBase<?> typeDes: group.getTypeDesignations()){
175
            this.typeDesignations.put(typeDes.getUuid(), typeDes);
176
        }
177
        //findTypifiedName();
178
        mapAndSort();
179
    }
180

    
181
    public TypeDesignationSetContainer(TaxonName typifiedName) {
182
        this.typifiedName = typifiedName;
183
    }
184

    
185
// **************************************************************************/
186

    
187
    /**
188
     * Add one or more TypeDesignations to the manager. This causes re-grouping and re-ordering
189
     * of all managed TypeDesignations.
190
     *
191
     * @param containgEntity
192
     * @param typeDesignations
193
     */
194
    public void addTypeDesigations(TypeDesignationBase<?> ... typeDesignations){
195
        for (TypeDesignationBase<?> typeDes: typeDesignations){
196
            this.typeDesignations.put(typeDes.getUuid(), typeDes);
197
        }
198
        mapAndSort();
199
    }
200

    
201
    public TaxonName getTypifiedName() {
202
        return typifiedName;
203
    }
204

    
205
    public void setNameTypeBaseEntityType(NameTypeBaseEntityType nameTypeBaseEntityType){
206
        this.nameTypeBaseEntityType = nameTypeBaseEntityType;
207
    }
208

    
209
    public NameTypeBaseEntityType getNameTypeBaseEntityType(){
210
        return nameTypeBaseEntityType;
211
    }
212

    
213
// ******************************** METHODS *********************************/
214

    
215
    /**
216
     * Groups and orders all managed TypeDesignations.
217
     */
218
    protected void mapAndSort() {
219

    
220
        Map<VersionableEntity,TypeDesignationSet> byBaseEntityByTypeStatus = new HashMap<>();
221
        this.typeDesignations.values().forEach(td -> mapTypeDesignation(byBaseEntityByTypeStatus, td));
222
        orderedByTypesByBaseEntity = orderByTypeByBaseEntity(byBaseEntityByTypeStatus);
223
    }
224

    
225
    private void mapTypeDesignation(Map<VersionableEntity,TypeDesignationSet> byBaseEntityByTypeStatus,
226
            TypeDesignationBase<?> td){
227

    
228
        td = HibernateProxyHelper.deproxy(td);
229
        TypeDesignationStatusBase<?> status = td.getTypeStatus();
230

    
231
        try {
232
            VersionableEntity baseEntity = baseEntity(td);
233

    
234
            TaggedTextBuilder workingsetBuilder = new TaggedTextBuilder();
235
            boolean withCitation = true;
236
            TypeDesignationSetFormatter.buildTaggedTextForSingleType(td, withCitation, workingsetBuilder, 0);
237

    
238
            @SuppressWarnings({ "unchecked", "rawtypes" })
239
            TypeDesignationDTO<?> typeDesignationDTO
240
                = new TypeDesignationDTO(
241
                    td.getClass(),
242
                    td.getUuid(),
243
                    workingsetBuilder.getTaggedText(),
244
                    getTypeUuid(td));
245

    
246
            if(!byBaseEntityByTypeStatus.containsKey(baseEntity)){
247
                byBaseEntityByTypeStatus.put(baseEntity, new TypeDesignationSet(baseEntity));
248
            }
249
            byBaseEntityByTypeStatus.get(baseEntity).insert(status, typeDesignationDTO);
250

    
251
        } catch (DataIntegrityException e){
252
            problems.add(e.getMessage());
253
        }
254
    }
255

    
256
    /**
257
     * Returns the uuid of the type designated by this {@link TypeDesignationDTO#}.
258
     * This is either a TaxonName or a {@link SpecimenOrObservationBase}.
259
     */
260
    private UUID getTypeUuid(TypeDesignationBase<?> td) {
261
        IdentifiableEntity<?> type;
262
        if (td instanceof SpecimenTypeDesignation){
263
            type = ((SpecimenTypeDesignation) td).getTypeSpecimen();
264
        }else if (td instanceof NameTypeDesignation){
265
            type = ((NameTypeDesignation) td).getTypeName();
266
        }else{
267
            type = null;
268
        }
269
        return type == null? null : type.getUuid();
270
    }
271

    
272
    protected VersionableEntity baseEntity(TypeDesignationBase<?> td) throws DataIntegrityException {
273

    
274
        VersionableEntity baseEntity = null;
275
        if(td instanceof SpecimenTypeDesignation){
276
            SpecimenTypeDesignation std = (SpecimenTypeDesignation) td;
277
            FieldUnit fu = findFieldUnit(std.getTypeSpecimen());
278
            if(fu != null){
279
                baseEntity = fu;
280
            } else if(((SpecimenTypeDesignation) td).getTypeSpecimen() != null){
281
                baseEntity = ((SpecimenTypeDesignation) td).getTypeSpecimen();
282
            }
283
        } else if(td instanceof NameTypeDesignation){
284
            if(nameTypeBaseEntityType == NameTypeBaseEntityType.NAME_TYPE_DESIGNATION){
285
                baseEntity = td;
286
            } else {
287
                // only other option is TaxonName
288
                baseEntity = ((NameTypeDesignation)td).getTypeName();
289
            }
290
        }
291
        if(baseEntity == null) {
292
            throw new DataIntegrityException("Incomplete TypeDesignation, no type missin in " + td.toString());
293
        }
294
        return baseEntity;
295
    }
296

    
297
    //TODO maybe not needed anymore
298
    private static TypedEntityReference<? extends VersionableEntity> makeEntityReference(VersionableEntity baseEntity) {
299

    
300
        baseEntity = CdmBase.deproxy(baseEntity);
301
        String label = TypeDesignationSetFormatter.entityLabel(baseEntity);
302

    
303
        TypedEntityReference<? extends VersionableEntity> baseEntityReference =
304
                new TypedEntityReference<>(baseEntity.getClass(), baseEntity.getUuid(), label);
305

    
306
        return baseEntityReference;
307
    }
308

    
309
    private LinkedHashMap<VersionableEntity,TypeDesignationSet> orderByTypeByBaseEntity(
310
            Map<VersionableEntity,TypeDesignationSet> stringsByTypeByBaseEntity){
311

    
312
       // order the FieldUnit TypeName keys
313
       Set<Entry<VersionableEntity,TypeDesignationSet>> entrySet
314
               = stringsByTypeByBaseEntity.entrySet();
315
       LinkedList<Entry<VersionableEntity,TypeDesignationSet>> baseEntityKeyList
316
               = new LinkedList<>(entrySet);
317
       Collections.sort(baseEntityKeyList, entryComparator);
318

    
319
       // new LinkedHashMap for the ordered FieldUnitOrTypeName keys
320
       LinkedHashMap<VersionableEntity,TypeDesignationSet> stringsOrderedbyBaseEntityOrderdByType
321
           = new LinkedHashMap<>(stringsByTypeByBaseEntity.size());
322

    
323
       for(Entry<VersionableEntity,TypeDesignationSet> entry : baseEntityKeyList){
324
           VersionableEntity baseEntity = entry.getKey();
325
           TypeDesignationSet typeDesignationSet = stringsByTypeByBaseEntity.get(baseEntity);
326
           // order the TypeDesignationStatusBase keys
327
            List<TypeDesignationStatusBase<?>> keyList = new LinkedList<>(typeDesignationSet.keySet());
328
            Collections.sort(keyList, new TypeDesignationStatusComparator());
329
            // new LinkedHashMap for the ordered TypeDesignationStatusBase keys
330
            TypeDesignationSet orderedStringsByOrderedTypes = new TypeDesignationSet(
331
                    typeDesignationSet.getBaseEntity());
332
            keyList.forEach(key -> orderedStringsByOrderedTypes.put(key, typeDesignationSet.get(key)));
333
            stringsOrderedbyBaseEntityOrderdByType.put(baseEntity, orderedStringsByOrderedTypes);
334
        }
335

    
336
        return stringsOrderedbyBaseEntityOrderdByType;
337
    }
338

    
339
    /**
340
     * FIXME use the validation framework validators to store the validation problems!!!
341
     *
342
     * @throws TypeDesignationSetException
343
     */
344
    private void findTypifiedName() throws TypeDesignationSetException {
345

    
346
        List<String> problems = new ArrayList<>();
347

    
348
        TaxonName typifiedName = null;
349

    
350
        for(TypeDesignationBase<?> typeDesignation : typeDesignations.values()){
351
            typeDesignation.getTypifiedNames();
352
            if(typeDesignation.getTypifiedNames().isEmpty()){
353

    
354
                //TODO instead throw RegistrationValidationException()
355
                problems.add("Missing typifiedName in " + typeDesignation.toString());
356
                continue;
357
            }
358
            if(typeDesignation.getTypifiedNames().size() > 1){
359
                //TODO instead throw RegistrationValidationException()
360
                problems.add("Multiple typifiedName in " + typeDesignation.toString());
361
                continue;
362
            }
363
            if(typifiedName == null){
364
                // remember
365
                typifiedName = typeDesignation.getTypifiedNames().iterator().next();
366
            } else {
367
                // compare
368
                TaxonName otherTypifiedName = typeDesignation.getTypifiedNames().iterator().next();
369
                if(!typifiedName.getUuid().equals(otherTypifiedName.getUuid())){
370
                    //TODO instead throw RegistrationValidationException()
371
                    problems.add("Multiple typifiedName in " + typeDesignation.toString());
372
                }
373
            }
374
        }
375
        if(!problems.isEmpty()){
376
            // FIXME use the validation framework
377
            throw new TypeDesignationSetException("Inconsistent type designations", problems);
378
        }
379

    
380
        if(typifiedName != null){
381
            // ON SUCCESS -------------------
382
            this.typifiedName = typifiedName;
383
        }
384
    }
385

    
386
    /**
387
     * @return the title cache of the typifying name or <code>null</code>
388
     */
389
    public String getTypifiedNameCache() {
390
        if(typifiedName != null){
391
            return typifiedName.getTitleCache();
392
        }
393
        return null;
394
    }
395

    
396
    /**
397
     * @return the title cache of the typifying name or <code>null</code>
398
     */
399
    public EntityReference getTypifiedNameAsEntityRef() {
400
       return new EntityReference(typifiedName.getUuid(), typifiedName.getTitleCache());
401
    }
402

    
403
    public Collection<TypeDesignationBase<?>> getTypeDesignations() {
404
        return typeDesignations.values();
405
    }
406

    
407
    public TypeDesignationBase<?> findTypeDesignation(UUID uuid) {
408
        return this.typeDesignations.get(uuid);
409
    }
410

    
411
    public Map<VersionableEntity,TypeDesignationSet> getOrderedTypeDesignationSets() {
412
        return orderedByTypesByBaseEntity;
413
    }
414

    
415
    private FieldUnit findFieldUnit(DerivedUnit du) {
416

    
417
        if(du == null || du.getOriginals() == null || du.getOriginals().isEmpty()){
418
            return null;
419
        }
420
        @SuppressWarnings("rawtypes")
421
        Set<SpecimenOrObservationBase> originals = du.getOriginals();
422
        @SuppressWarnings("rawtypes")
423
        Optional<SpecimenOrObservationBase> fieldUnit = originals.stream()
424
                .filter(original -> original instanceof FieldUnit).findFirst();
425
        if (fieldUnit.isPresent()) {
426
            return (FieldUnit) fieldUnit.get();
427
        } else {
428
            for (@SuppressWarnings("rawtypes") SpecimenOrObservationBase sob : originals) {
429
                if (sob instanceof DerivedUnit) {
430
                    FieldUnit fu = findFieldUnit((DerivedUnit) sob);
431
                    if (fu != null) {
432
                        return fu;
433
                    }
434
                }
435
            }
436
        }
437

    
438
        return null;
439
    }
440

    
441
    public String print(boolean withCitation, boolean withStartingTypeLabel, boolean withNameIfAvailable) {
442
        return new TypeDesignationSetFormatter(withCitation, withStartingTypeLabel, withNameIfAvailable).format(this);
443
    }
444

    
445
    public String print(boolean withCitation, boolean withStartingTypeLabel, boolean withNameIfAvailable, HTMLTagRules htmlRules) {
446
        return new TypeDesignationSetFormatter(withCitation, withStartingTypeLabel, withNameIfAvailable).format(this, htmlRules);
447
    }
448

    
449

    
450
    class DataIntegrityException extends Exception {
451

    
452
        private static final long serialVersionUID = 1464726696296824905L;
453

    
454
        public DataIntegrityException(String string) {
455
            super(string);
456
        }
457
    }
458
}
(3-3/4)