ref #8297 refactor condensed distribution string handling
authorAndreas Müller <a.mueller@bgbm.org>
Tue, 23 Feb 2021 15:18:40 +0000 (16:18 +0100)
committerAndreas Müller <a.mueller@bgbm.org>
Tue, 23 Feb 2021 17:20:33 +0000 (18:20 +0100)
21 files changed:
cdmlib-commons/src/main/java/eu/etaxonomy/cdm/common/TripleResult.java [new file with mode: 0644]
cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposer.java [new file with mode: 0644]
cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposerBase.java [deleted file]
cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionConfiguration.java [new file with mode: 0644]
cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionRecipe.java
cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/EditGeoService.java
cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/EditGeoServiceUtilities.java
cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/EuroPlusMedCondensedDistributionComposer.java [deleted file]
cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/FloraCubaCondensedDistributionComposer.java [deleted file]
cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/FloraCubaCondensedDistributionComposerOld.java [deleted file]
cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/ICondensedDistributionComposer.java [deleted file]
cdmlib-ext/src/test/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposerEuroMedTest.java [new file with mode: 0644]
cdmlib-ext/src/test/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposerFloraCubaTest.java [moved from cdmlib-ext/src/test/java/eu/etaxonomy/cdm/ext/geo/FloraCubaCondensedDistributionComposerTest.java with 95% similarity]
cdmlib-ext/src/test/java/eu/etaxonomy/cdm/ext/geo/EuroPlusMedCondensedDistributionComposerTest.java [deleted file]
cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/MarkerType.java
cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/description/PresenceAbsenceTerm.java
cdmlib-model/src/main/java/eu/etaxonomy/cdm/strategy/cache/HTMLTagRules.java
cdmlib-model/src/main/java/eu/etaxonomy/cdm/strategy/cache/TaggedCacheHelper.java
cdmlib-model/src/main/java/eu/etaxonomy/cdm/strategy/cache/TaggedText.java
cdmlib-model/src/test/java/eu/etaxonomy/cdm/strategy/cache/TaggedCacheHelperTest.java [new file with mode: 0644]
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/dto/CondensedDistribution.java

