2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.cdm
.vaadin
.util
.converter
;
11 import java
.util
.ArrayList
;
12 import java
.util
.Arrays
;
13 import java
.util
.Collection
;
14 import java
.util
.Collections
;
15 import java
.util
.Comparator
;
16 import java
.util
.HashMap
;
17 import java
.util
.LinkedHashMap
;
18 import java
.util
.LinkedList
;
19 import java
.util
.List
;
21 import java
.util
.Optional
;
24 import eu
.etaxonomy
.cdm
.api
.facade
.DerivedUnitFacadeCacheStrategy
;
25 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
26 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
27 import eu
.etaxonomy
.cdm
.model
.common
.TermVocabulary
;
28 import eu
.etaxonomy
.cdm
.model
.name
.NameTypeDesignation
;
29 import eu
.etaxonomy
.cdm
.model
.name
.SpecimenTypeDesignation
;
30 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
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
.FieldUnit
;
35 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
36 import eu
.etaxonomy
.cdm
.vaadin
.model
.EntityReference
;
37 import eu
.etaxonomy
.cdm
.vaadin
.model
.TypedEntityReference
;
38 import eu
.etaxonomy
.cdm
.vaadin
.view
.registration
.RegistrationValidationException
;
41 * Manages a collection of {@link TypeDesignationBase TypeDesignations} for the same typified name.
43 * Type designations are ordered by the base type which is a {@link TaxonName} for {@link NameTypeDesignation NameTypeDesignations} or
44 * in case of {@link SpecimenTypeDesignation SpecimenTypeDesignations} the associate {@link FieldUnit} or the {@link DerivedUnit}
45 * if the former is missing. The type designations per base type are furthermore ordered by the {@link TypeDesignationStatusBase}.
47 * The TypeDesignationSetManager also provides string representations of the whole ordered set of all
48 * {@link TypeDesignationBase TypeDesignations} and of the TypeDesignationWorkingSets:
50 * <li>{@link #print()})
51 * <li>{@link #getOrderdTypeDesignationWorkingSets()} ... {@link TypeDesignationWorkingSet#getRepresentation()}
53 * Prior using the representations you need to trigger their generation by calling {@link #buildString()}
55 * @author a.kohlbecker
59 public class TypeDesignationSetManager
{
62 private static final String TYPE_STATUS_SEPARATOR
= "; ";
64 private static final String TYPE_SEPARATOR
= "; ";
66 private static final String TYPE_DESIGNATION_SEPARATOR
= ", ";
68 private Collection
<TypeDesignationBase
> typeDesignations
;
70 private int workingSetIdAutoIncrement
= 0;
73 * Groups the EntityReferences for each of the TypeDesignations by the according TypeDesignationStatus.
74 * The TypeDesignationStatusBase keys are already ordered by the term order defined in the vocabulary.
76 private LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> orderedByTypesByBaseEntity
;
78 private EntityReference typifiedName
;
80 private String finalString
= null;
82 final NullTypeDesignationStatus NULL_STATUS
= new NullTypeDesignationStatus();
84 private List
<String
> probelms
= new ArrayList
<>();
87 * @param containgEntity
89 * @throws RegistrationValidationException
92 public TypeDesignationSetManager(Collection
<TypeDesignationBase
> typeDesignations
) throws RegistrationValidationException
{
93 this.typeDesignations
= typeDesignations
;
94 this.typifiedName
= findTypifiedName();
99 * @param typifiedName2
101 public TypeDesignationSetManager(TaxonName typifiedName
) {
102 this.typeDesignations
= new ArrayList
<>();
103 this.typifiedName
= new EntityReference(typifiedName
.getId(), typifiedName
.getTitleCache());
107 * Add one or more TypeDesignations to the manager. This causes re-grouping and re-ordering
108 * of all managed TypeDesignations.
110 * @param containgEntity
111 * @param typeDesignations
113 public void addTypeDesigations(CdmBase containgEntity
, TypeDesignationBase
... typeDesignations
){
114 this.typeDesignations
.addAll(Arrays
.asList(typeDesignations
));
119 * Groups and orders all managed TypeDesignations.
121 * @param containgEntity
123 protected void mapAndSort() {
125 Map
<TypedEntityReference
, TypeDesignationWorkingSet
> byBaseEntityByTypeStatus
= new HashMap
<>();
126 this.typeDesignations
.forEach(td
-> mapTypeDesignation(byBaseEntityByTypeStatus
, td
));
127 orderedByTypesByBaseEntity
= orderByTypeByBaseEntity(byBaseEntityByTypeStatus
);
133 * @param containgEntity
134 * @param byBaseEntityByTypeStatus
137 private void mapTypeDesignation(Map
<TypedEntityReference
, TypeDesignationWorkingSet
> byBaseEntityByTypeStatus
,
138 TypeDesignationBase
<?
> td
){
140 TypeDesignationStatusBase
<?
> status
= td
.getTypeStatus();
143 final IdentifiableEntity
<?
> baseEntity
= baseEntity(td
);
144 final TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
= makeEntityReference(baseEntity
);
146 EntityReference typeDesignationEntityReference
= new EntityReference(td
.getId(), stringify(td
));
148 TypeDesignationWorkingSet typedesignationWorkingSet
;
149 if(!byBaseEntityByTypeStatus
.containsKey(baseEntityReference
)){
150 byBaseEntityByTypeStatus
.put(baseEntityReference
, new TypeDesignationWorkingSet(baseEntity
, baseEntityReference
));
153 typedesignationWorkingSet
= byBaseEntityByTypeStatus
.get(baseEntityReference
);
154 typedesignationWorkingSet
.insert(status
, typeDesignationEntityReference
);
155 } catch (DataIntegrityException e
){
156 probelms
.add(e
.getMessage());
163 * @throws DataIntegrityException
165 protected IdentifiableEntity
<?
> baseEntity(TypeDesignationBase
<?
> td
) throws DataIntegrityException
{
167 IdentifiableEntity
<?
> baseEntity
= null;
168 if(td
instanceof SpecimenTypeDesignation
){
169 SpecimenTypeDesignation std
= (SpecimenTypeDesignation
) td
;
170 FieldUnit fu
= findFieldUnit(std
);
173 } else if(((SpecimenTypeDesignation
) td
).getTypeSpecimen() != null){
174 baseEntity
= ((SpecimenTypeDesignation
) td
).getTypeSpecimen();
176 } else if(td
instanceof NameTypeDesignation
){
177 baseEntity
= ((NameTypeDesignation
)td
).getTypeName();
179 if(baseEntity
== null) {
180 throw new DataIntegrityException("Incomplete TypeDesignation, no type missin in " + td
.toString());
189 protected TypedEntityReference
<IdentifiableEntity
<?
>> makeEntityReference(IdentifiableEntity
<?
> baseEntity
) {
192 if(baseEntity
instanceof FieldUnit
){
193 label
= ((FieldUnit
)baseEntity
).getTitleCache();
196 TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
= new TypedEntityReference(baseEntity
.getClass(), baseEntity
.getId(), label
);
198 return baseEntityReference
;
202 private LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> orderByTypeByBaseEntity(
203 Map
<TypedEntityReference
, TypeDesignationWorkingSet
> stringsByTypeByBaseEntity
){
205 // order the FieldUnit TypeName keys
206 List
<TypedEntityReference
> baseEntityKeyList
= new LinkedList
<>(stringsByTypeByBaseEntity
.keySet());
207 Collections
.sort(baseEntityKeyList
, new Comparator
<TypedEntityReference
>(){
209 * Sorts the base entities (TypedEntityReference) in the following order:
212 * 2. DerivedUnit (in case of missing FieldUnit we expect the base type to be DerivedUnit)
218 public int compare(TypedEntityReference o1
, TypedEntityReference o2
) {
220 Class type1
= o1
.getType();
221 Class type2
= o2
.getType();
223 if(!type1
.equals(type2
)) {
224 if(type1
.equals(FieldUnit
.class) || type2
.equals(FieldUnit
.class)){
226 return type1
.equals(FieldUnit
.class) ?
-1 : 1;
228 // name types last (in case of missing FieldUnit we expect the base type to be DerivedUnit which comes into the middle)
229 return type2
.equals(TaxonName
.class) ?
-1 : 1;
232 return o1
.getLabel().compareTo(o2
.getLabel());
236 // new LinkedHashMap for the ordered FieldUnitOrTypeName keys
237 LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> stringsOrderedbyBaseEntityOrderdByType
= new LinkedHashMap
<>(stringsByTypeByBaseEntity
.size());
239 for(TypedEntityReference baseEntityRef
: baseEntityKeyList
){
241 TypeDesignationWorkingSet typeDesignationWorkingSet
= stringsByTypeByBaseEntity
.get(baseEntityRef
);
242 // order the TypeDesignationStatusBase keys
243 List
<TypeDesignationStatusBase
<?
>> keyList
= new LinkedList
<>(typeDesignationWorkingSet
.keySet());
244 Collections
.sort(keyList
, new Comparator
<TypeDesignationStatusBase
>() {
245 @SuppressWarnings("unchecked")
247 public int compare(TypeDesignationStatusBase o1
, TypeDesignationStatusBase o2
) {
248 // fix inverted order of cdm terms by -1*
249 return -1 * o1
.compareTo(o2
);
252 // new LinkedHashMap for the ordered TypeDesignationStatusBase keys
253 TypeDesignationWorkingSet orderedStringsByOrderedTypes
= new TypeDesignationWorkingSet(
254 typeDesignationWorkingSet
.getBaseEntity(),
256 orderedStringsByOrderedTypes
.setWorkingSetId(typeDesignationWorkingSet
.workingSetId
); // preserve original workingSetId
257 keyList
.forEach(key
-> orderedStringsByOrderedTypes
.put(key
, typeDesignationWorkingSet
.get(key
)));
258 stringsOrderedbyBaseEntityOrderdByType
.put(baseEntityRef
, orderedStringsByOrderedTypes
);
261 return stringsOrderedbyBaseEntityOrderdByType
;
265 private LinkedHashMap<TypedEntityReference, LinkedHashMap<String, Collection<EntityReference>>> buildOrderedRepresentations(){
267 orderedStringsByOrderedTypes.keySet().forEach(
268 key -> orderedRepresentations.put(
269 getTypeDesignationStytusLabel(key),
270 orderedStringsByOrderedTypes.get(key))
272 return orderedRepresentations;
276 public TypeDesignationSetManager
buildString(){
278 if(finalString
== null){
281 if(getTypifiedNameCache() != null){
282 finalString
+= getTypifiedNameCache() + " ";
286 if(orderedByTypesByBaseEntity
!= null){
287 for(TypedEntityReference baseEntityRef
: orderedByTypesByBaseEntity
.keySet()) {
288 StringBuilder sb
= new StringBuilder();
290 sb
.append(TYPE_SEPARATOR
);
292 boolean isNameTypeDesignation
= false;
293 if(SpecimenOrObservationBase
.class.isAssignableFrom(baseEntityRef
.getType())){
296 sb
.append("NameType: ");
297 isNameTypeDesignation
= true;
299 if(!baseEntityRef
.getLabel().isEmpty()){
300 sb
.append(baseEntityRef
.getLabel()).append(" ");
302 TypeDesignationWorkingSet typeDesignationWorkingSet
= orderedByTypesByBaseEntity
.get(baseEntityRef
);
303 if(!isNameTypeDesignation
){
306 int typeStatusCount
= 0;
307 for(TypeDesignationStatusBase
<?
> typeStatus
: typeDesignationWorkingSet
.keySet()) {
308 if(typeStatusCount
++ > 0){
309 sb
.append(TYPE_STATUS_SEPARATOR
);
311 boolean isPlural
= typeDesignationWorkingSet
.get(typeStatus
).size() > 1;
312 if(!typeStatus
.equals(NULL_STATUS
)) {
313 sb
.append(typeStatus
.getLabel());
320 int typeDesignationCount
= 0;
321 for(EntityReference typeDesignationEntityReference
: typeDesignationWorkingSet
.get(typeStatus
)) {
322 if(typeDesignationCount
++ > 0){
323 sb
.append(TYPE_DESIGNATION_SEPARATOR
);
325 sb
.append(typeDesignationEntityReference
.getLabel());
328 if(!isNameTypeDesignation
){
331 typeDesignationWorkingSet
.setRepresentation(sb
.toString());
332 finalString
+= typeDesignationWorkingSet
.getRepresentation();
340 * FIXME use the validation framework validators and to store the validation problems!!!
343 * @throws RegistrationValidationException
345 private EntityReference
findTypifiedName() throws RegistrationValidationException
{
347 List
<String
> problems
= new ArrayList
<>();
349 TaxonName typifiedName
= null;
351 for(TypeDesignationBase
<?
> typeDesignation
: typeDesignations
){
352 typeDesignation
.getTypifiedNames();
353 if(typeDesignation
.getTypifiedNames().isEmpty()){
355 //TODO instead throw RegistrationValidationException()
356 problems
.add("Missing typifiedName in " + typeDesignation
.toString());
359 if(typeDesignation
.getTypifiedNames().size() > 1){
360 //TODO instead throw RegistrationValidationException()
361 problems
.add("Multiple typifiedName in " + typeDesignation
.toString());
364 if(typifiedName
== null){
366 typifiedName
= typeDesignation
.getTypifiedNames().iterator().next();
369 TaxonName otherTypifiedName
= typeDesignation
.getTypifiedNames().iterator().next();
370 if(typifiedName
.getId() != otherTypifiedName
.getId()){
371 //TODO instead throw RegistrationValidationException()
372 problems
.add("Multiple typifiedName in " + typeDesignation
.toString());
377 if(!problems
.isEmpty()){
378 // FIXME use the validation framework
379 throw new RegistrationValidationException("Inconsistent type designations", problems
);
382 if(typifiedName
!= null){
383 return new EntityReference(typifiedName
.getId(), typifiedName
.getTitleCache());
390 * @return the title cache of the typifying name or <code>null</code>
392 public String
getTypifiedNameCache() {
393 if(typifiedName
!= null){
394 return typifiedName
.getLabel();
400 * @return the title cache of the typifying name or <code>null</code>
402 public EntityReference
getTypifiedName() {
410 public Collection
<TypeDesignationBase
> getTypeDesignations() {
411 return typeDesignations
;
418 public TypeDesignationBase
findTypeDesignation(EntityReference typeDesignationRef
) {
419 for(TypeDesignationBase td
: typeDesignations
){
420 if(td
.getId() == typeDesignationRef
.getId()){
424 // TODO Auto-generated method stub
429 public LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> getOrderdTypeDesignationWorkingSets() {
430 return orderedByTypesByBaseEntity
;
437 private String
stringify(TypeDesignationBase td
) {
439 if(td
instanceof NameTypeDesignation
){
440 return stringify((NameTypeDesignation
)td
);
442 return stringify((SpecimenTypeDesignation
)td
, false);
451 protected String
stringify(NameTypeDesignation td
) {
453 StringBuffer sb
= new StringBuffer();
455 if(td
.getTypeName() != null){
456 sb
.append(td
.getTypeName().getTitleCache());
458 if(td
.getCitation() != null){
459 sb
.append(" ").append(td
.getCitation().getTitleCache());
460 if(td
.getCitationMicroReference() != null){
461 sb
.append(":").append(td
.getCitationMicroReference());
464 if(td
.isNotDesignated()){
465 sb
.append(" not designated");
467 if(td
.isRejectedType()){
468 sb
.append(" rejected");
470 if(td
.isConservedType()){
471 sb
.append(" conserved");
473 return sb
.toString();
480 private String
stringify(SpecimenTypeDesignation td
, boolean useFullTitleCache
) {
483 if(useFullTitleCache
){
484 if(td
.getTypeSpecimen() != null){
485 String nameTitleCache
= td
.getTypeSpecimen().getTitleCache();
486 if(getTypifiedNameCache() != null){
487 nameTitleCache
= nameTitleCache
.replace(getTypifiedNameCache(), "");
489 result
+= nameTitleCache
;
492 if(td
.getTypeSpecimen() != null){
493 DerivedUnit du
= td
.getTypeSpecimen();
494 if(du
.isProtectedTitleCache()){
495 result
+= du
.getTitleCache();
497 DerivedUnitFacadeCacheStrategy cacheStrategy
= new DerivedUnitFacadeCacheStrategy();
498 result
+= cacheStrategy
.getTitleCache(du
, true);
503 if(td
.getCitation() != null){
504 result
+= " " + td
.getCitation().getTitleCache();
505 if(td
.getCitationMicroReference() != null){
506 result
+= " :" + td
.getCitationMicroReference();
509 if(td
.isNotDesignated()){
510 result
+= " not designated";
522 private FieldUnit
findFieldUnit(SpecimenTypeDesignation td
) {
524 DerivedUnit du
= td
.getTypeSpecimen();
525 return findFieldUnit(du
);
528 private FieldUnit
findFieldUnit(DerivedUnit du
) {
530 if(du
== null || du
.getOriginals() == null){
533 @SuppressWarnings("rawtypes")
534 Set
<SpecimenOrObservationBase
> originals
= du
.getDerivedFrom().getOriginals();
535 @SuppressWarnings("rawtypes")
536 Optional
<SpecimenOrObservationBase
> fieldUnit
= originals
.stream()
537 .filter(original
-> original
instanceof FieldUnit
).findFirst();
538 if (fieldUnit
.isPresent()) {
539 return (FieldUnit
) fieldUnit
.get();
541 for (@SuppressWarnings("rawtypes")
542 SpecimenOrObservationBase sob
: originals
) {
543 if (sob
instanceof DerivedUnit
) {
544 FieldUnit fu
= findFieldUnit((DerivedUnit
) sob
);
555 public String
print() {
556 return finalString
.trim();
560 * TypeDesignations which refer to the same FieldUnit (SpecimenTypeDesignation) or TaxonName
561 * (NameTypeDesignation) form a working set. The <code>TypeDesignationWorkingSet</code> internally
562 * works with EnityReferences to the actual TypeDesignations.
564 * The EntityReferences for TypeDesignations are grouped by the according TypeDesignationStatus.
565 * The TypeDesignationStatusBase keys can be ordered by the term order defined in the vocabulary.
567 * A workingset can be referenced by the <code>workingSetId</code>, this is a autoincrement
568 * value which is created during the process of determining the workingsets in a collection of
571 * TODO: consider using a concatenation of baseEntity.getClass() + baseEntity.getId() as workingset identifier
573 public class TypeDesignationWorkingSet
extends LinkedHashMap
<TypeDesignationStatusBase
<?
>, Collection
<EntityReference
>> {
575 private static final long serialVersionUID
= -1329007606500890729L;
577 String workingSetRepresentation
= null;
579 TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
;
581 IdentifiableEntity
<?
> baseEntity
;
583 List
<DerivedUnit
> derivedUnits
= null;
585 int workingSetId
= workingSetIdAutoIncrement
++;
588 * @param baseEntityReference
590 public TypeDesignationWorkingSet(IdentifiableEntity
<?
> baseEntity
, TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
) {
591 this.baseEntity
= baseEntity
;
592 this.baseEntityReference
= baseEntityReference
;
598 public IdentifiableEntity
<?
> getBaseEntity() {
602 public List
<EntityReference
> getTypeDesignations() {
603 List
<EntityReference
> typeDesignations
= new ArrayList
<>();
604 this.values().forEach(typeDesignationReferences
-> typeDesignationReferences
.forEach(td
-> typeDesignations
.add(td
)));
605 return typeDesignations
;
610 * @param typeDesignationEntityReference
612 public void insert(TypeDesignationStatusBase
<?
> status
, EntityReference typeDesignationEntityReference
) {
615 status
= NULL_STATUS
;
617 if(!containsKey(status
)){
618 put(status
, new ArrayList
<EntityReference
>());
620 get(status
).add(typeDesignationEntityReference
);
624 * @return the workingSetId
626 public int getWorkingSetId() {
631 * @param workingSetId the workingSetId to set
633 public void setWorkingSetId(int workingSetId
) {
634 this.workingSetId
= workingSetId
;
637 public String
getRepresentation() {
638 return workingSetRepresentation
;
641 public void setRepresentation(String representation
){
642 this.workingSetRepresentation
= representation
;
646 * A reference to the entity which is the common base entity for all TypeDesignations in this workingset.
647 * For a {@link SpecimenTypeDesignation} this is usually the {@link FieldUnit} if it is present. Otherwise it can also be
648 * a {@link DerivedUnit} or something else depending on the specific use case.
650 * @return the baseEntityReference
652 public TypedEntityReference
getBaseEntityReference() {
653 return baseEntityReference
;
657 public String
toString(){
658 if(workingSetRepresentation
!= null){
659 return workingSetRepresentation
;
661 return super.toString();
668 public boolean isSpecimenTypeDesigationWorkingSet() {
669 return SpecimenOrObservationBase
.class.isAssignableFrom(baseEntityReference
.getType());
672 public TypeDesignationWorkingSetType
getWorkingsetType() {
673 return isSpecimenTypeDesigationWorkingSet() ? TypeDesignationWorkingSetType
.SPECIMEN_TYPE_DESIGNATION_WORKINGSET
: TypeDesignationWorkingSetType
.NAME_TYPE_DESIGNATION_WORKINGSET
;
678 public enum TypeDesignationWorkingSetType
{
679 SPECIMEN_TYPE_DESIGNATION_WORKINGSET
,
680 NAME_TYPE_DESIGNATION_WORKINGSET
,
683 @SuppressWarnings({ "deprecation", "serial" })
684 class NullTypeDesignationStatus
extends TypeDesignationStatusBase
<NullTypeDesignationStatus
>{
690 public void resetTerms() {
699 protected void setDefaultTerms(TermVocabulary
<NullTypeDesignationStatus
> termVocabulary
) {