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 org
.hibernate
.search
.hcore
.util
.impl
.HibernateHelper
;
26 import eu
.etaxonomy
.cdm
.api
.facade
.DerivedUnitFacadeCacheStrategy
;
27 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
28 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
29 import eu
.etaxonomy
.cdm
.model
.common
.TermVocabulary
;
30 import eu
.etaxonomy
.cdm
.model
.name
.NameTypeDesignation
;
31 import eu
.etaxonomy
.cdm
.model
.name
.SpecimenTypeDesignation
;
32 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
33 import eu
.etaxonomy
.cdm
.model
.name
.TypeDesignationBase
;
34 import eu
.etaxonomy
.cdm
.model
.name
.TypeDesignationStatusBase
;
35 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
36 import eu
.etaxonomy
.cdm
.model
.occurrence
.FieldUnit
;
37 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
38 import eu
.etaxonomy
.cdm
.vaadin
.model
.EntityReference
;
39 import eu
.etaxonomy
.cdm
.vaadin
.model
.TypedEntityReference
;
40 import eu
.etaxonomy
.cdm
.vaadin
.view
.registration
.RegistrationValidationException
;
43 * Manages a collection of {@link TypeDesignationBase TypeDesignations} for the same typified name.
45 * Type designations are ordered by the base type which is a {@link TaxonName} for {@link NameTypeDesignation NameTypeDesignations} or
46 * in case of {@link SpecimenTypeDesignation SpecimenTypeDesignations} the associate {@link FieldUnit} or the {@link DerivedUnit}
47 * if the former is missing. The type designations per base type are furthermore ordered by the {@link TypeDesignationStatusBase}.
49 * The TypeDesignationSetManager also provides string representations of the whole ordered set of all
50 * {@link TypeDesignationBase TypeDesignations} and of the TypeDesignationWorkingSets:
52 * <li>{@link #print()})
53 * <li>{@link #getOrderdTypeDesignationWorkingSets()} ... {@link TypeDesignationWorkingSet#getRepresentation()}
55 * Prior using the representations you need to trigger their generation by calling {@link #buildString()}
57 * @author a.kohlbecker
61 public class TypeDesignationSetManager
{
64 private static final String TYPE_STATUS_SEPARATOR
= "; ";
66 private static final String TYPE_SEPARATOR
= "; ";
68 private static final String TYPE_DESIGNATION_SEPARATOR
= ", ";
70 private Collection
<TypeDesignationBase
> typeDesignations
;
72 private int workingSetIdAutoIncrement
= 0;
75 * Groups the EntityReferences for each of the TypeDesignations by the according TypeDesignationStatus.
76 * The TypeDesignationStatusBase keys are already ordered by the term order defined in the vocabulary.
78 private LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> orderedByTypesByBaseEntity
;
80 private EntityReference typifiedNameRef
;
82 private TaxonName typifiedName
;
84 private String finalString
= null;
86 final NullTypeDesignationStatus NULL_STATUS
= new NullTypeDesignationStatus();
88 private List
<String
> probelms
= new ArrayList
<>();
90 private boolean printCitation
= false;
93 * @param containgEntity
95 * @throws RegistrationValidationException
98 public TypeDesignationSetManager(Collection
<TypeDesignationBase
> typeDesignations
) throws RegistrationValidationException
{
99 this.typeDesignations
= typeDesignations
;
105 * @param typifiedName2
107 public TypeDesignationSetManager(TaxonName typifiedName
) {
108 this.typeDesignations
= new ArrayList
<>();
109 this.typifiedNameRef
= new EntityReference(typifiedName
.getId(), typifiedName
.getTitleCache());
113 * Add one or more TypeDesignations to the manager. This causes re-grouping and re-ordering
114 * of all managed TypeDesignations.
116 * @param containgEntity
117 * @param typeDesignations
119 public void addTypeDesigations(CdmBase containgEntity
, TypeDesignationBase
... typeDesignations
){
120 this.typeDesignations
.addAll(Arrays
.asList(typeDesignations
));
125 * Groups and orders all managed TypeDesignations.
127 * @param containgEntity
129 protected void mapAndSort() {
131 Map
<TypedEntityReference
, TypeDesignationWorkingSet
> byBaseEntityByTypeStatus
= new HashMap
<>();
132 this.typeDesignations
.forEach(td
-> mapTypeDesignation(byBaseEntityByTypeStatus
, td
));
133 orderedByTypesByBaseEntity
= orderByTypeByBaseEntity(byBaseEntityByTypeStatus
);
139 * @param containgEntity
140 * @param byBaseEntityByTypeStatus
143 private void mapTypeDesignation(Map
<TypedEntityReference
, TypeDesignationWorkingSet
> byBaseEntityByTypeStatus
,
144 TypeDesignationBase
<?
> td
){
146 TypeDesignationStatusBase
<?
> status
= td
.getTypeStatus();
149 final IdentifiableEntity
<?
> baseEntity
= baseEntity(td
);
150 final TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
= makeEntityReference(baseEntity
);
152 EntityReference typeDesignationEntityReference
= new EntityReference(td
.getId(), stringify(td
));
154 TypeDesignationWorkingSet typedesignationWorkingSet
;
155 if(!byBaseEntityByTypeStatus
.containsKey(baseEntityReference
)){
156 byBaseEntityByTypeStatus
.put(baseEntityReference
, new TypeDesignationWorkingSet(baseEntity
, baseEntityReference
));
159 typedesignationWorkingSet
= byBaseEntityByTypeStatus
.get(baseEntityReference
);
160 typedesignationWorkingSet
.insert(status
, typeDesignationEntityReference
);
161 } catch (DataIntegrityException e
){
162 probelms
.add(e
.getMessage());
169 * @throws DataIntegrityException
171 protected IdentifiableEntity
<?
> baseEntity(TypeDesignationBase
<?
> td
) throws DataIntegrityException
{
173 IdentifiableEntity
<?
> baseEntity
= null;
174 if(td
instanceof SpecimenTypeDesignation
){
175 SpecimenTypeDesignation std
= (SpecimenTypeDesignation
) td
;
176 FieldUnit fu
= findFieldUnit(std
);
179 } else if(((SpecimenTypeDesignation
) td
).getTypeSpecimen() != null){
180 baseEntity
= ((SpecimenTypeDesignation
) td
).getTypeSpecimen();
182 } else if(td
instanceof NameTypeDesignation
){
183 baseEntity
= ((NameTypeDesignation
)td
).getTypeName();
185 if(baseEntity
== null) {
186 throw new DataIntegrityException("Incomplete TypeDesignation, no type missin in " + td
.toString());
195 protected TypedEntityReference
<IdentifiableEntity
<?
>> makeEntityReference(IdentifiableEntity
<?
> baseEntity
) {
197 baseEntity
= (IdentifiableEntity
<?
>) HibernateHelper
.unproxy(baseEntity
);
199 if(baseEntity
instanceof FieldUnit
){
200 label
= ((FieldUnit
)baseEntity
).getTitleCache();
203 TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
= new TypedEntityReference(baseEntity
.getClass(), baseEntity
.getId(), label
);
205 return baseEntityReference
;
209 private LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> orderByTypeByBaseEntity(
210 Map
<TypedEntityReference
, TypeDesignationWorkingSet
> stringsByTypeByBaseEntity
){
212 // order the FieldUnit TypeName keys
213 List
<TypedEntityReference
> baseEntityKeyList
= new LinkedList
<>(stringsByTypeByBaseEntity
.keySet());
214 Collections
.sort(baseEntityKeyList
, new Comparator
<TypedEntityReference
>(){
216 * Sorts the base entities (TypedEntityReference) in the following order:
219 * 2. DerivedUnit (in case of missing FieldUnit we expect the base type to be DerivedUnit)
225 public int compare(TypedEntityReference o1
, TypedEntityReference o2
) {
227 Class type1
= o1
.getType();
228 Class type2
= o2
.getType();
230 if(!type1
.equals(type2
)) {
231 if(type1
.equals(FieldUnit
.class) || type2
.equals(FieldUnit
.class)){
233 return type1
.equals(FieldUnit
.class) ?
-1 : 1;
235 // name types last (in case of missing FieldUnit we expect the base type to be DerivedUnit which comes into the middle)
236 return type2
.equals(TaxonName
.class) ?
-1 : 1;
239 return o1
.getLabel().compareTo(o2
.getLabel());
243 // new LinkedHashMap for the ordered FieldUnitOrTypeName keys
244 LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> stringsOrderedbyBaseEntityOrderdByType
= new LinkedHashMap
<>(stringsByTypeByBaseEntity
.size());
246 for(TypedEntityReference baseEntityRef
: baseEntityKeyList
){
248 TypeDesignationWorkingSet typeDesignationWorkingSet
= stringsByTypeByBaseEntity
.get(baseEntityRef
);
249 // order the TypeDesignationStatusBase keys
250 List
<TypeDesignationStatusBase
<?
>> keyList
= new LinkedList
<>(typeDesignationWorkingSet
.keySet());
251 Collections
.sort(keyList
, new Comparator
<TypeDesignationStatusBase
>() {
252 @SuppressWarnings("unchecked")
254 public int compare(TypeDesignationStatusBase o1
, TypeDesignationStatusBase o2
) {
255 // fix inverted order of cdm terms by -1*
256 return -1 * o1
.compareTo(o2
);
259 // new LinkedHashMap for the ordered TypeDesignationStatusBase keys
260 TypeDesignationWorkingSet orderedStringsByOrderedTypes
= new TypeDesignationWorkingSet(
261 typeDesignationWorkingSet
.getBaseEntity(),
263 orderedStringsByOrderedTypes
.setWorkingSetId(typeDesignationWorkingSet
.workingSetId
); // preserve original workingSetId
264 keyList
.forEach(key
-> orderedStringsByOrderedTypes
.put(key
, typeDesignationWorkingSet
.get(key
)));
265 stringsOrderedbyBaseEntityOrderdByType
.put(baseEntityRef
, orderedStringsByOrderedTypes
);
268 return stringsOrderedbyBaseEntityOrderdByType
;
272 private LinkedHashMap<TypedEntityReference, LinkedHashMap<String, Collection<EntityReference>>> buildOrderedRepresentations(){
274 orderedStringsByOrderedTypes.keySet().forEach(
275 key -> orderedRepresentations.put(
276 getTypeDesignationStytusLabel(key),
277 orderedStringsByOrderedTypes.get(key))
279 return orderedRepresentations;
283 public TypeDesignationSetManager
buildString(){
285 if(finalString
== null){
288 if(getTypifiedNameCache() != null){
289 finalString
+= getTypifiedNameCache() + " ";
293 if(orderedByTypesByBaseEntity
!= null){
294 for(TypedEntityReference baseEntityRef
: orderedByTypesByBaseEntity
.keySet()) {
295 StringBuilder sb
= new StringBuilder();
297 sb
.append(TYPE_SEPARATOR
);
299 boolean isNameTypeDesignation
= false;
300 if(SpecimenOrObservationBase
.class.isAssignableFrom(baseEntityRef
.getType())){
303 sb
.append("NameType: ");
304 isNameTypeDesignation
= true;
306 if(!baseEntityRef
.getLabel().isEmpty()){
307 sb
.append(baseEntityRef
.getLabel()).append(" ");
309 TypeDesignationWorkingSet typeDesignationWorkingSet
= orderedByTypesByBaseEntity
.get(baseEntityRef
);
310 if(!isNameTypeDesignation
){
313 int typeStatusCount
= 0;
314 for(TypeDesignationStatusBase
<?
> typeStatus
: typeDesignationWorkingSet
.keySet()) {
315 if(typeStatusCount
++ > 0){
316 sb
.append(TYPE_STATUS_SEPARATOR
);
318 boolean isPlural
= typeDesignationWorkingSet
.get(typeStatus
).size() > 1;
319 if(!typeStatus
.equals(NULL_STATUS
)) {
320 sb
.append(typeStatus
.getLabel());
327 int typeDesignationCount
= 0;
328 for(EntityReference typeDesignationEntityReference
: typeDesignationWorkingSet
.get(typeStatus
)) {
329 if(typeDesignationCount
++ > 0){
330 sb
.append(TYPE_DESIGNATION_SEPARATOR
);
332 sb
.append(typeDesignationEntityReference
.getLabel());
335 if(!isNameTypeDesignation
){
338 typeDesignationWorkingSet
.setRepresentation(sb
.toString());
339 finalString
+= typeDesignationWorkingSet
.getRepresentation();
347 * FIXME use the validation framework validators and to store the validation problems!!!
350 * @throws RegistrationValidationException
352 private void findTypifiedName() throws RegistrationValidationException
{
354 List
<String
> problems
= new ArrayList
<>();
356 TaxonName typifiedName
= null;
358 for(TypeDesignationBase
<?
> typeDesignation
: typeDesignations
){
359 typeDesignation
.getTypifiedNames();
360 if(typeDesignation
.getTypifiedNames().isEmpty()){
362 //TODO instead throw RegistrationValidationException()
363 problems
.add("Missing typifiedName in " + typeDesignation
.toString());
366 if(typeDesignation
.getTypifiedNames().size() > 1){
367 //TODO instead throw RegistrationValidationException()
368 problems
.add("Multiple typifiedName in " + typeDesignation
.toString());
371 if(typifiedName
== null){
373 typifiedName
= typeDesignation
.getTypifiedNames().iterator().next();
376 TaxonName otherTypifiedName
= typeDesignation
.getTypifiedNames().iterator().next();
377 if(typifiedName
.getId() != otherTypifiedName
.getId()){
378 //TODO instead throw RegistrationValidationException()
379 problems
.add("Multiple typifiedName in " + typeDesignation
.toString());
384 if(!problems
.isEmpty()){
385 // FIXME use the validation framework
386 throw new RegistrationValidationException("Inconsistent type designations", problems
);
389 if(typifiedName
!= null){
390 // ON SUCCESS -------------------
391 this.typifiedName
= typifiedName
;
392 this.typifiedNameRef
= new EntityReference(typifiedName
.getId(), typifiedName
.getTitleCache());
399 * @return the title cache of the typifying name or <code>null</code>
401 public String
getTypifiedNameCache() {
402 if(typifiedNameRef
!= null){
403 return typifiedNameRef
.getLabel();
409 * @return the title cache of the typifying name or <code>null</code>
411 public EntityReference
getTypifiedNameRef() {
413 return typifiedNameRef
;
419 public Collection
<TypeDesignationBase
> getTypeDesignations() {
420 return typeDesignations
;
427 public TypeDesignationBase
findTypeDesignation(EntityReference typeDesignationRef
) {
428 for(TypeDesignationBase td
: typeDesignations
){
429 if(td
.getId() == typeDesignationRef
.getId()){
433 // TODO Auto-generated method stub
438 public LinkedHashMap
<TypedEntityReference
, TypeDesignationWorkingSet
> getOrderdTypeDesignationWorkingSets() {
439 return orderedByTypesByBaseEntity
;
446 private String
stringify(TypeDesignationBase td
) {
448 if(td
instanceof NameTypeDesignation
){
449 return stringify((NameTypeDesignation
)td
);
451 return stringify((SpecimenTypeDesignation
)td
, false);
460 protected String
stringify(NameTypeDesignation td
) {
462 StringBuffer sb
= new StringBuffer();
464 if(td
.getTypeName() != null){
465 sb
.append(td
.getTypeName().getTitleCache());
467 if(td
.getCitation() != null){
468 sb
.append(" ").append(td
.getCitation().getTitleCache());
469 if(td
.getCitationMicroReference() != null){
470 sb
.append(":").append(td
.getCitationMicroReference());
473 if(td
.isNotDesignated()){
474 sb
.append(" not designated");
476 if(td
.isRejectedType()){
477 sb
.append(" rejected");
479 if(td
.isConservedType()){
480 sb
.append(" conserved");
482 return sb
.toString();
489 private String
stringify(SpecimenTypeDesignation td
, boolean useFullTitleCache
) {
492 if(useFullTitleCache
){
493 if(td
.getTypeSpecimen() != null){
494 String nameTitleCache
= td
.getTypeSpecimen().getTitleCache();
495 if(getTypifiedNameCache() != null){
496 nameTitleCache
= nameTitleCache
.replace(getTypifiedNameCache(), "");
498 result
+= nameTitleCache
;
501 if(td
.getTypeSpecimen() != null){
502 DerivedUnit du
= td
.getTypeSpecimen();
503 if(du
.isProtectedTitleCache()){
504 result
+= du
.getTitleCache();
506 DerivedUnitFacadeCacheStrategy cacheStrategy
= new DerivedUnitFacadeCacheStrategy();
507 result
+= cacheStrategy
.getTitleCache(du
, true);
512 if(isPrintCitation() && td
.getCitation() != null){
513 if(td
.getCitation().getAbbrevTitle() != null){
514 result
+= " " + td
.getCitation().getAbbrevTitle();
516 result
+= " " + td
.getCitation().getTitleCache();
518 if(td
.getCitationMicroReference() != null){
519 result
+= " :" + td
.getCitationMicroReference();
522 if(td
.isNotDesignated()){
523 result
+= " not designated";
535 private FieldUnit
findFieldUnit(SpecimenTypeDesignation td
) {
537 DerivedUnit du
= td
.getTypeSpecimen();
538 return findFieldUnit(du
);
541 private FieldUnit
findFieldUnit(DerivedUnit du
) {
543 if(du
== null || du
.getOriginals() == null){
546 @SuppressWarnings("rawtypes")
547 Set
<SpecimenOrObservationBase
> originals
= du
.getDerivedFrom().getOriginals();
548 @SuppressWarnings("rawtypes")
549 Optional
<SpecimenOrObservationBase
> fieldUnit
= originals
.stream()
550 .filter(original
-> original
instanceof FieldUnit
).findFirst();
551 if (fieldUnit
.isPresent()) {
552 return (FieldUnit
) fieldUnit
.get();
554 for (@SuppressWarnings("rawtypes")
555 SpecimenOrObservationBase sob
: originals
) {
556 if (sob
instanceof DerivedUnit
) {
557 FieldUnit fu
= findFieldUnit((DerivedUnit
) sob
);
568 public String
print() {
569 return finalString
.trim();
573 * @return the printCitation
575 public boolean isPrintCitation() {
576 return printCitation
;
580 * @param printCitation the printCitation to set
582 public void setPrintCitation(boolean printCitation
) {
583 this.printCitation
= printCitation
;
587 * @return the typifiedName
589 public TaxonName
getTypifiedName() {
594 * TypeDesignations which refer to the same FieldUnit (SpecimenTypeDesignation) or TaxonName
595 * (NameTypeDesignation) form a working set. The <code>TypeDesignationWorkingSet</code> internally
596 * works with EnityReferences to the actual TypeDesignations.
598 * The EntityReferences for TypeDesignations are grouped by the according TypeDesignationStatus.
599 * The TypeDesignationStatusBase keys can be ordered by the term order defined in the vocabulary.
601 * A workingset can be referenced by the <code>workingSetId</code>, this is a autoincrement
602 * value which is created during the process of determining the workingsets in a collection of
605 * TODO: consider using a concatenation of baseEntity.getClass() + baseEntity.getId() as workingset identifier
607 public class TypeDesignationWorkingSet
extends LinkedHashMap
<TypeDesignationStatusBase
<?
>, Collection
<EntityReference
>> {
609 private static final long serialVersionUID
= -1329007606500890729L;
611 String workingSetRepresentation
= null;
613 TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
;
615 IdentifiableEntity
<?
> baseEntity
;
617 List
<DerivedUnit
> derivedUnits
= null;
619 int workingSetId
= workingSetIdAutoIncrement
++;
622 * @param baseEntityReference
624 public TypeDesignationWorkingSet(IdentifiableEntity
<?
> baseEntity
, TypedEntityReference
<IdentifiableEntity
<?
>> baseEntityReference
) {
625 this.baseEntity
= baseEntity
;
626 this.baseEntityReference
= baseEntityReference
;
632 public IdentifiableEntity
<?
> getBaseEntity() {
636 public List
<EntityReference
> getTypeDesignations() {
637 List
<EntityReference
> typeDesignations
= new ArrayList
<>();
638 this.values().forEach(typeDesignationReferences
-> typeDesignationReferences
.forEach(td
-> typeDesignations
.add(td
)));
639 return typeDesignations
;
644 * @param typeDesignationEntityReference
646 public void insert(TypeDesignationStatusBase
<?
> status
, EntityReference typeDesignationEntityReference
) {
649 status
= NULL_STATUS
;
651 if(!containsKey(status
)){
652 put(status
, new ArrayList
<EntityReference
>());
654 get(status
).add(typeDesignationEntityReference
);
658 * @return the workingSetId
660 public int getWorkingSetId() {
665 * @param workingSetId the workingSetId to set
667 public void setWorkingSetId(int workingSetId
) {
668 this.workingSetId
= workingSetId
;
671 public String
getRepresentation() {
672 return workingSetRepresentation
;
675 public void setRepresentation(String representation
){
676 this.workingSetRepresentation
= representation
;
680 * A reference to the entity which is the common base entity for all TypeDesignations in this workingset.
681 * For a {@link SpecimenTypeDesignation} this is usually the {@link FieldUnit} if it is present. Otherwise it can also be
682 * a {@link DerivedUnit} or something else depending on the specific use case.
684 * @return the baseEntityReference
686 public TypedEntityReference
getBaseEntityReference() {
687 return baseEntityReference
;
691 public String
toString(){
692 if(workingSetRepresentation
!= null){
693 return workingSetRepresentation
;
695 return super.toString();
702 public boolean isSpecimenTypeDesigationWorkingSet() {
703 return SpecimenOrObservationBase
.class.isAssignableFrom(baseEntityReference
.getType());
706 public TypeDesignationWorkingSetType
getWorkingsetType() {
707 return isSpecimenTypeDesigationWorkingSet() ? TypeDesignationWorkingSetType
.SPECIMEN_TYPE_DESIGNATION_WORKINGSET
: TypeDesignationWorkingSetType
.NAME_TYPE_DESIGNATION_WORKINGSET
;
712 public enum TypeDesignationWorkingSetType
{
713 SPECIMEN_TYPE_DESIGNATION_WORKINGSET
,
714 NAME_TYPE_DESIGNATION_WORKINGSET
,
717 @SuppressWarnings({ "deprecation", "serial" })
718 class NullTypeDesignationStatus
extends TypeDesignationStatusBase
<NullTypeDesignationStatus
>{
724 public void resetTerms() {
733 protected void setDefaultTerms(TermVocabulary
<NullTypeDesignationStatus
> termVocabulary
) {