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
|
}
|