Project

General

Profile

Download (23 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2016 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.ext.geo;
10

    
11
import java.util.ArrayList;
12
import java.util.Arrays;
13
import java.util.Collection;
14
import java.util.Collections;
15
import java.util.Comparator;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.List;
19
import java.util.Map;
20
import java.util.Set;
21
import java.util.UUID;
22

    
23
import org.codehaus.plexus.util.StringUtils;
24

    
25
import eu.etaxonomy.cdm.api.service.dto.CondensedDistribution;
26
import eu.etaxonomy.cdm.common.CdmUtils;
27
import eu.etaxonomy.cdm.common.DoubleResult;
28
import eu.etaxonomy.cdm.common.TripleResult;
29
import eu.etaxonomy.cdm.common.UTF8;
30
import eu.etaxonomy.cdm.compare.common.OrderType;
31
import eu.etaxonomy.cdm.model.common.Language;
32
import eu.etaxonomy.cdm.model.description.Distribution;
33
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
34
import eu.etaxonomy.cdm.model.location.NamedArea;
35
import eu.etaxonomy.cdm.model.term.OrderedTermBase;
36
import eu.etaxonomy.cdm.model.term.Representation;
37

    
38
/**
39
 * Base class for condensed distribution composers
40
 *
41
 * @author a.mueller
42
 * @since 02.06.2016
43
 */
44
public class CondensedDistributionComposer {
45

    
46
    //for old handling of status symbols
47
    protected static Map<UUID, String> statusSymbols;
48

    
49
    static {
50

    
51
        // ==================================================
52
        // Mapping as defined in ticket https://dev.e-taxonomy.eu/redmine/issues/3907
53
        // ==================================================
54

    
55
        statusSymbols = new HashMap<> ();
56
        statusSymbols.put(PresenceAbsenceTerm.INTRODUCED_REPORTED_IN_ERROR().getUuid(), UTF8.EN_DASH.toString());
57
    }
58

    
59

    
60
    public enum SymbolUsage{
61
        Map,
62
        Symbol1,
63
        Symbol2,
64
        IdInVoc,
65
        AbbrevLabel;
66

    
67
        public String getSymbol(OrderedTermBase<?> term, List<Language> langs) {
68
            if (this == Map){
69
                //TODO not valid for areas yet
70
                return statusSymbols.get(term.getUuid());
71
            }else if (this == Symbol1){
72
                return term.getSymbol();
73
            }else if (this == Symbol2){
74
                return term.getSymbol2();
75
            }else if (this == IdInVoc){
76
                return term.getIdInVocabulary();
77
            }else if (this == AbbrevLabel){
78
                Representation r = term.getPreferredRepresentation(langs);
79
                if (r != null){
80
                    String abbrevLabel = r.getAbbreviatedLabel();
81
                    if (abbrevLabel != null){
82
                        return abbrevLabel;
83
                    }
84
                }
85
            }
86
            throw new RuntimeException("Unhandled enum value: " +  this);
87
        }
88
    }
89

    
90
    public CondensedDistribution createCondensedDistribution(Collection<Distribution> filteredDistributions,
91
            List<Language> languages, CondensedDistributionConfiguration config) {
92

    
93
        CondensedDistribution result = new CondensedDistribution();
94

    
95
        Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap = new HashMap<>();
96

    
97
        DoubleResult<List<AreaNode>, List<AreaNode>> step1_3 = createAreaTreesAndStatusMap(filteredDistributions, areaToStatusMap, config);
98
        List<AreaNode> topLevelNodes = step1_3.getFirstResult();
99
        List<AreaNode> introducedTopLevelNodes = step1_3.getSecondResult();
100

    
101
        //4. replace the area by the abbreviated representation and add symbols
102
        AreaNodeComparator areaNodeComparator = new AreaNodeComparator(config, languages);
103
        Collections.sort(topLevelNodes, areaNodeComparator);
104

    
105
        final boolean NOT_BOLED = false;
106
        final boolean NOT_HANDLED_BY_PARENT = false;
107

    
108
        List<AreaNode> outOfScopeNodes = new ArrayList<>();
109
        if (!topLevelNodes.isEmpty()){
110
            AreaNode areaOfScopeNode = topLevelNodes.remove(0);
111
            outOfScopeNodes = topLevelNodes;
112

    
113
            //handle areaOfScope  (endemic area)
114
            PresenceAbsenceTerm areaOfScopeStatus = areaToStatusMap.get(areaOfScopeNode.area);
115
            DoubleResult<String, Boolean> areaOfScopeStatusSymbol = statusSymbol(areaOfScopeStatus, config, languages, NOT_HANDLED_BY_PARENT);
116
            String areaOfScopeLabel = config.showAreaOfScopeLabel? makeAreaLabel(languages, areaOfScopeNode.area, config, null):"";
117
            result.addStatusAndAreaTaggedText(areaOfScopeStatusSymbol.getFirstResult(),
118
                    areaOfScopeLabel, areaOfScopeStatusSymbol.getSecondResult() || config.areasBold);
119

    
120
            //subareas
121
            handleSubAreas(result, areaOfScopeNode, config, areaNodeComparator, languages, areaToStatusMap,
122
                    areaOfScopeLabel, 0, NOT_BOLED, NOT_HANDLED_BY_PARENT, false);
123
        }
124

    
125
        //subareas with introduced status (if required by configuration)
126
        if (config.splitNativeAndIntroduced && !introducedTopLevelNodes.isEmpty()){
127
            String sep = (result.isEmpty()? "": " ") + config.introducedBracketStart;
128
            result.addSeparatorTaggedText(sep, NOT_BOLED);
129
            boolean isIntroduced = true;
130
            handleSubAreasLoop(result, introducedTopLevelNodes.get(0), config, areaNodeComparator, languages,
131
                    areaToStatusMap, "", 0, NOT_BOLED, NOT_HANDLED_BY_PARENT, isIntroduced);
132
            result.addSeparatorTaggedText(config.introducedBracketEnd, NOT_BOLED);
133
        }
134

    
135
        //outOfScope areas  (areas outside the endemic area)
136
        if (!outOfScopeNodes.isEmpty()){
137
            result.addPostSeparatorTaggedText(config.outOfScopeAreasSeperator);
138
            List<AreaNode> outOfScopeList = new ArrayList<>(outOfScopeNodes);
139
            Collections.sort(outOfScopeList, areaNodeComparator);
140
            boolean isFirst = true;
141
            for (AreaNode outOfScopeNode: outOfScopeList){
142
                String sep = isFirst ? "": " ";
143
                result.addSeparatorTaggedText(sep);
144
                handleSubAreaNode(languages, result, areaToStatusMap, areaNodeComparator,
145
                        outOfScopeNode, config, null, 0, NOT_BOLED, NOT_HANDLED_BY_PARENT, false);
146
                isFirst = false;
147
            }
148
        }
149

    
150
        return result;
151
    }
152

    
153
    protected Map<NamedArea, AreaNode>[] buildAreaHierarchie(Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap,
154
            CondensedDistributionConfiguration config) {
155

    
156
        Map<NamedArea, AreaNode> areaNodeMap = new HashMap<>();
157
        Map<NamedArea, AreaNode> introducedAreaNodeMap = new HashMap<>();
158

    
159
        for(NamedArea area : areaToStatusMap.keySet()) {
160
            Map<NamedArea, AreaNode> map = areaNodeMap;
161
            if (config.splitNativeAndIntroduced && isIntroduced(areaToStatusMap.get(area))){
162
                map = introducedAreaNodeMap;
163
            }
164
            mergeIntoHierarchy(areaToStatusMap.keySet(), map, area, config);
165
        }
166

    
167
        //TODO move this filter further up where native and introduced is still combined,
168
        // this makes it less complicated
169
        removeFallbackAreasWithChildDistributions(areaNodeMap, introducedAreaNodeMap, config);
170

    
171
        @SuppressWarnings("unchecked")
172
        Map<NamedArea, AreaNode>[] result = new Map[]{areaNodeMap,introducedAreaNodeMap};
173
        return result;
174
    }
175

    
176
    private void removeFallbackAreasWithChildDistributions(Map<NamedArea, AreaNode> areaNodeMap, Map<NamedArea, AreaNode> areaNodeMap2, CondensedDistributionConfiguration config) {
177
        Set<NamedArea> toBeDeletedAreas = new HashSet<>();
178
        Set<AreaNode> allNodes = new HashSet<>(areaNodeMap.values());
179
        allNodes.addAll(areaNodeMap2.values());
180
        for (AreaNode areaNode : allNodes){
181
            if (hasParentToBeRemoved(areaNode, config)){
182
                toBeDeletedAreas.add(areaNode.parent.area);
183
            }
184
        }
185
        for (NamedArea toBeDeletedArea : toBeDeletedAreas){
186
            removeFallbackArea(toBeDeletedArea, areaNodeMap);
187
            removeFallbackArea(toBeDeletedArea, areaNodeMap2);
188
        }
189
    }
190

    
191
    private void removeFallbackArea(NamedArea toBeDeletedArea, Map<NamedArea, AreaNode> areaNodeMap) {
192
        AreaNode toBeDeletedNode = areaNodeMap.get(toBeDeletedArea);
193
        if(toBeDeletedNode == null){
194
            return;
195
        }
196
        AreaNode parent = toBeDeletedNode.getParent();
197
        if (parent != null){
198
            parent.subAreas.remove(toBeDeletedNode);
199
        }
200
        for (AreaNode child : toBeDeletedNode.subAreas){
201
            if (parent != null){
202
                parent.addSubArea(child);
203
            }else{
204
                child.parent = null;
205
            }
206
        }
207
        areaNodeMap.remove(toBeDeletedNode.area);
208
    }
209

    
210
    private boolean hasParentToBeRemoved(AreaNode areaNode, CondensedDistributionConfiguration config) {
211
        if (areaNode.parent == null){
212
            return false;
213
        }
214
        boolean parentIsHidden = isHiddenOrFallback(areaNode.parent.area, config);
215
        return parentIsHidden;
216
    }
217

    
218
    private boolean isIntroduced(PresenceAbsenceTerm status) {
219
        return status.isAnyIntroduced();
220
    }
221

    
222
    private void mergeIntoHierarchy(Collection<NamedArea> areas,  //areas not really needed anymore if we don't use findParentIn
223
            Map<NamedArea, AreaNode> areaNodeMap, NamedArea area, CondensedDistributionConfiguration config) {
224

    
225
        AreaNode node = areaNodeMap.get(area);
226
        if(node == null) {
227
            // putting area into list of areas as node
228
            node = new AreaNode(area);
229
            areaNodeMap.put(area, node);
230
        }
231

    
232
        NamedArea parent = getNonFallbackParent(area, config);   // findParentIn(area, areas);
233

    
234
        if(parent != null) {
235
            AreaNode parentNode = areaNodeMap.get(parent);
236
            if(parentNode == null) {
237
                parentNode = new AreaNode(parent);
238
                areaNodeMap.put(parent, parentNode);
239
            }
240
            parentNode.addSubArea(node);
241
            mergeIntoHierarchy(areas, areaNodeMap, parentNode.area, config);  //recursive to top
242
        }
243
    }
244

    
245
    private NamedArea getNonFallbackParent(NamedArea area, CondensedDistributionConfiguration config) {
246
        NamedArea parent = area.getPartOf();
247
        //if done here the fallback test does not work anymore
248
//        while(parent != null && isHiddenOrFallback(parent, config)){
249
//            parent = parent.getPartOf();
250
//        }
251
        return parent;
252
    }
253

    
254
    private boolean isHiddenOrFallback(NamedArea area, CondensedDistributionConfiguration config) {
255
        if (config.hiddenAndFallbackAreaMarkers == null){
256
            return false;
257
        }
258
        for (UUID markerUuid : config.hiddenAndFallbackAreaMarkers){
259
            if (area.hasMarker(markerUuid, true)){
260
                return true;
261
            }
262
        }
263
        return false;
264
    }
265

    
266
    protected String makeAreaLabel(List<Language> langs, NamedArea area,
267
            CondensedDistributionConfiguration config, String parentAreaLabel) {
268
        //TODO config with symbols, not only idInVocabulary
269
        String label = config.areaSymbolField.getSymbol(area, langs);
270
        if (config.shortenSubAreaLabelsIfPossible && parentAreaLabel != null && !parentAreaLabel.isEmpty()){
271
            //TODO make brackets not hardcoded, but also allow [],- etc., but how?
272
            if (label.startsWith(parentAreaLabel+"(") && label.endsWith(")") ){
273
                label = label.substring(parentAreaLabel.length()+1, label.length()-1);
274
            }
275
        }
276

    
277
        return label;
278
    }
279

    
280
    protected TripleResult<String, Boolean, Boolean> statusSymbolForArea(AreaNode areaNode, Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap,
281
            CondensedDistributionConfiguration config, List<Language> languages,boolean onlyIntroduced) {
282

    
283
        if (!config.showStatusOnParentAreaIfAllSame ){
284
            return statusSymbol(areaToStatusMap.get(areaNode.area), config, languages, false);
285
        }else{
286
            Set<PresenceAbsenceTerm> statusList = getStatusRecursive(areaNode, areaToStatusMap, new HashSet<>(), onlyIntroduced);
287
            if (statusList.isEmpty()){
288
                return statusSymbol(areaToStatusMap.get(areaNode.area), config, languages, false);
289
            }else if (statusList.size() == 1){
290
                return statusSymbol(statusList.iterator().next(), config, languages, true);
291
            }else{
292
                //subarea status is handled at subarea level, usually parent area status is empty as the parent area will not have a status
293
                if (areaToStatusMap.get(areaNode.area) == null && containsBoldAreas(statusList, config)){
294
                    //if parent area status is empty and at least one subarea has status native the parent area should also be bold (#8297#note-15)
295
                    return new TripleResult<>("", true, false);
296
                }else{
297
                    return statusSymbol(areaToStatusMap.get(areaNode.area), config, languages, false);
298
                }
299
            }
300
        }
301
    }
302

    
303
    private boolean containsBoldAreas(Set<PresenceAbsenceTerm> statusList, CondensedDistributionConfiguration config) {
304
        for (PresenceAbsenceTerm status : statusList){
305
            if (config.statusForBoldAreas.contains(status.getUuid())){
306
                return true;
307
            }
308
        }
309
        return false;
310
    }
311

    
312
    private Set<PresenceAbsenceTerm> getStatusRecursive(AreaNode areaNode,
313
            Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap, Set<PresenceAbsenceTerm> statusList,
314
            boolean onlyIntroduced) {
315

    
316
        PresenceAbsenceTerm status = areaToStatusMap.get(areaNode.area);
317
        if (status != null && (!onlyIntroduced || isIntroduced(status))){
318
            statusList.add(status);
319
        }
320

    
321
        for (AreaNode subNode : areaNode.subAreas){
322
            statusList.addAll(getStatusRecursive(subNode, areaToStatusMap, statusList, onlyIntroduced));
323
        }
324
        return statusList;
325
    }
326

    
327
    /**
328
     * @param status
329
     * @param config
330
     *              configuration
331
     * @param statusHandledByParent
332
     *              indicates if the status is handled by the parent. Will be passed to the (third) result
333
     * @return
334
     *      a triple result with the first result being the symbol string, the second result
335
     *      being the isBold flag and the third result indicates if this symbol includes information
336
     *      for all sub-areas (which passes the input parameter) to the output here
337
     */
338
    protected TripleResult<String, Boolean, Boolean> statusSymbol(PresenceAbsenceTerm status,
339
            CondensedDistributionConfiguration config, List<Language> languages, boolean statusHandledByParent) {
340

    
341
        List<SymbolUsage> symbolPreferences = Arrays.asList(config.statusSymbolField);
342
        if(status == null) {
343
            return new TripleResult<>("", false, statusHandledByParent);
344
        }
345

    
346
        //usually the symbols should all come from the same field, but in case they don't ...
347
        if (config.showAnyStatusSmbol){
348
            for (SymbolUsage usage : SymbolUsage.values()){
349
                if (!symbolPreferences.contains(usage)){
350
                    symbolPreferences.add(usage);
351
                }
352
            }
353
        }
354

    
355
        for (SymbolUsage usage: symbolPreferences){
356
            String symbol = usage.getSymbol(status, languages);
357
            if (symbol != null){
358
                return new TripleResult<>(symbol, isBoldStatus(status, config), statusHandledByParent);
359
            }
360
        }
361

    
362
        return new TripleResult<>("", isBoldStatus(status, config), statusHandledByParent);
363
    }
364

    
365
    private Boolean isBoldStatus(PresenceAbsenceTerm status, CondensedDistributionConfiguration config) {
366
        return config.statusForBoldAreas.contains(status.getUuid());
367
    }
368

    
369
    /**
370
     * Searches for the parent area of the area given as parameter in
371
     * the collection of areas.
372
     *
373
     * @parent area
374
     *      The area which parent area is to be searched
375
     * @param collection
376
     *      The areas to search in.
377
     *
378
     * @return
379
     *      Either the parent if it has been found or null.
380
     */
381
    protected NamedArea findParentIn(NamedArea area, Collection<NamedArea> areas) {
382
        NamedArea parent = area.getPartOf();
383
        if(parent != null && areas.contains(parent)){
384
            return parent;
385
        }
386
        return null;
387
    }
388

    
389
    protected class AreaNode {
390

    
391
        protected final NamedArea area;
392
        protected AreaNode parent = null;
393
        protected final Set<AreaNode> subAreas = new HashSet<>();
394

    
395
        public AreaNode(NamedArea area) {
396
            this.area = area;
397
        }
398

    
399
        public void addSubArea(AreaNode subArea) {
400
            subAreas.add(subArea);
401
            subArea.parent = this;
402
        }
403

    
404
        public AreaNode getParent() {
405
            return parent;
406
        }
407

    
408
        public boolean hasParent() {
409
            return getParent() != null;
410
        }
411

    
412
        public Collection<NamedArea> getSubareas() {
413
            Collection<NamedArea> areas = new HashSet<>();
414
            for(AreaNode node : subAreas) {
415
                areas.add(node.area);
416
            }
417
            return areas;
418
        }
419

    
420
        @Override
421
        public String toString() {
422
            return "AreaNode [area=" + area + "]";
423
        }
424
    }
425

    
426
    private class AreaNodeComparator implements Comparator<AreaNode>{
427
        CondensedDistributionConfiguration config;
428
        List<Language> languages;
429
        private AreaNodeComparator(CondensedDistributionConfiguration config, List<Language> languages){
430
            this.config = config;
431
            this.languages = languages;
432
        }
433

    
434
        @Override
435
        public int compare(AreaNode areaNode1, AreaNode areaNode2) {
436
            NamedArea area1 = areaNode1.area;
437
            NamedArea area2 = areaNode2.area;
438

    
439
            if (area1 == null && area2 == null){
440
                return 0;
441
            }else if (area1 == null){
442
                return -1;
443
            }else if (area2 == null){
444
                return 1;
445
            }else{
446
                if (config.orderType == OrderType.NATURAL) {
447
                    //- due to wrong ordering behavior in DefinedTerms
448
                    return - area1.compareTo(area2);
449
                }else{
450
                    String str1 = config.areaSymbolField.getSymbol(area1, languages);
451
                    String str2 = config.areaSymbolField.getSymbol(area2, languages);
452
                    return CdmUtils.nullSafeCompareTo(str1, str2);
453
                }
454
            }
455
        }
456
    }
457

    
458
    protected DoubleResult<List<AreaNode>, List<AreaNode>> createAreaTreesAndStatusMap(Collection<Distribution> filteredDistributions,
459
            Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap, CondensedDistributionConfiguration config){
460

    
461
        //we expect every area only to have 1 status  (multiple status should have been filtered beforehand)
462

    
463
        //1. compute all areas and their status
464
        for(Distribution distr : filteredDistributions) {
465
            PresenceAbsenceTerm status = distr.getStatus();
466
            NamedArea area = distr.getArea();
467

    
468
            //TODO needed? Do we only want to have areas with status?
469
            if(status == null || area == null) {
470
                continue;
471
            }
472

    
473
            areaToStatusMap.put(area, status);
474
        }
475

    
476
        //2. build the area hierarchy
477
        Map<NamedArea, AreaNode>[] areaNodeMaps = buildAreaHierarchie(areaToStatusMap, config);
478

    
479
        //3. find root nodes
480
        @SuppressWarnings("unchecked")
481
        List<AreaNode>[] topLevelNodes = new ArrayList[]{new ArrayList<>(), new ArrayList<>()};
482

    
483
        for (int i = 0; i < 2; i++){
484
            for(AreaNode node : areaNodeMaps[i].values()) {
485
                if(!node.hasParent() && ! topLevelNodes[i].contains(node)) {
486
                    topLevelNodes[i].add(node);
487
                }
488
            }
489
        }
490

    
491
        DoubleResult<List<AreaNode>, List<AreaNode>> result = new DoubleResult<>(topLevelNodes[0], topLevelNodes[1]);
492

    
493
        return result;
494
    }
495

    
496
    private void handleSubAreas(CondensedDistribution result, AreaNode areaNode, CondensedDistributionConfiguration config,
497
            AreaNodeComparator areaNodeComparator, List<Language> languages, Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap,
498
            String parentAreaLabel, int level, boolean isBold, boolean statusHandledByParent, boolean isIntroduced) {
499

    
500
        if (!areaNode.subAreas.isEmpty()) {
501
            String sepStart = getListValue(config.areaOfScopeSubAreaBracketStart, level, "(");
502
            if (StringUtils.isNotBlank(sepStart)){
503
                result.addSeparatorTaggedText(sepStart, isBold);
504
            }
505
            handleSubAreasLoop(result, areaNode, config, areaNodeComparator, languages, areaToStatusMap, parentAreaLabel,
506
                    level, isBold, statusHandledByParent, isIntroduced);
507
            String sepEnd = getListValue(config.areaOfScopeSubAreaBracketEnd, level, ")");
508
            if (StringUtils.isNotBlank(sepEnd)){
509
                result.addSeparatorTaggedText(sepEnd, isBold);
510
            }
511
        }
512
    }
513

    
514
    private void handleSubAreasLoop(CondensedDistribution result, AreaNode areaNode,
515
            CondensedDistributionConfiguration config, AreaNodeComparator areaNodeComparator, List<Language> languages,
516
            Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap, String parentLabel, int level, boolean isBold,
517
            boolean statusHandledByParent, boolean isIntroduced) {
518

    
519
        List<AreaNode> subAreas = new ArrayList<>(areaNode.subAreas);
520
        Collections.sort(subAreas, areaNodeComparator);
521
        boolean isFirst = true;
522
        for (AreaNode subAreaNode: subAreas){
523
            if (!isFirst){
524
                result.addSeparatorTaggedText(" ", isBold);
525
            }
526
            handleSubAreaNode(languages, result, areaToStatusMap, areaNodeComparator,
527
                    subAreaNode, config, parentLabel, level, isBold, statusHandledByParent, isIntroduced);
528
            isFirst = false;
529
        }
530
    }
531

    
532
    private String getListValue(List<String> list, int i, String defaultStr) {
533
        if (i < list.size()){
534
            return list.get(i);
535
        }
536
        return defaultStr;
537
    }
538

    
539
    private void handleSubAreaNode(List<Language> languages, CondensedDistribution result,
540
            Map<NamedArea, PresenceAbsenceTerm> areaToStatusMap, AreaNodeComparator areaNodeComparator,
541
            AreaNode areaNode, CondensedDistributionConfiguration config, String parentAreaLabel, int level,
542
            boolean parentIsBold, boolean statusHandledByParent, boolean isIntroduced) {
543

    
544
        level++;
545
        NamedArea area = areaNode.area;
546

    
547
        TripleResult<String, Boolean, Boolean> statusSymbol = statusHandledByParent?
548
                new TripleResult<>("", parentIsBold, statusHandledByParent):
549
                statusSymbolForArea(areaNode, areaToStatusMap, config, languages, isIntroduced);
550

    
551
        String areaLabel = makeAreaLabel(languages, area, config, parentAreaLabel);
552
        result.addStatusAndAreaTaggedText(statusSymbol.getFirstResult(), areaLabel, statusSymbol.getSecondResult() || config.areasBold);
553

    
554
        boolean isBold = statusSymbol.getSecondResult();
555
        boolean isHandledByParent = statusSymbol.getThirdResult();
556
        handleSubAreas(result, areaNode, config, areaNodeComparator, languages, areaToStatusMap, areaLabel, level,
557
                isBold, isHandledByParent, isIntroduced);
558
    }
559
}
(1-1/11)