From 2fd873741b7499170e008751cb2b409ae0fb8c92 Mon Sep 17 00:00:00 2001 From: Andreas Isler Date: Fri, 12 Jun 2026 10:58:35 +0200 Subject: [PATCH] removed warnings --- .../com/customer/work/service/JsonUtils.java | 6 +- .../com/customer/work/model/EmailDto.java | 45 +------- .../work/model/FlowableExcelMapper.java | 37 +++---- .../work/model/FlowableJsonParser.java | 86 +++++++++------- .../work/model/FlowableModelTestUtils.java | 97 +++++++++--------- .../src/test/resources/application.properties | 2 +- .../test-auto-deploy-apps/trackVarsApp.zip | Bin 0 -> 11660 bytes 7 files changed, 115 insertions(+), 158 deletions(-) create mode 100644 customer-work/src/test/resources/test-auto-deploy-apps/trackVarsApp.zip diff --git a/customer-work/src/main/java/com/customer/work/service/JsonUtils.java b/customer-work/src/main/java/com/customer/work/service/JsonUtils.java index c1b88e3..66319fb 100644 --- a/customer-work/src/main/java/com/customer/work/service/JsonUtils.java +++ b/customer-work/src/main/java/com/customer/work/service/JsonUtils.java @@ -44,7 +44,7 @@ public class JsonUtils { } public Map convertJsonNodeToMap(JsonNode jsonNode) { - return objectMapper.convertValue(jsonNode, new TypeReference>() {}); + return objectMapper.convertValue(jsonNode, new TypeReference<>() {}); } public Map flatten(Object payload) { @@ -57,7 +57,7 @@ public class JsonUtils { public Map unflatten(Map flatVars) { try { - return objectMapper.readValue(JsonUnflattener.unflatten(flatVars), Map.class); + return objectMapper.readValue(JsonUnflattener.unflatten(flatVars), new TypeReference<>() {}); } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -73,7 +73,7 @@ public class JsonUtils { } public Map convertObjectNodeToMap(ObjectNode objectNode) { - return objectMapper.convertValue(objectNode, new TypeReference>() {}); + return objectMapper.convertValue(objectNode, new TypeReference<>() {}); } public ObjectNode getEmptyObjectNode() { diff --git a/customer-work/src/test/java/com/customer/work/model/EmailDto.java b/customer-work/src/test/java/com/customer/work/model/EmailDto.java index 9261cb2..05060a5 100644 --- a/customer-work/src/test/java/com/customer/work/model/EmailDto.java +++ b/customer-work/src/test/java/com/customer/work/model/EmailDto.java @@ -2,48 +2,5 @@ package com.customer.work.model; import java.util.List; -public class EmailDto { - private String subject; - private List receiverList; - private String content; - private Object contentRaw; - - public EmailDto(String subject, List receiverList, String content, Object contentRaw) { - this.subject = subject; - this.receiverList = receiverList; - this.content = content; - this.contentRaw = contentRaw; - } - - public String getSubject() { - return subject; - } - - public void setSubject(String subject) { - this.subject = subject; - } - - public List getReceiverList() { - return receiverList; - } - - public void setReceiverList(List receiverList) { - this.receiverList = receiverList; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public Object getContentRaw() { - return contentRaw; - } - - public void setContentRaw(Object contentRaw) { - this.contentRaw = contentRaw; - } +public record EmailDto(String subject, List receiverList, String content, Object contentRaw) { } diff --git a/customer-work/src/test/java/com/customer/work/model/FlowableExcelMapper.java b/customer-work/src/test/java/com/customer/work/model/FlowableExcelMapper.java index 256f748..bb242b3 100644 --- a/customer-work/src/test/java/com/customer/work/model/FlowableExcelMapper.java +++ b/customer-work/src/test/java/com/customer/work/model/FlowableExcelMapper.java @@ -1,6 +1,5 @@ package com.customer.work.model; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -95,25 +94,23 @@ public class FlowableExcelMapper { } protected Object parseCellValue(String type, String cellValue) { - switch (cellValue) { - case "__NULL": return null; - case "__N_A": return "__N_A"; - } - switch (type) { - case "String": - switch (cellValue) { - case "__EMPTY": return ""; - case "__BLANK": return " "; - default: return cellValue; - } - case "Boolean": return Boolean.parseBoolean(cellValue); - case "Integer": return Integer.parseInt(cellValue); - case "Long": return Long.parseLong(cellValue); - case "Double": return Double.parseDouble(cellValue); - case "Date": return parseDate(cellValue); - default: - throw new IllegalArgumentException("Variable type not supported: " + type); - } + return switch (cellValue) { + case "__NULL" -> null; + case "__N_A" -> "__N_A"; + default -> switch (type) { + case "String" -> switch (cellValue) { + case "__EMPTY" -> ""; + case "__BLANK" -> " "; + default -> cellValue; + }; + case "Boolean" -> Boolean.parseBoolean(cellValue); + case "Integer" -> Integer.parseInt(cellValue); + case "Long" -> Long.parseLong(cellValue); + case "Double" -> Double.parseDouble(cellValue); + case "Date" -> parseDate(cellValue); + default -> throw new IllegalArgumentException("Variable type not supported: " + type); + }; + }; } protected Instant parseDate(String cellValue) { diff --git a/customer-work/src/test/java/com/customer/work/model/FlowableJsonParser.java b/customer-work/src/test/java/com/customer/work/model/FlowableJsonParser.java index b101726..f026936 100644 --- a/customer-work/src/test/java/com/customer/work/model/FlowableJsonParser.java +++ b/customer-work/src/test/java/com/customer/work/model/FlowableJsonParser.java @@ -1,6 +1,5 @@ package com.customer.work.model; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -9,16 +8,18 @@ import org.springframework.stereotype.Component; import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +/** + * Converts plain Java test data (maps, lists, scalars) into the variable types + * used by Flowable models. Maps and lists become JsonNodes by default; an inner + * map of the form {"__TYPE": "...", "__VALUE": ...} requests an explicit type. + */ @Component public class FlowableJsonParser { - private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final JavaTimeModule javaTimeModule = new JavaTimeModule(); - public static final String BOOLEAN = "Boolean"; public static final String STRING = "String"; public static final String INTEGER = "Integer"; @@ -30,28 +31,36 @@ public class FlowableJsonParser { public static final String MAP = "Map"; public static final String ARRAY_LIST = "ArrayList"; + protected final ObjectMapper objectMapper = new ObjectMapper(); public FlowableJsonParser() { - objectMapper.registerModule(javaTimeModule); + // Enable ObjectMapper for handling Instant as string + objectMapper.registerModule(new JavaTimeModule()); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); } - public Map parseMap(Object map) throws JsonProcessingException, ClassCastException { - return (Map) parseObject(map); + public Map parseMap(Object map) { + Object parsed = parseObject(map); + if (!(parsed instanceof Map)) { + throw new IllegalArgumentException("Not a map: " + map); + } + @SuppressWarnings("unchecked") + Map result = (Map) parsed; + return result; } - public Object parseObject(Object object) throws JsonProcessingException, ClassCastException { - if (object instanceof Map) { - Map resultMap = new LinkedHashMap<>(Map.of()); - for (Map.Entry mapEntry : ((Map) object).entrySet()) { - String key = mapEntry.getKey(); + public Object parseObject(Object object) { + if (object instanceof Map map) { + Map resultMap = new LinkedHashMap<>(); + for (Map.Entry mapEntry : map.entrySet()) { + String key = String.valueOf(mapEntry.getKey()); resultMap.put(key, parseMapEntry(key, mapEntry.getValue())); } return resultMap; } - if (object instanceof ArrayList) { - ArrayList resultList = new ArrayList<>(); - for (Object arrayEntry : (ArrayList) object) { + if (object instanceof List list) { + List resultList = new ArrayList<>(); + for (Object arrayEntry : list) { resultList.add(parseArrayEntry(arrayEntry)); } return resultList; @@ -59,52 +68,51 @@ public class FlowableJsonParser { return null; } - private Object parseMapEntry(String key, Object value) throws JsonProcessingException { - if (value instanceof Map) { - Map valueMap = (Map) value; - if (valueMap.containsKey("__TYPE") && valueMap.containsKey("__VALUE") && valueMap.size() == 2) { + private Object parseMapEntry(String key, Object value) { + if (value instanceof Map valueMap) { + if (valueMap.size() == 2 && valueMap.containsKey("__TYPE") && valueMap.containsKey("__VALUE")) { // if Map in Map, check if inner Map has explicit type (__TYPE and __VALUE) - return convertObjectExplicit(valueMap.get("__VALUE"), (String) valueMap.get("__TYPE")); + return convertObjectExplicit(valueMap.get("__VALUE"), String.valueOf(valueMap.get("__TYPE"))); } - if (Arrays.asList("__IN", "__OUT").contains(key)) { + if (FlowableModelTestUtils.IN_PARAM.equals(key) || FlowableModelTestUtils.OUT_PARAM.equals(key)) { // convert __IN and __OUT maps as Map return parseObject(value); } // convert Map to JsonObjectNode (default in models) return convertObjectExplicit(value, JSON_OBJECT_NODE); } - if (value instanceof ArrayList) { - // convert ArrayList to JsonArrayNode (default in models) + if (value instanceof List) { + // convert List to JsonArrayNode (default in models) return convertObjectExplicit(value, JSON_ARRAY_NODE); } // take json supported type (Boolean, String, Integer, Double) return value; } - private Object parseArrayEntry(Object value) throws JsonProcessingException { + private Object parseArrayEntry(Object value) { if (value instanceof Map) { // convert Map to JsonObjectNode (default in models) return convertObjectExplicit(value, JSON_OBJECT_NODE); } - if (value instanceof ArrayList) { - // convert ArrayList to JsonArrayNode (default in models) + if (value instanceof List) { + // convert List to JsonArrayNode (default in models) return convertObjectExplicit(value, JSON_ARRAY_NODE); } // take json supported type (Boolean, String, Integer, Double) return value; } - private Object convertObjectExplicit(Object value, String explicitType) throws JsonProcessingException { - switch (explicitType) { - case BOOLEAN: return Boolean.parseBoolean(value.toString()); - case STRING: return value.toString(); - case INTEGER: return Integer.parseInt(value.toString()); - case DOUBLE: return Double.parseDouble(value.toString()); - case LONG: return Long.parseLong(value.toString()); - case INSTANT: return Instant.parse(value.toString()); - case MAP: case ARRAY_LIST: return parseObject(value); - case JSON_OBJECT_NODE: case JSON_ARRAY_NODE: return objectMapper.convertValue(parseObject(value), JsonNode.class); - default: return value; - } + private Object convertObjectExplicit(Object value, String explicitType) { + return switch (explicitType) { + case BOOLEAN -> Boolean.parseBoolean(value.toString()); + case STRING -> value.toString(); + case INTEGER -> Integer.parseInt(value.toString()); + case DOUBLE -> Double.parseDouble(value.toString()); + case LONG -> Long.parseLong(value.toString()); + case INSTANT -> Instant.parse(value.toString()); + case MAP, ARRAY_LIST -> parseObject(value); + case JSON_OBJECT_NODE, JSON_ARRAY_NODE -> objectMapper.convertValue(parseObject(value), JsonNode.class); + default -> value; + }; } } diff --git a/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java b/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java index 8f90ef2..c395ad1 100644 --- a/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java +++ b/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java @@ -122,8 +122,8 @@ public class FlowableModelTestUtils { Assertions.assertThat(emails.size()).as("%s: invalid email number %s", hint, emailNumber).isGreaterThanOrEqualTo(emailNumber); EmailDto email = emails.get(emailNumber - 1); - Assertions.assertThat(email.getSubject()).as("%s: invalid subject %s", hint, email.getSubject()).endsWith(subject); - Assertions.assertThat(email.getReceiverList()).as("%s: invalid email receivers", hint) + Assertions.assertThat(email.subject()).as("%s: invalid subject %s", hint, email.subject()).endsWith(subject); + Assertions.assertThat(email.receiverList()).as("%s: invalid email receivers", hint) .containsExactlyInAnyOrder(receivers.split("[,\\s]+")); } @@ -227,14 +227,11 @@ public class FlowableModelTestUtils { return true; } // If test is an object node, check if all fields in test exist in root - if (test.isObject()){ - for (Iterator iterator = test.fieldNames(); iterator.hasNext(); ) { - String fieldName = iterator.next(); - JsonNode testValue = test.get(fieldName); - JsonNode rootValue = null; - if (root != null) { - rootValue = root.get(fieldName); - } + if (test.isObject()) { + for (Map.Entry testField : test.properties()) { + String fieldName = testField.getKey(); + JsonNode testValue = testField.getValue(); + JsonNode rootValue = root != null ? root.get(fieldName) : null; if (testValue.isTextual() && "__N_A".equals(testValue.asText())) { if (root != null && root.has(fieldName)) { return false; @@ -276,12 +273,8 @@ public class FlowableModelTestUtils { ObjectNode vars = jsonUtils.getEmptyObjectNode(); JsonNode rootParam = row.get("root"); if (rootParam != null) { - Iterator> rootVars = rootParam.fields(); - while (rootVars.hasNext()) { - Map.Entry rootVar = rootVars.next(); - String key = rootVar.getKey(); - JsonNode value = rootVar.getValue(); - vars.set(key, value); + for (Map.Entry rootVar : rootParam.properties()) { + vars.set(rootVar.getKey(), rootVar.getValue()); } } JsonNode inParam = row.get("in"); @@ -335,20 +328,20 @@ public class FlowableModelTestUtils { public Map testObjExcelRow(String path, Map row) { Map vars = new LinkedHashMap<>(); - Map rootParam = (Map) row.get("root"); + Map rootParam = getMapParam(row, "root", path); if (rootParam != null) { vars.putAll(rootParam); } - Map inParam = (Map) row.get("in"); + Map inParam = getMapParam(row, "in", path); if (inParam != null) { vars.put(IN_PARAM, inParam); } - Map outParam = (Map) row.get("out"); + Map outParam = getMapParam(row, "out", path); if (outParam != null) { vars.put(OUT_PARAM, outParam); } String idParam = (String) row.get("id"); - Map test =(Map) row.get("test"); + Map test = getMapParam(row, "test", path); int timerCount = 0; Object timerNode = row.get("timer"); if (timerNode != null) timerCount = (Integer) timerNode; @@ -388,6 +381,16 @@ public class FlowableModelTestUtils { return result; } + @SuppressWarnings("unchecked") + protected Map getMapParam(Map row, String key, String path) { + Object value = row.get(key); + if (value == null) { + return null; + } + Assertions.assertThat(value).as("Parameter '%s' in %s must be an object", key, path).isInstanceOf(Map.class); + return (Map) value; + } + public void exportApp(String rootUrl, String workspaceKey, String appModelKey, String username, String password) { RestClient restClient = RestClient.builder() .baseUrl(rootUrl) @@ -416,7 +419,9 @@ public class FlowableModelTestUtils { public ObjectNode loadObjectNodeFromResources(String path) { if (path == null || path.isEmpty()) return jsonUtils.getEmptyObjectNode(); try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path)) { - Assertions.assertThat(inputStream).as("Resource not found: %s", path).isNotNull(); + if (inputStream == null) { + throw new IllegalArgumentException("Resource not found: " + path); + } JsonNode jsonNode = jsonUtils.convertJsonStringToJsonNode(new String(inputStream.readAllBytes(), StandardCharsets.UTF_8)); Assertions.assertThat(jsonNode).as("Resource %s is not a JSON object", path).isInstanceOf(ObjectNode.class); return (ObjectNode) jsonNode; @@ -433,27 +438,21 @@ public class FlowableModelTestUtils { public Map loadTestVarsFromResources(String path) { ObjectNode node = loadObjectNodeFromResources(path); Map vars = new LinkedHashMap<>(); - for (Iterator> fields = node.fields(); fields.hasNext(); ) { - Map.Entry field = fields.next(); + for (Map.Entry field : node.properties()) { String key = field.getKey(); switch (key) { - case ROOT_PARAM: + case ROOT_PARAM -> { Assertions.assertThat(field.getValue().isObject()) .as("%s in %s must be a JSON object", ROOT_PARAM, path).isTrue(); - for (Iterator rootKeys = field.getValue().fieldNames(); rootKeys.hasNext(); ) { - String rootKey = rootKeys.next(); - Assertions.assertThat(rootKey.startsWith("__")) - .as("Root parameter '%s' in %s must not start with '__'", rootKey, path).isFalse(); + for (Map.Entry rootVar : field.getValue().properties()) { + Assertions.assertThat(rootVar.getKey().startsWith("__")) + .as("Root parameter '%s' in %s must not start with '__'", rootVar.getKey(), path).isFalse(); } vars.putAll(jsonUtils.convertJsonNodeToMap(field.getValue())); - break; - case IN_PARAM: - case OUT_PARAM: - vars.put(key, jsonUtils.convertJsonNodeToMap(field.getValue())); - break; - default: - throw new IllegalArgumentException("Implicit parameter '" + key + "' in " + path - + ": parameters must be declared explicitly inside " + ROOT_PARAM + ", " + IN_PARAM + " or " + OUT_PARAM); + } + case IN_PARAM, OUT_PARAM -> vars.put(key, jsonUtils.convertJsonNodeToMap(field.getValue())); + default -> throw new IllegalArgumentException("Implicit parameter '" + key + "' in " + path + + ": parameters must be declared explicitly inside " + ROOT_PARAM + ", " + IN_PARAM + " or " + OUT_PARAM); } } return vars; @@ -505,8 +504,8 @@ public class FlowableModelTestUtils { public void completeTask(String taskId, Map completionVariables, String outcome) { CompleteFormRepresentation form = new CompleteFormRepresentation(); - for (String key : completionVariables.keySet()) { - form.setValues(key, completionVariables.get(key)); + for (Map.Entry entry : completionVariables.entrySet()) { + form.setValues(entry.getKey(), entry.getValue()); } form.setOutcome(outcome); platformTaskService.completeTaskForm(taskId, form); @@ -526,8 +525,8 @@ public class FlowableModelTestUtils { return convertHistVariableListToMap(historicVariableInstanceList); } - private HashMap convertHistVariableListToMap(List historicVariableInstanceList) { - HashMap variableMap = new HashMap<>(); + private Map convertHistVariableListToMap(List historicVariableInstanceList) { + Map variableMap = new HashMap<>(); for (HistoricVariableInstance historicVariableInstance : historicVariableInstanceList) { variableMap.put(historicVariableInstance.getVariableName(), historicVariableInstance.getValue()); } @@ -584,11 +583,9 @@ public class FlowableModelTestUtils { CallActivity callActivity = new CallActivity(); callActivity.setId(testKey); callActivity.setCalledElement(testKey); - JsonNode inNode = variables.get(IN_PARAM); - if (inNode instanceof ObjectNode) { - Map inMap = jsonUtils.convertObjectNodeToMap((ObjectNode) inNode); - ArrayList inParameters = new ArrayList<>(); - for (Map.Entry entry : inMap.entrySet()) { + if (variables.get(IN_PARAM) instanceof ObjectNode inNode) { + List inParameters = new ArrayList<>(); + for (Map.Entry entry : inNode.properties()) { IOParameter ioParameter = new IOParameter(); ioParameter.setSourceExpression("${" + IN_PARAM + "." + entry.getKey() + "}"); ioParameter.setTarget(entry.getKey()); @@ -596,14 +593,12 @@ public class FlowableModelTestUtils { } callActivity.setInParameters(inParameters); } - JsonNode outNode = variables.get(OUT_PARAM); - if (outNode instanceof ObjectNode) { - Map outMap = (Map) jsonUtils.convertJsonNodeToMap(variables).get(OUT_PARAM); - ArrayList outParameters = new ArrayList<>(); - for (Map.Entry entry : outMap.entrySet()) { + if (variables.get(OUT_PARAM) instanceof ObjectNode outNode) { + List outParameters = new ArrayList<>(); + for (Map.Entry entry : outNode.properties()) { IOParameter ioParameter = new IOParameter(); ioParameter.setSource(entry.getKey()); - ioParameter.setTarget(entry.getValue()); + ioParameter.setTarget(entry.getValue().asText()); outParameters.add(ioParameter); } callActivity.setOutParameters(outParameters); diff --git a/customer-work/src/test/resources/application.properties b/customer-work/src/test/resources/application.properties index 9fd4faf..50bda6c 100644 --- a/customer-work/src/test/resources/application.properties +++ b/customer-work/src/test/resources/application.properties @@ -7,7 +7,7 @@ flowable.async-executor-activate=false flowable.async-history-executor-activate=false flowable.indexing.enabled=false management.health.elasticsearch.enabled=false -management.metrics.export.elastic.enabled=false +management.elastic.metrics.export.enabled=false # Set debug level in tests logging.level.com.flowable=INFO diff --git a/customer-work/src/test/resources/test-auto-deploy-apps/trackVarsApp.zip b/customer-work/src/test/resources/test-auto-deploy-apps/trackVarsApp.zip new file mode 100644 index 0000000000000000000000000000000000000000..21cb2acd1414692fa5c4021ea1a0e6a8e035ee13 GIT binary patch literal 11660 zcmb7~1yEeew)b&&cP9+)7BqtdcXxM(;1b;3-2wy;?iyT!ySrO(d*s~vos)CkcW>2O zQ#Ca`Q@v*I-rfIR{aZ^;5*z{p1O^5Mq=>&o3*r~ZYRrocPD6F99!S~L*86w1M5)5i z`2&!-N-M!V=f`bg0f~IVbw<~nmm9j3B!KCx+8VM3f9B8}EG%d4X!U^F%?h)Tea(t! z7L*($?PEmHykG^C?h{|Am337O>*YkkD~&V|dxX*QodQrYS6>ay(Qu5o9Qy(43Zw-H zZ&)ie=L}cl6}m)=y5d!B$afE(GDq8i2zFspZ0=+E536C^3q93d^Fk%932P#0r*%n81O%w=wYo z19@xzr%^-yebj{X9gSHS46UtgM%AosH@Q$=+B?XS&&+ieR}~=2Za!zJ(5xH|RmfN<>728VFzCDBwfC5Vp9 ziM76i{w&k?+5A!;V}m_c_%m8Vr{85&8zvcH^XZ0Nqru>Am@$5G5tma@cyB(w>r45W zhs){ak=2Xd9>;fbD)yG|bu93_<1Vi{yu87h86?4S3@C_I^&0)hpLGnh(ky&gkm3U@ zr0)r1+j9q}j>GFCyW5>>%+05kk~g3ot8-7ZE_s&F084roA!s9t7|v+p(m~W=RVX>d z${FCp&N3lf&S5+rmtEDO3I?%eR^bSNEoXo&#TbZsk*=kA9f@Mf-lPOgyIj=Z63`eu zTrG5JH>Gz4^Kyu&)1*K!0dP#;8UfaNSvSlT&L~z)S=0%|h!A5?k(#<_@A8euD5q=}BZ_dO7KNb?; zm@rIdP}G+@UvEQ`}#T}0dE7q00Q zrxa~FvP73VrQD?6j^Oln&9jy3VH{gAT``|q;4CbXWIwPweF9w$$HW=T^G4;8zn%*& ziU>@(qTFmr)IIyLDRI+5B;~VlJztS7$b?tA6i+d+D>{VFk6=tLBuK?0%?h@H6b9#| zgD!4WQHvq*vLNRhb0nsGgQw&GvvY`+hPt+MdRAsjSX0xPNy(w`n#kX!#I!jENq=DQ zNY_K7eZnWdMbaP|P;K@CmlGdDNj<{H>u}^iKI_h_oOhK|5>>2KlJZz<%$_1g(9t;) zH+p&Stp`J%bnlD^-H9v#hi z3Sfo?9H3|@p6Q9QO2+VhwxSw?mTqZ3rsu!SdUQMHXq-1HzJ`i0lmIRX+*rYj2^&hq z{qRoF)`@Wkl()(HWB&_h2+!dsgR+$Nu+58F&J8DusN9d-;^9<*}aL*ji+Kl9i- zJ>L6yILpADkJwMBW1nXZK7O?#P_y)C28-*!v;h_igJ2mu3D#C9r-+y{D4&G7Rorgq zX=Z-N*2cWd^*(_!b2`J{vF;M2ANJs^pmrfikPg&7m}Q+i?1n=}L6lqK1I&TBR)$}7 zB04FOjB!o>$0STZPq)1pd{EvDxq&Ac>RNrs2-c!wpaxt$hK;y?DAy#IpdX8QA6(N^ z0-jBisMsl~DA=hnE!aG==p46giN|`U6Q8n3hk~#PTaZ$+C@g>}S+B6O`Mq|WAAMv4 z8UtXsg89n@xrDMo`7Wdcx01OL**px1#GWd(kst&omU$_-cy9hD8iIpw&M@xZLy>mv zosufxSr5BH;a_c=u!Im@eVWt&qA&lH#cXNQ_sJd-zU^sx@ z^rC%EItghd8J33JS>ODjGVohyE8&32pUOxkGDSP@_5@K-4=c)Ycls$m(d>w?DWfMg zv6t}yANK&tZXQMR=>CjrV{R^^TNE^zjhmR7G9N0Cb4 z&zx;qy%X7#+GQVLi4$#W+(QdUV_{^gt@1Mz1$Dn%rWxuf@bIH zB_B0}^Us{54Q%K^sU(X7r)nIp@io@f=!866H;bp<)k2#-9BIGwAx$T1M3FX_u1+CpzxxicOJ`^_zwVbx-W-pyyEG*sru&g`rC_IU;EGj(NIPa)m>7rFvT0E4HSP6R= zAGu@vwPrr0HZNVh?b4vI|832n{d1@OW67MTTiPyhU_7tt=E8zkgVtSN3UOFrr*1ic zZq}w`+6cmQnru+h;-CdQ zS6C>0Qub~4*WMm)K777wHYl}quYAvtK}Iak5ds+bPNn@x#{9=@?X|mhZ@4;UFl!{l zkBNux8<{4DqAN^4lQ=wG*c+9}vu}cOGS(K+Yziyz7j3^U#j*y&%YER_?R3%GWWPSg z^SseJOv4GNsZqNd6nj|`F;?7?Rpkt=JW9B3IV|EYFch~Z`M}!In6#f+B8M_Y(gqGM z2DVN>&+A|g-RYlD9Xl8^gcppA35}u_x>nq#8aBCs1=*yX7}V)@2LhZl#Ztad|5ovS zS2R#FaUi3!uJ;%`Oy~y9emK)kY;;l%`kS~NXdt-?bl}Dk3L|)2fwQb0ATD&u?F`f> zM0-E9^&4^%vtA!0C!V+sFKJ*prhYP*jD2IH=nqE$kZ5Qug)Fe>PA&v^2u_R#t!;2* zh8$V(%b+$%-;|-Q$$U%>J@tz&xKRkTz#IX+%Bz+BmzkZrOY&uiGT^@V!}O8ajwif9 z=>0~jY9a6S5X~rGIEBSPF!!c22@LTQNCU3L`>VO*QGRz8e*bWGQ5=6HgAJCi?Yv%K z7PeL_po?9&`{&|qxpt(x0&ZeX&IfX}KqnsYK)dg3{ejuXVjZ34s`NNx)#9CF2*!nJ z>EJ$4ve2x`p$saTjVHk&;@R9GV*VMS$-o|fFV^!ie)r@!xv>&|H63lkh^(q{rfTA+ zW}2h25;7RnLzIsS-oe;+^00f%(H3`K!wS@0_c~1RvlCp504Sh3K}Z-*gt1-~DBf(% zBu>z@LCcQkWP0g5$7XIV33mL&5hs9}NiEGawmB8&ch#$$9YupGF)AYxWl?E+t zqqU2GMk9-(?AdttVj8Czjte=!2#OaJ2vAGqt*GMCAe^ZHp%)Kfp&Je5dFG}7!!EK| zY3*y%eK)m4p)KT@vd&|^hMwplhtq-AH&S-WziS68GjdIRF37*2)G%l} zWrt&=y~)e&3@nC;q|g?-gQ|FFVwDRQNo@7BK2C)|EK?-TCIqj9S8LCXV1(Fv67 zG-PpNN7ezJbO9wLkmMmIE{p2^D2}acn>)=Pq|kfQT){52i*NH-gtb{f_~p`hQDCFs z49ys6!fNcxlv({9;R;@p)2gy{jk5KiPf7Jb3HxwG+NT`fGtFbYb=Up3EDndc{Nq^QqY5E2S@=UrYHJ{Ist!3|V@3Z)gomW{}E zFIVY|USMj6^&VlpV*t&{m0B!8&%-Uu3~{1_oMV z$?J1wqht0ncuGk2x+f@Em8x5F;HlJ@a9pcgxiJ(x4}V5i;O-KsQnE5($k|+rAL`6B zmO2j>Cw@Gx&E?9N9$OUN>=Ri}Fh;DK`rh2g?ND_EB1CC{m%oJDWk|=+rycn^uu=t| zjAkd_4#oh=YC+XGUxu+eBB}0g{W(^aTd&5>rS>Oq(6k~LG(6A|Y2C2kyNG>-!x*=B zMBEVbTfk_JaYLw^o3S}{V4fJy+P~tk?6AjUy}DZTDt!73`Ad$=8)E!rAwfXgi2j=# zBfiP8lA@BX5EIj%N2cga4wM(~4zkFHL}}n>NQjL3P8BNpmeM#L<@cE}oXqP>Jy8Z% zvn8)>hI^yFomFk#^R`ATS0f{=TNeRc{azS&ni+Q8`#+p)tfo4rO+j-@bMF^gY@J4< z^O5~Q7-1>F5*k6V5|BshQ0&*Lom*BA!&Nxx)YWJhMm1`M({RuI+8=5EhWy0vee{A&2Ip$YU^4^&RUNN0E1pM2;NA*HVK72jRj$V(1f*zZz^7iwlU7Vu7(aCiX>9Rw5GH zFAuDy<|79&$DsK`2u-DXgjZQ}O8fg|UoM=ZZFk`5%5@_~_x9l+NRbDhlrjkVQRWDK z*_KzwHjtkAC>H?&%Ny(oA>%DO|8CbfB%51CNlsmDoHUOWYhA9lACT0I6A+8>-YtiP zL?{fAa*lvO9c=}j07_R5KvhF;ahBVV;({-1v$Rf*#ZXBOlS?9011@# z&!bnT{su+mvoW!rT)8!v>2iyCW;lQXz1AKHHT$Way~K+zjMEjEnkg+)jL0*E-Iv2` z0bZ(+{tSlI5!rA&Es9`!U=H{PRef37Jje{tw*!;h!<8_k zLkU?T?oO_b421xbOFM}s8B%y&zCiJJbf7=Zl2mB+wyGh2iG8MMB|)~NaQ^O-exuqc z&2F!|Zds$~47yhkX>M;OoCQrUM`$!AC3Z?V1zBZ02xXTO`~Z2j2s45(@3#^M`gIFA z+DzJc#P_8Ymc8uF-4f1>PT z?tya)XVg-yhfTjtbLLW#IYE#~&B3Y@;hYUmTm8~$-4fvfC}hd5xa2ly8;Cymk+9Ma z&y4FjrwnzPDC%O>7BR`WSv7kr9>anutC??`i(?)JJ3r(2-fd5!*$;`-lsZm~7TYa; zQev}RL&r=`s&mwKuzzlBSJtq8^r{&Gs5^6!X6`1lAx5L+;9G3VYU}{1g;(h}+?6ql+?hjD$2_d$ypO^P41}~FTL)|UKard0TZ`e>S0~ESvRh0j&$Nl-2Vr1EtDpU*@$*84!~D@@I}dv} z3_V&_0Iy!AZ=B_8zl7aBb*kHz8|zuGMCrg;p2mxn@{YYLGs;Q0X3Ju!aj5~As`~Oa zNG*p#t^}bzkt25{ANyfbip8uby>cLbx{sMEV489r(?K>U9=SxWgUw|9tM%aH&-~3G zOH;);=Dtr^IA!dj>k9bRp#)Sz1b$NlsnoR>psUrCgAvf{E@W-K31uFk)u8d}`SI7M zIy{|;k)YRE3tOdY%(=W3e7HHcL}ieNOlYs!kDV0o{DSnFu@&{QX`AJewR>68eMaDZ zJy@G;6>{3Rj+u&=r)|+js-I)l(FK1**9{iPt&p*;D-h_ba4ZV&u5Efwr$9I9Q<{<$ zz)(9&iPCY!-hc*8>y0|9dN!Rz=PeK3=HIPa-Y?u;*_wHh=zfSOqV+*II)i3LUVR8Z zvwS(&B%XQpcX_?cc|O($7q0sn#hwx(PFg$u}P~J z7Vtw6Zy#$tVZ^y@5&Y%IG^w-#kHz2B z&OyQ>RbYxiK^Eg$bmWh#v<&cK^^9y}0e|cs8|q#Bfb*~4B}BYXc?`N>D10&CVYcG4 zHa99{oq?DgE-<=z&}`L3gHhWSoj|NEnC~0zwJbLhSjKl3Y}z57F;*uecWob2da5{P6GLQ&#P}F-sv4$(R@_Q7anFgqteu1~Ykz=pFM@$pLl&7V!^zBORA`)p zG;og!d@8l1g+-1xH`ba7oNv+AFFKem-VBhk$ZEBC>{RhwW zf!K?ee|s!?Z)@{Eugm`zmdG(NaWELzS=-F~o8+$Q#)hBLZbJg8!Bkd&vWbff@8?TA zN--VcL0b{U88~bk7GJ&VuXZ8g9BOA)yvVassjhfgiFlj@tdc9LgJSg7j*3OKRWZ-4 z*1A?(i@dY6<`xfwU)1M(ac`K6WrX!ni4Kb4CH$ibtvS-0J2K{;?J8nHtWLJ}Pjy&7 z3C+>h6t8&UA=u6U3Tr62up7)nm=xy_PcBZKtr+z}E#}EF+&GNn$A;xYuSTvp_Xh*n z$kb7oWxFONdGBqZiH8mC;(M5>`WcY%5x(Tma(~^_Zp!&7jfBE`i^LjgRRVMYT_&?_ z2Rd?zQqDt>qUfBI0Jb(afgS{;&3d+i8wC4&8ep(7+GKQ)K*&a71sIRRPgY7$_F16^ zq~`33oaN2xbM(rgjZR?OU(c$+c~Z z!}{Y!vJ%BZj!kz!1BFxXd|AQ_lpy0bu#Pb!9|D4CXDtSKcsLBSH%QYYh`{RarzYJMNZYscuADZ7t3oU5kJUM{aK?6{krhL$WoPw)y4+paxm zTaVX(=m&qkYu}VsQ%n+#N!!sf*yODN6uY~ z#@Ec@up*hh$zI4KUrlSSZ$S}=U!}W@nZcEX9h?}W%EXXGsvPz$2Na^BL(s;mE(47chj1o`@@Cza#rXO*W`O3iToAq~roiWf#*Sqvg*}S4p1nD{(F|eQ(2wJ{Aa`-C(LG&JUx`Hv zumyI!wu6T)5h|`a7e`S&vZRH!#|C=KUDj`YDF$tvStw; z%~P}?Xn$#n)9^_>m#3^_N!B7qwbCjAw16Wik6*^qJN#g%Z_Zn7Gi5msbEKx~{D?wMd7 zlQ%qpM35scWDUK12oX-83eQP#(!BoY!DmFzN8Ij@v$w!A z|5--9y3__yaE3_sw1Qd2syY&aQ>z~g=gd5ZP5cX4JjoAijwMkx#JHJL$>ENEttyGF zvyTIoiJI~A6255*I9wlej^;O816xfJTTPl;gOn+4yB_*3InpX)8zvsG^P{aJ;wE>K zdSi*S-`y~xJhOVsiof!nydwSzR!-(+PZ!?=(H`@^iQ?Ojf29WH9Bd7Z9UcD=#}f_9 zw=~x;dKMe0au}gR0=q%2H%;x-ps-Yo_5SeGOsOdpR8}Z1bTnUM)JJzI)E`|;&&=GE zvK@6T_>B8@UYtOK7z_PlY8MFkxOJ(!ydvhlMpopqyk;9=2KM1$@Rnua71e6)UQJm1oND<^cAkl8;R2An&9oDtjd1=RbGy_exnllb;vz>&!Za>vt9vL3Tp=G6+pNu0u3K1aD2F<#tWm zkU}qH&f4mZH5d6$F-pm6Lx_T=cs?(hC#vu&QkQ{%)o-hb57Q+yT6`$Qh&U9it3a|ba?h!EiLw?^O@-unL3OJ|;-Y(ie2lrcy0;-(hS3i}>t9ITQ1rct@QM*IgQS4hw7(}oEN%TnJx)Gi5%pD`J@ zc7@bHvnWkJ0<8$uE}r+(U))ypvF}`uQR^etvH=DuJiv5D922)!p+s64Ov;%|`eU~E zrbC^(RmNm}v6C6&m+^=B)ohL5g43^k&UU9usjGbleYu!K*k zYPs8%SC3e9-xH35jomQy9Y6AN5^PHaf zmh-sD2|;6<(v+@GF_6^*d2JQt7U_1*FbcS=ZO<%W@0mx?e+Lw%3Y(rq7ZKHs&?hG5 zc;{xwf*ySqoJlS84V4GFx4q54^6kCR0Qrn*!Otidy$$!%ru%ks#U5}V$Vr^~sS^o> z5oV^xM`AY@TWSo|2@sCNbG<%_0|xy)TM3n?K!1>EBfc#EYvSH?97e)%c5FrGaUf}F z6a$$2Dhm&zBfF*OJjco2NB+44(J`x+3n`g~&D9V?vN@gllD)+XgxlsS!qElF;R9!S zwP|cy=d~g>oa!we9S2>!bsoZ-LSM*Nzwg%{xi=nkZ8@^x)fi0nki@y`!L>)Bt#|zk zGoBgc=QwMdlRiIp4|$^xh=N@V)u%0O>!R9~Vyw8{i`>=aQxkr0g!*#ynCSQuP=n#V z1%gduMUv*Va?mm`oLN)-?&_w$Q+7wYp(SKw`I)~6k&Wk7YU*iUS{t9%FhtZat+t{H zS{zCMmO8q)TT&){=San1monXQ>jFtsh5*{M*AuWfc3impgpV{mU8y39vkG0t2l zXsTP@o%J5wRY__%BZR52qK(8RZOoFi^AdnKG9l_(PLq_k)~lux_s9Is z4+`rI-+5(0SH9+;d&~&#$bgzqd^w{XG-&QahHV#OTh2qt-a=cYqBhA}Dw4Y~V`ItQ z3#rtHP6$d7$U!uLz0{1@NwF6vDk3OCf9628(9tnPb*B*>t;+E3KTz}19qCG7$2gX?cD~$+ux`lbcVrqwlO&?^ zR?*M&GQeb5i%SZkN=-<3D<@0MjoE0Rf2s`~5#{{0YtOiv$q z*?as%=Ti{)qite!kkvPvbg8Hl zMk9cf1)%i|lTc-F|YK*YWk+2E2aRh)8n>cgl)Bz_4t=r?&0O+;QOiY;v|`{KUi z=Jqr>T8w$j3&gNA$h{WDKHJJrH)*(>O(mWKm@{#jmggiE4<{jkls;uR3iN8?Fyz^W>l3yHw=rXQ=K7pMnyY6_@OtZ2RjLK;Xvp(@*YAcI0 zVCW;mqk=yQpiFQrAR^N5wM+Z4Dp43AljN?o&`@#-(CT9aAVQAfm&=i`B#-J{e;_*p zW4zI|e?VZD=va&NPTg;FXKSIAC|j|N^d8Jd+zM$C3Z{5>js@!DK9iwC?WjSSDNK8s zB|7(nxvQmD={TfQw4&4EGqfsNt8d$}@|4F;g?M-qB%p75yS!UFuc?`oY~t)oth;#L zv7+?SDEs;c<7Z8tWJi398&8n`uY$tzZ$YuP%@_Rz`~5Cgc66VIG)|Hgw*(^Ne zD~jUle3?bN;Em_1p3it%TIuKQR4Ni55S>l3GWSwbwNpHoZOKj$6|nl8N;JI`=RUhv z5l`xs_^2=cWVnv5K}IK5Aw}eV&x}Y8NI$e3i?7l@c5d-*g?+Hc!=VEmzk9NCv7Ik8 zPK)AWJAJX+YjDS6U9I_s)qp?1w8Tcgb$@y@q4_=NCvb=>mq}wDECMlD}-tqYCoZ~f2e3<1X z>Ge|$kA?Y`xXJcFvULJZK#WHVm)8lU`LTXq+j7}Bzu6*%?ljN}F$?YNI`qpisb=k` zNP~JsFH(fGZ)Q#6LYf1+9_RJEM`&3#RTE)^{X|_^=di&azeFCqr1dQYC{ugs#|$Ab zLC-tp_>J)TA)_|{L^ob!=7y`?LI zU~}jOH>jlqk;@a%p_kO|+1Cb_m|tH|0?h z@vv$UUBdsgFiMvEsvULsa#j)QXOAUY)OTgp zj!_*3^(vHyPTrxlBYBg^rgBO@sO6?hwAS%P9Uf6o;nLIw8U);|u?HqwFdL$t8sCY7 ztDjVXDKyi|0;0_>!)>0pR~53&En)oG{j}0HHC?eEJR{e73q6-)ue)XfUArIqi6YKn z*;T*Owxv^Nr9R59O~bN{3K%fUsUt|BsfZgGJwsFAO)angU^%kJIF<|q-uPi;=!G2O)vEqpkNK-s z_3!p4M*nx^s+{ECd;eZn^{Zy{FXMP)f`9h@tAg{N4t_5(`MZO%x4`Oe4*pe)^-l-C zr$v9&qWop+Z=m>Z4*sZ2`Dg#%^9sKxQC-`juf9Dhb zbn*MP^()%_%fQI~w~K$pzW?;``yBnd55>3W`p@V1@5}htb|)tZ3H9s0;NM;&Z