Project

General

Profile

Download (17.7 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.RegistrationValidationException;
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
    private NameTypeBaseEntityType nameTypeBaseEntityType = NameTypeBaseEntityType.NAME_TYPE_DESIGNATION;
70

    
71
    private Map<UUID,TypeDesignationBase<?>> typeDesignations = new HashMap<>();
72

    
73
    private TaxonName typifiedName;
74

    
75
    /**
76
     * Sorts the base entities (TypedEntityReference) in the following order:
77
     *
78
     * 1. FieldUnits
79
     * 2. DerivedUnit (in case of missing FieldUnit we expect the base type to be DerivedUnit)
80
     * 3. NameType
81
     *
82
     * {@inheritDoc}
83
     */
84
    private Comparator<Entry<VersionableEntity,TypeDesignationSet>> entryComparator = (o1,o2)->{
85

    
86
         TypeDesignationSet ws1 = o1.getValue();
87
         TypeDesignationSet ws2 = o2.getValue();
88

    
89
         if (ws1.getWorkingsetType() != ws2.getWorkingsetType()){
90
             //first specimen types, then name types (very rare case anyway)
91
             return ws1.getWorkingsetType() == TypeDesignationSetType.NAME_TYPE_DESIGNATION_SET? 1:-1;
92
         }
93

    
94
         boolean hasStatus1 = !ws1.keySet().contains(null) && !ws1.keySet().contains(NullTypeDesignationStatus.SINGLETON());
95
         boolean hasStatus2 = !ws2.keySet().contains(null) && !ws2.keySet().contains(NullTypeDesignationStatus.SINGLETON());
96
         if (hasStatus1 != hasStatus2){
97
             //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
98
             return hasStatus1? 1:-1;
99
         }
100

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

    
103
         Class<?> type1 = o1.getKey().getClass();
104
         Class<?> type2 = o2.getKey().getClass();
105

    
106
         if(!type1.equals(type2)) {
107
             if(type1.equals(FieldUnit.class) || type2.equals(FieldUnit.class)){
108
                 // FieldUnits first
109
                 return type1.equals(FieldUnit.class) ? -1 : 1;
110
             } else {
111
                 // name types last (in case of missing FieldUnit we expect the base type to be DerivedUnit which comes into the middle)
112
                 return type2.equals(TaxonName.class) || type2.equals(NameTypeDesignation.class) ? -1 : 1;
113
             }
114
         } else {
115
//             tdType1 = ws1.getTypeDesignations().stream().map(td->td.get).sorted(null).findFirst().orElseGet(()->{return null;});
116
             String label1 = TypeDesignationSetFormatter.entityLabel(o1.getKey());
117
             String label2 = TypeDesignationSetFormatter.entityLabel(o2.getKey());
118
             return label1.compareTo(label2);
119
         }
120
     };
121

    
122
    /**
123
     * Groups the EntityReferences for each of the TypeDesignations by the according TypeDesignationStatus.
124
     * The TypeDesignationStatusBase keys are already ordered by the term order defined in the vocabulary.
125
     */
126
    private LinkedHashMap<VersionableEntity,TypeDesignationSet> orderedByTypesByBaseEntity;
127

    
128
    private List<String> problems = new ArrayList<>();
129

    
130
// **************************** CONSTRUCTOR ***********************************/
131

    
132
    public TypeDesignationSetContainer(@SuppressWarnings("rawtypes") Collection<TypeDesignationBase> typeDesignations)
133
            throws RegistrationValidationException{
134
    	this(typeDesignations, null);
135
    }
136

    
137
    public TypeDesignationSetContainer(@SuppressWarnings("rawtypes") Collection<TypeDesignationBase> typeDesignations,
138
            TaxonName typifiedName)
139
            throws RegistrationValidationException  {
140
        for (TypeDesignationBase<?> typeDes:typeDesignations){
141
            this.typeDesignations.put(typeDes.getUuid(), typeDes);
142
        }
143
        try {
144
        	findTypifiedName();
145
        }catch (RegistrationValidationException e) {
146
        	if (typifiedName == null) {
147
        		throw e;
148
        	}
149
        	this.typifiedName = typifiedName;
150
        }
151

    
152
        mapAndSort();
153
    }
154

    
155
    public TypeDesignationSetContainer(HomotypicalGroup group) {
156
        for (TypeDesignationBase<?> typeDes: group.getTypeDesignations()){
157
            this.typeDesignations.put(typeDes.getUuid(), typeDes);
158
        }
159
        //findTypifiedName();
160
        mapAndSort();
161
    }
162

    
163
    public TypeDesignationSetContainer(TaxonName typifiedName) {
164
        this.typifiedName = typifiedName;
165
    }
166

    
167
// **************************************************************************/
168

    
169
    /**
170
     * Add one or more TypeDesignations to the manager. This causes re-grouping and re-ordering
171
     * of all managed TypeDesignations.
172
     *
173
     * @param containgEntity
174
     * @param typeDesignations
175
     */
176
    public void addTypeDesigations(TypeDesignationBase<?> ... typeDesignations){
177
        for (TypeDesignationBase<?> typeDes: typeDesignations){
178
            this.typeDesignations.put(typeDes.getUuid(), typeDes);
179
        }
180
        mapAndSort();
181
    }
182

    
183
    public TaxonName getTypifiedName() {
184
        return typifiedName;
185
    }
186

    
187
    public void setNameTypeBaseEntityType(NameTypeBaseEntityType nameTypeBaseEntityType){
188
        this.nameTypeBaseEntityType = nameTypeBaseEntityType;
189
    }
190

    
191
    public NameTypeBaseEntityType getNameTypeBaseEntityType(){
192
        return nameTypeBaseEntityType;
193
    }
194

    
195
// ******************************** METHODS *********************************/
196

    
197
    /**
198
     * Groups and orders all managed TypeDesignations.
199
     */
200
    protected void mapAndSort() {
201

    
202
        Map<VersionableEntity,TypeDesignationSet> byBaseEntityByTypeStatus = new HashMap<>();
203
        this.typeDesignations.values().forEach(td -> mapTypeDesignation(byBaseEntityByTypeStatus, td));
204
        orderedByTypesByBaseEntity = orderByTypeByBaseEntity(byBaseEntityByTypeStatus);
205
    }
206

    
207
    private void mapTypeDesignation(Map<VersionableEntity,TypeDesignationSet> byBaseEntityByTypeStatus,
208
            TypeDesignationBase<?> td){
209

    
210
        td = HibernateProxyHelper.deproxy(td);
211
        TypeDesignationStatusBase<?> status = td.getTypeStatus();
212

    
213
        try {
214
            VersionableEntity baseEntity = baseEntity(td);
215

    
216
            TaggedTextBuilder workingsetBuilder = new TaggedTextBuilder();
217
            boolean withCitation = true;
218
            TypeDesignationSetFormatter.buildTaggedTextForSingleType(td, withCitation, workingsetBuilder, 0);
219

    
220
            @SuppressWarnings({ "unchecked", "rawtypes" })
221
            TypeDesignationDTO<?> typeDesignationDTO
222
                = new TypeDesignationDTO(
223
                    td.getClass(),
224
                    td.getUuid(),
225
                    workingsetBuilder.getTaggedText(),
226
                    getTypeUuid(td));
227

    
228
            if(!byBaseEntityByTypeStatus.containsKey(baseEntity)){
229
                byBaseEntityByTypeStatus.put(baseEntity, new TypeDesignationSet(baseEntity));
230
            }
231
            byBaseEntityByTypeStatus.get(baseEntity).insert(status, typeDesignationDTO);
232

    
233
        } catch (DataIntegrityException e){
234
            problems.add(e.getMessage());
235
        }
236
    }
237

    
238
    /**
239
     * Returns the uuid of the type designated by this {@link TypeDesignationDTO#}.
240
     * This is either a TaxonName or a {@link SpecimenOrObservationBase}.
241
     */
242
    private UUID getTypeUuid(TypeDesignationBase<?> td) {
243
        IdentifiableEntity<?> type;
244
        if (td instanceof SpecimenTypeDesignation){
245
            type = ((SpecimenTypeDesignation) td).getTypeSpecimen();
246
        }else if (td instanceof NameTypeDesignation){
247
            type = ((NameTypeDesignation) td).getTypeName();
248
        }else{
249
            type = null;
250
        }
251
        return type == null? null : type.getUuid();
252
    }
253

    
254
    protected VersionableEntity baseEntity(TypeDesignationBase<?> td) throws DataIntegrityException {
255

    
256
        VersionableEntity baseEntity = null;
257
        if(td instanceof SpecimenTypeDesignation){
258
            SpecimenTypeDesignation std = (SpecimenTypeDesignation) td;
259
            FieldUnit fu = findFieldUnit(std.getTypeSpecimen());
260
            if(fu != null){
261
                baseEntity = fu;
262
            } else if(((SpecimenTypeDesignation) td).getTypeSpecimen() != null){
263
                baseEntity = ((SpecimenTypeDesignation) td).getTypeSpecimen();
264
            }
265
        } else if(td instanceof NameTypeDesignation){
266
            if(nameTypeBaseEntityType == NameTypeBaseEntityType.NAME_TYPE_DESIGNATION){
267
                baseEntity = td;
268
            } else {
269
                // only other option is TaxonName
270
                baseEntity = ((NameTypeDesignation)td).getTypeName();
271
            }
272
        }
273
        if(baseEntity == null) {
274
            throw new DataIntegrityException("Incomplete TypeDesignation, no type missin in " + td.toString());
275
        }
276
        return baseEntity;
277
    }
278

    
279
    //TODO maybe not needed anymore
280
    protected static TypedEntityReference<? extends VersionableEntity> makeEntityReference(VersionableEntity baseEntity) {
281

    
282
        baseEntity = CdmBase.deproxy(baseEntity);
283
        String label = TypeDesignationSetFormatter.entityLabel(baseEntity);
284

    
285
        TypedEntityReference<? extends VersionableEntity> baseEntityReference =
286
                new TypedEntityReference<>(baseEntity.getClass(), baseEntity.getUuid(), label);
287

    
288
        return baseEntityReference;
289
    }
290

    
291
    private LinkedHashMap<VersionableEntity,TypeDesignationSet> orderByTypeByBaseEntity(
292
            Map<VersionableEntity,TypeDesignationSet> stringsByTypeByBaseEntity){
293

    
294
       // order the FieldUnit TypeName keys
295
       Set<Entry<VersionableEntity,TypeDesignationSet>> entrySet
296
               = stringsByTypeByBaseEntity.entrySet();
297
       LinkedList<Entry<VersionableEntity,TypeDesignationSet>> baseEntityKeyList
298
               = new LinkedList<>(entrySet);
299
       Collections.sort(baseEntityKeyList, entryComparator);
300

    
301
       // new LinkedHashMap for the ordered FieldUnitOrTypeName keys
302
       LinkedHashMap<VersionableEntity,TypeDesignationSet> stringsOrderedbyBaseEntityOrderdByType
303
           = new LinkedHashMap<>(stringsByTypeByBaseEntity.size());
304

    
305
       for(Entry<VersionableEntity,TypeDesignationSet> entry : baseEntityKeyList){
306
           VersionableEntity baseEntity = entry.getKey();
307
           TypeDesignationSet typeDesignationSet = stringsByTypeByBaseEntity.get(baseEntity);
308
           // order the TypeDesignationStatusBase keys
309
            List<TypeDesignationStatusBase<?>> keyList = new LinkedList<>(typeDesignationSet.keySet());
310
            Collections.sort(keyList, new TypeDesignationStatusComparator());
311
            // new LinkedHashMap for the ordered TypeDesignationStatusBase keys
312
            TypeDesignationSet orderedStringsByOrderedTypes = new TypeDesignationSet(
313
                    typeDesignationSet.getBaseEntity());
314
            keyList.forEach(key -> orderedStringsByOrderedTypes.put(key, typeDesignationSet.get(key)));
315
            stringsOrderedbyBaseEntityOrderdByType.put(baseEntity, orderedStringsByOrderedTypes);
316
        }
317

    
318
        return stringsOrderedbyBaseEntityOrderdByType;
319
    }
320

    
321
    /**
322
     * FIXME use the validation framework validators to store the validation problems!!!
323
     *
324
     * @return
325
     * @throws RegistrationValidationException
326
     */
327
    private void findTypifiedName() throws RegistrationValidationException {
328

    
329
        List<String> problems = new ArrayList<>();
330

    
331
        TaxonName typifiedName = null;
332

    
333
        for(TypeDesignationBase<?> typeDesignation : typeDesignations.values()){
334
            typeDesignation.getTypifiedNames();
335
            if(typeDesignation.getTypifiedNames().isEmpty()){
336

    
337
                //TODO instead throw RegistrationValidationException()
338
                problems.add("Missing typifiedName in " + typeDesignation.toString());
339
                continue;
340
            }
341
            if(typeDesignation.getTypifiedNames().size() > 1){
342
                //TODO instead throw RegistrationValidationException()
343
                problems.add("Multiple typifiedName in " + typeDesignation.toString());
344
                continue;
345
            }
346
            if(typifiedName == null){
347
                // remember
348
                typifiedName = typeDesignation.getTypifiedNames().iterator().next();
349
            } else {
350
                // compare
351
                TaxonName otherTypifiedName = typeDesignation.getTypifiedNames().iterator().next();
352
                if(!typifiedName.getUuid().equals(otherTypifiedName.getUuid())){
353
                    //TODO instead throw RegistrationValidationException()
354
                    problems.add("Multiple typifiedName in " + typeDesignation.toString());
355
                }
356
            }
357
        }
358
        if(!problems.isEmpty()){
359
            // FIXME use the validation framework
360
            throw new RegistrationValidationException("Inconsistent type designations", problems);
361
        }
362

    
363
        if(typifiedName != null){
364
            // ON SUCCESS -------------------
365
            this.typifiedName = typifiedName;
366
        }
367
    }
368

    
369
    /**
370
     * @return the title cache of the typifying name or <code>null</code>
371
     */
372
    public String getTypifiedNameCache() {
373
        if(typifiedName != null){
374
            return typifiedName.getTitleCache();
375
        }
376
        return null;
377
    }
378

    
379
    /**
380
     * @return the title cache of the typifying name or <code>null</code>
381
     */
382
    public EntityReference getTypifiedNameAsEntityRef() {
383
       return new EntityReference(typifiedName.getUuid(), typifiedName.getTitleCache());
384
    }
385

    
386
    public Collection<TypeDesignationBase<?>> getTypeDesignations() {
387
        return typeDesignations.values();
388
    }
389

    
390
    public TypeDesignationBase<?> findTypeDesignation(UUID uuid) {
391
        return this.typeDesignations.get(uuid);
392
    }
393

    
394
    public Map<VersionableEntity,TypeDesignationSet> getOrderedTypeDesignationSets() {
395
        return orderedByTypesByBaseEntity;
396
    }
397

    
398
    private FieldUnit findFieldUnit(DerivedUnit du) {
399

    
400
        if(du == null || du.getOriginals() == null || du.getOriginals().isEmpty()){
401
            return null;
402
        }
403
        @SuppressWarnings("rawtypes")
404
        Set<SpecimenOrObservationBase> originals = du.getOriginals();
405
        @SuppressWarnings("rawtypes")
406
        Optional<SpecimenOrObservationBase> fieldUnit = originals.stream()
407
                .filter(original -> original instanceof FieldUnit).findFirst();
408
        if (fieldUnit.isPresent()) {
409
            return (FieldUnit) fieldUnit.get();
410
        } else {
411
            for (@SuppressWarnings("rawtypes") SpecimenOrObservationBase sob : originals) {
412
                if (sob instanceof DerivedUnit) {
413
                    FieldUnit fu = findFieldUnit((DerivedUnit) sob);
414
                    if (fu != null) {
415
                        return fu;
416
                    }
417
                }
418
            }
419
        }
420

    
421
        return null;
422
    }
423

    
424
    public String print(boolean withCitation, boolean withStartingTypeLabel, boolean withNameIfAvailable) {
425
        return new TypeDesignationSetFormatter(withCitation, withStartingTypeLabel, withNameIfAvailable).format(this);
426
    }
427

    
428
    public String print(boolean withCitation, boolean withStartingTypeLabel, boolean withNameIfAvailable, HTMLTagRules htmlRules) {
429
        return new TypeDesignationSetFormatter(withCitation, withStartingTypeLabel, withNameIfAvailable).format(this, htmlRules);
430
    }
431

    
432

    
433
    class DataIntegrityException extends Exception {
434

    
435
        private static final long serialVersionUID = 1464726696296824905L;
436

    
437
        public DataIntegrityException(String string) {
438
            super(string);
439
        }
440
    }
441
}
(3-3/4)