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
.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
;
20 import java
.util
.Optional
;
23 import eu
.etaxonomy
.cdm
.api
.facade
.DerivedUnitFacadeCacheStrategy
;
24 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
25 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
26 import eu
.etaxonomy
.cdm
.model
.common
.TermVocabulary
;
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
.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
<>();
88 * @throws RegistrationValidationException
91 public TypeDesignationSetManager(CdmBase containgEntity
, Collection
<TypeDesignationBase
> typeDesignations
) throws RegistrationValidationException
{
92 this.typeDesignations
= typeDesignations
;
93 Map
<TypedEntityReference
, TypeDesignationWorkingSet
> byBaseEntityByTypeStatus
= new HashMap
<>();
94 typeDesignations
.forEach(td
-> mapTypeDesignation(containgEntity
, byBaseEntityByTypeStatus
, td
));
95 orderedByTypesByBaseEntity
= orderByTypeByBaseEntity(byBaseEntityByTypeStatus
);
96 this.typifiedName
= findTypifiedName();
100 private void mapTypeDesignation(CdmBase containgEntity
, Map
<TypedEntityReference
, TypeDesignationWorkingSet
> byBaseEntityByTypeStatus
,
101 TypeDesignationBase
<?
> td
){
103 TypeDesignationStatusBase
<?
> status
= td
.getTypeStatus();
106 final IdentifiableEntity
<?
> baseEntity
= baseEntity(td
);
107 final TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
= makeEntityReference(baseEntity
);
109 EntityReference typeDesignationEntityReference
= new EntityReference(td
.getId(), stringify(td
));
111 TypeDesignationWorkingSet typedesignationWorkingSet
;
112 if(!byBaseEntityByTypeStatus
.containsKey(baseEntityReference
)){
113 TypedEntityReference containigEntityReference
= new TypedEntityReference(containgEntity
.getClass(), containgEntity
.getId(), containgEntity
.toString());
114 byBaseEntityByTypeStatus
.put(baseEntityReference
, new TypeDesignationWorkingSet(containigEntityReference
, baseEntity
, baseEntityReference
));
117 typedesignationWorkingSet
= byBaseEntityByTypeStatus
.get(baseEntityReference
);
118 typedesignationWorkingSet
.insert(status
, typeDesignationEntityReference
);
119 } catch (DataIntegrityException e
){
120 probelms
.add(e
.getMessage());
127 * @throws DataIntegrityException
129 protected IdentifiableEntity
<?
> baseEntity(TypeDesignationBase
<?
> td
) throws DataIntegrityException
{
131 IdentifiableEntity
<?
> baseEntity
= null;
132 if(td
instanceof SpecimenTypeDesignation
){
133 SpecimenTypeDesignation std
= (SpecimenTypeDesignation
) td
;
134 FieldUnit fu
= findFieldUnit(std
);
137 } else if(((SpecimenTypeDesignation
) td
).getTypeSpecimen() != null){
138 baseEntity
= ((SpecimenTypeDesignation
) td
).getTypeSpecimen();
140 } else if(td
instanceof NameTypeDesignation
){
141 baseEntity
= ((NameTypeDesignation
)td
).getTypeName();
143 if(baseEntity
== null) {
144 throw new DataIntegrityException("Incomplete TypeDesignation, no type missin in " + td
.toString());
153 protected TypedEntityReference
<IdentifiableEntity
<?
>> makeEntityReference(IdentifiableEntity
<?
> baseEntity
) {
156 if(baseEntity
instanceof FieldUnit
){
157 label
= ((FieldUnit
)baseEntity
).getTitleCache();
160 TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
= new TypedEntityReference(baseEntity
.getClass(), baseEntity
.getId(), label
);
162 return baseEntityReference
;
166 private LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> orderByTypeByBaseEntity(
167 Map
<TypedEntityReference
, TypeDesignationWorkingSet
> stringsByTypeByBaseEntity
){
169 // order the FieldUnit TypeName keys
170 List
<TypedEntityReference
> baseEntityKeyList
= new LinkedList
<>(stringsByTypeByBaseEntity
.keySet());
171 Collections
.sort(baseEntityKeyList
, new Comparator
<TypedEntityReference
>(){
173 * Sorts the base entities (TypedEntityReference) in the following order:
176 * 2. DerivedUnit (in case of missing FieldUnit we expect the base type to be DerivedUnit)
182 public int compare(TypedEntityReference o1
, TypedEntityReference o2
) {
184 Class type1
= o1
.getType();
185 Class type2
= o2
.getType();
187 if(!type1
.equals(type2
)) {
188 if(type1
.equals(FieldUnit
.class) || type2
.equals(FieldUnit
.class)){
190 return type1
.equals(FieldUnit
.class) ?
-1 : 1;
192 // name types last (in case of missing FieldUnit we expect the base type to be DerivedUnit which comes into the middle)
193 return type2
.equals(TaxonName
.class) ?
-1 : 1;
196 return o1
.getLabel().compareTo(o2
.getLabel());
200 // new LinkedHashMap for the ordered FieldUnitOrTypeName keys
201 LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> stringsOrderedbyBaseEntityOrderdByType
= new LinkedHashMap
<>(stringsByTypeByBaseEntity
.size());
203 for(TypedEntityReference baseEntityRef
: baseEntityKeyList
){
205 TypeDesignationWorkingSet typeDesignationWorkingSet
= stringsByTypeByBaseEntity
.get(baseEntityRef
);
206 // order the TypeDesignationStatusBase keys
207 List
<TypeDesignationStatusBase
<?
>> keyList
= new LinkedList
<>(typeDesignationWorkingSet
.keySet());
208 Collections
.sort(keyList
, new Comparator
<TypeDesignationStatusBase
>() {
209 @SuppressWarnings("unchecked")
211 public int compare(TypeDesignationStatusBase o1
, TypeDesignationStatusBase o2
) {
212 // fix inverted order of cdm terms by -1*
213 return -1 * o1
.compareTo(o2
);
216 // new LinkedHashMap for the ordered TypeDesignationStatusBase keys
217 TypeDesignationWorkingSet orderedStringsByOrderedTypes
= new TypeDesignationWorkingSet(typeDesignationWorkingSet
.getContainigEntityReference(),
218 typeDesignationWorkingSet
.getBaseEntity(),
220 orderedStringsByOrderedTypes
.setWorkingSetId(typeDesignationWorkingSet
.workingSetId
); // preserve original workingSetId
221 keyList
.forEach(key
-> orderedStringsByOrderedTypes
.put(key
, typeDesignationWorkingSet
.get(key
)));
222 stringsOrderedbyBaseEntityOrderdByType
.put(baseEntityRef
, orderedStringsByOrderedTypes
);
225 return stringsOrderedbyBaseEntityOrderdByType
;
229 private LinkedHashMap<TypedEntityReference, LinkedHashMap<String, Collection<EntityReference>>> buildOrderedRepresentations(){
231 orderedStringsByOrderedTypes.keySet().forEach(
232 key -> orderedRepresentations.put(
233 getTypeDesignationStytusLabel(key),
234 orderedStringsByOrderedTypes.get(key))
236 return orderedRepresentations;
240 public TypeDesignationSetManager
buildString(){
242 if(finalString
== null){
245 if(getTypifiedNameCache() != null){
246 finalString
+= getTypifiedNameCache() + " ";
250 for(TypedEntityReference baseEntityRef
: orderedByTypesByBaseEntity
.keySet()) {
251 StringBuilder sb
= new StringBuilder();
253 sb
.append(TYPE_SEPARATOR
);
255 boolean isNameTypeDesignation
= false;
256 if(SpecimenOrObservationBase
.class.isAssignableFrom(baseEntityRef
.getType())){
259 sb
.append("NameType: ");
260 isNameTypeDesignation
= true;
262 if(!baseEntityRef
.getLabel().isEmpty()){
263 sb
.append(baseEntityRef
.getLabel()).append(" ");
265 TypeDesignationWorkingSet typeDesignationWorkingSet
= orderedByTypesByBaseEntity
.get(baseEntityRef
);
266 if(!isNameTypeDesignation
){
269 int typeStatusCount
= 0;
270 for(TypeDesignationStatusBase
<?
> typeStatus
: typeDesignationWorkingSet
.keySet()) {
271 if(typeStatusCount
++ > 0){
272 sb
.append(TYPE_STATUS_SEPARATOR
);
274 boolean isPlural
= typeDesignationWorkingSet
.get(typeStatus
).size() > 1;
275 if(!typeStatus
.equals(NULL_STATUS
)) {
276 sb
.append(typeStatus
.getLabel());
283 int typeDesignationCount
= 0;
284 for(EntityReference typeDesignationEntityReference
: typeDesignationWorkingSet
.get(typeStatus
)) {
285 if(typeDesignationCount
++ > 0){
286 sb
.append(TYPE_DESIGNATION_SEPARATOR
);
288 sb
.append(typeDesignationEntityReference
.getLabel());
291 if(!isNameTypeDesignation
){
294 typeDesignationWorkingSet
.setRepresentation(sb
.toString());
295 finalString
+= typeDesignationWorkingSet
.getRepresentation();
303 * FIXME use the validation framework validators and to store the validation problems!!!
306 * @throws RegistrationValidationException
308 private EntityReference
findTypifiedName() throws RegistrationValidationException
{
310 List
<String
> problems
= new ArrayList
<>();
312 TaxonName typifiedName
= null;
314 for(TypeDesignationBase
<?
> typeDesignation
: typeDesignations
){
315 typeDesignation
.getTypifiedNames();
316 if(typeDesignation
.getTypifiedNames().isEmpty()){
318 //TODO instead throw RegistrationValidationException()
319 problems
.add("Missing typifiedName in " + typeDesignation
.toString());
322 if(typeDesignation
.getTypifiedNames().size() > 1){
323 //TODO instead throw RegistrationValidationException()
324 problems
.add("Multiple typifiedName in " + typeDesignation
.toString());
327 if(typifiedName
== null){
329 typifiedName
= typeDesignation
.getTypifiedNames().iterator().next();
332 TaxonName otherTypifiedName
= typeDesignation
.getTypifiedNames().iterator().next();
333 if(typifiedName
.getId() != otherTypifiedName
.getId()){
334 //TODO instead throw RegistrationValidationException()
335 problems
.add("Multiple typifiedName in " + typeDesignation
.toString());
340 if(!problems
.isEmpty()){
341 // FIXME use the validation framework
342 throw new RegistrationValidationException("Inconsistent type designations", problems
);
345 if(typifiedName
!= null){
346 return new EntityReference(typifiedName
.getId(), typifiedName
.getTitleCache());
353 * @return the title cache of the typifying name or <code>null</code>
355 public String
getTypifiedNameCache() {
356 if(typifiedName
!= null){
357 return typifiedName
.getLabel();
363 * @return the title cache of the typifying name or <code>null</code>
365 public EntityReference
getTypifiedName() {
373 public Collection
<TypeDesignationBase
> getTypeDesignations() {
374 return typeDesignations
;
381 public TypeDesignationBase
findTypeDesignation(EntityReference typeDesignationRef
) {
382 for(TypeDesignationBase td
: typeDesignations
){
383 if(td
.getId() == typeDesignationRef
.getId()){
387 // TODO Auto-generated method stub
392 public LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> getOrderdTypeDesignationWorkingSets() {
393 return orderedByTypesByBaseEntity
;
400 private String
stringify(TypeDesignationBase td
) {
402 if(td
instanceof NameTypeDesignation
){
403 return stringify((NameTypeDesignation
)td
);
405 return stringify((SpecimenTypeDesignation
)td
, false);
414 protected String
stringify(NameTypeDesignation td
) {
416 StringBuffer sb
= new StringBuffer();
418 if(td
.getTypeName() != null){
419 sb
.append(td
.getTypeName().getTitleCache());
421 if(td
.getCitation() != null){
422 sb
.append(" ").append(td
.getCitation().getTitleCache());
423 if(td
.getCitationMicroReference() != null){
424 sb
.append(":").append(td
.getCitationMicroReference());
427 if(td
.isNotDesignated()){
428 sb
.append(" not designated");
430 if(td
.isRejectedType()){
431 sb
.append(" rejected");
433 if(td
.isConservedType()){
434 sb
.append(" conserved");
436 return sb
.toString();
443 private String
stringify(SpecimenTypeDesignation td
, boolean useFullTitleCache
) {
446 if(useFullTitleCache
){
447 if(td
.getTypeSpecimen() != null){
448 String nameTitleCache
= td
.getTypeSpecimen().getTitleCache();
449 if(getTypifiedNameCache() != null){
450 nameTitleCache
= nameTitleCache
.replace(getTypifiedNameCache(), "");
452 result
+= nameTitleCache
;
455 if(td
.getTypeSpecimen() != null){
456 DerivedUnit du
= td
.getTypeSpecimen();
457 if(du
.isProtectedTitleCache()){
458 result
+= du
.getTitleCache();
460 DerivedUnitFacadeCacheStrategy cacheStrategy
= new DerivedUnitFacadeCacheStrategy();
461 result
+= cacheStrategy
.getTitleCache(du
, true);
466 if(td
.getCitation() != null){
467 result
+= " " + td
.getCitation().getTitleCache();
468 if(td
.getCitationMicroReference() != null){
469 result
+= " :" + td
.getCitationMicroReference();
472 if(td
.isNotDesignated()){
473 result
+= " not designated";
485 private FieldUnit
findFieldUnit(SpecimenTypeDesignation td
) {
487 DerivedUnit du
= td
.getTypeSpecimen();
488 return findFieldUnit(du
);
491 private FieldUnit
findFieldUnit(DerivedUnit du
) {
493 if(du
== null || du
.getOriginals() == null){
496 @SuppressWarnings("rawtypes")
497 Set
<SpecimenOrObservationBase
> originals
= du
.getDerivedFrom().getOriginals();
498 @SuppressWarnings("rawtypes")
499 Optional
<SpecimenOrObservationBase
> fieldUnit
= originals
.stream()
500 .filter(original
-> original
instanceof FieldUnit
).findFirst();
501 if (fieldUnit
.isPresent()) {
502 return (FieldUnit
) fieldUnit
.get();
504 for (@SuppressWarnings("rawtypes")
505 SpecimenOrObservationBase sob
: originals
) {
506 if (sob
instanceof DerivedUnit
) {
507 FieldUnit fu
= findFieldUnit((DerivedUnit
) sob
);
518 public String
print() {
523 * Groups the EntityReferences for TypeDesignations by the according TypeDesignationStatus.
524 * The TypeDesignationStatusBase keys can be ordered by the term order defined in the vocabulary.
526 public class TypeDesignationWorkingSet
extends LinkedHashMap
<TypeDesignationStatusBase
<?
>, Collection
<EntityReference
>> {
528 private static final long serialVersionUID
= -1329007606500890729L;
530 String workingSetRepresentation
= null;
532 TypedEntityReference
<?
> containigEntityReference
;
534 TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
;
536 IdentifiableEntity
<?
> baseEntity
;
538 List
<DerivedUnit
> derivedUnits
= null;
540 int workingSetId
= workingSetIdAutoIncrement
++;
543 * @param baseEntityReference
545 public TypeDesignationWorkingSet(TypedEntityReference
<?
extends VersionableEntity
> containigEntityReference
, IdentifiableEntity
<?
> baseEntity
, TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
) {
546 this.containigEntityReference
= containigEntityReference
;
547 this.baseEntity
= baseEntity
;
548 this.baseEntityReference
= baseEntityReference
;
554 public IdentifiableEntity
<?
> getBaseEntity() {
558 public List
<EntityReference
> getTypeDesignations() {
559 List
<EntityReference
> typeDesignations
= new ArrayList
<>();
560 this.values().forEach(typeDesignationReferences
-> typeDesignationReferences
.forEach(td
-> typeDesignations
.add(td
)));
561 return typeDesignations
;
566 * @param typeDesignationEntityReference
568 public void insert(TypeDesignationStatusBase
<?
> status
, EntityReference typeDesignationEntityReference
) {
571 status
= NULL_STATUS
;
573 if(!containsKey(status
)){
574 put(status
, new ArrayList
<EntityReference
>());
576 get(status
).add(typeDesignationEntityReference
);
580 * @return the workingSetId
582 public int getWorkingSetId() {
587 * @param workingSetId the workingSetId to set
589 public void setWorkingSetId(int workingSetId
) {
590 this.workingSetId
= workingSetId
;
593 public String
getRepresentation() {
594 return workingSetRepresentation
;
597 public void setRepresentation(String representation
){
598 this.workingSetRepresentation
= representation
;
602 * A reference to the entity which is the common base entity for all TypeDesignations in this workingset.
603 * For a {@link SpecimenTypeDesignation} this is usually the {@link FieldUnit} if it is present. Otherwise it can also be
604 * a {@link DerivedUnit} or something else depending on the specific use case.
606 * @return the baseEntityReference
608 public TypedEntityReference
getBaseEntityReference() {
609 return baseEntityReference
;
613 * A reference to the entity which contains the TypeDesignations bundled in this working set.
614 * This can be for example a {@link TaxonName} or a {@link Registration} entity.
616 * @return the baseEntityReference
618 public TypedEntityReference
getContainigEntityReference() {
619 return containigEntityReference
;
623 public String
toString(){
624 if(workingSetRepresentation
!= null){
625 return workingSetRepresentation
;
627 return super.toString();
634 public boolean isSpecimenTypeDesigationWorkingSet() {
635 return SpecimenOrObservationBase
.class.isAssignableFrom(baseEntityReference
.getType());
638 public TypeDesignationWorkingSetType
getWorkingsetType() {
639 return isSpecimenTypeDesigationWorkingSet() ? TypeDesignationWorkingSetType
.SPECIMEN_TYPE_DESIGNATION_WORKINGSET
: TypeDesignationWorkingSetType
.NAME_TYPE_DESIGNATION_WORKINGSET
;
644 public enum TypeDesignationWorkingSetType
{
645 SPECIMEN_TYPE_DESIGNATION_WORKINGSET
,
646 NAME_TYPE_DESIGNATION_WORKINGSET
,
649 @SuppressWarnings({ "deprecation", "serial" })
650 class NullTypeDesignationStatus
extends TypeDesignationStatusBase
<NullTypeDesignationStatus
>{
656 public void resetTerms() {
665 protected void setDefaultTerms(TermVocabulary
<NullTypeDesignationStatus
> termVocabulary
) {