Project

General

Profile

Download (19.2 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.TypeDesignationWorkingSet.TypeDesignationWorkingSetType;
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.IdentifiableSource;
25
import eu.etaxonomy.cdm.model.common.Language;
26
import eu.etaxonomy.cdm.model.common.VersionableEntity;
27
import eu.etaxonomy.cdm.model.name.NameTypeDesignation;
28
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
29
import eu.etaxonomy.cdm.model.name.TextualTypeDesignation;
30
import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
31
import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;
32
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
33
import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;
34
import eu.etaxonomy.cdm.model.reference.OriginalSourceBase;
35
import eu.etaxonomy.cdm.model.reference.Reference;
36
import eu.etaxonomy.cdm.ref.TypedEntityReference;
37
import eu.etaxonomy.cdm.strategy.cache.HTMLTagRules;
38
import eu.etaxonomy.cdm.strategy.cache.TagEnum;
39
import eu.etaxonomy.cdm.strategy.cache.TaggedCacheHelper;
40
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
41
import eu.etaxonomy.cdm.strategy.cache.TaggedTextBuilder;
42
import eu.etaxonomy.cdm.strategy.cache.occurrence.DerivedUnitDefaultCacheStrategy;
43

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

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

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

    
67
    public TypeDesignationSetFormatter(boolean withCitation, boolean withStartingTypeLabel,
68
            boolean withNameIfAvailable) {
69
        this.withCitation = withCitation;
70
        this.withStartingTypeLabel = withStartingTypeLabel;
71
        this.withNameIfAvailable = withNameIfAvailable;
72
    }
73

    
74
    public String format(TypeDesignationSetManager manager){
75
        return TaggedCacheHelper.createString(toTaggedText(manager));
76
    }
77

    
78
    public String format(TypeDesignationSetManager manager, HTMLTagRules htmlTagRules){
79
        return TaggedCacheHelper.createString(toTaggedText(manager), htmlTagRules);
80
    }
81

    
82
    public List<TaggedText> toTaggedText(TypeDesignationSetManager manager){
83
        return buildTaggedText(manager);
84
    }
85

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

    
89
        TaggedTextBuilder finalBuilder = new TaggedTextBuilder();
90

    
91
        if(withNameIfAvailable && manager.getTypifiedNameCache() != null){
92
            finalBuilder.add(TagEnum.name, manager.getTypifiedNameCache(), TypedEntityReference.fromEntity(manager.getTypifiedName(), false));
93
            finalBuilder.addPostSeparator(POST_NAME_SEPARTOR);
94
        }
95

    
96
        int typeSetCount = 0;
97
        Map<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet> orderedByTypesByBaseEntity
98
                    = manager.getOrderedTypeDesignationWorkingSets();
99
        TypeDesignationWorkingSetType lastWsType = null;
100
        if (orderedByTypesByBaseEntity != null){
101
            for(TypedEntityReference<?> baseEntityRef : orderedByTypesByBaseEntity.keySet()) {
102
                buildTaggedTextForSingleTypeSet(manager, withBrackets, finalBuilder,
103
                        typeSetCount, baseEntityRef, lastWsType);
104
                lastWsType = orderedByTypesByBaseEntity.get(baseEntityRef).getWorkingsetType();
105
                typeSetCount++;
106
            }
107
        }
108
        return finalBuilder.getTaggedText();
109
    }
