Project

General

Profile

Download (19.3 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2020 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.Collections;
13
import java.util.List;
14
import java.util.Map;
15
import java.util.UUID;
16

    
17
import org.apache.commons.lang3.StringUtils;
18

    
19
import eu.etaxonomy.cdm.api.service.name.TypeDesignationSet.TypeDesignationSetType;
20
import eu.etaxonomy.cdm.common.CdmUtils;
21
import eu.etaxonomy.cdm.common.UTF8;
22
import eu.etaxonomy.cdm.format.reference.OriginalSourceFormatter;
23
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
24
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
25
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
26
import eu.etaxonomy.cdm.model.common.Language;
27
import eu.etaxonomy.cdm.model.common.VersionableEntity;
28
import eu.etaxonomy.cdm.model.name.NameTypeDesignation;
29
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
30
import eu.etaxonomy.cdm.model.name.TextualTypeDesignation;
31
import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
32
import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;
33
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
34
import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;
35
import eu.etaxonomy.cdm.model.reference.OriginalSourceBase;
36
import eu.etaxonomy.cdm.model.reference.Reference;
37
import eu.etaxonomy.cdm.ref.TypedEntityReference;
38
import eu.etaxonomy.cdm.strategy.cache.HTMLTagRules;
39
import eu.etaxonomy.cdm.strategy.cache.TagEnum;
40
import eu.etaxonomy.cdm.strategy.cache.TaggedCacheHelper;
41
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
42
import eu.etaxonomy.cdm.strategy.cache.TaggedTextBuilder;
43
import eu.etaxonomy.cdm.strategy.cache.occurrence.DerivedUnitDefaultCacheStrategy;
44

    
45
/**
46
 * @author a.mueller
47
 * @since 24.11.2020
48
 */
49
public class TypeDesignationSetFormatter {
50

    
51
    private static final String TYPE_STATUS_SEPARATOR = "; ";
52
    private static final String TYPE_SEPARATOR = "; ";
53
    private static final String TYPE_DESIGNATION_SEPARATOR = ", ";
54
    private static final String TYPE_STATUS_PARENTHESIS_LEFT = " (";
55
    private static final String TYPE_STATUS_PARENTHESIS_RIGHT = ")";
56
    private static final String REFERENCE_PARENTHESIS_RIGHT = "]";
57
    private static final String REFERENCE_PARENTHESIS_LEFT = " [";
58
    private static final String REFERENCE_DESIGNATED_BY = " designated by ";
59
    private static final String REFERENCE_FIDE = "fide ";
60
    private static final String SOURCE_SEPARATOR = ", ";
61
    private static final String POST_STATUS_SEPARATOR = ": ";
62
    private static final String POST_NAME_SEPARTOR = UTF8.EN_DASH_SPATIUM.toString();
63

    
64
    private boolean withCitation;
65
    private boolean withStartingTypeLabel;
66
    private boolean withNameIfAvailable;
67

    
68
    public static String entityLabel(VersionableEntity baseEntity) {
69
        String label = "";
70
        if(baseEntity instanceof IdentifiableEntity<?>){
71
            label = ((IdentifiableEntity<?>)baseEntity).getTitleCache();
72
        }
73
        //TODO
74
//        else {
75
//            label = baseEntity.toString();
76
//        }
77
        return label;
78
    }
79

    
80
    public TypeDesignationSetFormatter(boolean withCitation, boolean withStartingTypeLabel,
81
            boolean withNameIfAvailable) {
82
        this.withCitation = withCitation;
83
        this.withStartingTypeLabel = withStartingTypeLabel;
84
        this.withNameIfAvailable = withNameIfAvailable;
85
    }
86

    
87
    public String format(TypeDesignationSetManager manager){
88
        return TaggedCacheHelper.createString(toTaggedText(manager));
89
    }
90

    
91
    public String format(TypeDesignationSetManager manager, HTMLTagRules htmlTagRules){
92
        return TaggedCacheHelper.createString(toTaggedText(manager), htmlTagRules);
93
    }
94

    
95
    public List<TaggedText> toTaggedText(TypeDesignationSetManager manager){
96
        return buildTaggedText(manager);
97
    }
98

    
99
    private List<TaggedText> buildTaggedText(TypeDesignationSetManager manager){
100
        boolean withBrackets = true;  //still unclear if this should become a parameter or should be always true
101

    
102
        TaggedTextBuilder finalBuilder = new TaggedTextBuilder();
103

    
104
        if(withNameIfAvailable && manager.getTypifiedNameCache() != null){
105
            finalBuilder.add(TagEnum.name, manager.getTypifiedNameCache(), TypedEntityReference.fromEntity(manager.getTypifiedName(), false));
106
            finalBuilder.addPostSeparator(POST_NAME_SEPARTOR);
107
        }
108

    
109
        int typeSetCount = 0;
110
        Map<VersionableEntity,TypeDesignationSet> orderedByTypesByBaseEntity
111
                    = manager.getOrderedTypeDesignationSets();
112
        TypeDesignationSetType lastWsType = null;
113
        if (orderedByTypesByBaseEntity != null){
114
            for(VersionableEntity baseEntity : orderedByTypesByBaseEntity.keySet()) {
115
                buildTaggedTextForSingleTypeSet(manager, withBrackets, finalBuilder,
116
                        typeSetCount, baseEntity, lastWsType);
117
                lastWsType = orderedByTypesByBaseEntity.get(baseEntity).getWorkingsetType();
118
                typeSetCount++;
119
            }
120
        }
121
        return finalBuilder.getTaggedText();
122
    }
123

    
124
    private void buildTaggedTextForSingleTypeSet(TypeDesignationSetManager manager, boolean withBrackets,
125
            TaggedTextBuilder finalBuilder, int typeSetCount, VersionableEntity baseEntity, TypeDesignationSetType lastWsType) {
126

    
127
        Map<VersionableEntity,TypeDesignationSet>
128
                orderedByTypesByBaseEntity = manager.getOrderedTypeDesignationSets();
129
        TypeDesignationSet typeDesignationSet = orderedByTypesByBaseEntity.get(baseEntity);
130

    
131
        TaggedTextBuilder workingsetBuilder = new TaggedTextBuilder();
132
        if(typeSetCount > 0){
133
            workingsetBuilder.add(TagEnum.separator, TYPE_SEPARATOR);
134
        }else if (withStartingTypeLabel){
135
            //TODO this is not really exact as we may want to handle specimen types and
136
            //name types separately, but this is such a rare case (if at all) and
137
            //increases complexity so it is not yet implemented
138
            boolean isPlural = hasMultipleTypes(orderedByTypesByBaseEntity);
139
            if(typeDesignationSet.getWorkingsetType().isSpecimenType()){
140
                workingsetBuilder.add(TagEnum.label, (isPlural? "Types:": "Type:"));
141
            } else if (typeDesignationSet.getWorkingsetType().isNameType()){
142
                workingsetBuilder.add(TagEnum.label, (isPlural? "Nametypes:": "Nametype:"));
143
            } else {
144
                //do nothing for now
145
            }
146
        }
147

    
148
        boolean hasExplicitBaseEntity = hasExplicitBaseEntity(baseEntity, typeDesignationSet);
149
        if(hasExplicitBaseEntity && !entityLabel(baseEntity).isEmpty()){
150
            workingsetBuilder.add(TagEnum.specimenOrObservation, entityLabel(baseEntity), baseEntity);
151
        }
152
        int typeStatusCount = 0;
153
        if (withBrackets && hasExplicitBaseEntity){
154
            workingsetBuilder.add(TagEnum.separator, TYPE_STATUS_PARENTHESIS_LEFT);
155
        }
156
        for(TypeDesignationStatusBase<?> typeStatus : typeDesignationSet.keySet()) {
157
            typeStatusCount = buildTaggedTextForSingleTypeStatus(manager, workingsetBuilder,
158
                    typeDesignationSet, typeStatusCount, typeStatus,
159
                    lastWsType, typeSetCount);
160
        }
161
        if (withBrackets && hasExplicitBaseEntity){
162
            workingsetBuilder.add(TagEnum.separator, TYPE_STATUS_PARENTHESIS_RIGHT);
163
        }
164
        typeDesignationSet.setRepresentation(workingsetBuilder.toString());
165
        finalBuilder.addAll(workingsetBuilder);
166
        return;
167
    }
168

    
169
    /**
170
     * Checks if the baseType is the same as the (only?) type in the type designation workingset.
171
     */
172
    private boolean hasExplicitBaseEntity(VersionableEntity baseEntity,
173
            TypeDesignationSet typeDesignationSet) {
174
        if (!typeDesignationSet.isSpecimenWorkingSet()){
175
            return false;   //name type designations are not handled here
176
        }else{
177
            UUID baseUuid = baseEntity.getUuid();
178
            for (TypeDesignationDTO<?> dto: typeDesignationSet.getTypeDesignations()){
179
                if (!baseUuid.equals(dto.getTypeUuid())){
180
                    return true;
181
                }
182
            }
183
        }
184
        return false;
185
    }
186

    
187
    private int buildTaggedTextForSingleTypeStatus(TypeDesignationSetManager manager,
188
            TaggedTextBuilder workingsetBuilder, TypeDesignationSet typeDesignationSet,
189
            int typeStatusCount, TypeDesignationStatusBase<?> typeStatus,
190
            TypeDesignationSetType lastWsType, int typeSetCount) {
191

    
192
        //starting separator
193
        if(typeStatusCount++ > 0){
194
            workingsetBuilder.add(TagEnum.separator, TYPE_STATUS_SEPARATOR);
195
        }
196

    
197
        boolean isPlural = typeDesignationSet.get(typeStatus).size() > 1;
198
        String label = null;
199
        if(typeStatus != TypeDesignationSet.NULL_STATUS){
200
            label = typeStatus.getLabel();
201
        }else if (typeDesignationSet.getWorkingsetType() != lastWsType
202
                && (workingsetBuilder.size() > 0 && typeSetCount > 0)){
203
            //only for the first name type (coming after a specimen type add the label (extremely rare case, if at all existing)
204
            if (typeDesignationSet.getWorkingsetType().isNameType()) {
205
                label = "nametype";
206
            }else if (typeDesignationSet.getWorkingsetType().isSpecimenType()) {
207
                label = "type";
208
            }
209
        }
210
        if (label != null){
211
            label = (isPlural ? label + "s" : label);
212
            if (workingsetBuilder.size() == 0){
213
                label = StringUtils.capitalize(label);
214
            }
215
            workingsetBuilder.add(TagEnum.label, label);
216
            workingsetBuilder.add(TagEnum.postSeparator, POST_STATUS_SEPARATOR);
217
        }
218

    
219
        //designation + sources
220
        int typeDesignationCount = 0;
221
        for(TypeDesignationDTO<?> typeDesignationDTO : createSortedList(typeDesignationSet, typeStatus)) {
222
            TypeDesignationBase<?> typeDes = manager.findTypeDesignation(typeDesignationDTO.getUuid());
223

    
224
            typeDesignationCount = buildTaggedTextForSingleType(typeDes, withCitation,
225
                    workingsetBuilder, typeDesignationCount);
226
        }
227
        return typeStatusCount;
228
    }
229

    
230
    protected static int buildTaggedTextForSingleType(TypeDesignationBase<?> typeDes, boolean withCitation,
231
            TaggedTextBuilder workingsetBuilder, int typeDesignationCount) {
232

    
233
        if(typeDesignationCount++ > 0){
234
            workingsetBuilder.add(TagEnum.separator, TYPE_DESIGNATION_SEPARATOR);
235
        }
236
        buildTaggedTextForTypeDesignationBase(typeDes, workingsetBuilder);
237
        if (withCitation){
238

    
239
            //lectotype source
240
            OriginalSourceBase lectoSource = typeDes.getDesignationSource();
241
            if (hasLectoSource(typeDes)){
242
                workingsetBuilder.add(TagEnum.separator, REFERENCE_DESIGNATED_BY);
243
                addSource(workingsetBuilder, lectoSource);
244
            }
245
            //general sources
246
            if (!typeDes.getSources().isEmpty()) {
247
                workingsetBuilder.add(TagEnum.separator, REFERENCE_PARENTHESIS_LEFT + REFERENCE_FIDE);
248
                int count = 0;
249
                for (IdentifiableSource source: typeDes.getSources()){
250
                    if (count++ > 0){
251
                        workingsetBuilder.add(TagEnum.separator, SOURCE_SEPARATOR);
252
                    }
253
                    addSource(workingsetBuilder, source);
254
                }
255
                workingsetBuilder.add(TagEnum.separator, REFERENCE_PARENTHESIS_RIGHT);
256
            }
257
        }
258

    
259
        return typeDesignationCount;
260
    }
261

    
262
    /**
263
     * Adds the tags for the given source.
264
     */
265
    private static void addSource(TaggedTextBuilder workingsetBuilder,
266
            OriginalSourceBase source) {
267
        Reference ref = source.getCitation();
268
        if (ref != null){
269
            String citation = OriginalSourceFormatter.INSTANCE.format(source);
270
            workingsetBuilder.add(TagEnum.reference, citation, TypedEntityReference.fromEntity(ref, false));
271
        }
272
    }
273

    
274
    private static boolean hasLectoSource(TypeDesignationBase<?> typeDes) {
275
        return typeDes.getDesignationSource() != null &&
276
                    (typeDes.getDesignationSource().getCitation() != null
277
                      || isNotBlank(typeDes.getDesignationSource().getCitationMicroReference())
278
                     );
279
    }
280

    
281
    private List<TypeDesignationDTO> createSortedList(
282
            TypeDesignationSet typeDesignationSet, TypeDesignationStatusBase<?> typeStatus) {
283

    
284
        List<TypeDesignationDTO> typeDesignationDTOs = new ArrayList<>(typeDesignationSet.get(typeStatus));
285
        Collections.sort(typeDesignationDTOs);
286
        return typeDesignationDTOs;
287
    }
288

    
289
    /**
290
     * Returns <code>true</code> if the working set has either multiple working sets
291
     * or if it has a single working set but this workingset has multiple type designations.
292
     */
293
    private boolean hasMultipleTypes(
294
            Map<VersionableEntity,TypeDesignationSet> typeWorkingSets) {
295
        if (typeWorkingSets == null || typeWorkingSets.isEmpty()){
296
            return false;
297
        }else if (typeWorkingSets.keySet().size() > 1) {
298
            return true;
299
        }
300
        TypeDesignationSet singleSet = typeWorkingSets.values().iterator().next();
301
        return singleSet.getTypeDesignations().size() > 1;
302
    }
303

    
304
    private static void buildTaggedTextForTypeDesignationBase(TypeDesignationBase<?> td,
305
            TaggedTextBuilder workingsetBuilder) {
306
        TypedEntityReference<?> typeDesignationEntity = TypedEntityReference.fromEntity(td, false);
307
        if(td instanceof NameTypeDesignation){
308
            buildTaggedTextForNameTypeDesignation((NameTypeDesignation)td, workingsetBuilder, typeDesignationEntity);
309
        } else if (td instanceof TextualTypeDesignation){
310
            buildTaggedTextForTextualTypeDesignation((TextualTypeDesignation)td, workingsetBuilder, typeDesignationEntity);
311
        } else if (td instanceof SpecimenTypeDesignation){
312
            buildTaggedTextForSpecimenTypeDesignation((SpecimenTypeDesignation)td, false, workingsetBuilder, typeDesignationEntity);
313
        }else{
314
            throw new RuntimeException("Unknown TypeDesignation type");
315
        }
316
    }
317

    
318

    
319
    private static void buildTaggedTextForNameTypeDesignation(NameTypeDesignation td, TaggedTextBuilder workingsetBuilder,
320
            TypedEntityReference<?> typeDesignationEntity) {
321

    
322
        if (td.getTypeName() != null){
323
            workingsetBuilder.addAll(td.getTypeName().cacheStrategy().getTaggedTitle(td.getTypeName()));
324
        }
325

    
326
        String flags = null;
327

    
328
        if(td.isNotDesignated()){
329
            flags = "not designated";
330
        }
331
        if(td.isRejectedType()){
332
            flags = CdmUtils.concat(", ", flags, "rejected");
333
        }
334
        if(td.isConservedType()){
335
            flags = CdmUtils.concat(", ", flags, "conserved");
336
        }
337
        if (flags != null){
338
            workingsetBuilder.add(TagEnum.typeDesignation, flags, typeDesignationEntity);
339
        }
340
    }
341

    
342
    private static void buildTaggedTextForTextualTypeDesignation(TextualTypeDesignation td,
343
            TaggedTextBuilder workingsetBuilder, TypedEntityReference<?> typeDesignationEntity) {
344

    
345
        String result = td.getPreferredText(Language.DEFAULT());
346
        if (td.isVerbatim()){
347
            result = "\"" + result + "\"";  //TODO which character to use?
348
        }
349
        workingsetBuilder.add(TagEnum.typeDesignation, result, typeDesignationEntity);
350
    }
351

    
352
    private static void buildTaggedTextForSpecimenTypeDesignation(SpecimenTypeDesignation td, boolean useTitleCache,
353
            TaggedTextBuilder workingsetBuilder, TypedEntityReference<?> typeDesignationEntity) {
354

    
355
        if(useTitleCache){
356
            String typeDesigTitle = "";
357
            if(td.getTypeSpecimen() != null){
358
                String nameTitleCache = td.getTypeSpecimen().getTitleCache();
359
                //TODO is this needed?
360
//                if(getTypifiedNameCache() != null){
361
//                    nameTitleCache = nameTitleCache.replace(getTypifiedNameCache(), "");
362
//                }
363
                typeDesigTitle += nameTitleCache;
364
            }
365
            workingsetBuilder.add(TagEnum.typeDesignation, typeDesigTitle, typeDesignationEntity);
366
        } else {
367
            if (td.getTypeSpecimen() == null){
368
                workingsetBuilder.add(TagEnum.typeDesignation, "", typeDesignationEntity);
369
            }else{
370
                DerivedUnit du = td.getTypeSpecimen();
371
                if(du.isProtectedTitleCache()){
372
                    workingsetBuilder.add(TagEnum.typeDesignation, du.getTitleCache(), typeDesignationEntity);
373
                } else {
374
                    du = HibernateProxyHelper.deproxy(du);
375
                    boolean isMediaSpecimen = du instanceof MediaSpecimen;
376
                    String typeSpecimenTitle = (isMediaSpecimen ? "[icon] " : "");
377
                    if(isMediaSpecimen
378
                                && HibernateProxyHelper.deproxyOrNull(du.getCollection()) == null  //TODO not sure if only checking the collection is enough, but as we also check existence of sources now the case that only an accession number exists is also covered here
379
                                && ((MediaSpecimen)du).getMediaSpecimen() != null
380
                                && !((MediaSpecimen)du).getMediaSpecimen().getSources().isEmpty()
381
                            ){
382
                        // special case of a published image which is not covered by the DerivedUnitFacadeCacheStrategy
383
                        workingsetBuilder.add(TagEnum.typeDesignation, "[icon] in", typeDesignationEntity); //TODO how to better use tagged text here, the type designation itself has no real text; we could include the sources but that makes them unusable as sources :-(
384
                        MediaSpecimen msp = (MediaSpecimen)du;
385
                        int count = 0;
386
                        for(IdentifiableSource source : msp.getMediaSpecimen().getSources()){
387
                            if (source.getType().isPrimarySource()){
388
                                if (count++ > 0){
389
                                    workingsetBuilder.add(TagEnum.separator, SOURCE_SEPARATOR);
390
                                }
391
                                addSource(workingsetBuilder, source);
392
                            }
393
                        }
394
                    } else {
395
                        DerivedUnitDefaultCacheStrategy cacheStrategy = DerivedUnitDefaultCacheStrategy.NewInstance(true, false, true, " ");
396
                        String titleCache = cacheStrategy.getTitleCache(du, true);
397
                        // removing parentheses from code + accession number, see https://dev.e-taxonomy.eu/redmine/issues/8365
398
                        titleCache = titleCache.replaceAll("[\\(\\)]", "");
399
                        typeSpecimenTitle += titleCache;
400
                        workingsetBuilder.add(TagEnum.typeDesignation, typeSpecimenTitle, typeDesignationEntity);
401
                    }
402
                } //protected titleCache
403
            }//fi specimen == null
404
        }//fi useTitelCache
405

    
406
        if(td.isNotDesignated()){
407
            //this should not happen together with a defined specimen, therefore we may handle it in a separate tag
408
            workingsetBuilder.add(TagEnum.typeDesignation, "not designated", typeDesignationEntity);
409
        }
410
    }
411

    
412
    private static boolean isNotBlank(String str){
413
        return StringUtils.isNotBlank(str);
414
    }
415
}
(3-3/4)