ref #10089 rename TypeDesignationWorkingSet to TypeDesignationSet in cdmlib
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / name / TypeDesignationSetManager.java
1 /**
2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.cdm.api.service.name;
10
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.Comparator;
15 import java.util.HashMap;
16 import java.util.LinkedHashMap;
17 import java.util.LinkedList;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.Optional;
22 import java.util.Set;
23 import java.util.UUID;
24
25 import eu.etaxonomy.cdm.api.service.exception.RegistrationValidationException;
26 import eu.etaxonomy.cdm.api.service.name.TypeDesignationSet.TypeDesignationSetType;
27 import eu.etaxonomy.cdm.compare.name.NullTypeDesignationStatus;
28 import eu.etaxonomy.cdm.compare.name.TypeDesignationStatusComparator;
29 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
30 import eu.etaxonomy.cdm.model.common.CdmBase;
31 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
32 import eu.etaxonomy.cdm.model.common.VersionableEntity;
33 import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
34 import eu.etaxonomy.cdm.model.name.NameTypeDesignation;
35 import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
36 import eu.etaxonomy.cdm.model.name.TaxonName;
37 import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
38 import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;
39 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
40 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
41 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
42 import eu.etaxonomy.cdm.ref.EntityReference;
43 import eu.etaxonomy.cdm.ref.TypedEntityReference;
44 import eu.etaxonomy.cdm.strategy.cache.HTMLTagRules;
45 import eu.etaxonomy.cdm.strategy.cache.TaggedTextBuilder;
46
47 /**
48 * 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 TypeDesignationSets:
56 * <ul>
57 * <li>{@link #print()}
58 * <li>{@link #getOrderedTypeDesignationSets()} ... {@link TypeDesignationSet#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<VersionableEntity,TypeDesignationSet>> entryComparator = (o1,o2)->{
91
92 TypeDesignationSet ws1 = o1.getValue();
93 TypeDesignationSet 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() == TypeDesignationSetType.NAME_TYPE_DESIGNATION_SET? 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().getClass();
110 Class<?> type2 = o2.getKey().getClass();
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 // tdType1 = ws1.getTypeDesignations().stream().map(td->td.get).sorted(null).findFirst().orElseGet(()->{return null;});
122 String label1 = TypeDesignationSetFormatter.entityLabel(o1.getKey());
123 String label2 = TypeDesignationSetFormatter.entityLabel(o2.getKey());
124 return label1.compareTo(label2);
125 }
126 };
127
128 /**
129 * Groups the EntityReferences for each of the TypeDesignations by the according TypeDesignationStatus.
130 * The TypeDesignationStatusBase keys are already ordered by the term order defined in the vocabulary.
131 */
132 private LinkedHashMap<VersionableEntity,TypeDesignationSet> orderedByTypesByBaseEntity;
133
134 private List<String> problems = new ArrayList<>();
135
136 // **************************** CONSTRUCTOR ***********************************/
137
138 public TypeDesignationSetManager(@SuppressWarnings("rawtypes") Collection<TypeDesignationBase> typeDesignations)
139 throws RegistrationValidationException{
140 this(typeDesignations, null);
141 }
142
143 public TypeDesignationSetManager(@SuppressWarnings("rawtypes") Collection<TypeDesignationBase> typeDesignations,
144 TaxonName typifiedName)
145 throws RegistrationValidationException {
146 for (TypeDesignationBase<?> typeDes:typeDesignations){
147 this.typeDesignations.put(typeDes.getUuid(), typeDes);
148 }
149 try {
150 findTypifiedName();
151 }catch (RegistrationValidationException e) {
152 if (typifiedName == null) {
153 throw e;
154 }
155 this.typifiedName = typifiedName;
156 }
157
158 mapAndSort();
159 }
160
161 public TypeDesignationSetManager(HomotypicalGroup group) {
162 for (TypeDesignationBase<?> typeDes: group.getTypeDesignations()){
163 this.typeDesignations.put(typeDes.getUuid(), typeDes);
164 }
165 //findTypifiedName();
166 mapAndSort();
167 }
168
169 public TypeDesignationSetManager(TaxonName typifiedName) {
170 this.typifiedName = typifiedName;
171 }
172
173 // **************************************************************************/
174
175 /**
176 * Add one or more TypeDesignations to the manager. This causes re-grouping and re-ordering
177 * of all managed TypeDesignations.
178 *
179 * @param containgEntity
180 * @param typeDesignations
181 */
182 public void addTypeDesigations(TypeDesignationBase<?> ... typeDesignations){
183 for (TypeDesignationBase<?> typeDes: typeDesignations){
184 this.typeDesignations.put(typeDes.getUuid(), typeDes);
185 }
186 mapAndSort();
187 }
188
189 public TaxonName getTypifiedName() {
190 return typifiedName;
191 }
192
193 public void setNameTypeBaseEntityType(NameTypeBaseEntityType nameTypeBaseEntityType){
194 this.nameTypeBaseEntityType = nameTypeBaseEntityType;
195 }
196
197 public NameTypeBaseEntityType getNameTypeBaseEntityType(){
198 return nameTypeBaseEntityType;
199 }
200
201 // ******************************** METHODS *********************************/
202
203 /**
204 * Groups and orders all managed TypeDesignations.
205 */
206 protected void mapAndSort() {
207
208 Map<VersionableEntity,TypeDesignationSet> byBaseEntityByTypeStatus = new HashMap<>();
209 this.typeDesignations.values().forEach(td -> mapTypeDesignation(byBaseEntityByTypeStatus, td));
210 orderedByTypesByBaseEntity = orderByTypeByBaseEntity(byBaseEntityByTypeStatus);
211 }
212
213 private void mapTypeDesignation(Map<VersionableEntity,TypeDesignationSet> byBaseEntityByTypeStatus,
214 TypeDesignationBase<?> td){
215
216 td = HibernateProxyHelper.deproxy(td);
217 TypeDesignationStatusBase<?> status = td.getTypeStatus();
218
219 try {
220 final VersionableEntity baseEntity = baseEntity(td);
221 // final TypedEntityReference<? extends VersionableEntity> baseEntityReference = makeEntityReference(baseEntity);
222
223 TaggedTextBuilder workingsetBuilder = new TaggedTextBuilder();
224 boolean withCitation = true;
225 TypeDesignationSetFormatter.buildTaggedTextForSingleType(td, withCitation, workingsetBuilder, 0);
226
227 @SuppressWarnings({ "unchecked", "rawtypes" })
228 TypeDesignationDTO<?> typeDesignationDTO
229 = new TypeDesignationDTO(
230 td.getClass(),
231 td.getUuid(),
232 workingsetBuilder.getTaggedText(),
233 getTypeUuid(td));
234
235 if(!byBaseEntityByTypeStatus.containsKey(baseEntity)){
236 byBaseEntityByTypeStatus.put(baseEntity, new TypeDesignationSet(baseEntity));
237 }
238 byBaseEntityByTypeStatus.get(baseEntity).insert(status, typeDesignationDTO);
239
240 } catch (DataIntegrityException e){
241 problems.add(e.getMessage());
242 }
243 }
244
245
246 /**
247 * Returns the uuid of the type designated by this {@link TypeDesignationDTO#}.
248 * This is either a TaxonName or a {@link SpecimenOrObservationBase}.
249 */
250 private UUID getTypeUuid(TypeDesignationBase<?> td) {
251 IdentifiableEntity<?> type;
252 if (td instanceof SpecimenTypeDesignation){
253 type = ((SpecimenTypeDesignation) td).getTypeSpecimen();
254 }else if (td instanceof NameTypeDesignation){
255 type = ((NameTypeDesignation) td).getTypeName();
256 }else{
257 type = null;
258 }
259 return type == null? null : type.getUuid();
260 }
261
262 protected VersionableEntity baseEntity(TypeDesignationBase<?> td) throws DataIntegrityException {
263
264 VersionableEntity baseEntity = null;
265 if(td instanceof SpecimenTypeDesignation){
266 SpecimenTypeDesignation std = (SpecimenTypeDesignation) td;
267 FieldUnit fu = findFieldUnit(std.getTypeSpecimen());
268 if(fu != null){
269 baseEntity = fu;
270 } else if(((SpecimenTypeDesignation) td).getTypeSpecimen() != null){
271 baseEntity = ((SpecimenTypeDesignation) td).getTypeSpecimen();
272 }
273 } else if(td instanceof NameTypeDesignation){
274 if(nameTypeBaseEntityType == NameTypeBaseEntityType.NAME_TYPE_DESIGNATION){
275 baseEntity = td;
276 } else {
277 // only other option is TaxonName
278 baseEntity = ((NameTypeDesignation)td).getTypeName();
279 }
280 }
281 if(baseEntity == null) {
282 throw new DataIntegrityException("Incomplete TypeDesignation, no type missin in " + td.toString());
283 }
284 return baseEntity;
285 }
286
287 //TODO maybe not needed anymore
288 protected static TypedEntityReference<? extends VersionableEntity> makeEntityReference(VersionableEntity baseEntity) {
289
290 baseEntity = CdmBase.deproxy(baseEntity);
291 String label = TypeDesignationSetFormatter.entityLabel(baseEntity);
292
293 TypedEntityReference<? extends VersionableEntity> baseEntityReference =
294 new TypedEntityReference<>(baseEntity.getClass(), baseEntity.getUuid(), label);
295
296 return baseEntityReference;
297 }
298
299 private LinkedHashMap<VersionableEntity,TypeDesignationSet> orderByTypeByBaseEntity(
300 Map<VersionableEntity,TypeDesignationSet> stringsByTypeByBaseEntity){
301
302 // order the FieldUnit TypeName keys
303 Set<Entry<VersionableEntity,TypeDesignationSet>> entrySet
304 = stringsByTypeByBaseEntity.entrySet();
305 LinkedList<Entry<VersionableEntity,TypeDesignationSet>> baseEntityKeyList
306 = new LinkedList<>(entrySet);
307 Collections.sort(baseEntityKeyList, entryComparator);
308
309 // new LinkedHashMap for the ordered FieldUnitOrTypeName keys
310 LinkedHashMap<VersionableEntity,TypeDesignationSet> stringsOrderedbyBaseEntityOrderdByType
311 = new LinkedHashMap<>(stringsByTypeByBaseEntity.size());
312
313 for(Entry<VersionableEntity,TypeDesignationSet> entry : baseEntityKeyList){
314 VersionableEntity baseEntity = entry.getKey();
315 TypeDesignationSet typeDesignationSet = stringsByTypeByBaseEntity.get(baseEntity);
316 // order the TypeDesignationStatusBase keys
317 List<TypeDesignationStatusBase<?>> keyList = new LinkedList<>(typeDesignationSet.keySet());
318 Collections.sort(keyList, new TypeDesignationStatusComparator());
319 // new LinkedHashMap for the ordered TypeDesignationStatusBase keys
320 TypeDesignationSet orderedStringsByOrderedTypes = new TypeDesignationSet(
321 typeDesignationSet.getBaseEntity());
322 keyList.forEach(key -> orderedStringsByOrderedTypes.put(key, typeDesignationSet.get(key)));
323 stringsOrderedbyBaseEntityOrderdByType.put(baseEntity, orderedStringsByOrderedTypes);
324 }
325
326 return stringsOrderedbyBaseEntityOrderdByType;
327 }
328
329 /**
330 * FIXME use the validation framework validators to store the validation problems!!!
331 *
332 * @return
333 * @throws RegistrationValidationException
334 */
335 private void findTypifiedName() throws RegistrationValidationException {
336
337 List<String> problems = new ArrayList<>();
338
339 TaxonName typifiedName = null;
340
341 for(TypeDesignationBase<?> typeDesignation : typeDesignations.values()){
342 typeDesignation.getTypifiedNames();
343 if(typeDesignation.getTypifiedNames().isEmpty()){
344
345 //TODO instead throw RegistrationValidationException()
346 problems.add("Missing typifiedName in " + typeDesignation.toString());
347 continue;
348 }
349 if(typeDesignation.getTypifiedNames().size() > 1){
350 //TODO instead throw RegistrationValidationException()
351 problems.add("Multiple typifiedName in " + typeDesignation.toString());
352 continue;
353 }
354 if(typifiedName == null){
355 // remember
356 typifiedName = typeDesignation.getTypifiedNames().iterator().next();
357 } else {
358 // compare
359 TaxonName otherTypifiedName = typeDesignation.getTypifiedNames().iterator().next();
360 if(!typifiedName.getUuid().equals(otherTypifiedName.getUuid())){
361 //TODO instead throw RegistrationValidationException()
362 problems.add("Multiple typifiedName in " + typeDesignation.toString());
363 }
364 }
365 }
366 if(!problems.isEmpty()){
367 // FIXME use the validation framework
368 throw new RegistrationValidationException("Inconsistent type designations", problems);
369 }
370
371 if(typifiedName != null){
372 // ON SUCCESS -------------------
373 this.typifiedName = typifiedName;
374 }
375 }
376
377 /**
378 * @return the title cache of the typifying name or <code>null</code>
379 */
380 public String getTypifiedNameCache() {
381 if(typifiedName != null){
382 return typifiedName.getTitleCache();
383 }
384 return null;
385 }
386
387 /**
388 * @return the title cache of the typifying name or <code>null</code>
389 */
390 public EntityReference getTypifiedNameAsEntityRef() {
391 return new EntityReference(typifiedName.getUuid(), typifiedName.getTitleCache());
392 }
393
394 public Collection<TypeDesignationBase<?>> getTypeDesignations() {
395 return typeDesignations.values();
396 }
397
398 public TypeDesignationBase<?> findTypeDesignation(UUID uuid) {
399 return this.typeDesignations.get(uuid);
400 }
401
402 public Map<VersionableEntity,TypeDesignationSet> getOrderedTypeDesignationSets() {
403 return orderedByTypesByBaseEntity;
404 }
405
406 private FieldUnit findFieldUnit(DerivedUnit du) {
407
408 if(du == null || du.getOriginals() == null || du.getOriginals().isEmpty()){
409 return null;
410 }
411 @SuppressWarnings("rawtypes")
412 Set<SpecimenOrObservationBase> originals = du.getOriginals();
413 @SuppressWarnings("rawtypes")
414 Optional<SpecimenOrObservationBase> fieldUnit = originals.stream()
415 .filter(original -> original instanceof FieldUnit).findFirst();
416 if (fieldUnit.isPresent()) {
417 return (FieldUnit) fieldUnit.get();
418 } else {
419 for (@SuppressWarnings("rawtypes") SpecimenOrObservationBase sob : originals) {
420 if (sob instanceof DerivedUnit) {
421 FieldUnit fu = findFieldUnit((DerivedUnit) sob);
422 if (fu != null) {
423 return fu;
424 }
425 }
426 }
427 }
428
429 return null;
430 }
431
432 public String print(boolean withCitation, boolean withStartingTypeLabel, boolean withNameIfAvailable) {
433 return new TypeDesignationSetFormatter(withCitation, withStartingTypeLabel, withNameIfAvailable).format(this);
434 }
435
436 public String print(boolean withCitation, boolean withStartingTypeLabel, boolean withNameIfAvailable, HTMLTagRules htmlRules) {
437 return new TypeDesignationSetFormatter(withCitation, withStartingTypeLabel, withNameIfAvailable).format(this, htmlRules);
438 }
439
440
441 class DataIntegrityException extends Exception {
442
443 private static final long serialVersionUID = 1464726696296824905L;
444
445 public DataIntegrityException(String string) {
446 super(string);
447 }
448 }
449 }