110

    
111
    private void buildTaggedTextForSingleTypeSet(TypeDesignationSetManager manager, boolean withBrackets,
112
            TaggedTextBuilder finalBuilder, int typeSetCount, TypedEntityReference<?> baseEntityRef, TypeDesignationWorkingSetType lastWsType) {
113

    
114
        Map<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet>
115
                orderedByTypesByBaseEntity = manager.getOrderedTypeDesignationWorkingSets();
116
        TypeDesignationWorkingSet typeDesignationWorkingSet = orderedByTypesByBaseEntity.get(baseEntityRef);
117

    
118
        TaggedTextBuilder workingsetBuilder = new TaggedTextBuilder();
119
        if(typeSetCount > 0){
120
            workingsetBuilder.add(TagEnum.separator, TYPE_SEPARATOR);
121
        }else if (withStartingTypeLabel){
122
            //TODO this is not really exact as we may want to handle specimen types and
123
            //name types separately, but this is such a rare case (if at all) and
124
            //increases complexity so it is not yet implemented
125
            boolean isPlural = hasMultipleTypes(orderedByTypesByBaseEntity);
126
            if(typeDesignationWorkingSet.getWorkingsetType().isSpecimenType()){
127
                workingsetBuilder.add(TagEnum.label, (isPlural? "Types:": "Type:"));
128
            } else if (typeDesignationWorkingSet.getWorkingsetType().isNameType()){
129
                workingsetBuilder.add(TagEnum.label, (isPlural? "Nametypes:": "Nametype:"));
130
            } else {
131
                //do nothing for now
132
            }
133
        }
134

    
135
        boolean hasExplicitBaseEntity = hasExplicitBaseEntity(baseEntityRef, typeDesignationWorkingSet);
136
        if(hasExplicitBaseEntity && !baseEntityRef.getLabel().isEmpty()){
137
            workingsetBuilder.add(TagEnum.specimenOrObservation, baseEntityRef.getLabel(), baseEntityRef);
138
        }
139
        int typeStatusCount = 0;
140
        if (withBrackets && hasExplicitBaseEntity){
141
            workingsetBuilder.add(TagEnum.separator, TYPE_STATUS_PARENTHESIS_LEFT);
142
        }
143
        for(TypeDesignationStatusBase<?> typeStatus : typeDesignationWorkingSet.keySet()) {
144
            typeStatusCount = buildTaggedTextForSingleTypeStatus(manager, workingsetBuilder,
145
                    typeDesignationWorkingSet, typeStatusCount, typeStatus,
146
                    lastWsType, typeSetCount);
147
        }
148
        if (withBrackets && hasExplicitBaseEntity){
149
            workingsetBuilder.add(TagEnum.separator, TYPE_STATUS_PARENTHESIS_RIGHT);
150
        }
151
        typeDesignationWorkingSet.setRepresentation(workingsetBuilder.toString());
152
        finalBuilder.addAll(workingsetBuilder);
153
        return;
154
    }
155

    
156
    /**
157
     * Checks if the baseType is the same as the (only?) type in the type designation workingset.
158
     */
159
    private boolean hasExplicitBaseEntity(TypedEntityReference<?> baseEntityRef,
160
            TypeDesignationWorkingSet typeDesignationWorkingSet) {
161
        if (!typeDesignationWorkingSet.isSpecimenWorkingSet()){
162
            return false;   //name type designations are not handled here
163
        }else{
164
            UUID baseUuid = baseEntityRef.getUuid();
165
            for (TypeDesignationDTO<?> dto: typeDesignationWorkingSet.getTypeDesignations()){
166
                if (!baseUuid.equals(dto.getTypeUuid())){
167
                    return true;
168
                }
169
            }
170
        }
171
        return false;
172
    }
173

    
174
    private int buildTaggedTextForSingleTypeStatus(TypeDesignationSetManager manager,
175
            TaggedTextBuilder workingsetBuilder, TypeDesignationWorkingSet typeDesignationWorkingSet,
176
            int typeStatusCount, TypeDesignationStatusBase<?> typeStatus,
177
            TypeDesignationWorkingSetType lastWsType, int typeSetCount) {
178

    
179
        //starting separator
180
        if(typeStatusCount++ > 0){
181
            workingsetBuilder.add(TagEnum.separator, TYPE_STATUS_SEPARATOR);
182
        }
183

    
184
        boolean isPlural = typeDesignationWorkingSet.get(typeStatus).size() > 1;
185
        String label = null;
186
        if(typeStatus != TypeDesignationWorkingSet.NULL_STATUS){
187
            label = typeStatus.getLabel();
188
        }else if (typeDesignationWorkingSet.getWorkingsetType() != lastWsType
189
                && (workingsetBuilder.size() > 0 && typeSetCount > 0)){
190
            //only for the first name type (coming after a specimen type add the label (extremely rare case, if at all existing)
191
            if (typeDesignationWorkingSet.getWorkingsetType().isNameType()) {
192
                label = "nametype";
193
            }else if (typeDesignationWorkingSet.getWorkingsetType().isSpecimenType()) {
194
                label = "type";
195
            }
196
        }
197
        if (label != null){
198
            label = (isPlural ? label + "s" : label);
199
            if (workingsetBuilder.size() == 0){
200
                label = StringUtils.capitalize(label);
201
            }
202
            workingsetBuilder.add(TagEnum.label, label);
203
            workingsetBuilder.add(TagEnum.postSeparator, POST_STATUS_SEPARATOR);
204
        }
205

    
206
        //designation + sources
207
        int typeDesignationCount = 0;
208
        for(TypeDesignationDTO<?> typeDesignationDTO : createSortedList(typeDesignationWorkingSet, typeStatus)) {
209
            TypeDesignationBase<?> typeDes = manager.findTypeDesignation(typeDesignationDTO.getUuid());
210

    
211
            typeDesignationCount = buildTaggedTextForSingleType(typeDes, withCitation,
212
                    workingsetBuilder, typeDesignationCount);
213
        }
214
        return typeStatusCount;
215
    }