diff --git a/cdmlib-commons/src/main/java/eu/etaxonomy/cdm/common/TripleResult.java b/cdmlib-commons/src/main/java/eu/etaxonomy/cdm/common/TripleResult.java
new file mode 100644 (file)
index 0000000..f62eb01
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+* Copyright (C) 2021 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package eu.etaxonomy.cdm.common;
+
+/**
+ * @author a.mueller
+ * @since 18.02.2021
+ */
+public class TripleResult<S extends Object, T extends Object, U extends Object>
+        extends DoubleResult<S,T> {
+
+    private U thirdResult;
+
+    public TripleResult(S firstResult, T secondResult, U thirdResult) {
+        super(firstResult, secondResult);
+        this.thirdResult = thirdResult;
+    }
+
+    public U getThirdResult() {
+        return thirdResult;
+    }
+
+    public void setThirdResult(U thirdResult) {
+        this.thirdResult = thirdResult;
+    }
+
+}
diff --git a/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposer.java b/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposer.java
new file mode 100644 (file)
index 0000000..5eb8d4e
--- /dev/null
@@ -0,0 +1,472 @@
+/**
+* Copyright (C) 2016 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package eu.etaxonomy.cdm.ext.geo;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.codehaus.plexus.util.StringUtils;
+
+import eu.etaxonomy.cdm.api.service.dto.CondensedDistribution;
+import eu.etaxonomy.cdm.common.DoubleResult;
+import eu.etaxonomy.cdm.common.TripleResult;
+import eu.etaxonomy.cdm.common.UTF8;
+import eu.etaxonomy.cdm.model.common.Language;
+import eu.etaxonomy.cdm.model.description.Distribution;
+import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
+import eu.etaxonomy.cdm.model.location.NamedArea;
+import eu.etaxonomy.cdm.model.term.Representation;
+
+/**
+ * Base class for condensed distribution composers
+ *
+ * @author a.mueller
+ * @since 02.06.2016
+ */
+public class CondensedDistributionComposer {
+
+    //for old handling of status symbols
+    protected static Map<UUID, String> statusSymbols;
+
+    static {
+
+        // ==================================================
+        // Mapping as defined in ticket https://dev.e-taxonomy.eu/redmine/issues/3907
+        // ==================================================
+
+        statusSymbols = new HashMap<> ();
+        statusSymbols.put(PresenceAbsenceTerm.INTRODUCED_REPORTED_IN_ERROR().getUuid(), UTF8.EN_DASH.toString());
+    }
+
+
+    public enum StatusSymbolUsage{
+        Map,
+        Symbol1,
+        Symbol2,
+        IdInVoc,
+        AbbrelLabel;
+
+        public String getSymbol(PresenceAbsenceTerm status) {
+            if (this == Map){
+                return statusSymbols.get(status.getUuid());
+            }else if (this == Symbol1){
+                return status.getSymbol();
+            }else if (this == Symbol2){
+                return status.getSymbol2();
+            }else if (this == IdInVoc){
+                return status.getIdInVocabulary();
+            }else if (this == AbbrelLabel){
+                Representation r = status.getPreferredRepresentation((Language)null);
+                if (r != null){
+                    String abbrevLabel = r.getAbbreviatedLabel();
+                    if (abbrevLabel != null){
+                        return abbrevLabel;
+                    }
+                }
+            }
+            throw new RuntimeException("Unhandled enum value: " +  this);
+        }
+    }
+
+    public CondensedDistribution createCondensedDistribution(Collection<Distribution> filteredDistributions,
+            List<Language> languages, CondensedDistributionConfiguration config) {
+
+        CondensedDistribution result = new CondensedDistribution();
+
+        Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap = new HashMap<>();
+
+        DoubleResult<List<AreaNode>, List<AreaNode>> step1_3 = createAreaTreesAndStatusMap(filteredDistributions, areaToStatusMap, config);
+        List<AreaNode> topLevelNodes = step1_3.getFirstResult();
+        List<AreaNode> introducedTopLevelNodes = step1_3.getSecondResult();
+
+        //4. replace the area by the abbreviated representation and add symbols
+        AreaNodeComparator areaNodeComparator = new AreaNodeComparator();
+        Collections.sort(topLevelNodes, areaNodeComparator);
+
+        final boolean NOT_BOLED = false;
+        final boolean NOT_HANDLED_BY_PARENT = false;
+
+        List<AreaNode> outOfScopeNodes = new ArrayList<>();
+        if (!topLevelNodes.isEmpty()){
+            AreaNode areaOfScopeNode = topLevelNodes.remove(0);
+            outOfScopeNodes = topLevelNodes;
+
+            //handle areaOfScope  (endemic area)
+            PresenceAbsenceTerm areaOfScopeStatus = areaToStatusMap.get(areaOfScopeNode.area);
+            DoubleResult<String, Boolean> areaOfScopeStatusSymbol = statusSymbol(areaOfScopeStatus, config, NOT_HANDLED_BY_PARENT);
+            String areaOfScopeLabel = config.showAreaOfScopeLabel? makeAreaLabel(languages, areaOfScopeNode.area, config, null):"";
+            result.addStatusAndAreaTaggedText(areaOfScopeStatusSymbol.getFirstResult(),
+                    areaOfScopeLabel, areaOfScopeStatusSymbol.getSecondResult() || config.areasBold);
+
+            //subareas
+            handleSubAreas(result, areaOfScopeNode, config, areaNodeComparator, languages, areaToStatusMap,
+                    areaOfScopeLabel, 0, NOT_BOLED, NOT_HANDLED_BY_PARENT, false);
+        }
+
+        //subareas with introduced status (if required by configuration)
+        if (config.splitNativeAndIntroduced && !introducedTopLevelNodes.isEmpty()){
+            String sep = (result.isEmpty()? "": " ") + config.introducedBracketStart;
+            result.addSeparatorTaggedText(sep, NOT_BOLED);
+            boolean isIntroduced = true;
+            handleSubAreasLoop(result, introducedTopLevelNodes.get(0), config, areaNodeComparator, languages,
+                    areaToStatusMap, "", 0, NOT_BOLED, NOT_HANDLED_BY_PARENT, isIntroduced);
+            result.addSeparatorTaggedText(config.introducedBracketEnd, NOT_BOLED);
+        }
+
+        //outOfScope areas  (areas outside the endemic area)
+        if (!outOfScopeNodes.isEmpty()){
+            result.addPostSeparatorTaggedText(config.outOfScopeAreasSeperator);
+            List<AreaNode> outOfScopeList = new ArrayList<>(outOfScopeNodes);
+            Collections.sort(outOfScopeList, areaNodeComparator);
+            boolean isFirst = true;
+            for (AreaNode outOfScopeNode: outOfScopeList){
+                String sep = isFirst ? "": " ";
+                result.addSeparatorTaggedText(sep);
+                handleSubAreaNode(languages, result, areaToStatusMap, areaNodeComparator,
+                        outOfScopeNode, config, null, 0, NOT_BOLED, NOT_HANDLED_BY_PARENT, false);
+                isFirst = false;
+            }
+        }
+
+        return result;
+    }
+
+    protected Map<NamedArea, AreaNode>[] buildAreaHierarchie(Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap,
+            CondensedDistributionConfiguration config) {
+
+        Map<NamedArea, AreaNode> areaNodeMap = new HashMap<>();
+        Map<NamedArea, AreaNode> introducedAreaNodeMap = new HashMap<>();
+
+        for(NamedArea area : areaToStatusMap.keySet()) {
+            Map<NamedArea, AreaNode> map = areaNodeMap;
+            if (config.splitNativeAndIntroduced && isIntroduced(areaToStatusMap.get(area))){
+                map = introducedAreaNodeMap;
+            }
+            mergeIntoHierarchy(areaToStatusMap.keySet(), map, area, config);
+        }
+        @SuppressWarnings("unchecked")
+        Map<NamedArea, AreaNode>[] result = new Map[]{areaNodeMap,introducedAreaNodeMap};
+        return result;
+    }
+
+    private boolean isIntroduced(PresenceAbsenceTerm status) {
+        return status.isAnyIntroduced();
+    }
+
+    private void mergeIntoHierarchy(Collection<NamedArea> areas,  //areas not really needed anymore if we don't use findParentIn
+            Map<NamedArea, AreaNode> areaNodeMap, NamedArea area, CondensedDistributionConfiguration config) {
+
+        AreaNode node = areaNodeMap.get(area);
+        if(node == null) {
+            // putting area into list of areas as node
+            node = new AreaNode(area);
+            areaNodeMap.put(area, node);
+        }
+
+        NamedArea parent = getNonFallbackParent(area, config);   // findParentIn(area, areas);
+
+        if(parent != null) {
+            AreaNode parentNode = areaNodeMap.get(parent);
+            if(parentNode == null) {
+                parentNode = new AreaNode(parent);
+                areaNodeMap.put(parent, parentNode);
+            }
+            parentNode.addSubArea(node);
+            mergeIntoHierarchy(areas, areaNodeMap, parentNode.area, config);  //recursive to top
+        }
+    }
+
+    private NamedArea getNonFallbackParent(NamedArea area, CondensedDistributionConfiguration config) {
+        NamedArea parent = area.getPartOf();
+        while(parent != null && parent.hasMarker(config.fallbackAreaMarker, true)){
+            parent = parent.getPartOf();
+        }
+        return parent;
+    }
+
+    protected String makeAreaLabel(List<Language> langs, NamedArea area,
+            CondensedDistributionConfiguration config, String parentAreaLabel) {
+        //TODO config with symbols, not only idInVocabulary
+        String label = area.getIdInVocabulary() != null ? area.getIdInVocabulary() :area.getPreferredRepresentation(langs).getAbbreviatedLabel();
+        if (config.shortenSubAreaLabelsIfPossible && parentAreaLabel != null && !parentAreaLabel.isEmpty()){
+            //TODO make brackets not hardcoded, but also allow [],- etc., but how?
+            if (label.startsWith(parentAreaLabel+"(") && label.endsWith(")") ){
+                label = label.substring(parentAreaLabel.length()+1, label.length()-1);
+            }
+        }
+
+        return label;
+    }
+
+    protected TripleResult<String, Boolean, Boolean> statusSymbolForArea(AreaNode areaNode, Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap,
+            CondensedDistributionConfiguration config, boolean onlyIntroduced) {
+
+        if (!config.showStatusOnParentAreaIfAllSame ){
+            return statusSymbol(areaToStatusMap.get(areaNode.area), config, false);
+        }else{
+            Set<PresenceAbsenceTerm> statusList = getStatusRecursive(areaNode, areaToStatusMap, new HashSet<>(), onlyIntroduced);
+            if (statusList.isEmpty()){
+                return statusSymbol(areaToStatusMap.get(areaNode.area), config, false);
+            }else if (statusList.size() == 1){
+                return statusSymbol(statusList.iterator().next(), config, true);
+            }else{
+                //subarea status is handled at subarea level, usually parent area status is empty as the parent area will not have a status
+                return statusSymbol(areaToStatusMap.get(areaNode.area), config, false);
+            }
+        }
+    }
+
+    private Set<PresenceAbsenceTerm> getStatusRecursive(AreaNode areaNode,
+            Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap, Set<PresenceAbsenceTerm> statusList,
+            boolean onlyIntroduced) {
+
+        PresenceAbsenceTerm status = areaToStatusMap.get(areaNode.area);
+        if (status != null && (!onlyIntroduced || isIntroduced(status))){
+            statusList.add(status);
+        }
+
+        for (AreaNode subNode : areaNode.subAreas){
+            statusList.addAll(getStatusRecursive(subNode, areaToStatusMap, statusList, onlyIntroduced));
+        }
+        return statusList;
+    }
+
+    /**
+     * @param status
+     * @param config configuration
+     * @param statusHandledByParent indicates if the status is handled by the parent. Will be passed to the (third) result
+     * @return a triple result with the first result being the symbol string, the second result being the isBold flag
+     *     and the third result indicates if this symbol includes information for all sub-areas (which passes the input parameter)
+     *     to the output here
+     */
+    protected TripleResult<String, Boolean, Boolean> statusSymbol(PresenceAbsenceTerm status,
+            CondensedDistributionConfiguration config, boolean statusHandledByParent) {
+
+        List<StatusSymbolUsage> symbolPreferences = Arrays.asList(config.statusSymbolField);
+        if(status == null) {
+            return new TripleResult<>("", false, statusHandledByParent);
+        }
+
+        //usually the symbols should all come from the same field, but in case they don't ...
+        if (config.showAnyStatusSmbol){
+            for (StatusSymbolUsage usage : StatusSymbolUsage.values()){
+                if (!symbolPreferences.contains(usage)){
+                    symbolPreferences.add(usage);
+                }
+            }
+        }
+
+        for (StatusSymbolUsage usage: symbolPreferences){
+            String symbol = usage.getSymbol(status);
+            if (symbol != null){
+                return new TripleResult<>(symbol, isBoldStatus(status, config), statusHandledByParent);
+            }
+        }
+
+        return new TripleResult<>("", isBoldStatus(status, config), statusHandledByParent);
+    }
+
+    private Boolean isBoldStatus(PresenceAbsenceTerm status, CondensedDistributionConfiguration config) {
+        return config.statusForBoldAreas.contains(status.getUuid());
+    }
+
+    /**
+     * Searches for the parent area of the area given as parameter in
+     * the collection of areas.
+     *
+     * @parent area
+     *      The area which parent area is to be searched
+     * @param collection
+     *      The areas to search in.
+     *
+     * @return
+     *      Either the parent if it has been found or null.
+     */
+    protected NamedArea findParentIn(NamedArea area, Collection<NamedArea> areas) {
+        NamedArea parent = area.getPartOf();
+        if(parent != null && areas.contains(parent)){
+            return parent;
+        }
+        return null;
+    }
+
+    protected class AreaNode {
+
+        protected final NamedArea area;
+        protected AreaNode parent = null;
+        protected final Set<AreaNode> subAreas = new HashSet<>();
+
+        /**
+         * @param area
+         */
+        public AreaNode(NamedArea area) {
+            this.area = area;
+        }
+
+        public void addSubArea(AreaNode subArea) {
+            subAreas.add(subArea);
+            subArea.parent = this;
+        }
+
+        public AreaNode getParent() {
+            return parent;
+        }
+
+        public boolean hasParent() {
+            return getParent() != null;
+        }
+
+        public Collection<NamedArea> getSubareas() {
+            Collection<NamedArea> areas = new HashSet<>();
+            for(AreaNode node : subAreas) {
+                areas.add(node.area);
+            }
+            return areas;
+        }
+
+        @Override
+        public String toString() {
+            return "AreaNode [area=" + area + "]";
+        }
+    }
+
+    protected class AreaNodeComparator implements Comparator<AreaNode>{
+
+        @Override
+        public int compare(AreaNode areaNode1, AreaNode areaNode2) {
+            NamedArea area1 = areaNode1.area;
+            NamedArea area2 = areaNode2.area;
+
+            if (area1 == null && area2 == null){
+                return 0;
+            }else if (area1 == null){
+                return -1;
+            }else if (area2 == null){
+                return 1;
+            }else{
+                //- due to wrong ordering behavior in DefinedTerms
+                return - area1.compareTo(area2);
+            }
+        }
+    }
+
+    protected DoubleResult<List<AreaNode>, List<AreaNode>> createAreaTreesAndStatusMap(Collection<Distribution> filteredDistributions,
+            Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap, CondensedDistributionConfiguration config){
+
+        //we expect every area only to have 1 status  (multiple status should have been filtered beforehand)
+
+        //1. compute all areas and their status
+        for(Distribution distr : filteredDistributions) {
+            PresenceAbsenceTerm status = distr.getStatus();
+            NamedArea area = distr.getArea();
+
+            //TODO needed? Do we only want to have areas with status?
+            if(status == null || area == null) {
+                continue;
+            }
+
+            areaToStatusMap.put(area, status);
+        }
+
+        //2. build the area hierarchy
+        Map<NamedArea, AreaNode>[] areaNodeMaps = buildAreaHierarchie(areaToStatusMap, config);
+
+        //3. find root nodes
+        @SuppressWarnings("unchecked")
+        List<AreaNode>[] topLevelNodes = new ArrayList[]{new ArrayList<>(), new ArrayList<>()};
+
+        for (int i = 0; i < 2; i++){
+            for(AreaNode node : areaNodeMaps[i].values()) {
+                if(!node.hasParent() && ! topLevelNodes[i].contains(node)) {
+                    topLevelNodes[i].add(node);
+                }
+            }
+        }
+
+        DoubleResult<List<AreaNode>, List<AreaNode>> result = new DoubleResult<>(topLevelNodes[0], topLevelNodes[1]);
+
+        return result;
+    }
+
+    private void handleSubAreas(CondensedDistribution result, AreaNode areaNode, CondensedDistributionConfiguration config,
+            AreaNodeComparator areaNodeComparator, List<Language> languages, Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap,
+            String parentAreaLabel, int level, boolean isBold, boolean statusHandledByParent, boolean isIntroduced) {
+
+        if (!areaNode.subAreas.isEmpty()) {
+            String sepStart = getListValue(config.areaOfScopeSubAreaBracketStart, level, "(");
+            if (StringUtils.isNotBlank(sepStart)){
+                result.addSeparatorTaggedText(sepStart, isBold);
+            }
+            handleSubAreasLoop(result, areaNode, config, areaNodeComparator, languages, areaToStatusMap, parentAreaLabel,
+                    level, isBold, statusHandledByParent, isIntroduced);
+            String sepEnd = getListValue(config.areaOfScopeSubAreaBracketEnd, level, ")");
+            if (StringUtils.isNotBlank(sepEnd)){
+                result.addSeparatorTaggedText(sepEnd, isBold);
+            }
+            if (isBold){
+                result.addSeparatorTaggedText("", true);  //previous separator is otherwise not included in bold html tag by TaggedCacheHelper
+            }
+        }
+    }
+
+    private void handleSubAreasLoop(CondensedDistribution result, AreaNode areaNode,
+            CondensedDistributionConfiguration config, AreaNodeComparator areaNodeComparator, List<Language> languages,
+            Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap, String parentLabel, int level, boolean isBold,
+            boolean statusHandledByParent, boolean isIntroduced) {
+
+        List<AreaNode> subAreas = new ArrayList<>(areaNode.subAreas);
+        Collections.sort(subAreas, areaNodeComparator);
+        boolean isFirst = true;
+        for (AreaNode subAreaNode: subAreas){
+            if (!isFirst){
+                result.addSeparatorTaggedText(" ", isBold);
+            }
+            handleSubAreaNode(languages, result, areaToStatusMap, areaNodeComparator,
+                    subAreaNode, config, parentLabel, level, isBold, statusHandledByParent, isIntroduced);
+            isFirst = false;
+        }
+    }
+
+    private String getListValue(List<String> list, int i, String defaultStr) {
+        if (i < list.size()){
+            return list.get(i);
+        }
+        return defaultStr;
+    }
+
+    private void handleSubAreaNode(List<Language> languages, CondensedDistribution result,
+            Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap, AreaNodeComparator areaNodeComparator,
+            AreaNode areaNode, CondensedDistributionConfiguration config, String parentAreaLabel, int level,
+            boolean parentIsBold, boolean statusHandledByParent, boolean isIntroduced) {
+
+        level++;
+        NamedArea area = areaNode.area;
+
+        TripleResult<String, Boolean, Boolean> statusSymbol = statusHandledByParent?
+                new TripleResult<>("", parentIsBold, statusHandledByParent):
+                statusSymbolForArea(areaNode, areaToStatusMap, config, isIntroduced);
+
+        String areaLabel = makeAreaLabel(languages, area, config, parentAreaLabel);
+        result.addStatusAndAreaTaggedText(statusSymbol.getFirstResult(), areaLabel, statusSymbol.getSecondResult() || config.areasBold);
+
+        boolean isBold = statusSymbol.getSecondResult();
+        boolean isHandledByParent = statusSymbol.getThirdResult();
+        handleSubAreas(result, areaNode, config, areaNodeComparator, languages, areaToStatusMap, areaLabel, level,
+                isBold, isHandledByParent, isIntroduced);
+    }
+}
diff --git a/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposerBase.java b/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposerBase.java
deleted file mode 100644 (file)
index 2464cc1..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-/**
-* Copyright (C) 2016 EDIT
-* European Distributed Institute of Taxonomy
-* http://www.e-taxonomy.eu
-*
-* The contents of this file are subject to the Mozilla Public License Version 1.1
-* See LICENSE.TXT at the top of this package for the full license terms.
-*/
-package eu.etaxonomy.cdm.ext.geo;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-import eu.etaxonomy.cdm.model.common.Language;
-import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
-import eu.etaxonomy.cdm.model.location.NamedArea;
-import eu.etaxonomy.cdm.model.term.Representation;
-
-/**
- * Base class for condensed distribution composers
- *
- * @author a.mueller
- * @since 02.06.2016
- */
-public abstract class CondensedDistributionComposerBase implements ICondensedDistributionComposer{
-
-    protected static Map<UUID, String> statusSymbols;
-
-    protected String areaPreTag = "<b>";
-    protected String areaPostTag = "</b>";
-
-    protected boolean replaceCommonAreaLabelStart;
-
-    //for future use in combined DistributionComposer
-    private boolean sortByStatus;
-
-    protected String makeAreaLabel(List<Language> langs, NamedArea area) {
-        String result = area.getIdInVocabulary() != null ? area.getIdInVocabulary() :area.getPreferredRepresentation(langs).getAbbreviatedLabel();
-        return areaPreTag + result + areaPostTag;
-    }
-
-    protected String statusSymbol(PresenceAbsenceTerm status) {
-        if(status == null) {
-            return "";
-        }
-        String symbol = statusSymbols.get(status.getUuid());
-        if(symbol != null) {
-            return symbol;
-        }else if (status.getSymbol() != null){
-            return status.getSymbol();
-        }else if (status.getIdInVocabulary() != null){
-            return status.getIdInVocabulary();
-        }else {
-            Representation r = status.getPreferredRepresentation((Language)null);
-            if (r != null){
-                String abbrevLabel = r.getAbbreviatedLabel();
-                if (abbrevLabel != null){
-                    return abbrevLabel;
-                }
-            }
-        }
-
-        return "n.a.";
-    }
-
-    /**
-     * Searches for the parent are of the area given as parameter in
-     * the Collection of areas.
-     *
-     * @parent area
-     *      The area whose parent area is to be searched
-     * @param collection
-     *      The areas to search in.
-     *
-     * @return
-     *      Either the parent if it has been found or null.
-     */
-    protected NamedArea findParentIn(NamedArea area, Collection<NamedArea> areas) {
-        NamedArea parent = area.getPartOf();
-        if(parent != null && areas.contains(parent)){
-            return parent;
-        }
-        return null;
-    }
-
-    protected class AreaNode {
-
-        protected final NamedArea area;
-        protected AreaNode parent = null;
-        protected final Set<AreaNode> subAreas = new HashSet<>();
-
-        /**
-         * @param area
-         */
-        public AreaNode(NamedArea area) {
-            this.area = area;
-        }
-
-        public void addSubArea(AreaNode subArea) {
-            subAreas.add(subArea);
-            subArea.parent = this;
-        }
-
-        public AreaNode getParent() {
-            return parent;
-        }
-
-        public boolean hasParent() {
-            return getParent() != null;
-        }
-
-        public Collection<NamedArea> getSubareas() {
-            Collection<NamedArea> areas = new HashSet<>();
-            for(AreaNode node : subAreas) {
-                areas.add(node.area);
-            }
-            return areas;
-        }
-
-        @Override
-        public String toString() {
-            return "AreaNode [area=" + area + "]";
-        }
-    }
-
-
-    /**
-     * @param status
-     * @return
-     */
-    //Keep here only in case new version does creates problems in E+M
-    //can be deleted if no problem occurs
-    private String statusSymbolEuroMedOld(PresenceAbsenceTerm status) {
-        if(status == null) {
-            return "";
-        }
-        String symbol = statusSymbols.get(status.getUuid());
-        if(symbol == null) {
-            symbol = "";
-        }
-        return symbol;
-    }
-
-
-    public String getAreaPreTag() {
-        return areaPreTag;
-    }
-
-    public void setAreaPreTag(String areaPreTag) {
-        this.areaPreTag = areaPreTag;
-    }
-
-    public String getAreaPostTag() {
-        return areaPostTag;
-    }
-
-    public void setAreaPostTag(String areaPostTag) {
-        this.areaPostTag = areaPostTag;
-    }
-}
diff --git a/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionConfiguration.java b/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionConfiguration.java
new file mode 100644 (file)
index 0000000..8862296
--- /dev/null
@@ -0,0 +1,81 @@
+/**
+* Copyright (C) 2021 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package eu.etaxonomy.cdm.ext.geo;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import eu.etaxonomy.cdm.common.UTF8;
+import eu.etaxonomy.cdm.ext.geo.CondensedDistributionComposer.StatusSymbolUsage;
+import eu.etaxonomy.cdm.model.common.MarkerType;
+import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
+
+/**
+ * @author a.mueller
+ * @since 17.02.2021
+ */
+public class CondensedDistributionConfiguration{
+
+    //if true, all areas are shown in bold, no matter which status they have
+    public boolean areasBold = false;  //true for Cuba
+
+    //if true the area of scope (e.g. EM) area label is shown, otherwise only
+    //the status symbol (usually an endemism information is shown, if at all)
+    public boolean showAreaOfScopeLabel = false;   //true for Cuba
+
+    //the separator before the list of outOfScope areas
+    public String outOfScopeAreasSeperator = " " + UTF8.EN_DASH.toString() + " ";   //not needed for E+M
+
+    public boolean splitNativeAndIntroduced = true;  //false for Cuba
+
+    public String introducedBracketStart = "[";
+
+    public String introducedBracketEnd = "]";
+
+    //if true a subarea Az(F) will be shortened to F if it is shown in brackets after the parent area
+    public boolean shortenSubAreaLabelsIfPossible = true;  //not relevant for Cuba
+
+    public boolean showStatusOnParentAreaIfAllSame = true;  //false for Cuba
+
+    public List<String> areaOfScopeSubAreaBracketStart = Arrays.asList(new String[]{" ","("});  // "(","(" for Cuba
+
+    public List<String> areaOfScopeSubAreaBracketEnd = Arrays.asList(new String[]{"",")"});    // ")",")" for Cuba
+
+    public List<UUID> statusForBoldAreas = Arrays.asList(new UUID[]{PresenceAbsenceTerm.uuidNative});  //empty for Cuba as for Cuba all areas are bold
+
+    public StatusSymbolUsage statusSymbolField = StatusSymbolUsage.Symbol2;   //currently Symbol1, but may change in future
+
+    //if true, any non-empty symbol is taken from symbol2, symbol1, idInVoc and abbrevLabel according to the given order
+    public boolean showAnyStatusSmbol = false;   //usually does not make sense to mix symbol fields
+
+    public UUID fallbackAreaMarker = MarkerType.uuidFallbackArea;
+
+//************************** FACTORY ***************************************/
+
+    public static CondensedDistributionConfiguration NewDefaultInstance() {
+        CondensedDistributionConfiguration result = new CondensedDistributionConfiguration();
+        return result;
+    }
+
+    public static CondensedDistributionConfiguration NewCubaInstance() {
+        CondensedDistributionConfiguration result = new CondensedDistributionConfiguration();
+        result.areasBold = true;
+        result.showAreaOfScopeLabel = true;
+        result.splitNativeAndIntroduced = false;
+        result.shortenSubAreaLabelsIfPossible = false;
+        result.showStatusOnParentAreaIfAllSame = false;
+        result.areaOfScopeSubAreaBracketStart.set(0, "(");
+        result.areaOfScopeSubAreaBracketEnd.set(0, ")");
+        result.statusForBoldAreas = new ArrayList<>();
+        result.statusSymbolField = StatusSymbolUsage.Symbol1;
+        return result;
+    }
+}
\ No newline at end of file
index e672c3597e96ad73a87e0241a2f884fff48c51a6..ee311da1edf59893b5cec21d7821debef8a2f9f6 100644 (file)
@@ -8,16 +8,15 @@
 */
 package eu.etaxonomy.cdm.ext.geo;
 
