Project

General

Profile

Download (18.6 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.TypeDesignationWorkingSet.TypeDesignationWorkingSetType;
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
 * Manages a 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 NameTypeDesignations} or
51
 * in case of {@link SpecimenTypeDesignation SpecimenTypeDesignations} the  associate {@link FieldUnit} or the {@link DerivedUnit}
52
 * if the former is missing. The type designations per base type are furthermore ordered by the {@link TypeDesignationStatusBase}.
53
 *
54
 * The TypeDesignationSetManager also provides string representations of the whole ordered set of all
55
 * {@link TypeDesignationBase TypeDesignations} and of the TypeDesignationWorkingSets:
56
 * <ul>
57
 *  <li>{@link #print()}
58
 *  <li>{@link #getOrderedTypeDesignationWorkingSets()} ... {@link TypeDesignationWorkingSet#getLabel()}
59
 * </ul>
60
 * Prior using the representations you need to trigger their generation by calling {@link #buildString()}
61
 *
62
 * @author a.kohlbecker
63
 * @since Mar 10, 2017
64
 */
65
public class TypeDesignationSetManager {
66

    
67
    //currently not really in use
68
    enum NameTypeBaseEntityType{
69
        NAME_TYPE_DESIGNATION,
70
        TYPE_NAME;
71
    }
72

    
73
    private NameTypeBaseEntityType nameTypeBaseEntityType = NameTypeBaseEntityType.NAME_TYPE_DESIGNATION;
74

    
75
    private Map<UUID,TypeDesignationBase<?>> typeDesignations = new HashMap<>();
76

    
77
    private TaxonName typifiedName;
78

    
79
    private Class tdType1;
80

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

    
92
         TypeDesignationWorkingSet ws1 = o1.getValue();
93
         TypeDesignationWorkingSet ws2 = o2.getValue();
94

    
95
         if (ws1.getWorkingsetType() != ws2.getWorkingsetType()){
96
             //first specimen types, then name types (very rare case anyway)
97
             return ws1.getWorkingsetType() == TypeDesignationWorkingSetType.NAME_TYPE_DESIGNATION_WORKINGSET? 1:-1;
98
         }
99

    
100
         boolean hasStatus1 = !ws1.keySet().contains(null) && !ws1.keySet().contains(NullTypeDesignationStatus.SINGLETON());
101
         boolean hasStatus2 = !ws2.keySet().contains(null) && !ws2.keySet().contains(NullTypeDesignationStatus.SINGLETON());
102
         if (hasStatus1 != hasStatus2){
103
             //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
104
             return hasStatus1? 1:-1;
105
         }
106

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

    
109
         Class<?> type1 = o1.getKey().getType();
110
         Class<?> type2 = o2.getKey().getType();
111

    
112
         if(!type1.equals(type2)) {
113
             if(type1.equals(FieldUnit.class) || type2.equals(FieldUnit.class)){
114
                 // FieldUnits first
115
                 return type1.equals(FieldUnit.class) ? -1 : 1;
116
             } else {
117
                 // name types last (in case of missing FieldUnit we expect the base type to be DerivedUnit which comes into the middle)
118
                 return type2.equals(TaxonName.class) || type2.equals(NameTypeDesignation.class) ? -1 : 1;
119
             }
120
         } else {
121
             return o1.getKey().getLabel().compareTo(o2.getKey().getLabel());
122
         }
123
     };
124

    
125
    /**
126
     * Groups the EntityReferences for each of the TypeDesignations by the according TypeDesignationStatus.
127
     * The TypeDesignationStatusBase keys are already ordered by the term order defined in the vocabulary.
128
     */
129
    private LinkedHashMap<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet> orderedByTypesByBaseEntity;
130

    
131
    private List<String> problems = new ArrayList<>();
132

    
133
// **************************** CONSTRUCTOR ***********************************/
134

    
135
    public TypeDesignationSetManager(@SuppressWarnings("rawtypes") Collection<TypeDesignationBase> typeDesignations)
136
            throws RegistrationValidationException{
137
    	this(typeDesignations, null);
138
    }
139

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

    
155
        mapAndSort();
156
    }
157

    
158
    public TypeDesignationSetManager(HomotypicalGroup group) {
159
        for (TypeDesignationBase<?> typeDes: group.getTypeDesignations()){
160
            this.typeDesignations.put(typeDes.getUuid(), typeDes);
161
        }
162
        //findTypifiedName();
163
        mapAndSort();
164
    }
165

    
166
    public TypeDesignationSetManager(TaxonName typifiedName) {
167
        this.typifiedName = typifiedName;
168
    }
169

    
170
// **************************************************************************/
171

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

    
186
    public TaxonName getTypifiedName() {
187
        return typifiedName;
188
    }
189

    
190
    public void setNameTypeBaseEntityType(NameTypeBaseEntityType nameTypeBaseEntityType){
191
        this.nameTypeBaseEntityType = nameTypeBaseEntityType;
192
    }
193

    
194
    public NameTypeBaseEntityType getNameTypeBaseEntityType(){
195
        return nameTypeBaseEntityType;
196
    }
197

    
198
// ******************************** METHODS *********************************/
199

    
200
    /**
201
     * Groups and orders all managed TypeDesignations.
202
     */
203
    protected void mapAndSort() {
204

    
205
        Map<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet> byBaseEntityByTypeStatus = new HashMap<>();
206
        this.typeDesignations.values().forEach(td -> mapTypeDesignation(byBaseEntityByTypeStatus, td));
207
        orderedByTypesByBaseEntity = orderByTypeByBaseEntity(byBaseEntityByTypeStatus);
208
    }
209

    
210
    private void mapTypeDesignation(Map<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet> byBaseEntityByTypeStatus,
211
            TypeDesignationBase<?> td){
212

    
213
        td = HibernateProxyHelper.deproxy(td);
214
        TypeDesignationStatusBase<?> status = td.getTypeStatus();
215

    
216
        try {
217
            final VersionableEntity baseEntity = baseEntity(td);
218
            final TypedEntityReference<? extends VersionableEntity> baseEntityReference = makeEntityReference(baseEntity);
219

    
220
            TaggedTextBuilder workingsetBuilder = new TaggedTextBuilder();
221
            boolean withCitation = true;
222
            TypeDesignationSetFormatter.buildTaggedTextForSingleType(td, withCitation, workingsetBuilder, 0);
223

    
224
            @SuppressWarnings({ "unchecked", "rawtypes" })
225
            TypeDesignationDTO<?> typeDesignationDTO
226
                = new TypeDesignationDTO(
227
                    td.getClass(),
228
                    td.getUuid(),
229
                    workingsetBuilder.getTaggedText(),
230
                    getTypeUuid(td));
231

    
232
            if(!byBaseEntityByTypeStatus.containsKey(baseEntityReference)){
233
                byBaseEntityByTypeStatus.put(baseEntityReference, new TypeDesignationWorkingSet(baseEntity, baseEntityReference));
234
            }
235
            byBaseEntityByTypeStatus.get(baseEntityReference).insert(status, typeDesignationDTO);
236

    
237
        } catch (DataIntegrityException e){
238
            problems.add(e.getMessage());
239
        }
240
    }
241

    
242

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

    
259
    protected VersionableEntity baseEntity(TypeDesignationBase<?> td) throws DataIntegrityException {
260

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

    
284
    protected static TypedEntityReference<? extends VersionableEntity> makeEntityReference(VersionableEntity baseEntity) {
285

    
286
        baseEntity = CdmBase.deproxy(baseEntity);
287
        String label = entityLabel(baseEntity);
288

    
289
        TypedEntityReference<? extends VersionableEntity> baseEntityReference =
290
                new TypedEntityReference<>(baseEntity.getClass(), baseEntity.getUuid(), label);
291

    
292
        return baseEntityReference;
293
    }
294

    
295
    //TODO move to formatter?
296
    protected static String entityLabel(VersionableEntity baseEntity) {
297
        String label = "";
298
        if(baseEntity instanceof IdentifiableEntity<?>){
299
                label = ((IdentifiableEntity<?>)baseEntity).getTitleCache();
300
        }
301
        return label;
302
    }
303

    
304
    private LinkedHashMap<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet> orderByTypeByBaseEntity(
305
            Map<TypedEntityReference<? extends VersionableEntity>,TypeDesignationWorkingSet> stringsByTypeByBaseEntity){
306

    
307
       // order the FieldUnit TypeName keys
308
       Set<Entry<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet>> entrySet
309
               = stringsByTypeByBaseEntity.entrySet();
310
       LinkedList<Entry<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet>> baseEntityKeyList
311
               = new LinkedList<>(entrySet);
312
       Collections.sort(baseEntityKeyList, entryComparator);
313

    
314
       // new LinkedHashMap for the ordered FieldUnitOrTypeName keys
315
       LinkedHashMap<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet> stringsOrderedbyBaseEntityOrderdByType
316
           = new LinkedHashMap<>(stringsByTypeByBaseEntity.size());
317

    
318
       for(Entry<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet> entry : baseEntityKeyList){
319
           TypedEntityReference<? extends VersionableEntity> baseEntityRef = entry.getKey();
320
           TypeDesignationWorkingSet typeDesignationWorkingSet = stringsByTypeByBaseEntity.get(baseEntityRef);
321
           // order the TypeDesignationStatusBase keys
322
            List<TypeDesignationStatusBase<?>> keyList = new LinkedList<>(typeDesignationWorkingSet.keySet());
323
            Collections.sort(keyList, new TypeDesignationStatusComparator());
324
            // new LinkedHashMap for the ordered TypeDesignationStatusBase keys
325
            TypeDesignationWorkingSet orderedStringsByOrderedTypes = new TypeDesignationWorkingSet(
326
                    typeDesignationWorkingSet.getBaseEntity());
327
            keyList.forEach(key -> orderedStringsByOrderedTypes.put(key, typeDesignationWorkingSet.get(key)));
328
            stringsOrderedbyBaseEntityOrderdByType.put(baseEntityRef, orderedStringsByOrderedTypes);
329
       }
330

    
331
        return stringsOrderedbyBaseEntityOrderdByType;
332
    }
333

    
334

    
335
    /**
336
     * FIXME use the validation framework validators to store the validation problems!!!
337
     *
338
     * @return
339
     * @throws RegistrationValidationException
340
     */
341
    private void findTypifiedName() throws RegistrationValidationException {
342

    
343
        List<String> problems = new ArrayList<>();
344

    
345
        TaxonName typifiedName = null;
346

    
347
        for(TypeDesignationBase<?> typeDesignation : typeDesignations.values()){
348
            typeDesignation.getTypifiedNames();
349
            if(typeDesignation.getTypifiedNames().isEmpty()){
350

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

    
377
        if(typifiedName != null){
378
            // ON SUCCESS -------------------
379
            this.typifiedName = typifiedName;
380
        }
381
    }
382

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

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

    
400
    public Collection<TypeDesignationBase<?>> getTypeDesignations() {
401
        return typeDesignations.values();
402
    }
403

    
404
    public TypeDesignationBase<?> findTypeDesignation(UUID uuid) {
405
        return this.typeDesignations.get(uuid);
406
    }
407

    
408
    public LinkedHashMap<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet> getOrderedTypeDesignationWorkingSets() {
409
        return orderedByTypesByBaseEntity;
410
    }
411

    
412
    private FieldUnit findFieldUnit(DerivedUnit du) {
413

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

    
435
        return null;
436
    }
437

    
438
    public String print(boolean withCitation, boolean withStartingTypeLabel, boolean withNameIfAvailable) {
439
        return new TypeDesignationSetFormatter(withCitation, withStartingTypeLabel, withNameIfAvailable).format(this);
440
    }
441

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

    
446

    
447
    class DataIntegrityException extends Exception {
448

    
449
        private static final long serialVersionUID = 1464726696296824905L;
450

    
451
        public DataIntegrityException(String string) {
452
            super(string);
453
        }
454
    }
455
}
(3-3/4)