216

    
217
    protected static int buildTaggedTextForSingleType(TypeDesignationBase<?> typeDes, boolean withCitation,
218
            TaggedTextBuilder workingsetBuilder, int typeDesignationCount) {
219

    
220
        if(typeDesignationCount++ > 0){
221
            workingsetBuilder.add(TagEnum.separator, TYPE_DESIGNATION_SEPARATOR);
222
        }
223
        buildTaggedTextForTypeDesignationBase(typeDes, workingsetBuilder);
224
        if (withCitation){
225

    
226
            //lectotype source
227
            OriginalSourceBase lectoSource = typeDes.getDesignationSource();
228
            if (hasLectoSource(typeDes)){
229
                workingsetBuilder.add(TagEnum.separator, REFERENCE_DESIGNATED_BY);
230
                addSource(workingsetBuilder, lectoSource);
231
            }
232
            //general sources
233
            if (!typeDes.getSources().isEmpty()) {
234
                workingsetBuilder.add(TagEnum.separator, REFERENCE_PARENTHESIS_LEFT + REFERENCE_FIDE);
235
                int count = 0;
236
                for (IdentifiableSource source: typeDes.getSources()){
237
                    if (count++ > 0){
238
                        workingsetBuilder.add(TagEnum.separator, SOURCE_SEPARATOR);
239
                    }
240
                    addSource(workingsetBuilder, source);
241
                }
242
                workingsetBuilder.add(TagEnum.separator, REFERENCE_PARENTHESIS_RIGHT);
243
            }
244
        }
245

    
246
        return typeDesignationCount;
247
    }
248

    
249
    /**
250
     * Adds the tags for the given source.
251
     */
252
    private static void addSource(TaggedTextBuilder workingsetBuilder,
253
            OriginalSourceBase source) {
254
        Reference ref = source.getCitation();
255
        if (ref != null){
256
            String citation = OriginalSourceFormatter.INSTANCE.format(source);
257
            workingsetBuilder.add(TagEnum.reference, citation, TypedEntityReference.fromEntity(ref, false));
258
        }
259
    }
260

    
261
    private static boolean hasLectoSource(TypeDesignationBase<?> typeDes) {
262
        return typeDes.getDesignationSource() != null &&
263
                    (typeDes.getDesignationSource().getCitation() != null
264
                      || isNotBlank(typeDes.getDesignationSource().getCitationMicroReference())
265
                     );
266
    }
267

    
268
    private List<TypeDesignationDTO> createSortedList(
269
            TypeDesignationWorkingSet typeDesignationWorkingSet, TypeDesignationStatusBase<?> typeStatus) {
270

    
271
        List<TypeDesignationDTO> typeDesignationDTOs = new ArrayList<>(typeDesignationWorkingSet.get(typeStatus));
272
        Collections.sort(typeDesignationDTOs);
273
        return typeDesignationDTOs;
274
    }
275

    
276
    /**
277
     * Returns <code>true</code> if the working set has either multiple working sets
278
     * or if it has a single working set but this workingset has multiple type designations.
279
     */
280
    private boolean hasMultipleTypes(
281
            Map<TypedEntityReference<? extends VersionableEntity>, TypeDesignationWorkingSet> typeWorkingSets) {
282
        if (typeWorkingSets == null || typeWorkingSets.isEmpty()){
283
            return false;
284
        }else if (typeWorkingSets.keySet().size() > 1) {
285
            return true;
286
        }
287
        TypeDesignationWorkingSet singleSet = typeWorkingSets.values().iterator().next();
288
        return singleSet.getTypeDesignations().size() > 1;
289
    }
290

    
291
    private static void buildTaggedTextForTypeDesignationBase(TypeDesignationBase<?> td,
292
            TaggedTextBuilder workingsetBuilder) {
293
        TypedEntityReference<?> typeDesignationEntity = TypedEntityReference.fromEntity(td, false);
294
        if(td instanceof NameTypeDesignation){
295
            buildTaggedTextForNameTypeDesignation((NameTypeDesignation)td, workingsetBuilder, typeDesignationEntity);
296
        } else if (td instanceof TextualTypeDesignation){
297
            buildTaggedTextForTextualTypeDesignation((TextualTypeDesignation)td, workingsetBuilder, typeDesignationEntity);
298
        } else if (td instanceof SpecimenTypeDesignation){
299
            buildTaggedTextForSpecimenTypeDesignation((SpecimenTypeDesignation)td, false, workingsetBuilder, typeDesignationEntity);
300
        }else{
301
            throw new RuntimeException("Unknown TypeDesignation type");
302
        }
303
    }
