Project

General

Profile

Download (35 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.Optional;
21
import java.util.Set;
22
import java.util.UUID;
23

    
24
import org.apache.commons.lang3.StringUtils;
25
import org.hibernate.search.hcore.util.impl.HibernateHelper;
26

    
27
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeCacheStrategy;
28
import eu.etaxonomy.cdm.api.service.exception.RegistrationValidationException;
29
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
30
import eu.etaxonomy.cdm.model.common.CdmBase;
31
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
32
import eu.etaxonomy.cdm.model.common.Language;
33
import eu.etaxonomy.cdm.model.common.TermVocabulary;
34
import eu.etaxonomy.cdm.model.common.VersionableEntity;
35
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
36
import eu.etaxonomy.cdm.model.name.NameTypeDesignation;
37
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
38
import eu.etaxonomy.cdm.model.name.TaxonName;
39
import eu.etaxonomy.cdm.model.name.TextualTypeDesignation;
40
import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
41
import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;
42
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
43
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
44
import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;
45
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
46
import eu.etaxonomy.cdm.model.reference.Reference;
47
import eu.etaxonomy.cdm.ref.EntityReference;
48
import eu.etaxonomy.cdm.ref.TypedEntityReference;
49
import eu.etaxonomy.cdm.strategy.cache.TagEnum;
50
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
51
import eu.etaxonomy.cdm.strategy.cache.TaggedTextBuilder;
52
import eu.etaxonomy.cdm.strategy.cache.reference.DefaultReferenceCacheStrategy;
53
/**
54
 * Manages a collection of {@link TypeDesignationBase TypeDesignations} for the same typified name.
55
 *
56
 * Type designations are ordered by the base type which is a {@link TaxonName} for {@link NameTypeDesignation NameTypeDesignations} or
57
 * in case of {@link SpecimenTypeDesignation SpecimenTypeDesignations} the  associate {@link FieldUnit} or the {@link DerivedUnit}
58
 * if the former is missing. The type designations per base type are furthermore ordered by the {@link TypeDesignationStatusBase}.
59
 *
60
 * The TypeDesignationSetManager also provides string representations of the whole ordered set of all
61
 * {@link TypeDesignationBase TypeDesignations} and of the TypeDesignationWorkingSets:
62
 * <ul>
63
 *  <li>{@link #print()}
64
 *  <li>{@link #getOrderdTypeDesignationWorkingSets()} ... {@link TypeDesignationWorkingSet#getRepresentation()}
65
 * </ul>
66
 * Prior using the representations you need to trigger their generation by calling {@link #buildString()}
67
 *
68
 * @author a.kohlbecker
69
 * @since Mar 10, 2017
70
 *
71
 */
72
public class TypeDesignationSetManager {
73

    
74
    enum NameTypeBaseEntityType{
75

    
76
        NAME_TYPE_DESIGNATION,
77
        TYPE_NAME;
78

    
79
    }
80

    
81
    private static final String TYPE_STATUS_SEPARATOR = "; ";
82

    
83
    private static final String TYPE_SEPARATOR = "; ";
84

    
85
    private static final String TYPE_DESIGNATION_SEPARATOR = ", ";
86
    private static final String TYPE_STATUS_SEPARATOR_WITHCITATION = ": ";
87
    private static final String TYPE_STATUS_PARENTHESIS_LEFT = " (";
88
    private static final String TYPE_STATUS_PARENTHESIS_RIGHT = ")";
89
    private static final String REFERENCE_PARENTHESIS_RIGHT = "]";
90
    private static final String REFERENCE_PARENTHESIS_LEFT = " [";
91
    private static final String REFERENCE_FIDE = "fide ";
92

    
93
    private Map<UUID,TypeDesignationBase<?>> typeDesignations;
94

    
95
    private NameTypeBaseEntityType nameTypeBaseEntityType = NameTypeBaseEntityType.NAME_TYPE_DESIGNATION;
96

    
97
    /**
98
     * Groups the EntityReferences for each of the TypeDesignations by the according TypeDesignationStatus.
99
     * The TypeDesignationStatusBase keys are already ordered by the term order defined in the vocabulary.
100
     */
101
    private LinkedHashMap<TypedEntityReference, TypeDesignationWorkingSet> orderedByTypesByBaseEntity;
102

    
103
    private EntityReference typifiedNameRef;
104

    
105
    private TaxonName typifiedName;
106

    
107
    private String finalString = null;
108

    
109
    final NullTypeDesignationStatus NULL_STATUS = new NullTypeDesignationStatus();
110

    
111
    private List<String> problems = new ArrayList<>();
112

    
113
    private boolean printCitation = false;
114
    private boolean useShortCitation = false;
115

    
116
    private List<TaggedText> taggedText;
117

    
118
    /**
119
     * @param containgEntity
120
     * @param taxonName
121
     * @throws RegistrationValidationException
122
     *
123
     */
124
    public TypeDesignationSetManager(Collection<TypeDesignationBase> typeDesignations) throws RegistrationValidationException {
125
        if (this.typeDesignations == null){
126
            this.typeDesignations = new HashMap<>();
127
        }
128
        for (TypeDesignationBase<?> typeDes:typeDesignations){
129
            this.typeDesignations.put(typeDes.getUuid(), typeDes);
130
        }
131
        findTypifiedName();
132
        mapAndSort();
133
    }
134

    
135
    /**
136
     * @param containgEntity
137
     * @param taxonName
138
     * @throws RegistrationValidationException
139
     *
140
     */
141
    public TypeDesignationSetManager(HomotypicalGroup group) throws RegistrationValidationException {
142
        if (this.typeDesignations == null){
143
            this.typeDesignations = new HashMap<>();
144
        }
145
        for (TypeDesignationBase<?> typeDes:group.getTypeDesignations()){
146
            this.typeDesignations.put(typeDes.getUuid(), typeDes);
147
        }
148
        //findTypifiedName();
149
        mapAndSort();
150
    }
151

    
152
    /**
153
     * @param typifiedName2
154
     */
155
    public TypeDesignationSetManager(TaxonName typifiedName) {
156
        this.typeDesignations = new HashMap<>();
157
        this.typifiedNameRef = new EntityReference(typifiedName.getUuid(), typifiedName.getTitleCache());
158
    }
159

    
160
    /**
161
     * Add one or more TypeDesignations to the manager. This causes re-grouping and re-ordering
162
     * of all managed TypeDesignations.
163
     *
164
     * @param containgEntity
165
     * @param typeDesignations
166
     */
167
    public void addTypeDesigations(CdmBase containgEntity, TypeDesignationBase ... typeDesignations){
168
        for (TypeDesignationBase<?> typeDes: typeDesignations){
169
            this.typeDesignations.put(typeDes.getUuid(), typeDes);
170
        }
171
       mapAndSort();
172
    }
173

    
174
    /**
175
     * Groups and orders all managed TypeDesignations.
176
     *
177
     * @param containgEntity
178
     */
179
    protected void mapAndSort() {
180
        finalString = null;
181
        Map<TypedEntityReference<?>, TypeDesignationWorkingSet> byBaseEntityByTypeStatus = new HashMap<>();
182

    
183
        this.typeDesignations.values().forEach(td -> mapTypeDesignation(byBaseEntityByTypeStatus, td));
184
        orderedByTypesByBaseEntity = orderByTypeByBaseEntity(byBaseEntityByTypeStatus);
185
    }
186

    
187

    
188
    /**
189
     * @param byBaseEntityByTypeStatus
190
     * @param td
191
     */
192
    private void mapTypeDesignation(Map<TypedEntityReference<?>, TypeDesignationWorkingSet> byBaseEntityByTypeStatus,
193
            TypeDesignationBase<?> td){
194

    
195
        TypeDesignationStatusBase<?> status = td.getTypeStatus();
196

    
197
        try {
198
            final VersionableEntity baseEntity = baseEntity(td);
199
            final TypedEntityReference<VersionableEntity> baseEntityReference = makeEntityReference(baseEntity);
200

    
201
            TypedEntityReference<?> typeDesignationEntityReference = new TypedEntityReference<>(
202
                    HibernateProxyHelper.deproxy(td).getClass(),
203
                    td.getUuid(),
204
                    stringify(td));
205

    
206
            if(!byBaseEntityByTypeStatus.containsKey(baseEntityReference)){
207
                byBaseEntityByTypeStatus.put(baseEntityReference, new TypeDesignationWorkingSet(baseEntity, baseEntityReference));
208
            }
209
            byBaseEntityByTypeStatus.get(baseEntityReference).insert(status, typeDesignationEntityReference);
210

    
211
        } catch (DataIntegrityException e){
212
            problems.add(e.getMessage());
213
        }
214
    }
215

    
216
    /**
217
     * @param td
218
     * @return
219
     * @throws DataIntegrityException
220
     */
221
    protected VersionableEntity baseEntity(TypeDesignationBase<?> td) throws DataIntegrityException {
222

    
223
        VersionableEntity baseEntity = null;
224
        if(td  instanceof SpecimenTypeDesignation){
225
            SpecimenTypeDesignation std = (SpecimenTypeDesignation) td;
226
            FieldUnit fu = findFieldUnit(std);
227
            if(fu != null){
228
                baseEntity = fu;
229
            } else if(((SpecimenTypeDesignation) td).getTypeSpecimen() != null){
230
                baseEntity = ((SpecimenTypeDesignation) td).getTypeSpecimen();
231
            }
232
        } else if(td instanceof NameTypeDesignation){
233
            if(nameTypeBaseEntityType == NameTypeBaseEntityType.NAME_TYPE_DESIGNATION){
234
                baseEntity = td;
235
            } else {
236
                // only other option is TaxonName
237
                baseEntity = ((NameTypeDesignation)td).getTypeName();
238
            }
239
        }
240
        if(baseEntity == null) {
241
            throw new DataIntegrityException("Incomplete TypeDesignation, no type missin in " + td.toString());
242
        }
243
        return baseEntity;
244
    }
245

    
246
    /**
247
     * @param td
248
     * @return
249
     */
250
    protected TypedEntityReference<VersionableEntity> makeEntityReference(VersionableEntity baseEntity) {
251

    
252
        baseEntity = (VersionableEntity) HibernateHelper.unproxy(baseEntity);
253
        String label = "";
254
        if(baseEntity  instanceof FieldUnit){
255
                label = ((FieldUnit)baseEntity).getTitleCache();
256
        }
257

    
258
        TypedEntityReference<VersionableEntity> baseEntityReference = new TypedEntityReference(baseEntity.getClass(), baseEntity.getUuid(), label);
259

    
260
        return baseEntityReference;
261
    }
262

    
263

    
264
    private LinkedHashMap<TypedEntityReference, TypeDesignationWorkingSet> orderByTypeByBaseEntity(
265
            Map<TypedEntityReference<?>, TypeDesignationWorkingSet> stringsByTypeByBaseEntity){
266

    
267
       // order the FieldUnit TypeName keys
268
       List<TypedEntityReference<?>> baseEntityKeyList = new LinkedList<>(stringsByTypeByBaseEntity.keySet());
269
       Collections.sort(baseEntityKeyList, new Comparator<TypedEntityReference<?>>(){
270
        /**
271
         * Sorts the base entities (TypedEntityReference) in the following order:
272
         *
273
         * 1. FieldUnits
274
         * 2. DerivedUnit (in case of missing FieldUnit we expect the base type to be DerivedUnit)
275
         * 3. NameType
276
         *
277
         * {@inheritDoc}
278
         */
279
        @Override
280
        public int compare(TypedEntityReference<?> o1, TypedEntityReference<?> o2) {
281

    
282
            Class<?> type1 = o1.getType();
283
            Class<?> type2 = o2.getType();
284

    
285
            if(!type1.equals(type2)) {
286
                if(type1.equals(FieldUnit.class) || type2.equals(FieldUnit.class)){
287
                    // FieldUnits first
288
                    return type1.equals(FieldUnit.class) ? -1 : 1;
289
                } else {
290
                    // name types last (in case of missing FieldUnit we expect the base type to be DerivedUnit which comes into the middle)
291
                    return type2.equals(TaxonName.class) || type2.equals(NameTypeDesignation.class) ? -1 : 1;
292
                }
293
            } else {
294
                return o1.getLabel().compareTo(o2.getLabel());
295
            }
296
        }});
297

    
298
       // new LinkedHashMap for the ordered FieldUnitOrTypeName keys
299
       LinkedHashMap<TypedEntityReference, TypeDesignationWorkingSet> stringsOrderedbyBaseEntityOrderdByType = new LinkedHashMap<>(stringsByTypeByBaseEntity.size());
300

    
301
       for(TypedEntityReference baseEntityRef : baseEntityKeyList){
302

    
303
           TypeDesignationWorkingSet typeDesignationWorkingSet = stringsByTypeByBaseEntity.get(baseEntityRef);
304
           // order the TypeDesignationStatusBase keys
305
            List<TypeDesignationStatusBase<?>> keyList = new LinkedList<>(typeDesignationWorkingSet.keySet());
306
            Collections.sort(keyList, new TypeDesignationStatusComparator());
307
            // new LinkedHashMap for the ordered TypeDesignationStatusBase keys
308
            TypeDesignationWorkingSet orderedStringsByOrderedTypes = new TypeDesignationWorkingSet(
309
                    typeDesignationWorkingSet.getBaseEntity(),
310
                    baseEntityRef);
311
            keyList.forEach(key -> orderedStringsByOrderedTypes.put(key, typeDesignationWorkingSet.get(key)));
312
            stringsOrderedbyBaseEntityOrderdByType.put(baseEntityRef, orderedStringsByOrderedTypes);
313
       }
314

    
315
        return stringsOrderedbyBaseEntityOrderdByType;
316
    }
317

    
318
    /*
319
    private LinkedHashMap<TypedEntityReference, LinkedHashMap<String, Collection<EntityReference>>> buildOrderedRepresentations(){
320

    
321
        orderedStringsByOrderedTypes.keySet().forEach(
322
                key -> orderedRepresentations.put(
323
                        getTypeDesignationStytusLabel(key),
324
                        orderedStringsByOrderedTypes.get(key))
325
                );
326
        return orderedRepresentations;
327
    }
328
*/
329

    
330
    public void buildString(){
331

    
332
        if(finalString == null){
333

    
334
            TaggedTextBuilder finalBuilder = new TaggedTextBuilder();
335
            finalString = "";
336

    
337
            if(getTypifiedNameCache() != null){
338
                finalString += getTypifiedNameCache() + " ";
339
                finalBuilder.add(TagEnum.name, getTypifiedNameCache(), new TypedEntityReference<>(TaxonName.class, getTypifiedNameRef().getUuid()));
340
            }
341

    
342
            int typeCount = 0;
343
            if(orderedByTypesByBaseEntity != null){
344
                for(TypedEntityReference<?> baseEntityRef : orderedByTypesByBaseEntity.keySet()) {
345

    
346
                    TaggedTextBuilder workingsetBuilder = new TaggedTextBuilder();
347
                    if(typeCount++ > 0){
348
                        workingsetBuilder.add(TagEnum.separator, TYPE_SEPARATOR);
349
                    }
350
                    boolean isNameTypeDesignation = false;
351
                    if(SpecimenOrObservationBase.class.isAssignableFrom(baseEntityRef.getType()) ){
352
                        workingsetBuilder.add(TagEnum.label, "Type:");
353
                    } else{
354
                        workingsetBuilder.add(TagEnum.label, "NameType:");
355
                        isNameTypeDesignation = true;
356
                    }
357
                    if(!baseEntityRef.getLabel().isEmpty()){
358
                        workingsetBuilder.add(TagEnum.specimenOrObservation, baseEntityRef.getLabel(), baseEntityRef);
359
                    }
360
                    TypeDesignationWorkingSet typeDesignationWorkingSet = orderedByTypesByBaseEntity.get(baseEntityRef);
361
                    int typeStatusCount = 0;
362
                    for(TypeDesignationStatusBase<?> typeStatus : typeDesignationWorkingSet.keySet()) {
363
                        if(typeStatusCount++  > 0){
364
                            workingsetBuilder.add(TagEnum.separator, TYPE_STATUS_SEPARATOR);
365

    
366
                        }
367
                        boolean isPlural = typeDesignationWorkingSet.get(typeStatus).size() > 1;
368
                        if(!typeStatus.equals(NULL_STATUS)) {
369

    
370
                            workingsetBuilder.add(TagEnum.label, typeStatus.getLabel() + (isPlural ? "s:" : ","));
371
                         }
372

    
373

    
374
                        int typeDesignationCount = 0;
375
                        for(TypedEntityReference<?> typeDesignationEntityReference : createSortedList(typeDesignationWorkingSet, typeStatus)) {
376

    
377
                            if(typeDesignationCount++  > 0){
378
                               workingsetBuilder.add(TagEnum.separator, TYPE_DESIGNATION_SEPARATOR);
379
                            }
380

    
381
                            workingsetBuilder.add(TagEnum.typeDesignation, typeDesignationEntityReference.getLabel(), typeDesignationEntityReference);
382
                        }
383

    
384
                    }
385
                    typeDesignationWorkingSet.setRepresentation(workingsetBuilder.toString());
386
                    finalString += typeDesignationWorkingSet.getRepresentation();
387
                    finalBuilder.addAll(workingsetBuilder);
388
                }
389
            }
390
            finalString = finalString.trim();
391
            taggedText = finalBuilder.getTaggedText();
392
        }
393
    }
394

    
395
    public void buildStringWithCitation(){
396

    
397
        if(finalString == null){
398

    
399
            TaggedTextBuilder finalBuilder = new TaggedTextBuilder();
400
            finalString = "";
401

    
402
            if(getTypifiedNameCache() != null){
403
                finalString += getTypifiedNameCache() + " ";
404
                finalBuilder.add(TagEnum.name, getTypifiedNameCache(), new TypedEntityReference<>(TaxonName.class, getTypifiedNameRef().getUuid()));
405
            }
406

    
407
            int typeCount = 0;
408
            if(orderedByTypesByBaseEntity != null){
409
                for(TypedEntityReference<?> baseEntityRef : orderedByTypesByBaseEntity.keySet()) {
410

    
411
                    TaggedTextBuilder workingsetBuilder = new TaggedTextBuilder();
412
                    if(typeCount++ > 0){
413
                        workingsetBuilder.add(TagEnum.separator, TYPE_SEPARATOR);
414
                    }
415
                    boolean isNameTypeDesignation = false;
416

    
417
                    if(!baseEntityRef.getLabel().isEmpty()){
418
                        workingsetBuilder.add(TagEnum.specimenOrObservation, baseEntityRef.getLabel(), baseEntityRef);
419
                    }
420
                    TypeDesignationWorkingSet typeDesignationWorkingSet = orderedByTypesByBaseEntity.get(baseEntityRef);
421
                    int typeStatusCount = 0;
422
                    for(TypeDesignationStatusBase<?> typeStatus : typeDesignationWorkingSet.keySet()) {
423
                        if(typeStatusCount++  > 0){
424
                            workingsetBuilder.add(TagEnum.separator, TYPE_STATUS_SEPARATOR);
425

    
426
                        }
427
                        boolean isPlural = typeDesignationWorkingSet.get(typeStatus).size() > 1;
428
                        if(!typeStatus.equals(NULL_STATUS)) {
429
                            workingsetBuilder.add(TagEnum.separator, TYPE_STATUS_PARENTHESIS_LEFT);
430
                            workingsetBuilder.add(TagEnum.label, typeStatus.getLabel() + (isPlural ? "s:" : ":"));
431
                         }
432
                        int typeDesignationCount = 0;
433
                        for(TypedEntityReference<?> typeDesignationEntityReference : createSortedList(typeDesignationWorkingSet, typeStatus)) {
434
                            if(typeDesignationCount++  > 0){
435
                               workingsetBuilder.add(TagEnum.separator, TYPE_DESIGNATION_SEPARATOR);
436
                            }
437

    
438
                            workingsetBuilder.add(TagEnum.typeDesignation, typeDesignationEntityReference.getLabel(), typeDesignationEntityReference);
439

    
440
                            TypeDesignationBase<?> typeDes =  typeDesignations.get(typeDesignationEntityReference.getUuid());
441
                            if (typeDes.getCitation() != null){
442
                               // workingsetBuilder.add(TagEnum.separator, REFERENCE_PARENTHESIS_LEFT);
443
                                String shortCitation = ((DefaultReferenceCacheStrategy)typeDes.getCitation().getCacheStrategy()).createShortCitation(typeDes.getCitation());
444
                                workingsetBuilder.add(TagEnum.reference, shortCitation, typeDesignationEntityReference);
445
                                //workingsetBuilder.add(TagEnum.separator, REFERENCE_PARENTHESIS_RIGHT);
446
                            }
447

    
448
                            if ((!typeStatus.equals(NULL_STATUS)) &&(typeDesignationCount ==  typeDesignationWorkingSet.get(typeStatus).size())){
449
                                workingsetBuilder.add(TagEnum.separator, TYPE_STATUS_PARENTHESIS_RIGHT);
450
                            }
451
                        }
452

    
453
                    }
454
                    typeDesignationWorkingSet.setRepresentation(workingsetBuilder.toString());
455
                    finalString += typeDesignationWorkingSet.getRepresentation();
456
                    finalBuilder.addAll(workingsetBuilder);
457
                }
458
            }
459
            finalString = finalString.trim();
460
            taggedText = finalBuilder.getTaggedText();
461
        }
462
    }
463

    
464
    /**
465
     * @param typeDesignationWorkingSet
466
     * @param typeStatus
467
     * @return
468
     */
469
    private List<TypedEntityReference<TypeDesignationBase<?>>> createSortedList(
470
            TypeDesignationWorkingSet typeDesignationWorkingSet, TypeDesignationStatusBase<?> typeStatus) {
471
        List<TypedEntityReference<TypeDesignationBase<?>>> typeDesignationEntityReferences = new ArrayList(typeDesignationWorkingSet.get(typeStatus));
472
        Collections.sort(typeDesignationEntityReferences, new TypedEntityComparator());
473
        return typeDesignationEntityReferences;
474
    }
475

    
476

    
477
    /**
478
     * FIXME use the validation framework validators to store the validation problems!!!
479
     *
480
     * @return
481
     * @throws RegistrationValidationException
482
     */
483
    private void findTypifiedName() throws RegistrationValidationException {
484

    
485
        List<String> problems = new ArrayList<>();
486

    
487
        TaxonName typifiedName = null;
488

    
489
        for(TypeDesignationBase<?> typeDesignation : typeDesignations.values()){
490
            typeDesignation.getTypifiedNames();
491
            if(typeDesignation.getTypifiedNames().isEmpty()){
492

    
493
                //TODO instead throw RegistrationValidationException()
494
                problems.add("Missing typifiedName in " + typeDesignation.toString());
495
                continue;
496
            }
497
            if(typeDesignation.getTypifiedNames().size() > 1){
498
              //TODO instead throw RegistrationValidationException()
499
                problems.add("Multiple typifiedName in " + typeDesignation.toString());
500
                continue;
501
            }
502
            if(typifiedName == null){
503
                // remember
504
                typifiedName = typeDesignation.getTypifiedNames().iterator().next();
505
            } else {
506
                // compare
507
                TaxonName otherTypifiedName = typeDesignation.getTypifiedNames().iterator().next();
508
                if(!typifiedName.getUuid().equals(otherTypifiedName.getUuid())){
509
                  //TODO instead throw RegistrationValidationException()
510
                    problems.add("Multiple typifiedName in " + typeDesignation.toString());
511
                }
512
            }
513

    
514
        }
515
        if(!problems.isEmpty()){
516
            // FIXME use the validation framework
517
            throw new RegistrationValidationException("Inconsistent type designations", problems);
518
        }
519

    
520
        if(typifiedName != null){
521
            // ON SUCCESS -------------------
522
            this.typifiedName = typifiedName;
523
            this.typifiedNameRef = new EntityReference(typifiedName.getUuid(), typifiedName.getTitleCache());
524

    
525
        }
526
    }
527

    
528

    
529
    /**
530
     * @return the title cache of the typifying name or <code>null</code>
531
     */
532
    public String getTypifiedNameCache() {
533
        if(typifiedNameRef != null){
534
            return typifiedNameRef.getLabel();
535
        }
536
        return null;
537
    }
538

    
539
    /**
540
     * @return the title cache of the typifying name or <code>null</code>
541
     */
542
    public EntityReference getTypifiedNameRef() {
543

    
544
       return typifiedNameRef;
545
    }
546

    
547
    /**
548
     * @return
549
     */
550
    public Collection<TypeDesignationBase<?>> getTypeDesignations() {
551
        return typeDesignations.values();
552
    }
553

    
554
    /**
555
     * @param ref
556
     * @return
557
     */
558
    public TypeDesignationBase<?> findTypeDesignation(EntityReference typeDesignationRef) {
559
        return this.typeDesignations.get(typeDesignationRef.getUuid());
560
    }
561

    
562

    
563
    public LinkedHashMap<TypedEntityReference, TypeDesignationWorkingSet> getOrderdTypeDesignationWorkingSets() {
564
        return orderedByTypesByBaseEntity;
565
    }
566

    
567
    /**
568
     * @param td
569
     * @return
570
     */
571
    private String stringify(TypeDesignationBase<?> td) {
572

    
573
        if(td instanceof NameTypeDesignation){
574
            return stringify((NameTypeDesignation)td);
575
        } else if (td instanceof TextualTypeDesignation){
576
            return stringify((TextualTypeDesignation)td);
577
        } else if (td instanceof SpecimenTypeDesignation){
578
            return stringify((SpecimenTypeDesignation)td, false);
579
        }else{
580
            throw new RuntimeException("Unknown TypeDesignation type");
581
        }
582
    }
583

    
584
    protected String stringify(TextualTypeDesignation td) {
585
        String result = td.getPreferredText(Language.DEFAULT());
586
        if (td.isVerbatim()){
587
            result = "\"" + result + "\"";  //TODO which character to use?
588
        }
589
        return result;
590
    }
591

    
592

    
593
    /**
594
     * @param td
595
     * @return
596
     */
597
    protected String stringify(NameTypeDesignation td) {
598

    
599
        StringBuffer sb = new StringBuffer();
600

    
601
        if(td.getTypeName() != null){
602
            sb.append(td.getTypeName().getTitleCache());
603
        }
604
        if(td.getCitation() != null){
605
            sb.append(" ").append(td.getCitation().getTitleCache());
606
            if(td.getCitationMicroReference() != null){
607
                sb.append(":").append(td.getCitationMicroReference());
608
            }
609
        }
610
        if(td.isNotDesignated()){
611
            sb.append(" not designated");
612
        }
613
        if(td.isRejectedType()){
614
            sb.append(" rejected");
615
        }
616
        if(td.isConservedType()){
617
            sb.append(" conserved");
618
        }
619
        return sb.toString();
620
    }
621

    
622
    /**
623
     * @param td
624
     * @return
625
     */
626
    private String stringify(SpecimenTypeDesignation td, boolean useFullTitleCache) {
627
        String  result = "";
628

    
629
        if(useFullTitleCache){
630
            if(td.getTypeSpecimen() != null){
631
                String nameTitleCache = td.getTypeSpecimen().getTitleCache();
632
                if(getTypifiedNameCache() != null){
633
                    nameTitleCache = nameTitleCache.replace(getTypifiedNameCache(), "");
634
                }
635
                result += nameTitleCache;
636
            }
637
        } else {
638
            if(td.getTypeSpecimen() != null){
639
                DerivedUnit du = td.getTypeSpecimen();
640
                if(du.isProtectedTitleCache()){
641
                    result += du.getTitleCache();
642
                } else {
643
                    du = HibernateProxyHelper.deproxy(du);
644
                    boolean isMediaSpecimen = du instanceof MediaSpecimen;
645
                    String typeSpecimenTitle = "";
646
                    if(isMediaSpecimen && HibernateProxyHelper.deproxyOrNull(du.getCollection()) == null) {
647
                        // special case of an published image which is not covered by the DerivedUnitFacadeCacheStrategy
648
                        MediaSpecimen msp = (MediaSpecimen)du;
649
                        if(msp.getMediaSpecimen() != null){
650
                            for(IdentifiableSource source : msp.getMediaSpecimen().getSources()){
651
                                String refDetailStr = source.getCitationMicroReference();
652
                                String referenceStr = source.getCitation() == null? "": source.getCitation().getTitleCache();
653
                                if(StringUtils.isNotBlank(source.getCitationMicroReference())){
654
                                    typeSpecimenTitle += refDetailStr;
655
                                }
656
                                if(!typeSpecimenTitle.isEmpty() && !referenceStr.isEmpty()){
657
                                    typeSpecimenTitle += " in ";
658
                                }
659
                                typeSpecimenTitle += referenceStr + " ";
660
                            }
661
                        }
662
                    } else {
663
                        DerivedUnitFacadeCacheStrategy cacheStrategy = new DerivedUnitFacadeCacheStrategy();
664
                        typeSpecimenTitle += cacheStrategy.getTitleCache(du, true);
665

    
666
                    }
667

    
668
                    result += (isMediaSpecimen ? "[icon] " : "") + typeSpecimenTitle.trim();
669
                }
670
            }
671
        }
672

    
673
        if(isPrintCitation() && td.getCitation() != null){
674
            Reference citation = HibernateProxyHelper.deproxy(td.getCitation(), Reference.class);
675
            if(citation.getAbbrevTitle() != null){
676

    
677
                result += " " + citation.getAbbrevTitle();
678
            } else {
679
                result += " " + citation.getTitleCache();
680
            }
681
            if(td.getCitationMicroReference() != null){
682
                result += " :" + td.getCitationMicroReference();
683
            }
684
        }
685
        if(td.isNotDesignated()){
686
            result += " not designated";
687
        }
688

    
689
        return result;
690
    }
691

    
692
    /**
693
     * @param td
694
     * @return
695
     * @deprecated
696
     */
697
    @Deprecated
698
    private FieldUnit findFieldUnit(SpecimenTypeDesignation td) {
699

    
700
        DerivedUnit du = td.getTypeSpecimen();
701
        return findFieldUnit(du);
702
    }
703

    
704
    private FieldUnit findFieldUnit(DerivedUnit du) {
705

    
706
        if(du == null || du.getOriginals() == null){
707
            return null;
708
        }
709
        @SuppressWarnings("rawtypes")
710
        Set<SpecimenOrObservationBase> originals = du.getDerivedFrom().getOriginals();
711
        @SuppressWarnings("rawtypes")
712
        Optional<SpecimenOrObservationBase> fieldUnit = originals.stream()
713
                .filter(original -> original instanceof FieldUnit).findFirst();
714
        if (fieldUnit.isPresent()) {
715
            return (FieldUnit) fieldUnit.get();
716
        } else {
717
            for (@SuppressWarnings("rawtypes")
718
            SpecimenOrObservationBase sob : originals) {
719
                if (sob instanceof DerivedUnit) {
720
                    FieldUnit fu = findFieldUnit((DerivedUnit) sob);
721
                    if (fu != null) {
722
                        return fu;
723
                    }
724
                }
725
            }
726
        }
727

    
728
        return null;
729
    }
730

    
731
    public String print() {
732
        buildString();
733
        return finalString;
734
    }
735

    
736
    public List<TaggedText> toTaggedText() {
737
        buildString();
738
        return taggedText;
739
    }
740

    
741
    public List<TaggedText> toTaggedTextWithCitation() {
742
        buildStringWithCitation();
743
        return taggedText;
744
    }
745

    
746

    
747
    /**
748
     * @return the printCitation
749
     */
750
    public boolean isPrintCitation() {
751
        return printCitation;
752
    }
753

    
754
    /**
755
     * @param printCitation the printCitation to set
756
     */
757
    public void setPrintCitation(boolean printCitation) {
758
        this.printCitation = printCitation;
759
    }
760

    
761
    /**
762
     * @return the typifiedName
763
     */
764
    public TaxonName getTypifiedName() {
765
        return typifiedName;
766
    }
767

    
768
    public void setNameTypeBaseEntityType(NameTypeBaseEntityType nameTypeBaseEntityType){
769
        this.nameTypeBaseEntityType = nameTypeBaseEntityType;
770
    }
771

    
772
    public NameTypeBaseEntityType getNameTypeBaseEntityType(){
773
        return nameTypeBaseEntityType;
774
    }
775

    
776
    public boolean isUseShortCitation() {
777
        return useShortCitation;
778
    }
779

    
780
    public void setUseShortCitation(boolean useShortCitation) {
781
        this.useShortCitation = useShortCitation;
782
    }
783

    
784
    /**
785
     * TypeDesignations which refer to the same FieldUnit (SpecimenTypeDesignation) or TaxonName
786
     * (NameTypeDesignation) form a working set. The <code>TypeDesignationWorkingSet</code> internally
787
     * works with EnityReferences to the actual TypeDesignations.
788
     *
789
     * The EntityReferences for TypeDesignations are grouped by the according TypeDesignationStatus.
790
     * The TypeDesignationStatusBase keys can be ordered by the term order defined in the vocabulary.
791
     *
792
     * A workingset can be referenced by the <code>baseEntityReference</code>.
793
     */
794
    public class TypeDesignationWorkingSet extends LinkedHashMap<TypeDesignationStatusBase<?>, Collection<TypedEntityReference>> {
795

    
796
        private static final long serialVersionUID = -1329007606500890729L;
797

    
798
        String workingSetRepresentation = null;
799

    
800
        TypedEntityReference<VersionableEntity> baseEntityReference;
801

    
802
        VersionableEntity baseEntity;
803

    
804
        List<DerivedUnit> derivedUnits = null;
805

    
806
        /**
807
         * @param baseEntityReference
808
         */
809
        public TypeDesignationWorkingSet(VersionableEntity baseEntity, TypedEntityReference<VersionableEntity> baseEntityReference) {
810
            this.baseEntity = baseEntity;
811
            this.baseEntityReference = baseEntityReference;
812
        }
813

    
814
        /**
815
         * @return
816
         */
817
        public VersionableEntity getBaseEntity() {
818
            return baseEntity;
819
        }
820

    
821
        public List<TypedEntityReference> getTypeDesignations() {
822
            List<TypedEntityReference> typeDesignations = new ArrayList<>();
823
            this.values().forEach(typeDesignationReferences -> typeDesignationReferences.forEach(td -> typeDesignations.add(td)));
824
            return typeDesignations;
825
        }
826

    
827

    
828

    
829
        /**
830
         * @param status
831
         * @param typeDesignationEntityReference
832
         */
833
        public void insert(TypeDesignationStatusBase<?> status, TypedEntityReference typeDesignationEntityReference) {
834

    
835
            if(status == null){
836
                status = NULL_STATUS;
837
            }
838
            if(!containsKey(status)){
839
                put(status, new ArrayList<>());
840
            }
841
            get(status).add(typeDesignationEntityReference);
842
        }
843

    
844

    
845
        public String getRepresentation() {
846
            return workingSetRepresentation;
847
        }
848

    
849
        public void setRepresentation(String representation){
850
            this.workingSetRepresentation = representation;
851
        }
852

    
853
        /**
854
         * A reference to the entity which is the common base entity for all TypeDesignations in this workingset.
855
         * For a {@link SpecimenTypeDesignation} this is usually the {@link FieldUnit} if it is present. Otherwise it can also be
856
         * a {@link DerivedUnit} or something else depending on the specific use case.
857
         *
858
         * @return the baseEntityReference
859
         */
860
        public TypedEntityReference<VersionableEntity> getBaseEntityReference() {
861
            return baseEntityReference;
862
        }
863

    
864
        @Override
865
        public String toString(){
866
            if(workingSetRepresentation != null){
867
                return workingSetRepresentation;
868
            } else {
869
                return super.toString();
870
            }
871
        }
872

    
873
        /**
874
         * @return
875
         */
876
        public boolean isSpecimenTypeDesigationWorkingSet() {
877
            return SpecimenOrObservationBase.class.isAssignableFrom(baseEntityReference.getType());
878
        }
879

    
880
        public TypeDesignationWorkingSetType getWorkingsetType() {
881
            return isSpecimenTypeDesigationWorkingSet() ? TypeDesignationWorkingSetType.SPECIMEN_TYPE_DESIGNATION_WORKINGSET : TypeDesignationWorkingSetType.NAME_TYPE_DESIGNATION_WORKINGSET;
882
        }
883

    
884
    }
885

    
886
    public enum TypeDesignationWorkingSetType {
887
        SPECIMEN_TYPE_DESIGNATION_WORKINGSET,
888
        NAME_TYPE_DESIGNATION_WORKINGSET,
889
    }
890

    
891
    @SuppressWarnings({ "deprecation", "serial" })
892
    class NullTypeDesignationStatus extends TypeDesignationStatusBase<NullTypeDesignationStatus>{
893

    
894
        @Override
895
        public void resetTerms() {}
896

    
897
        @Override
898
        protected void setDefaultTerms(TermVocabulary<NullTypeDesignationStatus> termVocabulary) {}
899

    
900
        @Override
901
        protected void setDefaultTerms(TermVocabulary<NullTypeDesignationStatus> termVocabulary) {
902
            // empty
903
        }
904

    
905
    }
906

    
907
    class DataIntegrityException extends Exception {
908

    
909
        private static final long serialVersionUID = 1464726696296824905L;
910

    
911
        /**
912
         * @param string
913
         */
914
        public DataIntegrityException(String string) {
915
            super(string);
916
        }
917
    }
918
}
(2-2/4)