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
.api
.service
.name
;
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
.Map
.Entry
;
21 import java
.util
.Optional
;
23 import java
.util
.UUID
;
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
;
48 * Manages a collection of {@link TypeDesignationBase type designations} for the same typified name.
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}.
54 * The TypeDesignationSetManager also provides string representations of the whole ordered set of all
55 * {@link TypeDesignationBase TypeDesignations} and of the TypeDesignationSets:
57 * <li>{@link #print()}
58 * <li>{@link #getOrderedTypeDesignationSets()} ... {@link TypeDesignationSet#getLabel()}
60 * Prior using the representations you need to trigger their generation by calling {@link #buildString()}
62 * @author a.kohlbecker
65 public class TypeDesignationSetManager
{
67 //currently not really in use
68 enum NameTypeBaseEntityType
{
69 NAME_TYPE_DESIGNATION
,
73 private NameTypeBaseEntityType nameTypeBaseEntityType
= NameTypeBaseEntityType
.NAME_TYPE_DESIGNATION
;
75 private Map
<UUID
,TypeDesignationBase
<?
>> typeDesignations
= new HashMap
<>();
77 private TaxonName typifiedName
;
79 private Class tdType1
;
82 * Sorts the base entities (TypedEntityReference) in the following order:
85 * 2. DerivedUnit (in case of missing FieldUnit we expect the base type to be DerivedUnit)
90 private Comparator
<Entry
<VersionableEntity
,TypeDesignationSet
>> entryComparator
= (o1
,o2
)->{
92 TypeDesignationSet ws1
= o1
.getValue();
93 TypeDesignationSet ws2
= o2
.getValue();
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;
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;
107 //boolean hasStatus1 = ws1.getTypeDesignations(); //.stream().filter(td -> td.getSt);
109 Class
<?
> type1
= o1
.getKey().getClass();
110 Class
<?
> type2
= o2
.getKey().getClass();
112 if(!type1
.equals(type2
)) {
113 if(type1
.equals(FieldUnit
.class) || type2
.equals(FieldUnit
.class)){
115 return type1
.equals(FieldUnit
.class) ?
-1 : 1;
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;
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
);
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.
132 private LinkedHashMap
<VersionableEntity
,TypeDesignationSet
> orderedByTypesByBaseEntity
;
134 private List
<String
> problems
= new ArrayList
<>();
136 // **************************** CONSTRUCTOR ***********************************/
138 public TypeDesignationSetManager(@SuppressWarnings("rawtypes") Collection
<TypeDesignationBase
> typeDesignations
)
139 throws RegistrationValidationException
{
140 this(typeDesignations
, null);
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
);
151 }catch (RegistrationValidationException e
) {
152 if (typifiedName
== null) {
155 this.typifiedName
= typifiedName
;
161 public TypeDesignationSetManager(HomotypicalGroup group
) {
162 for (TypeDesignationBase
<?
> typeDes
: group
.getTypeDesignations()){
163 this.typeDesignations
.put(typeDes
.getUuid(), typeDes
);
165 //findTypifiedName();
169 public TypeDesignationSetManager(TaxonName typifiedName
) {
170 this.typifiedName
= typifiedName
;
173 // **************************************************************************/
176 * Add one or more TypeDesignations to the manager. This causes re-grouping and re-ordering
177 * of all managed TypeDesignations.
179 * @param containgEntity
180 * @param typeDesignations
182 public void addTypeDesigations(TypeDesignationBase
<?
> ... typeDesignations
){
183 for (TypeDesignationBase
<?
> typeDes
: typeDesignations
){
184 this.typeDesignations
.put(typeDes
.getUuid(), typeDes
);
189 public TaxonName
getTypifiedName() {
193 public void setNameTypeBaseEntityType(NameTypeBaseEntityType nameTypeBaseEntityType
){
194 this.nameTypeBaseEntityType
= nameTypeBaseEntityType
;
197 public NameTypeBaseEntityType
getNameTypeBaseEntityType(){
198 return nameTypeBaseEntityType
;
201 // ******************************** METHODS *********************************/
204 * Groups and orders all managed TypeDesignations.
206 protected void mapAndSort() {
208 Map
<VersionableEntity
,TypeDesignationSet
> byBaseEntityByTypeStatus
= new HashMap
<>();
209 this.typeDesignations
.values().forEach(td
-> mapTypeDesignation(byBaseEntityByTypeStatus
, td
));
210 orderedByTypesByBaseEntity
= orderByTypeByBaseEntity(byBaseEntityByTypeStatus
);
213 private void mapTypeDesignation(Map
<VersionableEntity
,TypeDesignationSet
> byBaseEntityByTypeStatus
,
214 TypeDesignationBase
<?
> td
){
216 td
= HibernateProxyHelper
.deproxy(td
);
217 TypeDesignationStatusBase
<?
> status
= td
.getTypeStatus();
220 final VersionableEntity baseEntity
= baseEntity(td
);
221 // final TypedEntityReference<? extends VersionableEntity> baseEntityReference = makeEntityReference(baseEntity);
223 TaggedTextBuilder workingsetBuilder
= new TaggedTextBuilder();
224 boolean withCitation
= true;
225 TypeDesignationSetFormatter
.buildTaggedTextForSingleType(td
, withCitation
, workingsetBuilder
, 0);
227 @SuppressWarnings({ "unchecked", "rawtypes" })
228 TypeDesignationDTO
<?
> typeDesignationDTO
229 = new TypeDesignationDTO(
232 workingsetBuilder
.getTaggedText(),
235 if(!byBaseEntityByTypeStatus
.containsKey(baseEntity
)){
236 byBaseEntityByTypeStatus
.put(baseEntity
, new TypeDesignationSet(baseEntity
));
238 byBaseEntityByTypeStatus
.get(baseEntity
).insert(status
, typeDesignationDTO
);
240 } catch (DataIntegrityException e
){
241 problems
.add(e
.getMessage());
247 * Returns the uuid of the type designated by this {@link TypeDesignationDTO#}.
248 * This is either a TaxonName or a {@link SpecimenOrObservationBase}.
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();
259 return type
== null?
null : type
.getUuid();
262 protected VersionableEntity
baseEntity(TypeDesignationBase
<?
> td
) throws DataIntegrityException
{
264 VersionableEntity baseEntity
= null;
265 if(td
instanceof SpecimenTypeDesignation
){
266 SpecimenTypeDesignation std
= (SpecimenTypeDesignation
) td
;
267 FieldUnit fu
= findFieldUnit(std
.getTypeSpecimen());
270 } else if(((SpecimenTypeDesignation
) td
).getTypeSpecimen() != null){
271 baseEntity
= ((SpecimenTypeDesignation
) td
).getTypeSpecimen();
273 } else if(td
instanceof NameTypeDesignation
){
274 if(nameTypeBaseEntityType
== NameTypeBaseEntityType
.NAME_TYPE_DESIGNATION
){
277 // only other option is TaxonName
278 baseEntity
= ((NameTypeDesignation
)td
).getTypeName();
281 if(baseEntity
== null) {
282 throw new DataIntegrityException("Incomplete TypeDesignation, no type missin in " + td
.toString());
287 //TODO maybe not needed anymore
288 protected static TypedEntityReference
<?
extends VersionableEntity
> makeEntityReference(VersionableEntity baseEntity
) {
290 baseEntity
= CdmBase
.deproxy(baseEntity
);
291 String label
= TypeDesignationSetFormatter
.entityLabel(baseEntity
);
293 TypedEntityReference
<?
extends VersionableEntity
> baseEntityReference
=
294 new TypedEntityReference
<>(baseEntity
.getClass(), baseEntity
.getUuid(), label
);
296 return baseEntityReference
;
299 private LinkedHashMap
<VersionableEntity
,TypeDesignationSet
> orderByTypeByBaseEntity(
300 Map
<VersionableEntity
,TypeDesignationSet
> stringsByTypeByBaseEntity
){
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
);
309 // new LinkedHashMap for the ordered FieldUnitOrTypeName keys
310 LinkedHashMap
<VersionableEntity
,TypeDesignationSet
> stringsOrderedbyBaseEntityOrderdByType
311 = new LinkedHashMap
<>(stringsByTypeByBaseEntity
.size());
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
);
326 return stringsOrderedbyBaseEntityOrderdByType
;
330 * FIXME use the validation framework validators to store the validation problems!!!
333 * @throws RegistrationValidationException
335 private void findTypifiedName() throws RegistrationValidationException
{
337 List
<String
> problems
= new ArrayList
<>();
339 TaxonName typifiedName
= null;
341 for(TypeDesignationBase
<?
> typeDesignation
: typeDesignations
.values()){
342 typeDesignation
.getTypifiedNames();
343 if(typeDesignation
.getTypifiedNames().isEmpty()){
345 //TODO instead throw RegistrationValidationException()
346 problems
.add("Missing typifiedName in " + typeDesignation
.toString());
349 if(typeDesignation
.getTypifiedNames().size() > 1){
350 //TODO instead throw RegistrationValidationException()
351 problems
.add("Multiple typifiedName in " + typeDesignation
.toString());
354 if(typifiedName
== null){
356 typifiedName
= typeDesignation
.getTypifiedNames().iterator().next();
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());
366 if(!problems
.isEmpty()){
367 // FIXME use the validation framework
368 throw new RegistrationValidationException("Inconsistent type designations", problems
);
371 if(typifiedName
!= null){
372 // ON SUCCESS -------------------
373 this.typifiedName
= typifiedName
;
378 * @return the title cache of the typifying name or <code>null</code>
380 public String
getTypifiedNameCache() {
381 if(typifiedName
!= null){
382 return typifiedName
.getTitleCache();
388 * @return the title cache of the typifying name or <code>null</code>
390 public EntityReference
getTypifiedNameAsEntityRef() {
391 return new EntityReference(typifiedName
.getUuid(), typifiedName
.getTitleCache());
394 public Collection
<TypeDesignationBase
<?
>> getTypeDesignations() {
395 return typeDesignations
.values();
398 public TypeDesignationBase
<?
> findTypeDesignation(UUID uuid
) {
399 return this.typeDesignations
.get(uuid
);
402 public Map
<VersionableEntity
,TypeDesignationSet
> getOrderedTypeDesignationSets() {
403 return orderedByTypesByBaseEntity
;
406 private FieldUnit
findFieldUnit(DerivedUnit du
) {
408 if(du
== null || du
.getOriginals() == null || du
.getOriginals().isEmpty()){
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();
419 for (@SuppressWarnings("rawtypes") SpecimenOrObservationBase sob
: originals
) {
420 if (sob
instanceof DerivedUnit
) {
421 FieldUnit fu
= findFieldUnit((DerivedUnit
) sob
);
432 public String
print(boolean withCitation
, boolean withStartingTypeLabel
, boolean withNameIfAvailable
) {
433 return new TypeDesignationSetFormatter(withCitation
, withStartingTypeLabel
, withNameIfAvailable
).format(this);
436 public String
print(boolean withCitation
, boolean withStartingTypeLabel
, boolean withNameIfAvailable
, HTMLTagRules htmlRules
) {
437 return new TypeDesignationSetFormatter(withCitation
, withStartingTypeLabel
, withNameIfAvailable
).format(this, htmlRules
);
441 class DataIntegrityException
extends Exception
{
443 private static final long serialVersionUID
= 1464726696296824905L;
445 public DataIntegrityException(String string
) {