304

    
305

    
306
    private static void buildTaggedTextForNameTypeDesignation(NameTypeDesignation td, TaggedTextBuilder workingsetBuilder,
307
            TypedEntityReference<?> typeDesignationEntity) {
308

    
309
        if (td.getTypeName() != null){
310
            workingsetBuilder.addAll(td.getTypeName().cacheStrategy().getTaggedTitle(td.getTypeName()));
311
        }
312

    
313
        String flags = null;
314

    
315
        if(td.isNotDesignated()){
316
            flags = "not designated";
317
        }
318
        if(td.isRejectedType()){
319
            flags = CdmUtils.concat(", ", flags, "rejected");
320
        }
321
        if(td.isConservedType()){
322
            flags = CdmUtils.concat(", ", flags, "conserved");
323
        }
324
        if (flags != null){
325
            workingsetBuilder.add(TagEnum.typeDesignation, flags, typeDesignationEntity);
326
        }
327
    }
328

    
329
    private static void buildTaggedTextForTextualTypeDesignation(TextualTypeDesignation td,
330
            TaggedTextBuilder workingsetBuilder, TypedEntityReference<?> typeDesignationEntity) {
331

    
332
        String result = td.getPreferredText(Language.DEFAULT());
333
        if (td.isVerbatim()){
334
            result = "\"" + result + "\"";  //TODO which character to use?
335
        }
336
        workingsetBuilder.add(TagEnum.typeDesignation, result, typeDesignationEntity);
337
    }
338

    
339
    private static void buildTaggedTextForSpecimenTypeDesignation(SpecimenTypeDesignation td, boolean useTitleCache,
340
            TaggedTextBuilder workingsetBuilder, TypedEntityReference<?> typeDesignationEntity) {
341

    
342
        if(useTitleCache){
343
            String typeDesigTitle = "";
344
            if(td.getTypeSpecimen() != null){
345
                String nameTitleCache = td.getTypeSpecimen().getTitleCache();
346
                //TODO is this needed?
347
//                if(getTypifiedNameCache() != null){
348
//                    nameTitleCache = nameTitleCache.replace(getTypifiedNameCache(), "");
349
//                }
350
                typeDesigTitle += nameTitleCache;
351
            }
352
            workingsetBuilder.add(TagEnum.typeDesignation, typeDesigTitle, typeDesignationEntity);
353
        } else {
354
            if (td.getTypeSpecimen() == null){
355
                workingsetBuilder.add(TagEnum.typeDesignation, "", typeDesignationEntity);
356
            }else{
357
                DerivedUnit du = td.getTypeSpecimen();
358
                if(du.isProtectedTitleCache()){
359
                    workingsetBuilder.add(TagEnum.typeDesignation, du.getTitleCache(), typeDesignationEntity);
360
                } else {
361
                    du = HibernateProxyHelper.deproxy(du);
362
                    boolean isMediaSpecimen = du instanceof MediaSpecimen;
363
                    String typeSpecimenTitle = (isMediaSpecimen ? "[icon] " : "");
364
                    if(isMediaSpecimen
365
                                && 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
366
                                && ((MediaSpecimen)du).getMediaSpecimen() != null
367
                                && !((MediaSpecimen)du).getMediaSpecimen().getSources().isEmpty()
368
                            ){
369
                        // special case of a published image which is not covered by the DerivedUnitFacadeCacheStrategy
370
                        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 :-(
371
                        MediaSpecimen msp = (MediaSpecimen)du;
372
                        int count = 0;
373
                        for(IdentifiableSource source : msp.getMediaSpecimen().getSources()){
374
                            if (source.getType().isPrimarySource()){
375
                                if (count++ > 0){
376
                                    workingsetBuilder.add(TagEnum.separator, SOURCE_SEPARATOR);
377
                                }
378
                                addSource(workingsetBuilder, source);
379
                            }
380
                        }
381
                    } else {
382
                        DerivedUnitDefaultCacheStrategy cacheStrategy = DerivedUnitDefaultCacheStrategy.NewInstance(true, false, true, " ");
383
                        String titleCache = cacheStrategy.getTitleCache(du, true);
384
                        // removing parentheses from code + accession number, see https://dev.e-taxonomy.eu/redmine/issues/8365
385
                        titleCache = titleCache.replaceAll("[\\(\\)]", "");
386
                        typeSpecimenTitle += titleCache;
387
                        workingsetBuilder.add(TagEnum.typeDesignation, typeSpecimenTitle, typeDesignationEntity);
388
                    }
389
                } //protected titleCache
390
            }//fi specimen == null
391
        }//fi useTitelCache
392

    
393
        if(td.isNotDesignated()){
394
            //this should not happen together with a defined specimen, therefore we may handle it in a separate tag
395
            workingsetBuilder.add(TagEnum.typeDesignation, "not designated", typeDesignationEntity);
396
        }
397
    }
398

    
399
    private static boolean isNotBlank(String str){
400
        return StringUtils.isNotBlank(str);
401
    }
402
}
(2-2/4)