-import eu.etaxonomy.cdm.ext.geo.EuroPlusMedCondensedDistributionComposer;
-import eu.etaxonomy.cdm.ext.geo.FloraCubaCondensedDistributionComposer;
-import eu.etaxonomy.cdm.ext.geo.ICondensedDistributionComposer;
 import eu.etaxonomy.cdm.model.metadata.IKeyLabel;
 
 /**
  * @author a.kohlbecker
  * @since Jun 24, 2015
  *
+ * @deprecated the usage of this class is deprecated, please use {@link CondensedDistributionConfiguration} instead
  */
+ @Deprecated
 public enum CondensedDistributionRecipe implements IKeyLabel{
 
     /**
@@ -30,21 +29,15 @@ public enum CondensedDistributionRecipe implements IKeyLabel{
      *   <li>{@link http://dev.e-taxonomy.eu/trac/ticket/3907}</li>
      * </ul>
      */
-    EuroPlusMed("EuroPlusMed", "Euro + Med", EuroPlusMedCondensedDistributionComposer.class),
-    FloraCuba("FloraCuba", "Flora Cuba", FloraCubaCondensedDistributionComposer.class);
+    EuroPlusMed("EuroPlusMed", "Euro + Med"),
+    FloraCuba("FloraCuba", "Flora Cuba");
 
-    Class<? extends ICondensedDistributionComposer> implementation;
-    String label;
-    String key;
+    private String label;
+    private String key;
 
-    CondensedDistributionRecipe(String key, String label, Class<? extends ICondensedDistributionComposer> implementation) {
+    private CondensedDistributionRecipe(String key, String label) {
         this.key = key;
         this.label = label;
-        this.implementation = implementation;
-    }
-
-    public ICondensedDistributionComposer newCondensedDistributionComposerInstance() throws InstantiationException, IllegalAccessException {
-        return implementation.newInstance();
     }
 
     @Override
@@ -56,4 +49,12 @@ public enum CondensedDistributionRecipe implements IKeyLabel{
     public String getKey() {
         return key;
     }
+
+    public CondensedDistributionConfiguration toConfiguration(){
+        if (this == FloraCuba){
+            return CondensedDistributionConfiguration.NewCubaInstance();
+        } else {
+            return CondensedDistributionConfiguration.NewDefaultInstance();
+        }
+    }
 }
index f1dc79c7f4ad07c88aa928b45862acc92de51e66..437b38d4012537f5b7e567489954cf45a2c012de 100644 (file)
@@ -369,8 +369,9 @@ public class EditGeoService implements IEditGeoService {
         }
 
         if(parts.contains(InfoPart.condensedDistribution)) {
-            dto.setCondensedDistribution(EditGeoServiceUtilities.getCondensedDistribution(
-                    filteredDistributions, recipe, languages));
+            CondensedDistribution condensedDistribution = EditGeoServiceUtilities.getCondensedDistribution(
+                    filteredDistributions, recipe, languages);
+            dto.setCondensedDistribution(condensedDistribution);
         }
 
         if (parts.contains(InfoPart.mapUriParams)) {
index b71dbe81534b1dd214d13e7eb66e5e7bb3864bc3..05abb4450dc684d7387685ae3f4e8531f4fac893 100644 (file)
@@ -684,17 +684,10 @@ public class EditGeoServiceUtilities {
     public static CondensedDistribution getCondensedDistribution(Collection<Distribution> filteredDistributions,\r
             CondensedDistributionRecipe recipe, List<Language> langs) {\r
 \r
-        ICondensedDistributionComposer composer;\r
-        if(recipe == null) {\r
-            throw new NullPointerException("parameter recipe must not be null");\r
-        }\r
-        try {\r
-            composer = recipe.newCondensedDistributionComposerInstance();\r
-        } catch (InstantiationException|IllegalAccessException e) {\r
-            throw new RuntimeException(e);\r
-        }\r
+        CondensedDistributionComposer composer = new CondensedDistributionComposer();\r
+\r
         CondensedDistribution condensedDistribution = composer.createCondensedDistribution(\r
-                filteredDistributions,  langs);\r
+                filteredDistributions,  langs, recipe.toConfiguration());\r
         return condensedDistribution;\r
     }\r
 }\r
diff --git a/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/EuroPlusMedCondensedDistributionComposer.java b/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/EuroPlusMedCondensedDistributionComposer.java
deleted file mode 100644 (file)
index 5652cb7..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-/**
-* Copyright (C) 2015 EDIT
-* European Distributed Institute of Taxonomy
-* http://www.e-taxonomy.eu
-*
-* The contents of this file are subject to the Mozilla Public License Version 1.1
-* See LICENSE.TXT at the top of this package for the full license terms.
-*/
-package eu.etaxonomy.cdm.ext.geo;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-import org.apache.commons.lang.StringUtils;
-import org.apache.log4j.Logger;
-
-import eu.etaxonomy.cdm.api.service.dto.CondensedDistribution;
-import eu.etaxonomy.cdm.common.SetMap;
-import eu.etaxonomy.cdm.common.UTF8;
-import eu.etaxonomy.cdm.model.common.Language;
-import eu.etaxonomy.cdm.model.description.Distribution;
-import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
-import eu.etaxonomy.cdm.model.location.NamedArea;
-
-/**
- * @author a.kohlbecker
- * @since Jun 24, 2015
- */
-public class EuroPlusMedCondensedDistributionComposer
-        extends CondensedDistributionComposerBase {
-
-    @SuppressWarnings("unused")
-    private static final Logger logger = Logger.getLogger(EuroPlusMedCondensedDistributionComposer.class);
-
-    private final CondensedDistribution condensedDistribution;
-
-    private static Set<UUID> foreignStatusUuids;
-
-    // these status uuids are special for EuroPlusMed and might also be used
-    private final static UUID REPORTED_IN_ERROR_UUID =  UUID.fromString("38604788-cf05-4607-b155-86db456f7680");
-
-    static {
-
-        // ==================================================
-        // Mapping as defined in ticket https://dev.e-taxonomy.eu/redmine/issues/3907
-        // ==================================================
-
-        statusSymbols = new HashMap<> ();
-        // â—� endemic (U+25CF BLACK CIRCLE)
-        statusSymbols.put(PresenceAbsenceTerm.ENDEMIC_FOR_THE_RELEVANT_AREA().getUuid(), "\u25CF");
-
-        // Lu native (incl. archaeophytes) TODO status archaeophytes?
-        statusSymbols.put(PresenceAbsenceTerm.NATIVE().getUuid(), "");
-        statusSymbols.put(PresenceAbsenceTerm.NATIVE_FORMERLY_NATIVE().getUuid(), "");  //wanted? differs from default "ne"
-
-        // ?Lu doubtfully present (U+3F QUESTION MARK)
-        statusSymbols.put(PresenceAbsenceTerm.INTRODUCED_PRESENCE_QUESTIONABLE().getUuid(), "?");
-        statusSymbols.put(PresenceAbsenceTerm.NATIVE_PRESENCE_QUESTIONABLE().getUuid(), "?");
-        statusSymbols.put(PresenceAbsenceTerm.PRESENT_DOUBTFULLY().getUuid(), "?");
-
-        // dLu doubtfully native
-        statusSymbols.put(PresenceAbsenceTerm.NATIVE_DOUBTFULLY_NATIVE().getUuid(), "d");
-
-        // -Lu absent but reported in error (U+2D HYPHEN-MINUS)
-        statusSymbols.put(PresenceAbsenceTerm.INTRODUCED_REPORTED_IN_ERROR().getUuid(), UTF8.EN_DASH.toString());
-        statusSymbols.put(PresenceAbsenceTerm.NATIVE_REPORTED_IN_ERROR().getUuid(), UTF8.EN_DASH.toString());
-        statusSymbols.put(REPORTED_IN_ERROR_UUID, "-");
-
-        // â€ Lu (presumably) extinct (U+2020 DAGGER)
-        // no such status in database!!!
-
-        // [Lu] introduced (casual or naturalized) =  introduced, introduced: naturalized
-        statusSymbols.put(PresenceAbsenceTerm.INTRODUCED().getUuid(), ""); //wanted? differs from default "i"
-
-        // [aLu] casual alien = introduced: adventitious (casual)
-        statusSymbols.put(PresenceAbsenceTerm.CASUAL().getUuid(), "a");
-
-        // [cLu] cultivated
-        statusSymbols.put(PresenceAbsenceTerm.CULTIVATED() .getUuid(), "c");
-        statusSymbols.put(PresenceAbsenceTerm.INTRODUCED_CULTIVATED().getUuid(), "c");
-
-        // [nLu] naturalized
-        statusSymbols.put(PresenceAbsenceTerm.NATURALISED().getUuid(), "n");
-        statusSymbols.put(PresenceAbsenceTerm.NATURALISED().getUuid(), "n");
-
-        foreignStatusUuids = new HashSet<>();
-        foreignStatusUuids.add(PresenceAbsenceTerm.INTRODUCED().getUuid());
-        foreignStatusUuids.add(PresenceAbsenceTerm.NATURALISED().getUuid());
-        foreignStatusUuids.add(PresenceAbsenceTerm.CASUAL().getUuid());
-        foreignStatusUuids.add(PresenceAbsenceTerm.INTRODUCED_CULTIVATED().getUuid());
-        foreignStatusUuids.add(PresenceAbsenceTerm.NATURALISED().getUuid());
-        foreignStatusUuids.add(PresenceAbsenceTerm.CULTIVATED().getUuid());
-
-    }
-
-    public EuroPlusMedCondensedDistributionComposer() {
-        replaceCommonAreaLabelStart = true;
-        condensedDistribution = new CondensedDistribution();
-    }
-
-    @Override
-    public CondensedDistribution createCondensedDistribution(
-            Collection<Distribution> filteredDistributions,
-            List<Language> langs) {
-
-        //1. group by PresenceAbsenceTerms
-        SetMap<PresenceAbsenceTerm, NamedArea> areasByStatus = new SetMap<>();
-        for(Distribution d : filteredDistributions) {
-            PresenceAbsenceTerm status = d.getStatus();
-            if(status == null) {
-                continue;
-            }
-            areasByStatus.putItem(status, d.getArea());
-        }
-
-        //2. build the area hierarchy
-        for(PresenceAbsenceTerm status : areasByStatus.keySet()) {
-
-            Map<NamedArea, AreaNode> areaNodeMap = new HashMap<>();
-
-            for(NamedArea area : areasByStatus.get(status)) {
-                AreaNode node = areaNodeMap.get(area);
-                if (node == null){
-                    node = new AreaNode(area);
-                    areaNodeMap.put(area, node);
-                }
-
-                NamedArea parent = findParentIn(area, areasByStatus.get(status));
-                if(parent != null) {
-                    AreaNode parentNode = areaNodeMap.get(parent);;
-                    if (parentNode == null){
-                        parentNode = new AreaNode(parent);
-                        areaNodeMap.put(parent, parentNode);
-                    }
-                    parentNode.addSubArea(node);
-                }
-            }
-
-            //3. find root nodes
-            Set<AreaNode>hierarchy = new HashSet<>();
-            for(AreaNode node : areaNodeMap.values()) {
-                if(!node.hasParent()) {
-                    hierarchy.add(node);
-                }
-            }
-
-            //4. replace the area by the abbreviated representation and add symbols
-            for(AreaNode topLevelNode : hierarchy) {
-
-                StringBuilder areaStatusString = new StringBuilder();
-
-                String statusSymbol = statusSymbol(status);
-                areaStatusString.append(statusSymbol);
-
-                String areaLabel = topLevelNode.area.getPreferredRepresentation(langs).getAbbreviatedLabel();
-                areaStatusString.append(areaLabel);
-
-                if(!topLevelNode.subAreas.isEmpty()) {
-                    areaStatusString.append('(');
-                    subAreaLabels(langs, topLevelNode.subAreas, areaStatusString, statusSymbol, areaLabel);
-                    areaStatusString.append(')');
-                }
-
-                if(isForeignStatus(status)) {
-                    condensedDistribution.addForeignDistributionItem(status, areaStatusString.toString(), areaLabel);
-                } else {
-                    condensedDistribution.addIndigenousDistributionItem(status, areaStatusString.toString(), areaLabel);
-                }
-            }
-
-        }
-        //5. order the condensedDistributions alphabetically
-        condensedDistribution.sortForeign();
-        condensedDistribution.sortIndigenous();
-
-        return condensedDistribution;
-    }
-
-
-    private boolean isForeignStatus(PresenceAbsenceTerm status) {
-        return foreignStatusUuids.contains(status.getUuid());
-    }
-
-    private void subAreaLabels(List<Language> langs, Collection<AreaNode> nodes, StringBuilder areaString,
-            String statusSymbol, String parentLabel) {
-
-        List<String> subAreaLabels = new ArrayList<>();
-
-        for(AreaNode node : nodes) {
-
-            StringBuilder subAreaString = new StringBuilder();
-
-            subAreaString.append(statusSymbol);
-
-            String areaLabel = node.area.getPreferredRepresentation(langs).getAbbreviatedLabel();
-            if (replaceCommonAreaLabelStart){
-                String cleanSubAreaLabel = StringUtils.replaceEach(areaLabel, new String[] {parentLabel, "(", ")"}, new String[] {"", "", ""});
-                subAreaString.append(cleanSubAreaLabel);
-            }else{
-                subAreaString.append(areaLabel);
-            }
-
-            if(!node.subAreas.isEmpty()) {
-                subAreaString.append('(');
-                subAreaLabels(langs, node.subAreas, subAreaString, statusSymbol, areaLabel);
-                subAreaString.append(')');
-            }
-
-            subAreaLabels.add(subAreaString.toString());
-        }
-        Collections.sort(subAreaLabels);
-        areaString.append(StringUtils.join(subAreaLabels, " "));
-    }
-}
\ No newline at end of file
diff --git a/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/FloraCubaCondensedDistributionComposer.java b/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/FloraCubaCondensedDistributionComposer.java
deleted file mode 100644 (file)
index 5003f10..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-/**
-* Copyright (C) 2015 EDIT
-* European Distributed Institute of Taxonomy
-* http://www.e-taxonomy.eu
-*
-* The contents of this file are subject to the Mozilla Public License Version 1.1
-* See LICENSE.TXT at the top of this package for the full license terms.
-*/
-package eu.etaxonomy.cdm.ext.geo;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import org.apache.commons.lang.StringUtils;
-import org.apache.log4j.Logger;
-
-import eu.etaxonomy.cdm.api.service.dto.CondensedDistribution;
-import eu.etaxonomy.cdm.common.UTF8;
-import eu.etaxonomy.cdm.model.common.Language;
-import eu.etaxonomy.cdm.model.description.Distribution;
-import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
-import eu.etaxonomy.cdm.model.location.NamedArea;
-
-/**
- * @author a.mueller
- * @since Apr 05, 2016
- */
-public class FloraCubaCondensedDistributionComposer extends CondensedDistributionComposerBase {
-
-    @SuppressWarnings("unused")
-    private static final Logger logger = Logger.getLogger(FloraCubaCondensedDistributionComposer.class);
-
-//    private static Set<UUID> foreignStatusUuids;
-
-    //preliminary for Cuba, needs to be parameterized
-    private UUID uuidInternalArea = UUID.fromString("d0144a6e-0e17-4a1d-bce5-d464a2aa7229");  //Cuba
-
-    private String internalAreaSeparator = UTF8.EN_DASH.toString() + " ";
-
-    static {
-
-        // ==================================================
-        // Mapping as defined in ticket https://dev.e-taxonomy.eu/redmine/issues/5682
-        // ==================================================
-
-       statusSymbols = new HashMap<> ();
-       //no entries as we handle symbols now on model level
-
-    }
-
-// ***************************** GETTER/SETTER ***********************************/
-
-    public String getInternalAreaSeparator() {
-        return internalAreaSeparator;
-    }
-    public void setInternalAreaSeparator(String internalAreaSeparator) {
-        this.internalAreaSeparator = internalAreaSeparator;
-    }
-
-// ***********************************************************************
-
-    @Override
-    public CondensedDistribution createCondensedDistribution(Collection<Distribution> filteredDistributions,
-            List<Language> languages) {
-
-        CondensedDistribution result = new CondensedDistribution();
-
-        //we expect every area only to have 1 status  (multiple status should have been filtered beforehand)
-        Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap = new HashMap<>();
-
-
-        //1. compute all areas and their status
-        for(Distribution distr : filteredDistributions) {
-            PresenceAbsenceTerm status = distr.getStatus();
-            NamedArea area = distr.getArea();
-
-            //TODO needed? Do we only want to have areas with status?
-            if(status == null || area == null) {
-                continue;
-            }
-
-            areaToStatusMap.put(area, status);
-        }
-
-
-        //2. build the area hierarchy
-        Map<NamedArea, AreaNode> areaNodeMap = new HashMap<>();
-
-        for(NamedArea area : areaToStatusMap.keySet()) {
-            AreaNode node;
-            if(!areaNodeMap.containsKey(area)) {
-                // putting area into hierarchy as node
-                node = new AreaNode(area);
-                areaNodeMap.put(area, node);
-            } else {
-                //  is parent of another and thus already has a node
-                node = areaNodeMap.get(area);
-            }
-
-            NamedArea parent = findParentIn(area, areaToStatusMap.keySet());
-            if(parent != null) {
-                AreaNode parentNode;
-                if(!areaNodeMap.containsKey(parent)) {
-                    parentNode = new AreaNode(parent);
-                    areaNodeMap.put(parent, parentNode);
-                } else {
-                    parentNode = areaNodeMap.get(parent);
-                }
-                parentNode.addSubArea(node);
-            }
-        }
-
-        //3. find root nodes
-        List<AreaNode>topLevelNodes = new ArrayList<>();
-        for(AreaNode node : areaNodeMap.values()) {
-            if(!node.hasParent() && ! topLevelNodes.contains(node)) {
-                topLevelNodes.add(node);
-            }
-        }
-
-        //4. replace the area by the abbreviated representation and add symbols
-        boolean isFirstAfterAreaOfScope = false;
-        AreaNodeComparator areaNodeComparator = new AreaNodeComparator();
-
-        Collections.sort(topLevelNodes, areaNodeComparator);
-
-        for(AreaNode topLevelNode : topLevelNodes) {
-
-            StringBuilder areaStatusString = new StringBuilder();
-
-            NamedArea area = topLevelNode.area;
-            if (area.getUuid().equals(uuidInternalArea)){
-                isFirstAfterAreaOfScope = true;
-            }else if(isFirstAfterAreaOfScope && !area.getUuid().equals(uuidInternalArea)){
-                areaStatusString.append(internalAreaSeparator);
-                isFirstAfterAreaOfScope = false;
-            }
-
-
-            PresenceAbsenceTerm status = areaToStatusMap.get(area);
-            String statusSymbol = statusSymbol(areaToStatusMap.get(area));
-            areaStatusString.append(statusSymbol);
-
-            String areaLabel = makeAreaLabel(languages, area);
-            areaStatusString.append(areaLabel);
-
-            if(!topLevelNode.subAreas.isEmpty()) {
-                areaStatusString.append('(');
-                subAreaLabels(languages, topLevelNode.subAreas, areaStatusString, statusSymbol,
-                        areaLabel, areaToStatusMap, areaNodeComparator);
-                areaStatusString.append(')');
-            }
-
-//            if(isForeignStatus(status)) {
-//                condensedDistribution.addForeignDistributionItem(status, areaStatusString.toString(), areaLabel);
-//            } else {
-                result.addIndigenousDistributionItem(status, areaStatusString.toString(), areaLabel);
-//            }
-        }
-
-        return result;
-    }
-
-    /**
-     * Recursive call to create sub area label strings
-     * @param areaNodeComparator
-     */
-    private void subAreaLabels(List<Language> languages, Collection<AreaNode> nodes, StringBuilder totalStringBuilder,
-            String parentStatusSymbol, String parentLabel,
-            Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap, AreaNodeComparator areaNodeComparator) {
-
-        List<String> subAreaLabels = new ArrayList<>();
-
-        List<AreaNode> areaNodes = new ArrayList<>(nodes);
-        Collections.sort(areaNodes, areaNodeComparator);
-
-        for(AreaNode node : areaNodes) {
-
-            StringBuilder subAreaString = new StringBuilder();
-
-            NamedArea area = node.area;
-            PresenceAbsenceTerm status = areaToStatusMap.get(area);
-            String subAreaStatusSymbol = statusSymbol(status);
-            if (subAreaStatusSymbol != null && !subAreaStatusSymbol.equals(parentStatusSymbol)){
-                subAreaString.append(subAreaStatusSymbol);
-            }
-
-            String areaLabel = makeAreaLabel(languages, area);
-            if(replaceCommonAreaLabelStart){
-                String cleanSubAreaLabel = StringUtils.replaceEach(areaLabel, new String[] {parentLabel, "(", ")"}, new String[] {"", "", ""});
-                subAreaString.append(cleanSubAreaLabel);
-            }else{
-                subAreaString.append(areaLabel);
-            }
-
-            if(!node.subAreas.isEmpty()) {
-                subAreaString.append('(');
-                subAreaLabels(languages, node.subAreas, subAreaString, subAreaStatusSymbol, areaLabel,
-                        areaToStatusMap, areaNodeComparator);
-                subAreaString.append(')');
-            }
-
-            subAreaLabels.add(subAreaString.toString());
-        }
-//        Collections.sort(subAreaLabels);
-        totalStringBuilder.append(StringUtils.join(subAreaLabels, " "));
-    }
-
-    private class AreaNodeComparator implements Comparator<AreaNode>{
-
-        @Override
-        public int compare(AreaNode areaNode1, AreaNode areaNode2) {
-            NamedArea area1 = areaNode1.area;
-            NamedArea area2 = areaNode2.area;
-
-            if (area1 == null && area2 == null){
-                return 0;
-            }else if (area1 == null){
-                return -1;
-            }else if (area2 == null){
-                return 1;
-            }else{
-                //- due to wrong ordering behavior in DefinedTerms
-                return - area1.compareTo(area2);
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/FloraCubaCondensedDistributionComposerOld.java b/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/FloraCubaCondensedDistributionComposerOld.java
deleted file mode 100644 (file)
index cae908b..0000000
+++ /dev/null
@@ -1,224 +0,0 @@
-/**
-* Copyright (C) 2015 EDIT
-* European Distributed Institute of Taxonomy
-* http://www.e-taxonomy.eu
-*
-* The contents of this file are subject to the Mozilla Public License Version 1.1
-* See LICENSE.TXT at the top of this package for the full license terms.
-*/
-package eu.etaxonomy.cdm.ext.geo;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.UUID;
-
-import org.apache.commons.lang.StringUtils;
-import org.apache.log4j.Logger;
-
-import eu.etaxonomy.cdm.api.service.dto.CondensedDistribution;
-import eu.etaxonomy.cdm.common.UTF8;
-import eu.etaxonomy.cdm.model.common.CdmBase;
-import eu.etaxonomy.cdm.model.common.Language;
-import eu.etaxonomy.cdm.model.description.Distribution;
-import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
-import eu.etaxonomy.cdm.model.location.NamedArea;
-import eu.etaxonomy.cdm.model.term.DefinedTermBase;
-import eu.etaxonomy.cdm.model.term.OrderedTermVocabulary;
-
-/**
- * @author a.mueller
- * @since Apr 05, 2016
- *
- */
-public class FloraCubaCondensedDistributionComposerOld extends CondensedDistributionComposerBase {
-
-    @SuppressWarnings("unused")
-    private static final Logger logger = Logger.getLogger(FloraCubaCondensedDistributionComposerOld.class);
-
-//    private static Set<UUID> foreignStatusUuids;
-
-    private UUID uuidInternalArea = UUID.fromString("d0144a6e-0e17-4a1d-bce5-d464a2aa7229");  //Cuba
-
-    private String internalAreaSeparator = UTF8.EN_DASH.toString() + " ";
-
-
-//    // these status uuids are special for EuroPlusMed and might also be used
-//    private final static UUID REPORTED_IN_ERROR_UUID =  UUID.fromString("38604788-cf05-4607-b155-86db456f7680");
-
-    static {
-
-        // ==================================================
-        // Mapping as defined in ticket https://dev.e-taxonomy.eu/redmine/issues/5682
-        // ==================================================
-
-       statusSymbols = new HashMap<UUID, String> ();
-       //no entries as we handle symbols now on model level
-
-    }
-
-    /**
-     * {@inheritDoc}
-     * @return
-     */
-    @Override
-    public CondensedDistribution createCondensedDistribution(Collection<Distribution> filteredDistributions,
-            List<Language> languages) {
-
-        CondensedDistribution condensedDistribution = new CondensedDistribution();
-
-        //empty
-        if (filteredDistributions == null || filteredDistributions.isEmpty()){
-            return condensedDistribution;
-        }
-
-        OrderedTermVocabulary<NamedArea> areaVocabulary = CdmBase.deproxy(filteredDistributions.iterator().next().getArea().getVocabulary(), OrderedTermVocabulary.class);
-
-        //deproxy and reverse order
-        List<NamedArea> areaList = new ArrayList<>();
-        for (DefinedTermBase<NamedArea> dtb : areaVocabulary.getOrderedTerms()){
-            areaList.add(0, (NamedArea)CdmBase.deproxy(dtb));
-        }
-
-
-        boolean isFirstAfterInternalArea = false;
-        for (NamedArea area : areaList){
-            if (area.getPartOf() != null){
-                continue;  //subarea are handled later
-            }
-            StringBuilder areaStatusString = new StringBuilder();
-
-            Distribution distribution = getDistribution(area, filteredDistributions);
-            if (distribution == null){
-                continue;
-            }
-
-            if (area.getUuid().equals(uuidInternalArea)){
-                isFirstAfterInternalArea = true;
-            }else if(isFirstAfterInternalArea && !area.getUuid().equals(uuidInternalArea)){
-                areaStatusString.append(internalAreaSeparator);
-                isFirstAfterInternalArea = false;
-            }
-
-            PresenceAbsenceTerm status = distribution.getStatus();
-
-            String statusSymbol = statusSymbol(status);
-            areaStatusString.append(statusSymbol);
-
-            String areaLabel = makeAreaLabel(languages, area);
-            areaStatusString.append(areaLabel);
-
-            if(!area.getIncludes().isEmpty()) {
-//                areaStatusString.append('(');
-                subAreaLabels(languages, area.getIncludes(), areaStatusString, statusSymbol, areaLabel, filteredDistributions);
-//                areaStatusString.append(')');
-            }
-
-
-//            if(isForeignStatus(status)) {
-//                condensedDistribution.addForeignDistributionItem(status, areaStatusString.toString(), areaLabel);
-//            } else {
-                condensedDistribution.addIndigenousDistributionItem(status, areaStatusString.toString(), areaLabel);
-//            }
-
-        }
-
-//        }
-//        //5. order the condensedDistributions alphabetically
-//        // FIXME
-//        condensedDistribution.sortForeign();
-//        condensedDistribution.sortIndigenous();
-
-        return condensedDistribution;
-    }
-
-    /**
-     * @param area
-     * @param filteredDistributions
-     * @return
-     */
-    private Distribution getDistribution(NamedArea area, Collection<Distribution> filteredDistributions) {
-        for (Distribution dist : filteredDistributions){
-            if (dist.getArea() != null && dist.getArea().equals(area)){
-                return dist;
-            }
-        }
-        return null;
-    }
-
-
-//    private boolean isForeignStatus(PresenceAbsenceTerm status) {
-//        return foreignStatusUuids.contains(status.getUuid());
-//    }
-
-    /**
-     * @param langs
-     * @param node
-     * @param areaString
-     * @param statusSymbol
-     */
-    private void subAreaLabels(List<Language> langs, Collection<NamedArea> subAreas, StringBuilder areaString,
-            String statusSymbol, String parentLabel,
-            Collection<Distribution> filteredDistributions) {
-        //TODO very redundant with main method
-        List<String> subAreaLabels = new ArrayList<String>();
-
-        //deproxy and reverse order
-        List<NamedArea> areaList = new ArrayList<NamedArea>();
-        for (DefinedTermBase<NamedArea> dtb : subAreas){
-            areaList.add(0, (NamedArea)CdmBase.deproxy(dtb));
-        }
-//        Collections.sort(areaList);
-
-        for(NamedArea area : areaList) {
-
-            StringBuilder subAreaString = new StringBuilder();
-            Distribution distribution = getDistribution(area, filteredDistributions);
-            if (distribution == null){
-                continue;
-            }
-
-
-            PresenceAbsenceTerm status = distribution.getStatus();
-            String subAreaStatusSymbol = statusSymbol(status);
-            if (subAreaStatusSymbol != null && !subAreaStatusSymbol.equals(statusSymbol)){
-                subAreaString.append(subAreaStatusSymbol);
-            }
-
-            String areaLabel = makeAreaLabel(langs, area);
-//            String cleanSubAreaLabel = StringUtils.replaceEach(areaLabel, new String[] {parentLabel, "(", ")"}, new String[] {"", "", ""});
-            String cleanSubAreaLabel = areaLabel;
-            subAreaString.append(cleanSubAreaLabel);
-
-            if(!area.getIncludes().isEmpty()) {
-//                subAreaString.append('(');
-                subAreaLabels(langs, area.getIncludes(), subAreaString, subAreaStatusSymbol, areaLabel, filteredDistributions);
-//                subAreaString.append(')');
-            }
-
-            subAreaLabels.add(subAreaString.toString());
-        }
-
-//      Collections.sort(subAreaLabels);
-        if (!subAreaLabels.isEmpty()){
-            areaString.append("(" + StringUtils.join(subAreaLabels, " ") + ")");
-        }
-
-    }
-
-
-    public String getInternalAreaSeparator() {
-        return internalAreaSeparator;
-    }
-
-    public void setInternalAreaSeparator(String internalAreaSeparator) {
-        this.internalAreaSeparator = internalAreaSeparator;
-    }
-
-
-
-
-
-
-}
diff --git a/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/ICondensedDistributionComposer.java b/cdmlib-ext/src/main/java/eu/etaxonomy/cdm/ext/geo/ICondensedDistributionComposer.java
deleted file mode 100644 (file)
index ab8d949..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
-* Copyright (C) 2015 EDIT
-* European Distributed Institute of Taxonomy
-* http://www.e-taxonomy.eu
-*
-* The contents of this file are subject to the Mozilla Public License Version 1.1
-* See LICENSE.TXT at the top of this package for the full license terms.
-*/
-package eu.etaxonomy.cdm.ext.geo;
-
-import java.util.Collection;
-import java.util.List;
-
-import eu.etaxonomy.cdm.api.service.dto.CondensedDistribution;
-import eu.etaxonomy.cdm.model.common.Language;
-import eu.etaxonomy.cdm.model.description.Distribution;
-
-/**
- * @author a.kohlbecker
- * @since Jun 24, 2015
- */
-public interface ICondensedDistributionComposer {
-
-    public CondensedDistribution createCondensedDistribution(Collection<Distribution> filteredDistributions,
-            List<Language> langs);
-
-}
diff --git a/cdmlib-ext/src/test/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposerEuroMedTest.java b/cdmlib-ext/src/test/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposerEuroMedTest.java
new file mode 100644 (file)
index 0000000..267fc2e
--- /dev/null
@@ -0,0 +1,189 @@
+/**
+* Copyright (C) 2016 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package eu.etaxonomy.cdm.ext.geo;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import eu.etaxonomy.cdm.api.service.dto.CondensedDistribution;
+import eu.etaxonomy.cdm.ext.geo.CondensedDistributionComposer.StatusSymbolUsage;
+import eu.etaxonomy.cdm.model.common.Language;
+import eu.etaxonomy.cdm.model.common.MarkerType;
+import eu.etaxonomy.cdm.model.description.Distribution;
+import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
+import eu.etaxonomy.cdm.model.location.NamedArea;
+import eu.etaxonomy.cdm.model.term.OrderedTermVocabulary;
+import eu.etaxonomy.cdm.model.term.TermType;
+import eu.etaxonomy.cdm.test.TermTestBase;
+
+/**
+ * @author a.mueller
+ * @since 15.06.2016
+ */
+public class CondensedDistributionComposerEuroMedTest extends TermTestBase {
+
+    private NamedArea europe;
+    private NamedArea westEurope;
+    private NamedArea germany;
+    private NamedArea berlin;
+    private NamedArea bawue;
+    private NamedArea france;
+    private NamedArea ileDeFrance;
+    private NamedArea italy;
+    private NamedArea spain;
+
+    private Set<Distribution> distributions;
+    private List<Language> languages;
+
+    private CondensedDistributionComposer composer;
+    private CondensedDistributionConfiguration config;
+
+    private Distribution bawueDistribution;
+
+    @Before
+    public void setUp(){
+
+        @SuppressWarnings("unchecked")
+        OrderedTermVocabulary<NamedArea>  voc = OrderedTermVocabulary.NewInstance(TermType.NamedArea);
+        europe = NamedArea.NewInstance("", "Europe", "EU");
+        voc.addTerm(europe);
+
+        westEurope = NamedArea.NewInstance("", "West Europe", "WE");
+        westEurope.setPartOf(europe);
+        voc.addTerm(westEurope);
+        setAsFallback(westEurope);
+
+        //Germany
+        germany = NamedArea.NewInstance("", "Germany", "GER");
+        germany.setPartOf(europe);
+        berlin = NamedArea.NewInstance("", "Berlin", "GER(B)");
+        berlin.setPartOf(germany);
+        bawue = NamedArea.NewInstance("", "Baden Württemberg", "GER(BW)");
+        bawue.setPartOf(germany);
+        voc.addTerm(germany);
+        voc.addTerm(berlin);
+        voc.addTerm(bawue);
+
+        //France
+        france = NamedArea.NewInstance("", "France", "FR");
+        france.setPartOf(westEurope);
+        ileDeFrance = NamedArea.NewInstance("", "Ile-de-France", "FR(J)");
+        ileDeFrance.setPartOf(france);
+        voc.addTerm(france);
+        voc.addTerm(ileDeFrance);
+
+        //Italy
+        italy = NamedArea.NewInstance("", "Italy", "IT");
+        italy.setPartOf(europe);
+        voc.addTerm(italy);
+
+        //Spain
+        spain = NamedArea.NewInstance("", "Spain", "S");
+        spain.setPartOf(europe);
+        voc.addTerm(spain);
+
+        distributions = new HashSet<>();
+
+        languages = new ArrayList<>();
+
+        composer = new CondensedDistributionComposer();
+        config = CondensedDistributionConfiguration.NewDefaultInstance();
+        config.statusSymbolField = StatusSymbolUsage.Symbol1;
+    }
+
+    private void setAsFallback(NamedArea area) {
+        MarkerType fallbackMarkerType = MarkerType.NewInstance("Fallback area", "Fallback area", "fba");
+        fallbackMarkerType.setUuid(MarkerType.uuidFallbackArea);   //as long as it is not an official CDM marker type yet
+        area.addMarker(fallbackMarkerType, true);
+    }
+
+    private void createDefaultDistributions() {
+        distributions.add(Distribution.NewInstance(europe, PresenceAbsenceTerm.ENDEMIC_FOR_THE_RELEVANT_AREA()));
+        distributions.add(Distribution.NewInstance(germany, PresenceAbsenceTerm.NATIVE()));
+        bawueDistribution = Distribution.NewInstance(bawue, PresenceAbsenceTerm.NATIVE());
+        distributions.add(bawueDistribution);
+
+        distributions.add(Distribution.NewInstance(berlin, PresenceAbsenceTerm.NATIVE()));
+
+        distributions.add(Distribution.NewInstance(italy, PresenceAbsenceTerm.PRESENT_DOUBTFULLY()));
+
+        distributions.add(Distribution.NewInstance(ileDeFrance, PresenceAbsenceTerm.CULTIVATED()));
+
+        distributions.add(Distribution.NewInstance(spain, PresenceAbsenceTerm.NATURALISED()));
+    }
+
+    @Test
+    public void testEuroMedCondensedDistributionDefault() {
+        createDefaultDistributions();
+
+        CondensedDistribution condensedDistribution = composer.createCondensedDistribution(
+                distributions, languages, config);
+
+        Assert.assertEquals("● <b>GER(B BW)</b> ?IT [cFR(J) nS]", condensedDistribution.toString());
+    }
+
+    @Test
+    public void testEuroMedCondensedDistributionWithDifferingSubareaStatus() {
+        createDefaultDistributions();
+
+        bawueDistribution.setStatus(PresenceAbsenceTerm.NATIVE_DOUBTFULLY_NATIVE());
+        Assert.assertEquals("● <b>GER(B</b> dBW<b>)</b> ?IT [cFR(J) nS]", composer.createCondensedDistribution(distributions, languages, config).toString());
+
+        bawueDistribution.setStatus(PresenceAbsenceTerm.CASUAL());
+        Assert.assertEquals("● <b>GER(B)</b> ?IT [aGER(BW) cFR(J) nS]", composer.createCondensedDistribution(distributions, languages, config).toString());
+
+    }
+
+    @Test
+    public void testEuroMedCondensedDistributionWithParentStatus() {
+        createDefaultDistributions();
+
+        distributions.add(Distribution.NewInstance(france, PresenceAbsenceTerm.CASUAL()));
+
+        CondensedDistribution condensedDistribution = composer.createCondensedDistribution(
+                distributions, languages, config);
+
+        Assert.assertEquals("● <b>GER(B BW)</b> ?IT [aFR(cJ) nS]", condensedDistribution.toString());
+    }
+
+    @Test
+    public void testEuroMedCondensedDistributionNotEndemicOrOnlyIntroduced() {
+
+        distributions.add(Distribution.NewInstance(germany, PresenceAbsenceTerm.NATIVE()));
+        Assert.assertEquals("<b>GER</b>", composer.createCondensedDistribution(
+                distributions, languages, config).toString());
+
+        distributions.add(Distribution.NewInstance(europe, PresenceAbsenceTerm.ENDEMISM_UNKNOWN()));
+        Assert.assertEquals("<b>GER</b>", composer.createCondensedDistribution(
+                distributions, languages, config).toString());
+
+        distributions.clear();
+        distributions.add(Distribution.NewInstance(germany, PresenceAbsenceTerm.CASUAL()));
+        Assert.assertEquals("[aGER]", composer.createCondensedDistribution(
+                distributions, languages, config).toString());
+    }
+
+    @Test
+    public void testCubaCondensedDistributionWithEMTestData() {
+        createDefaultDistributions();
+
+        distributions.add(Distribution.NewInstance(france, PresenceAbsenceTerm.CASUAL()));
+
+        CondensedDistribution condensedDistribution = composer.createCondensedDistribution(
+                distributions, languages, CondensedDistributionConfiguration.NewCubaInstance());
+
+        Assert.assertEquals("●<b>EU</b>(<b>GER</b>(<b>GER(B) GER(BW)</b>) a<b>FR</b>(c<b>FR(J)</b>) ?<b>IT</b> n<b>S</b>)", condensedDistribution.toString());
+    }
+}
\ No newline at end of file
similarity index 95%
rename from cdmlib-ext/src/test/java/eu/etaxonomy/cdm/ext/geo/FloraCubaCondensedDistributionComposerTest.java
rename to cdmlib-ext/src/test/java/eu/etaxonomy/cdm/ext/geo/CondensedDistributionComposerFloraCubaTest.java
index 6b14a826617716cb96a69480f1a90d42ba18800d..797f83c7b016905658a74e1cf1d7edad9dca9537 100644 (file)
@@ -13,6 +13,7 @@ import java.util.Set;
 import java.util.UUID;
 
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -30,7 +31,9 @@ import eu.etaxonomy.cdm.test.TermTestBase;
  * @author a.mueller
  * @since 07.04.2016
  */
-public class FloraCubaCondensedDistributionComposerTest extends TermTestBase {
+public class CondensedDistributionComposerFloraCubaTest extends TermTestBase {
+
+    private CondensedDistributionConfiguration config;
 
     private static OrderedTermVocabulary<PresenceAbsenceTerm> statusVoc;
     private static OrderedTermVocabulary<NamedArea> cubaAreasVocabualary;
@@ -107,14 +110,17 @@ public class FloraCubaCondensedDistributionComposerTest extends TermTestBase {
 //      statusSymbols.put(UUID.fromString("71b72e24-c2b6-44a5-bdab-39f083bf0f06"), "-d");
     }
 
+    @Before
+    public void setUp(){
+        config = CondensedDistributionConfiguration.NewCubaInstance();
+    }
+
 
 // ********************* TESTS ******************************/
 
     @Test
     public void testCreateCondensedDistribution() {
-        FloraCubaCondensedDistributionComposer composer = new FloraCubaCondensedDistributionComposer();
-        composer.setAreaPreTag("<b>");
-        composer.setAreaPostTag("</b>");
+        CondensedDistributionComposer composer = new CondensedDistributionComposer();
 
         Set<Distribution> filteredDistributions = new HashSet<>();
         filteredDistributions.add(Distribution.NewInstance(cuba, PresenceAbsenceTerm.NATURALISED()));
@@ -125,10 +131,12 @@ public class FloraCubaCondensedDistributionComposerTest extends TermTestBase {
         filteredDistributions.add(Distribution.NewInstance(bahamas, PresenceAbsenceTerm.NATIVE()));
         filteredDistributions.add(Distribution.NewInstance(oldWorld, PresenceAbsenceTerm.NATIVE_PRESENCE_QUESTIONABLE()));
 
-        CondensedDistribution condensedDistribution = composer.createCondensedDistribution(filteredDistributions, null);
+        CondensedDistribution condensedDistribution = composer.createCondensedDistribution(filteredDistributions, null, config);
         String condensedString = condensedDistribution.toString();
 
-        Assert.assertEquals("Condensed string for Cuba differs", "n<b>Cu</b>(-d<b>CuW</b>(-c<b>PR*</b>) (c)<b>CuE</b>(n<b>Ho</b>)) " + composer.getInternalAreaSeparator() + "<b>Bah</b> ?<b>VM</b> ", condensedString);
+        Assert.assertEquals("Condensed string for Cuba differs",
+                "n<b>Cu</b>(-d<b>CuW</b>(-c<b>PR*</b>) (c)<b>CuE</b>(n<b>Ho</b>))" + config.outOfScopeAreasSeperator + "<b>Bah</b> ?<b>VM</b>",
+                condensedString);
 
         //TODO work in progress
     }
@@ -136,9 +144,7 @@ public class FloraCubaCondensedDistributionComposerTest extends TermTestBase {
     @Test
     public void testCreateCondensedDistributionOrderSubAreas() {
 
-        FloraCubaCondensedDistributionComposer composer = new FloraCubaCondensedDistributionComposer();
-        composer.setAreaPreTag("");
-        composer.setAreaPostTag("");
+        CondensedDistributionComposer composer = new CondensedDistributionComposer();
 
         Set<Distribution> filteredDistributions = new HashSet<>();
         filteredDistributions.add(Distribution.NewInstance(cuba, PresenceAbsenceTerm.NATURALISED()));
@@ -158,12 +164,12 @@ public class FloraCubaCondensedDistributionComposerTest extends TermTestBase {
         filteredDistributions.add(Distribution.NewInstance(bahamas, PresenceAbsenceTerm.NATIVE()));
         filteredDistributions.add(Distribution.NewInstance(oldWorld, PresenceAbsenceTerm.NATIVE_PRESENCE_QUESTIONABLE()));
 
-        CondensedDistribution condensedDistribution = composer.createCondensedDistribution(filteredDistributions, null);
-        String condensedString = condensedDistribution.toString();
+        config.areasBold = false;
+        CondensedDistribution condensedDistribution = composer.createCondensedDistribution(filteredDistributions, null, config);
 
         Assert.assertEquals("Condensed string for Cuba differs",
-                "nCu(-dCuW(PR* Art Hab* May Mat IJ) (c)CuE(nHo -cGu)) " + composer.getInternalAreaSeparator() + "Bah ?VM ",
-                condensedString);
+                "nCu(-dCuW(PR* Art Hab* May Mat IJ) (c)CuE(nHo -cGu))" + config.outOfScopeAreasSeperator + "Bah ?VM",
+                condensedDistribution.toString());
 
         //TODO work in progress
     }
diff --git a/cdmlib-ext/src/test/java/eu/etaxonomy/cdm/ext/geo/EuroPlusMedCondensedDistributionComposerTest.java b/cdmlib-ext/src/test/java/eu/etaxonomy/cdm/ext/geo/EuroPlusMedCondensedDistributionComposerTest.java
deleted file mode 100644 (file)
index a42e63d..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
-* Copyright (C) 2016 EDIT
-* European Distributed Institute of Taxonomy
-* http://www.e-taxonomy.eu
-*
-* The contents of this file are subject to the Mozilla Public License Version 1.1
-* See LICENSE.TXT at the top of this package for the full license terms.
-*/
-package eu.etaxonomy.cdm.ext.geo;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-import eu.etaxonomy.cdm.api.service.dto.CondensedDistribution;
-import eu.etaxonomy.cdm.model.common.Language;
-import eu.etaxonomy.cdm.model.description.Distribution;
-import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
-import eu.etaxonomy.cdm.model.location.NamedArea;
-import eu.etaxonomy.cdm.test.TermTestBase;
-
-/**
- * @author a.mueller
- * @since 15.06.2016
- */
-public class EuroPlusMedCondensedDistributionComposerTest extends TermTestBase {
-
-    @Test
-    public void testGetCondensedDistribution() {
-
-        //Germany
-        NamedArea germany = NamedArea.NewInstance("Germany", "", "GER");
-        NamedArea berlin = NamedArea.NewInstance("Berlin", "", "GER(B)");
-        berlin.setPartOf(germany);
-        NamedArea bawue = NamedArea.NewInstance("Baden Württemberg", "", "GER(BW)");
-        bawue.setPartOf(germany);
-        //France
-        NamedArea france = NamedArea.NewInstance("France", "", "FR");
-        NamedArea ileDeFrance = NamedArea.NewInstance("Ile-de-France", "", "FR(J)");
-        ileDeFrance.setPartOf(france);
-        //Italy
-        NamedArea italy = NamedArea.NewInstance("Italy", "", "IT");
-        //Spain
-        NamedArea spain = NamedArea.NewInstance("Spain", "", "S");
-
-        Set<Distribution> distributions = new HashSet<>();
-        distributions.add(Distribution.NewInstance(germany, PresenceAbsenceTerm.NATIVE()));
-        distributions.add(Distribution.NewInstance(bawue, PresenceAbsenceTerm.NATIVE()));
-        distributions.add(Distribution.NewInstance(berlin, PresenceAbsenceTerm.NATIVE()));
-
-        distributions.add(Distribution.NewInstance(italy, PresenceAbsenceTerm.PRESENT_DOUBTFULLY()));
-
-        distributions.add(Distribution.NewInstance(france, PresenceAbsenceTerm.CASUAL()));
-        distributions.add(Distribution.NewInstance(ileDeFrance, PresenceAbsenceTerm.CULTIVATED()));
-
-        distributions.add(Distribution.NewInstance(spain, PresenceAbsenceTerm.NATURALISED()));
-
-        List<Language> languages = new ArrayList<>();
-
-        CondensedDistribution condensedDistribution = EditGeoServiceUtilities.getCondensedDistribution(
-                distributions,
-                CondensedDistributionRecipe.EuroPlusMed,
-                languages);
-
-        Assert.assertEquals("GER(B BW) ?IT [aFR cFR(J) nS]", condensedDistribution.toString());
-    }
-}
\ No newline at end of file
index 9b6c8247f0c19fa1381bbba68ca42d03e902fe63..35056b48041ab4533575ac4e1ae43c27b3559f42 100644 (file)
@@ -62,6 +62,8 @@ public class MarkerType extends DefinedTermBase<MarkerType> {
     //E+M, maybe general in future
     public static final UUID uuidEpublished = UUID.fromString("212158af-c8cf-4b15-ab22-8d06667ea7e1");
 
+    public static final UUID uuidFallbackArea = UUID.fromString("e2b42891-aa85-4a09-981b-b7d8f5749c54");
+
 
        protected static Map<UUID, MarkerType> termMap = null;
 
index e648583136b47a2779c7b1ff6571e110f0ec4cb4..3ba98200cdbd5e061688d7985112c5a532c15572 100644 (file)
 package eu.etaxonomy.cdm.model.description;
 
 import java.text.ParseException;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 
 import javax.persistence.Entity;
@@ -713,5 +716,22 @@ public class PresenceAbsenceTerm extends OrderedTermBase<PresenceAbsenceTerm> {
                this.absenceTerm = isAbsenceTerm;
        }
 
-
-}
+       private Set<UUID> isAnyIntroduced;
+    public boolean isAnyIntroduced() {
+        if (isAnyIntroduced == null){
+            isAnyIntroduced = new HashSet<>(Arrays.asList(new UUID[]{
+                   uuidCasualPresenceQuestionable, uuidCasualReportedError,
+                   uuidCultivated, uuidCultivatedPresenceQuestionable, uuidCultivatedReportedError,
+                   uuidIntroduced, uuidIntroducedCultiated, uuidIntroducedDoubtfullyIntroduced,
+                   uuidIntroducedFormerlyIntroduced, uuidIntroducedPresenceQuestionable,
+                   uuidIntroducedReportedError, uuidIntroducedUncertainDegreeNaturalisation,
+                   uuidIntroducedAdventitious,
+                   uuidInvasive, uuidInvasivePresenceQuestionable,
+                   uuidNaturalised, uuidNaturalisedPresenceQuestionable, uuidNaturalisedReportedError,
+                   uuidIntroducedUncertainDegreeNaturalisation,
+                   uuidInvasive, uuidInvasivePresenceQuestionable, uuidNonInvasive, uuidNonInvasivePresenceQuestionable
+            }));
+        }
+        return isAnyIntroduced.contains(uuid);
+    }
+}
\ No newline at end of file
index 35136e8a731e8089fab2da8a4999341a5f3d295d..e5eb7fc5d161364afa09b3f7d4dd359bb8277a46 100644 (file)
@@ -17,6 +17,7 @@ import java.util.TreeSet;
 public class HTMLTagRules {\r
 \r
        private List<TagRule> rules = new ArrayList<>();\r
+       private boolean includeSingleInstanceHtml = false;\r
 \r
        private class TagRule{\r
                private TagRule(TagEnum type, String htmlTag){\r
@@ -39,6 +40,17 @@ public class HTMLTagRules {
                return this;\r
        }\r
 \r
+       public SortedSet<String> getRule(TaggedText taggedText){\r
+           SortedSet<String> result = new TreeSet<>();\r
+           if (taggedText != null){\r
+               result = getRule(taggedText.getType());\r
+               if (this.includeSingleInstanceHtml && !taggedText.htmlTags().isEmpty()){\r
+                   result.addAll(taggedText.htmlTags());\r
+               }\r
+           }\r
+           return result;\r
+       }\r
+\r
        public SortedSet<String> getRule(TagEnum type){\r
                SortedSet<String> result = new TreeSet<>();\r
                for (TagRule rule : rules){\r
@@ -58,15 +70,24 @@ public class HTMLTagRules {
                return false;\r
        }\r
 \r
+    public boolean isIncludeSingleInstanceHtml() {\r
+        return includeSingleInstanceHtml;\r
+    }\r
+\r
+    public void setIncludeSingleInstanceHtml(boolean includeSingleInstanceHtml) {\r
+        this.includeSingleInstanceHtml = includeSingleInstanceHtml;\r
+    }\r
+\r
        @Override\r
        public String toString(){\r
                String result = "HTMLTagRules[";\r
                for (TagRule rule : rules){\r
                        result += rule.toString() + ";";\r
                }\r
-               result = result.substring(0, result.length() -1) + "]";\r
+               result = result.substring(0, rules.isEmpty()? result.length(): result.length()-1) + "]";\r
                return result;\r
        }\r
 \r
 \r
+\r
 }\r
index 211f6c01d817f448b5dde9f1daa2885ec34ed40a..60b72043e18e131df5814b4544255ddaa2539a64 100644 (file)
@@ -8,11 +8,14 @@
 */
 package eu.etaxonomy.cdm.strategy.cache;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.SortedSet;
 import java.util.Stack;
 import java.util.TreeSet;
 
+import org.apache.commons.lang3.StringUtils;
+
 /**
  * Formatter class to create Strings from TaggedText Lists.
  *
@@ -21,6 +24,8 @@ import java.util.TreeSet;
  */
 public class TaggedCacheHelper {
 
+    private static final TaggedText WHITESPACE_TAG = TaggedText.NewWhitespaceInstance();
+
     /**
      * Creates a string from tagged text by concatenating all tags with a whitespace.
      * @param tags
@@ -50,13 +55,13 @@ public class TaggedCacheHelper {
     /**
      * Creates a string from tagged text by concatenating all tags. If no separator tag is defined
      * tags are separated by simple whitespace.
-     * @param tags
-     * @return
      */
-    public  static String createString(List<TaggedText> tags, HTMLTagRules htmlRules) {
+    public  static String createString(List<TaggedText> tagsOrigin, HTMLTagRules htmlRules) {
         if (htmlRules == null){
-            return createString(tags);
+            return createString(tagsOrigin);
         }
+        List<TaggedText> tags = new ArrayList<>(tagsOrigin);
+
         //add whitespace separators
         int index = 0;
         boolean wasSeparator = true;
@@ -64,7 +69,7 @@ public class TaggedCacheHelper {
 
             if (! tags.get(index).getType().isSeparator()){
                 if (wasSeparator == false){
-                    tags.add(index++, TaggedText.NewWhitespaceInstance());
+                    tags.add(index++, WHITESPACE_TAG);
                 }else{
                     wasSeparator = false;
                 }
@@ -80,18 +85,20 @@ public class TaggedCacheHelper {
         Stack<String> htmlStack = new Stack<>();
         for (int i = 0;  i < tags.size(); i++  ){
             TaggedText tag = tags.get(i);
-            TagEnum thisType = tag.getType();
-            TagEnum lastType = (i == 0? null : tags.get(i - 1).getType());
-            TagEnum nextType = (i + 1 >= tags.size() ? null : tags.get(i + 1).getType());
+            TaggedText lastTag = (i == 0? null : tags.get(i - 1));
+            TaggedText nextTag = (i + 1 >= tags.size() ? null : tags.get(i + 1));
+            TagEnum lastType = (lastTag == null ? null : lastTag.getType());
+            TagEnum nextType = (nextTag == null ? null : nextTag.getType());
 
             boolean isSeparator = tag.getType().isSeparator();
+            boolean isBlankSeparator = isSeparator && StringUtils.isBlank(tag.getText());
 
             //compute list of rules (tags)
             SortedSet<String> separatorRules;
-            if (isSeparator){
-                separatorRules = getCommonRules(htmlRules.getRule(lastType), htmlRules.getRule(nextType));
+            if (isBlankSeparator){
+                separatorRules = getCommonRules(htmlRules.getRule(lastTag), htmlRules.getRule(nextTag));
             }else{
-                separatorRules = htmlRules.getRule(thisType);
+                separatorRules = htmlRules.getRule(tag);
             }
 
             //Close all tags not used anymore and remove all common tags from list of rules
@@ -106,7 +113,7 @@ public class TaggedCacheHelper {
             }
 
             //open all tags not yet existing
-            if (! isSeparator){
+            if (! isBlankSeparator){
                 for (String rule : separatorRules){
                     htmlStack.add(rule);
                     result.append("<" +  rule + ">");
@@ -114,7 +121,7 @@ public class TaggedCacheHelper {
             }
 
             //add whitespace
-            if (lastType != null && ! lastType.isSeparator() && ! isSeparator && nextType != null){
+            if (!isSeparator && lastType != null && !lastType.isSeparator() && nextType != null){
                 result.append(" ");
             }
             result.append(tag.getText());
index 360476e643c5d2af817cb9539670f0eb07e214de..a2fbc15743c94844c8720f3f6085c23f852bf700 100644 (file)
@@ -9,6 +9,8 @@
 package eu.etaxonomy.cdm.strategy.cache;
 
 import java.io.Serializable;
+import java.util.SortedSet;
+import java.util.TreeSet;
 
 import org.apache.commons.lang3.StringUtils;
 
@@ -88,6 +90,13 @@ public class TaggedText implements Serializable{
         this.entityReference = entityReference;
     }
 
+    /**
+     * To be overridden by subclasses if needed.
+     */
+    public SortedSet<String> htmlTags() {
+        return new TreeSet<>();
+    }
+
 // **************************** TO STRING ***********************************/
 
        @Override
@@ -99,4 +108,5 @@ public class TaggedText implements Serializable{
                        return result;
                }
        }
+
 }
\ No newline at end of file
diff --git a/cdmlib-model/src/test/java/eu/etaxonomy/cdm/strategy/cache/TaggedCacheHelperTest.java b/cdmlib-model/src/test/java/eu/etaxonomy/cdm/strategy/cache/TaggedCacheHelperTest.java
new file mode 100644 (file)
index 0000000..754dc05
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+* Copyright (C) 2021 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package eu.etaxonomy.cdm.strategy.cache;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author a.mueller
+ * @since 23.02.2021
+ */
+public class TaggedCacheHelperTest {
+
+    private List<TaggedText> tags;
+
+    @Before
+    public void setUp() throws Exception {
+        tags = new ArrayList<>();
+        tags.add(TaggedText.NewInstance(TagEnum.label, "My"));
+        tags.add(TaggedText.NewInstance(TagEnum.label, "taxon"));
+    }
+
+    @Test
+    public void test() {
+        Assert.assertEquals("My taxon", TaggedCacheHelper.createString(tags));
+
+        tags.add(1, TaggedText.NewInstance(TagEnum.separator, ":"));
+        Assert.assertEquals("My:taxon", TaggedCacheHelper.createString(tags));
+    }
+
+    @Test
+    public void testHtmlRules() {
+        HTMLTagRules rules = new HTMLTagRules();
+        rules.addRule(TagEnum.label, "b");
+        Assert.assertEquals("<b>My taxon</b>", TaggedCacheHelper.createString(tags, rules));
+
+        tags.add(1, TaggedText.NewInstance(TagEnum.separator, ":"));
+        Assert.assertEquals("<b>My</b>:<b>taxon</b>", TaggedCacheHelper.createString(tags, rules));
+    }
+
+}
index ade75b284a58485160a135e54a003b6bde2da27b..b3be2eb61b8824be1316f54d8a01e930c35ae05d 100644 (file)
 package eu.etaxonomy.cdm.api.service.dto;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
+import java.util.SortedSet;
 
-import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
+import javax.swing.text.html.HTML;
+
+import eu.etaxonomy.cdm.common.CdmUtils;
+import eu.etaxonomy.cdm.strategy.cache.HTMLTagRules;
+import eu.etaxonomy.cdm.strategy.cache.TagEnum;
+import eu.etaxonomy.cdm.strategy.cache.TaggedCacheHelper;
+import eu.etaxonomy.cdm.strategy.cache.TaggedText;
 
 /**
+ * A class representing a condensed distribution stored as as list of {@link TaggedText}
+ * with TaggedText being extended by an isBold indicator.
+ *
+ * The class offers a method representing a string representation which uses {@link HTML}
+ * tag < b> to indicate bold text.
+ *
  * @author a.kohlbecker
+ * @author a.mueller
  * @since Jun 25, 2015
  */
 public class CondensedDistribution {
 
-    private List<DistributionItem> indigenous = new ArrayList<>();
-    private List<DistributionItem> foreign = new ArrayList<>();
+    private List<TaggedText> taggedText = new ArrayList<>();
 
-    public List<DistributionItem> getForeign() {
-        return foreign;
-    }
-    public void setForeign(List<DistributionItem> foreign) {
-        this.foreign = foreign;
-    }
+    public static class DistributionTaggedText extends TaggedText{
+        private static final long serialVersionUID = 3904027767908153947L;
+        private boolean bold = false;
 
-    public void setIndigenous(List<DistributionItem> indigenous) {
-        this.indigenous = indigenous;
-    }
-    public List<DistributionItem> getIndigenous() {
-        return indigenous;
-    }
+        public static DistributionTaggedText NewInstance(TagEnum type, String text){
+            return new DistributionTaggedText(type, text, false);
+        }
 
-    public void addForeignDistributionItem(PresenceAbsenceTerm status, String areaStatusLabel, String sortString) {
-        foreign.add(new DistributionItem(status, areaStatusLabel, sortString));
-    }
+        private DistributionTaggedText(TagEnum type, String text, boolean bold) {
+            super(type, text);
+            this.bold = bold;
+        }
+        public boolean isBold() {
+            return bold;
+        }
+        public void setBold(boolean bold) {
+            this.bold = bold;
+        }
 
-    public void addIndigenousDistributionItem(PresenceAbsenceTerm status, String areaStatusLabel, String sortString) {
-        indigenous.add(new DistributionItem(status, areaStatusLabel, sortString));
+        @Override
+        public SortedSet<String> htmlTags(){
+            SortedSet<String> result = super.htmlTags();
+            if (bold){
+                result.add("b");
+            }
+            return result;
+        }
     }
 
-
-    public void sortForeign() {
-        Collections.sort(foreign, new CondensedDistributionComparator());
+    public void addStatusAndAreaTaggedText(String status, String area, boolean bold) {
+        addTaggedText(TagEnum.symbol, status, false);
+        if (CdmUtils.isNotBlank(status) && CdmUtils.isNotBlank(area)){
+            addTaggedText(TagEnum.separator,"",false);
+        }
+        addTaggedText(TagEnum.label, area, bold);
     }
 
-    public void sortIndigenous() {
-        Collections.sort(indigenous, new CondensedDistributionComparator());
+    public void addSeparatorTaggedText(String sep){
+        addTaggedText(TagEnum.separator, sep, false);
+    }
+    public void addSeparatorTaggedText(String sep, boolean bold) {
+        addTaggedText(TagEnum.separator, sep, bold);
     }
 
-    @Override
-    public String toString() {
-
-        StringBuilder out = new StringBuilder();
-
-        boolean isFirst = true;
-        for(DistributionItem item : indigenous) {
-            if(!isFirst) {
-                out.append(" ");
-            }
-            out.append(item.areaStatusLabel);
-            isFirst = false;
-        }
+    /**
+     * @see TagEnum#postSeparator
+     */
+    public void addPostSeparatorTaggedText(String sep){
+        addTaggedText(TagEnum.postSeparator, sep, false);
+    }
 
-        if(!isFirst) {
-            out.append(" ");
+    public void addTaggedText(TagEnum type, String text, boolean bold) {
+        if (CdmUtils.isNotBlank(text)|| type.isSeparator() ){
+            DistributionTaggedText taggedText = DistributionTaggedText.NewInstance(type, text);
+            taggedText.bold = bold;
+            this.taggedText.add(taggedText);
         }
-        isFirst = true;
-        if(!foreign.isEmpty()) {
-            out.append("[");
-            for(DistributionItem item : foreign) {
-                if(!isFirst) {
-                    out.append(" ");
-                }
-                out.append(item.areaStatusLabel);
-                isFirst = false;
-            }
-            out.append("]");
-        }
-        return out.toString();
     }
 
-    public class DistributionItem {
-
-        private PresenceAbsenceTerm status;
-        private String areaStatusLabel;
-        private final String sortString;
-
-        public DistributionItem(PresenceAbsenceTerm status, String areaStatusLabel, String sortString) {
-            this.status = status;
-            this.areaStatusLabel = areaStatusLabel;
-            this.sortString = sortString;
-        }
-        public PresenceAbsenceTerm getStatus() {
-            return status;
-        }
-        public void setStatus(PresenceAbsenceTerm status) {
-            this.status = status;
-        }
+    /**
+     * @return <code>true</code> if no tagged text was added yet (the inner tagged text list is still empty).
+     */
+    public boolean isEmpty() {
+        return this.taggedText.isEmpty();
+    }
 
-        public String getAreaStatusLabel() {
-            return areaStatusLabel;
-        }
-        public void setAreaStatusLabel(String areaStatusLabel) {
-            this.areaStatusLabel = areaStatusLabel;
-        }
+    public String getHtmlText(){
+        return toString();
     }
 
-    class CondensedDistributionComparator implements Comparator<DistributionItem>{
+//***************** STRING **************************************/
 
-        @Override
-        public int compare(DistributionItem o1, DistributionItem o2) {
-            return o1.sortString.compareToIgnoreCase(o2.sortString);
-        }
+    @Override
+    public String toString(){
+        HTMLTagRules htmlTagRules = new HTMLTagRules();
+        htmlTagRules.setIncludeSingleInstanceHtml(true);
+        return TaggedCacheHelper.createString(this.taggedText, htmlTagRules);
     }
-}
+}
\ No newline at end of file