From e11c31948c605e14f01541d3f8c7e1d79748c67d Mon Sep 17 00:00:00 2001 From: Jem Bishop Date: Tue, 21 Nov 2023 13:50:49 +0000 Subject: [PATCH 01/92] add ability to restore PVs from server from values or from node supported --- .../saveandrestore/epics/RestoreResult.java | 23 +++ .../epics/SnapshotRestorer.java | 140 ++++++++++++++++++ .../SnapshotRestoreController.java | 64 ++++++++ 3 files changed, 227 insertions(+) create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/RestoreResult.java create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestoreController.java diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/RestoreResult.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/RestoreResult.java new file mode 100644 index 0000000000..aa5d6b5cd5 --- /dev/null +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/RestoreResult.java @@ -0,0 +1,23 @@ +package org.phoebus.service.saveandrestore.epics; + +import java.io.Serializable; + +import org.phoebus.applications.saveandrestore.model.SnapshotItem; + +public class RestoreResult implements Serializable { + private SnapshotItem snapshotItem; + private String errorMsg; + + public SnapshotItem getSnapshotItem() { + return snapshotItem; + } + public String getErrorMsg() { + return errorMsg; + } + public void setSnapshotItem(SnapshotItem snapshotItem) { + this.snapshotItem = snapshotItem; + } + public void setErrorMsg(String errorMsg) { + this.errorMsg = errorMsg; + } +} \ No newline at end of file diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java new file mode 100644 index 0000000000..4d5bb04ebb --- /dev/null +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java @@ -0,0 +1,140 @@ +package org.phoebus.service.saveandrestore.epics; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import org.epics.pva.client.PVAClient; +import org.epics.vtype.VBoolean; +import org.epics.vtype.VBooleanArray; +import org.epics.vtype.VByteArray; +import org.epics.vtype.VDoubleArray; +import org.epics.vtype.VEnum; +import org.epics.vtype.VEnumArray; +import org.epics.vtype.VFloatArray; +import org.epics.vtype.VIntArray; +import org.epics.vtype.VLongArray; +import org.epics.vtype.VNumber; +import org.epics.vtype.VNumberArray; +import org.epics.vtype.VShortArray; +import org.epics.vtype.VString; +import org.epics.vtype.VStringArray; +import org.epics.vtype.VType; +import org.epics.vtype.VUByteArray; +import org.epics.vtype.VUIntArray; +import org.epics.vtype.VULongArray; +import org.epics.vtype.VUShortArray; +import org.phoebus.applications.saveandrestore.model.SnapshotItem; +import org.phoebus.core.vtypes.VTypeHelper; +import org.phoebus.service.saveandrestore.epics.RestoreResult; +public class SnapshotRestorer { + + PVAClient pva; + private final Logger LOG = Logger.getLogger(SnapshotRestorer.class.getName()); + private long timeoutMillis = 5000; + + public SnapshotRestorer() throws Exception { + pva = new PVAClient(); + } + + /** Restore PV values from a list of snapshot items + * + *

Writes concurrently (with timeout) the pv value to the non null set PVs in the snapshot items. + Uses synchonized to ensure only one frontend can write at a time. + Returns a list of the snapshot items you have set, with an error message if an + error occurred. + * + * @param snapshotItems {@link SnapshotItem} + */ + public synchronized List restorePVValues(List snapshotItems) { + + var futures = snapshotItems.stream().filter( + (snapshot_item) -> snapshot_item.getConfigPv().getPvName() != null) + .map((snapshotItem) -> { + var pvName = snapshotItem.getConfigPv().getPvName(); + var pvValue = snapshotItem.getValue(); + var channel = pva.getChannel(pvName); + CompletableFuture connected = channel.connect().completeOnTimeout(false, timeoutMillis, TimeUnit.MILLISECONDS); + CompletableFuture writeFuture = connected.thenComposeAsync( + (isConnected) -> { + String error; + try { + if (isConnected) { + Object rawValue = vTypeToObject(pvValue); + channel.write(true, "value", rawValue).get(timeoutMillis, TimeUnit.MILLISECONDS); + error = null; + } else { + LOG.warning(String.format("Tried to set %s but the channel is disconnnected", pvName)); + error = "PV disconnected"; + } + } catch (Exception e) { + error = e.getMessage(); + LOG.warning(String.format("Error setting PV %s", error)); + } finally { + channel.close(); + } + + var restoreResult = new RestoreResult(); + restoreResult.setSnapshotItem(snapshotItem); + restoreResult.setErrorMsg(error); + return CompletableFuture.completedFuture(restoreResult); + }); + return writeFuture; + }) + .collect(Collectors.toList()); + + CompletableFuture all_done = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + + // Wait on the futures concurrently + all_done.join(); + + // Joins should not block as all the futures should be completed. + return futures.stream().map( + (future) -> future.join() + ).collect(Collectors.toList()); + } + + /** + * Convert a vType to its Object representation + * @param type {@link VType} + */ + private Object vTypeToObject(VType type) { + if (type == null) { + return null; + } + if (type instanceof VNumberArray) { + if (type instanceof VIntArray || type instanceof VUIntArray) { + return VTypeHelper.toIntegers(type); + } else if (type instanceof VDoubleArray) { + return VTypeHelper.toDoubles(type); + } else if (type instanceof VFloatArray) { + return VTypeHelper.toFloats(type); + } else if (type instanceof VLongArray || type instanceof VULongArray) { + return VTypeHelper.toLongs(type); + } else if (type instanceof VShortArray || type instanceof VUShortArray) { + return VTypeHelper.toShorts(type); + } else if (type instanceof VByteArray || type instanceof VUByteArray) { + return VTypeHelper.toBytes(type); + } + } else if (type instanceof VEnumArray) { + List data = ((VEnumArray) type).getData(); + return data.toArray(new String[data.size()]); + } else if (type instanceof VStringArray) { + List data = ((VStringArray) type).getData(); + return data.toArray(new String[data.size()]); + } else if (type instanceof VBooleanArray) { + return VTypeHelper.toBooleans(type); + } else if (type instanceof VNumber) { + return ((VNumber) type).getValue(); + } else if (type instanceof VEnum) { + return ((VEnum) type).getIndex(); + } else if (type instanceof VString) { + return ((VString) type).getValue(); + } else if (type instanceof VBoolean) { + return ((VBoolean) type).getValue(); + } + return null; + } +} diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestoreController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestoreController.java new file mode 100644 index 0000000000..e60e7e664a --- /dev/null +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestoreController.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2018 European Spallation Source ERIC. + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + *

+ * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + *

+ * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +package org.phoebus.service.saveandrestore.web.controllers; + +import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; +import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; +import org.phoebus.applications.saveandrestore.model.ConfigPv; +import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.Snapshot; +import org.phoebus.applications.saveandrestore.model.SnapshotData; +import org.phoebus.applications.saveandrestore.model.SnapshotItem; +import org.phoebus.service.saveandrestore.epics.SnapshotRestorer; +import org.phoebus.service.saveandrestore.epics.RestoreResult; +import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@SuppressWarnings("unused") +@RestController +public class SnapshotRestoreController extends BaseController { + + @Autowired + private NodeDAO nodeDAO; + + @PostMapping(value = "/restore/items", produces = JSON) + public List restoreFromSnapshotItems( + @RequestBody List snapshotItems) throws Exception { + var snapshotRestorer = new SnapshotRestorer(); + return snapshotRestorer.restorePVValues(snapshotItems); + } + @PostMapping(value = "/restore/node", produces = JSON) + public List restoreFromSnapshotNode( + @RequestParam(value = "parentNodeId") String parentNodeId) throws Exception { + var snapshotRestorer = new SnapshotRestorer(); + var snapshot = nodeDAO.getSnapshotData(parentNodeId); + return snapshotRestorer.restorePVValues(snapshot.getSnapshotItems()); + } + +} + From 9d82ad88d25c598e246b4b25f08ca4356456bace Mon Sep 17 00:00:00 2001 From: Jem Bishop Date: Tue, 21 Nov 2023 13:51:41 +0000 Subject: [PATCH 02/92] add core-pva dep --- services/save-and-restore/pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml index 39b45f3f9b..b91bad419d 100644 --- a/services/save-and-restore/pom.xml +++ b/services/save-and-restore/pom.xml @@ -64,6 +64,17 @@ 4.7.3-SNAPSHOT + + org.phoebus + core-pva + 4.7.3-SNAPSHOT + + + org.phoebus + core-pv + 4.7.3-SNAPSHOT + + org.springframework.boot spring-boot-starter From 60918bfa2635275f06969616f73d7e8fc07bcc09 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 20 Nov 2023 16:53:12 -0500 Subject: [PATCH 03/92] Merge master --- .../org/phoebus/pv/alarm/AlarmContext.java | 23 +- .../logging/ui/AlarmLogTableController.java | 2 +- .../applications/alarm/AlarmSystem.java | 2 + .../alarm/client/AlarmClient.java | 554 +++++++++--------- .../alarm/client/KafkaHelper.java | 8 +- .../resources/alarm_preferences.properties | 5 +- .../applications/alarm/ui/AlarmUI.java | 2 +- .../applications/alarm/ui/Messages.java | 2 + .../alarm/ui/table/AlarmTableUI.java | 25 +- .../alarm/ui/tree/DuplicatePVAction.java | 16 +- .../alarm/ui/tree/EnableComponentAction.java | 11 +- .../alarm/ui/tree/ItemConfigDialog.java | 105 ++-- .../applications/alarm/ui/messages.properties | 2 + .../phoebus/channel/views/ChannelInfo.java | 2 +- .../views/ui/ChannelFinderController.java | 2 +- app/display/editor/doc/widgets_properties.rst | 12 +- .../display/builder/editor/EditorGUI.java | 9 +- .../display/builder/editor/Messages.java | 2 + .../editor/properties/PropertyPanel.java | 82 ++- .../builder/editor/messages.properties | 2 + .../display/builder/editor/opieditor.css | 10 + .../builder/model/macros/MacroXMLUtil.java | 15 + .../ui/LogEntryCalenderViewController.java | 2 +- .../logbook/ui/LogbookSearchController.java | 2 +- core/pv/src/main/java/org/phoebus/pv/PV.java | 13 +- .../main/java/org/phoebus/pv/ca/JCA_PV.java | 4 +- .../main/java/org/phoebus/pv/opva/PVA_PV.java | 5 +- .../org/phoebus/pv/opva/PVGetHandler.java | 2 +- .../org/phoebus/pv/opva/PVPutHandler.java | 7 +- .../java/org/phoebus/pv/opva/PVRequester.java | 4 +- .../main/java/org/phoebus/pv/pva/PVA_PV.java | 7 +- .../java/org/epics/pva/client/PVAChannel.java | 30 +- .../pva/server/PVASearchMonitorMain.java | 19 +- .../dialog/ExceptionDetailsErrorDialog.java | 83 ++- .../ExceptionDetailsErrorDialogDemo.java | 3 +- ...ptionStacktraceDetailsErrorDialogDemo.java | 32 + ...tionStacktraceWithNullErrorDialogDemo.java | 38 ++ docs/source/architecture.rst | 2 +- docs/source/phoebus_architecture.png | Bin 0 -> 180763 bytes pom.xml | 4 +- .../alarm/logging/AlarmConfigLogger.java | 31 +- .../alarm/server/ServerModel.java | 23 +- 42 files changed, 743 insertions(+), 461 deletions(-) create mode 100644 core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionStacktraceDetailsErrorDialogDemo.java create mode 100644 core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionStacktraceWithNullErrorDialogDemo.java create mode 100644 docs/source/phoebus_architecture.png diff --git a/app/alarm/datasource/src/main/java/org/phoebus/pv/alarm/AlarmContext.java b/app/alarm/datasource/src/main/java/org/phoebus/pv/alarm/AlarmContext.java index c4b077d009..3c9547876c 100644 --- a/app/alarm/datasource/src/main/java/org/phoebus/pv/alarm/AlarmContext.java +++ b/app/alarm/datasource/src/main/java/org/phoebus/pv/alarm/AlarmContext.java @@ -116,11 +116,14 @@ public static synchronized void acknowledgePV(AlarmPV alarmPV, boolean ack) } if (node != null) { - try { - alarmModels.get(alarmPV.getInfo().getRoot()).acknowledge(node, ack); - } catch (Exception e) { - logger.log(Level.WARNING, "Failed to acknowledge alarm", e); - } + AlarmTreeItem alarmClientNode = node; + JobManager.schedule("Acknowledge/unacknowledge alarm", monitor -> { + try { + alarmModels.get(alarmPV.getInfo().getRoot()).acknowledge(alarmClientNode, ack); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to acknowledge alarm", e); + } + }); } } } @@ -153,8 +156,14 @@ public static synchronized void enablePV(AlarmPV alarmPV, boolean enable) for (AlarmClientLeaf pv : pvs) { final AlarmClientLeaf copy = pv.createDetachedCopy(); - if (copy.setEnabled(enable)) - alarmModels.get(alarmPV.getInfo().getRoot()).sendItemConfigurationUpdate(pv.getPathName(), copy); + if (copy.setEnabled(enable)){ + try { + alarmModels.get(alarmPV.getInfo().getRoot()).sendItemConfigurationUpdate(pv.getPathName(), copy); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to send item configuration update to " + alarmPV.getInfo().getRoot(), e); + throw e; + } + } } }); } diff --git a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableController.java b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableController.java index bad774d0a5..21abe8c271 100644 --- a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableController.java +++ b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableController.java @@ -559,7 +559,7 @@ public void createContextMenu() { } alarmInfo.show(); }), - (url, ex) -> ExceptionDetailsErrorDialog.openError("Alarm Log Info Error", ex.getMessage(), ex) + (url, ex) -> ExceptionDetailsErrorDialog.openError("Alarm Log Info Error", ex) ); }); diff --git a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/AlarmSystem.java b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/AlarmSystem.java index ca784cb6b4..07d64c9706 100644 --- a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/AlarmSystem.java +++ b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/AlarmSystem.java @@ -118,6 +118,8 @@ public class AlarmSystem extends AlarmSystemConstants /** "Disable until.." shortcuts */ @Preference public static String[] shelving_options; + @Preference public static int max_block_ms; + /** Macros used in UI display/command/web links */ public static MacroValueProvider macros; diff --git a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmClient.java b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmClient.java index 44ca4c0426..80d60fa470 100644 --- a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmClient.java +++ b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmClient.java @@ -7,18 +7,6 @@ *******************************************************************************/ package org.phoebus.applications.alarm.client; -import static org.phoebus.applications.alarm.AlarmSystem.logger; - -import java.time.Duration; -import java.time.Instant; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; - import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -33,71 +21,111 @@ import org.phoebus.applications.alarm.model.json.JsonTags; import org.phoebus.util.time.TimestampFormats; -/** Alarm client model +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; + +import static org.phoebus.applications.alarm.AlarmSystem.logger; + +/** + * Alarm client model * - *

Given an alarm configuration name like "Accelerator", - * subscribes to the "Accelerator" topic for configuration updates - * and the "AcceleratorState" topic for alarm state updates. + *

Given an alarm configuration name like "Accelerator", + * subscribes to the "Accelerator" topic for configuration updates + * and the "AcceleratorState" topic for alarm state updates. * - *

Updates from either topic are merged into an in-memory model - * of the complete alarm information, - * updating listeners with all changes. + *

Updates from either topic are merged into an in-memory model + * of the complete alarm information, + * updating listeners with all changes. * - * @author Kay Kasemir + * @author Kay Kasemir */ @SuppressWarnings("nls") -public class AlarmClient -{ - /** Kafka topics for config/status and commands */ +public class AlarmClient { + /** + * Kafka topics for config/status and commands + */ private final String config_topic, command_topic; - /** Listeners to this client */ + /** + * Listeners to this client + */ private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>(); - /** Alarm tree root */ + /** + * Alarm tree root + */ private final AlarmClientNode root; - /** Alarm tree Paths that have been deleted. + /** + * Timeout in seconds waiting for response from Kafka when sending producer messages. + */ + private static final int KAFKA_CLIENT_TIMEOUT = 10; + + /** + * Alarm tree Paths that have been deleted. * - *

Used to distinguish between paths that are not in the alarm tree - * because we have never seen a config or status update for them, - * and entries that have been deleted, so further state updates - * should be ignored until the item is again added (config message). + *

Used to distinguish between paths that are not in the alarm tree + * because we have never seen a config or status update for them, + * and entries that have been deleted, so further state updates + * should be ignored until the item is again added (config message). */ private final Set deleted_paths = ConcurrentHashMap.newKeySet(); - /** Flag for message handling thread to run or exit */ + /** + * Flag for message handling thread to run or exit + */ private final AtomicBoolean running = new AtomicBoolean(true); - /** Currently in maintenance mode? */ + /** + * Currently in maintenance mode? + */ private final AtomicBoolean maintenance_mode = new AtomicBoolean(false); - /** Currently in silent mode? */ + /** + * Currently in silent mode? + */ private final AtomicBoolean disable_notify = new AtomicBoolean(false); - /** Kafka consumer */ + /** + * Kafka consumer + */ private final Consumer consumer; - /** Kafka producer */ + /** + * Kafka producer + */ private final Producer producer; - /** Message handling thread */ + /** + * Message handling thread + */ private final Thread thread; - /** Time of last state update (ms), - * used to determine timeout + /** + * Time of last state update (ms), + * used to determine timeout */ private long last_state_update = 0; - /** Timeout, not seen any messages from server? */ + /** + * Timeout, not seen any messages from server? + */ private volatile boolean has_timed_out = false; - /** @param server Kafka Server host:port - * @param config_name Name of alarm tree root - * @param kafka_properties_file File to load additional kafka properties from + /** + * @param server Kafka Server host:port + * @param config_name Name of alarm tree root + * @param kafka_properties_file File to load additional kafka properties from */ - public AlarmClient(final String server, final String config_name, final String kafka_properties_file) - { + public AlarmClient(final String server, final String config_name, final String kafka_properties_file) { Objects.requireNonNull(server); Objects.requireNonNull(config_name); @@ -113,116 +141,125 @@ public AlarmClient(final String server, final String config_name, final String k thread.setDaemon(true); } - /** @param listener Listener to add */ - public void addListener(final AlarmClientListener listener) - { + /** + * @param listener Listener to add + */ + public void addListener(final AlarmClientListener listener) { listeners.add(listener); } - /** @param listener Listener to remove */ - public void removeListener(final AlarmClientListener listener) - { - if (! listeners.remove(listener)) + /** + * @param listener Listener to remove + */ + public void removeListener(final AlarmClientListener listener) { + if (!listeners.remove(listener)) throw new IllegalStateException("Unknown listener"); } - /** Start client - * @see #shutdown() + /** + * Start client + * + * @see #shutdown() */ - public void start() - { + public void start() { thread.start(); } - /** @return true if start() had been called */ - public boolean isRunning() - { + /** + * @return true if start() had been called + */ + public boolean isRunning() { return thread.isAlive(); } - /** @return Root of alarm configuration */ - public AlarmClientNode getRoot() - { + /** + * @return Root of alarm configuration + */ + public AlarmClientNode getRoot() { return root; } - /** @return Is alarm server in maintenance mode? */ - public boolean isMaintenanceMode() - { + /** + * @return Is alarm server in maintenance mode? + */ + public boolean isMaintenanceMode() { return maintenance_mode.get(); } - /** @return Is alarm server in disable notify mode? */ - public boolean isDisableNotify() - { + /** + * @return Is alarm server in disable notify mode? + */ + public boolean isDisableNotify() { return disable_notify.get(); } - /** @param maintenance Select maintenance mode? */ - public void setMode(final boolean maintenance) - { + /** + * Client code must not call this on the UI thread as it may block up to {@link #KAFKA_CLIENT_TIMEOUT} seconds. + * + * @param maintenance Select maintenance mode? + * @throws Exception if Kafka interaction fails for any reason. + */ + public void setMode(final boolean maintenance) throws Exception { final String cmd = maintenance ? JsonTags.MAINTENANCE : JsonTags.NORMAL; - try - { - final String json = new String (JsonModelWriter.commandToBytes(cmd)); + try { + final String json = new String(JsonModelWriter.commandToBytes(cmd)); final ProducerRecord record = new ProducerRecord<>(command_topic, AlarmSystem.COMMAND_PREFIX + root.getPathName(), json); - producer.send(record); - } - catch (final Exception ex) - { + producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS); + } catch (final Exception ex) { logger.log(Level.WARNING, "Cannot set mode for " + root + " to " + cmd, ex); + throw ex; } } - /** @param disable_notify Select notify disable ? */ - public void setNotify(final boolean disable_notify) - { + /** + * Client must not call this on the UI thread as it may block up to {@link #KAFKA_CLIENT_TIMEOUT} seconds. + * + * @param disable_notify Select notify disable ? + * @throws Exception if Kafka interaction fails for any reason. + */ + public void setNotify(final boolean disable_notify) throws Exception { final String cmd = disable_notify ? JsonTags.DISABLE_NOTIFY : JsonTags.ENABLE_NOTIFY; - try - { - final String json = new String (JsonModelWriter.commandToBytes(cmd)); + try { + final String json = new String(JsonModelWriter.commandToBytes(cmd)); final ProducerRecord record = new ProducerRecord<>(command_topic, AlarmSystem.COMMAND_PREFIX + root.getPathName(), json); - producer.send(record); - } - catch (final Exception ex) - { + producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS); + } catch (final Exception ex) { logger.log(Level.WARNING, "Cannot set mode for " + root + " to " + cmd, ex); + throw ex; } } - /** Background thread loop that checks for alarm tree updates */ - private void run() - { + /** + * Background thread loop that checks for alarm tree updates + */ + private void run() { // Send an initial "no server" notification, // to be cleared once we receive data from server. checkServerState(); - try - { - while (running.get()) - { + try { + while (running.get()) { checkUpdates(); checkServerState(); } - } - catch (final Throwable ex) - { + } catch (final Throwable ex) { if (running.get()) logger.log(Level.SEVERE, "Alarm client model error", ex); // else: Intended shutdown - } - finally - { + } finally { consumer.close(); producer.close(); } } - /** Time spent in checkUpdates() waiting for, well, updates */ + /** + * Time spent in checkUpdates() waiting for, well, updates + */ private static final Duration POLL_PERIOD = Duration.ofMillis(100); - /** Perform one check for updates */ - private void checkUpdates() - { + /** + * Perform one check for updates + */ + private void checkUpdates() { // Check for messages, with timeout. // TODO Because of Kafka bug, this will hang if Kafka isn't running. // Fixed according to https://issues.apache.org/jira/browse/KAFKA-1894 , @@ -232,20 +269,20 @@ private void checkUpdates() handleUpdate(record); } - /** Handle one received update - * @param record Kafka record + /** + * Handle one received update + * + * @param record Kafka record */ - private void handleUpdate(final ConsumerRecord record) - { + private void handleUpdate(final ConsumerRecord record) { final int sep = record.key().indexOf(':'); - if (sep < 0) - { + if (sep < 0) { logger.log(Level.WARNING, "Invalid key, expecting type:path, got " + record.key()); return; } - final String type = record.key().substring(0, sep+1); - final String path = record.key().substring(sep+1); + final String type = record.key().substring(0, sep + 1); + final String path = record.key().substring(sep + 1); final long timestamp = record.timestamp(); final String node_config = record.value(); @@ -253,34 +290,27 @@ private void handleUpdate(final ConsumerRecord record) logger.log(Level.WARNING, "Expect updates with CreateTime, got " + record.timestampType() + ": " + record.timestamp() + " " + path + " = " + node_config); logger.log(Level.FINE, () -> - record.topic() + " @ " + - TimestampFormats.MILLI_FORMAT.format(Instant.ofEpochMilli(timestamp)) + " " + - type + path + " = " + node_config); + record.topic() + " @ " + + TimestampFormats.MILLI_FORMAT.format(Instant.ofEpochMilli(timestamp)) + " " + + type + path + " = " + node_config); - try - { + try { // Only update listeners if the node changed AlarmTreeItem changed_node = null; final Object json = node_config == null ? null : JsonModelReader.parseJsonText(node_config); - if (type.equals(AlarmSystem.CONFIG_PREFIX)) - { - if (json == null) - { // No config -> Delete node + if (type.equals(AlarmSystem.CONFIG_PREFIX)) { + if (json == null) { // No config -> Delete node final AlarmTreeItem node = deleteNode(path); // If this was a known node, notify listeners - if (node != null) - { + if (node != null) { logger.log(Level.FINE, () -> "Delete " + path); for (final AlarmClientListener listener : listeners) listener.itemRemoved(node); } - } - else - { // Configuration update + } else { // Configuration update if (JsonModelReader.isStateUpdate(json)) logger.log(Level.WARNING, "Got config update with state content: " + record.key() + " " + node_config); - else - { + else { AlarmTreeItem node = findNode(path); // New node? Will need to send update. Otherwise update when there's a change if (node == null) @@ -289,27 +319,18 @@ private void handleUpdate(final ConsumerRecord record) changed_node = node; } } - } - else if (type.equals(AlarmSystem.STATE_PREFIX)) - { // State update - if (json == null) - { // State update for deleted node, ignore + } else if (type.equals(AlarmSystem.STATE_PREFIX)) { // State update + if (json == null) { // State update for deleted node, ignore logger.log(Level.FINE, () -> "Got state update for deleted node: " + record.key() + " " + node_config); return; - } - else if (! JsonModelReader.isStateUpdate(json)) - { + } else if (!JsonModelReader.isStateUpdate(json)) { logger.log(Level.WARNING, "Got state update with config content: " + record.key() + " " + node_config); return; - } - else if (deleted_paths.contains(path)) - { + } else if (deleted_paths.contains(path)) { // It it _deleted_?? logger.log(Level.FINE, () -> "Ignoring state for deleted item: " + record.key() + " " + node_config); return; - } - else - { + } else { AlarmTreeItem node = findNode(path); // New node? Create, and remember to notify if (node == null) @@ -334,40 +355,36 @@ else if (deleted_paths.contains(path)) // else: Neither config nor state update; ignore. // If there were changes, notify listeners - if (changed_node != null) - { + if (changed_node != null) { logger.log(Level.FINE, "Update " + path + " to " + changed_node.getState()); for (final AlarmClientListener listener : listeners) listener.itemUpdated(changed_node); } - } - catch (final Exception ex) - { + } catch (final Exception ex) { logger.log(Level.WARNING, - "Alarm config update error for path " + path + - ", config " + node_config, ex); + "Alarm config update error for path " + path + + ", config " + node_config, ex); } } - /** Find existing node + /** + * Find existing node * - * @param path Path to node - * @return Node, null if model does not contain the node - * @throws Exception on error + * @param path Path to node + * @return Node, null if model does not contain the node + * @throws Exception on error */ - private AlarmTreeItem findNode(final String path) throws Exception - { + private AlarmTreeItem findNode(final String path) throws Exception { final String[] path_elements = AlarmTreePath.splitPath(path); // Start of path must match the alarm tree root - if (path_elements.length < 1 || - !root.getName().equals(path_elements[0])) + if (path_elements.length < 1 || + !root.getName().equals(path_elements[0])) throw new Exception("Invalid path for alarm configuration " + root.getName() + ": " + path); // Walk down the path AlarmTreeItem node = root; - for (int i=1; i findNode(final String path) throws Exception return node; } - /** Delete node + /** + * Delete node * - *

It's OK to try delete an unknown node: - * The node might have once existed, but was then deleted. - * The last entry in the configuration database is then the deletion hint. - * A new model that reads this node-to-delete information - * thus never knew the node. + *

It's OK to try delete an unknown node: + * The node might have once existed, but was then deleted. + * The last entry in the configuration database is then the deletion hint. + * A new model that reads this node-to-delete information + * thus never knew the node. * - * @param path Path to node to delete - * @return Node that was removed, or null if model never knew that node - * @throws Exception on error + * @param path Path to node to delete + * @return Node that was removed, or null if model never knew that node + * @throws Exception on error */ - private AlarmTreeItem deleteNode(final String path) throws Exception - { + private AlarmTreeItem deleteNode(final String path) throws Exception { // Mark path as deleted so we ignore state updates deleted_paths.add(path); @@ -402,49 +419,44 @@ private AlarmTreeItem deleteNode(final String path) throws Exception return node; } - /** Find an existing alarm tree item or create a new one + /** + * Find an existing alarm tree item or create a new one * - *

Informs listener about created nodes, - * if necessary one notification for each created node along the path. + *

Informs listener about created nodes, + * if necessary one notification for each created node along the path. * - * @param path Alarm tree path - * @param is_leaf Is this the path to a leaf? - * @return {@link AlarmTreeItem} - * @throws Exception on error + * @param path Alarm tree path + * @param is_leaf Is this the path to a leaf? + * @return {@link AlarmTreeItem} + * @throws Exception on error */ - private AlarmTreeItem findOrCreateNode(final String path, final boolean is_leaf) throws Exception - { + private AlarmTreeItem findOrCreateNode(final String path, final boolean is_leaf) throws Exception { // In case it was previously deleted: deleted_paths.remove(path); final String[] path_elements = AlarmTreePath.splitPath(path); // Start of path must match the alarm tree root - if (path_elements.length < 1 || - !root.getName().equals(path_elements[0])) + if (path_elements.length < 1 || + !root.getName().equals(path_elements[0])) throw new Exception("Invalid path for alarm configuration " + root.getName() + ": " + path); // Walk down the path AlarmClientNode parent = root; - for (int i=1; i node = parent.getChild(name); // Create missing nodes - if (node == null) - { // Done when creating leaf - if (last && is_leaf) - { + if (node == null) { // Done when creating leaf + if (last && is_leaf) { node = new AlarmClientLeaf(parent.getPathName(), name); node.addToParent(parent); logger.log(Level.FINE, "Create " + path); for (final AlarmClientListener listener : listeners) listener.itemAdded(node); return node; - } - else - { + } else { node = new AlarmClientNode(parent.getPathName(), name); node.addToParent(parent); for (final AlarmClientListener listener : listeners) @@ -455,10 +467,10 @@ private AlarmTreeItem findOrCreateNode(final String path, final boolean is_le if (last) return node; // Found or created intermediate node; continue walking down the path - if (! (node instanceof AlarmClientNode)) + if (!(node instanceof AlarmClientNode)) throw new Exception("Expected intermediate node, found " + - node.getClass().getSimpleName() + " " + node.getName() + - " while traversing " + path); + node.getClass().getSimpleName() + " " + node.getName() + + " while traversing " + path); parent = (AlarmClientNode) node; } @@ -466,73 +478,69 @@ private AlarmTreeItem findOrCreateNode(final String path, final boolean is_le return parent; } - /** Add a component to the alarm tree - * @param path_name to parent Root or parent component under which to add the component - * @param new_name Name of the new component + /** + * Add a component to the alarm tree + * + * @param path_name to parent Root or parent component under which to add the component + * @param new_name Name of the new component */ - public void addComponent(final String path_name, final String new_name) throws Exception - { - try - { + public void addComponent(final String path_name, final String new_name) { + try { sendNewItemInfo(path_name, new_name, new AlarmClientNode(null, new_name)); - } - catch (final Exception ex) - { + } catch (final Exception ex) { logger.log(Level.WARNING, "Cannot add component " + new_name + " to " + path_name, ex); } } - /** Add a component to the alarm tree - * @param path_name to parent Root or parent component under which to add the component - * @param new_name Name of the new component + /** + * Add a component to the alarm tree + * + * @param path_name to parent Root or parent component under which to add the component + * @param new_name Name of the new component */ - public void addPV(final String path_name, final String new_name) - { - try - { + public void addPV(final String path_name, final String new_name) { + try { sendNewItemInfo(path_name, new_name, new AlarmClientLeaf(null, new_name)); - } - catch (final Exception ex) - { + } catch (final Exception ex) { logger.log(Level.WARNING, "Cannot add pv " + new_name + " to " + path_name, ex); } } - private void sendNewItemInfo(String path_name, final String new_name, final AlarmTreeItem content) throws Exception - { + private void sendNewItemInfo(String path_name, final String new_name, final AlarmTreeItem content) throws Exception { // Send message about new component. // All clients, including this one, will receive and then add the new component. final String new_path = AlarmTreePath.makePath(path_name, new_name); sendItemConfigurationUpdate(new_path, content); } - /** Send item configuration - * - *

All clients, including this one, will update when they receive the message + /** + * Client code must not call this on the UI thread as it may block up to {@link #KAFKA_CLIENT_TIMEOUT} seconds. + * Send item configuration. + *

All clients, including this one, will update when they receive the message * - * @param path Path to the item - * @param config A prototype item (path is ignored) that holds the new configuration - * @throws Exception on error + * @param path Path to the item + * @param config A prototype item (path is ignored) that holds the new configuration + * @throws Exception on error */ - public void sendItemConfigurationUpdate(final String path, final AlarmTreeItem config) throws Exception - { + public void sendItemConfigurationUpdate(final String path, final AlarmTreeItem config) throws Exception { final String json = new String(JsonModelWriter.toJsonBytes(config)); final ProducerRecord record = new ProducerRecord<>(config_topic, AlarmSystem.CONFIG_PREFIX + path, json); - producer.send(record); + producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS); } - /** Remove a component (and sub-items) from alarm tree - * @param item Item to remove - * @throws Exception on error + /** + * Client must should not call this on the UI thread as it may block up to 2x{@link #KAFKA_CLIENT_TIMEOUT} seconds. + * Remove a component (and sub-items) from alarm tree + * + * @param item Item to remove + * @throws Exception on error */ - public void removeComponent(final AlarmTreeItem item) throws Exception - { - try - { - // Depth first deletion of all child nodes. - final List> children = item.getChildren(); - for (final AlarmTreeItem child : children) - removeComponent(child); + public void removeComponent(final AlarmTreeItem item) throws Exception { + try { + // Depth first deletion of all child nodes. + final List> children = item.getChildren(); + for (final AlarmTreeItem child : children) + removeComponent(child); // Send message about item to remove // All clients, including this one, will receive and then remove the item. @@ -542,75 +550,67 @@ public void removeComponent(final AlarmTreeItem item) throws Exception // The id message must arrive before the tombstone. final String json = new String(JsonModelWriter.deleteMessageToBytes()); final ProducerRecord id = new ProducerRecord<>(config_topic, AlarmSystem.CONFIG_PREFIX + item.getPathName(), json); - producer.send(id); + producer.send(id).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS); final ProducerRecord tombstone = new ProducerRecord<>(config_topic, AlarmSystem.CONFIG_PREFIX + item.getPathName(), null); - producer.send(tombstone); - } - catch (Exception ex) - { + producer.send(tombstone).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS); + } catch (Exception ex) { throw new Exception("Error deleting " + item.getPathName(), ex); } } - /** @param item Item for which to acknowledge alarm - * @param acknowledge true to acknowledge, else un-acknowledge + /** + * Client must should not call this on the UI thread as it may block up to {@link #KAFKA_CLIENT_TIMEOUT} seconds. + * + * @param item Item for which to acknowledge alarm + * @param acknowledge true to acknowledge, else un-acknowledge */ - public void acknowledge(final AlarmTreeItem item, final boolean acknowledge) throws Exception - { - try - { + public void acknowledge(final AlarmTreeItem item, final boolean acknowledge) throws Exception { + try { final String cmd = acknowledge ? "acknowledge" : "unacknowledge"; - final String json = new String (JsonModelWriter.commandToBytes(cmd)); + final String json = new String(JsonModelWriter.commandToBytes(cmd)); final ProducerRecord record = new ProducerRecord<>(command_topic, AlarmSystem.COMMAND_PREFIX + item.getPathName(), json); - producer.send(record); - } - catch (final Exception ex) - { + producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS); + } catch (final Exception ex) { logger.log(Level.WARNING, "Cannot acknowledge component " + item, ex); throw ex; } } - /** @return true if connected to server, else updates have timed out */ - public boolean isServerAlive() - { + /** + * @return true if connected to server, else updates have timed out + */ + public boolean isServerAlive() { return !has_timed_out; } - /** Check if there have been any messages from server */ - private void checkServerState() - { + /** + * Check if there have been any messages from server + */ + private void checkServerState() { final long now = System.currentTimeMillis(); - if (now - last_state_update > AlarmSystem.idle_timeout_ms*3) - { - if (! has_timed_out) - { + if (now - last_state_update > AlarmSystem.idle_timeout_ms * 3) { + if (!has_timed_out) { has_timed_out = true; for (final AlarmClientListener listener : listeners) listener.serverStateChanged(false); } + } else if (has_timed_out) { + has_timed_out = false; + for (final AlarmClientListener listener : listeners) + listener.serverStateChanged(true); } - else - if (has_timed_out) - { - has_timed_out = false; - for (final AlarmClientListener listener : listeners) - listener.serverStateChanged(true); - } } - /** Stop client */ - public void shutdown() - { + /** + * Stop client + */ + public void shutdown() { running.set(false); consumer.wakeup(); - try - { + try { thread.join(2000); - } - catch (final InterruptedException ex) - { + } catch (final InterruptedException ex) { logger.log(Level.WARNING, thread.getName() + " thread doesn't shut down", ex); } logger.log(Level.INFO, () -> thread.getName() + " shut down"); diff --git a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/KafkaHelper.java b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/KafkaHelper.java index 1152df1edd..17fd53d4a3 100644 --- a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/KafkaHelper.java +++ b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/KafkaHelper.java @@ -31,6 +31,7 @@ import org.apache.kafka.streams.KafkaStreams; import org.apache.kafka.streams.StreamsBuilder; import org.apache.kafka.streams.StreamsConfig; +import org.phoebus.applications.alarm.AlarmSystem; /** Alarm client model * @@ -117,13 +118,12 @@ public static Producer connectProducer(final String kafka_server kafka_props.put("bootstrap.servers", kafka_servers); // Collect messages for 20ms until sending them out as a batch kafka_props.put("linger.ms", 20); + kafka_props.put("max.block.ms", AlarmSystem.max_block_ms == 0 ? 10000 : AlarmSystem.max_block_ms); // Write String key, value final Serializer serializer = new StringSerializer(); - final Producer producer = new KafkaProducer<>(kafka_props, serializer, serializer); - - return producer; + return new KafkaProducer<>(kafka_props, serializer, serializer); } /** @@ -162,7 +162,7 @@ static public Properties loadPropsFromFile(String filePath) { logger.fine("loading file from path: " + filePath); Properties properties = new Properties(); if(filePath != null && !filePath.isBlank()){ - try(FileInputStream file = new FileInputStream(filePath);){ + try(FileInputStream file = new FileInputStream(filePath)){ properties.load(file); } catch(IOException e) { logger.log(Level.SEVERE, "failed to load kafka properties", e); diff --git a/app/alarm/model/src/main/resources/alarm_preferences.properties b/app/alarm/model/src/main/resources/alarm_preferences.properties index bf46de2674..d2ba480fed 100644 --- a/app/alarm/model/src/main/resources/alarm_preferences.properties +++ b/app/alarm/model/src/main/resources/alarm_preferences.properties @@ -5,7 +5,7 @@ # Kafka Server host:port server=localhost:9092 -# A file to configure the properites of kafka clients +# A file to configure the properties of kafka clients kafka_properties= # Name of alarm tree root. @@ -140,3 +140,6 @@ shelving_options=1 hour, 6 hours, 12 hours, 1 day, 7 days, 30 days # # Format: M1=Value1, M2=Value2 macros=TOP=/home/controls/displays,WEBROOT=http://localhost/controls/displays + +# Max time in ms a producer call will block. +max_block_ms=10000 \ No newline at end of file diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmUI.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmUI.java index d4ba2afc16..e62af387bd 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmUI.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmUI.java @@ -200,7 +200,7 @@ public static Color getAlarmAreaPanelBackgroundColor(final SeverityLevel severit /** Verify authorization, qualified by model's current config * @param model Alarm client model - * @param auto Authorization name + * @param authorization Authorization name * @return true if the user has authorization */ private static boolean haveQualifiedAuthorization(final AlarmClient model, final String authorization) diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java index 389fb7e00a..f36654a84d 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java @@ -15,6 +15,8 @@ public class Messages public static String acknowledgeFailed; public static String addComponentFailed; + public static String disableAlarmFailed; + public static String enableAlarmFailed; public static String moveItemFailed; public static String removeComponentFailed; public static String renameItemFailed; diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/table/AlarmTableUI.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/table/AlarmTableUI.java index 85552c12a8..c07c48981b 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/table/AlarmTableUI.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/table/AlarmTableUI.java @@ -17,6 +17,7 @@ import java.util.logging.Level; import java.util.regex.Pattern; +import javafx.application.Platform; import javafx.scene.layout.Background; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Priority; @@ -28,12 +29,14 @@ import org.phoebus.applications.alarm.ui.AlarmContextMenuHelper; import org.phoebus.applications.alarm.ui.AlarmUI; import org.phoebus.applications.alarm.ui.tree.ConfigureComponentAction; +import org.phoebus.framework.jobs.Job; import org.phoebus.framework.jobs.JobManager; import org.phoebus.framework.persistence.Memento; import org.phoebus.framework.selection.Selection; import org.phoebus.framework.selection.SelectionService; import org.phoebus.ui.application.ContextMenuService; import org.phoebus.ui.application.SaveSnapshotAction; +import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; import org.phoebus.ui.javafx.Brightness; import org.phoebus.ui.javafx.ClearingTextField; import org.phoebus.ui.javafx.ImageCache; @@ -289,7 +292,17 @@ ToolBar getToolbar() private ToolBar createToolbar() { setMaintenanceMode(false); - server_mode.setOnAction(event -> client.setMode(! client.isMaintenanceMode())); + server_mode.setOnAction(event -> { + JobManager.schedule(client.isMaintenanceMode() ? "Disable maintenance mode" : "Enable maintenance mode", + monitor -> { + try { + client.setMode(! client.isMaintenanceMode()); + } catch (Exception e) { + Platform.runLater(() -> ExceptionDetailsErrorDialog.openError(e.getMessage(), e)); + } + }); + + }); // Could 'bind', // server_mode.disableProperty().bind(new SimpleBooleanProperty(!AlarmUI.mayModifyMode(client))); @@ -299,7 +312,15 @@ private ToolBar createToolbar() server_mode.setDisable(true); setDisableNotify(false); - server_notify.setOnAction(event -> client.setNotify(! client.isDisableNotify())); + server_notify.setOnAction(event -> { + JobManager.schedule(client.isDisableNotify() ? "Enable alarm notification" : "Disable alarm notification", monitor -> { + try { + client.setNotify(! client.isDisableNotify()); + } catch (Exception e) { + Platform.runLater(() -> ExceptionDetailsErrorDialog.openError(e.getMessage(), e)); + } + }); + }); if (!AlarmUI.mayDisableNotify(client)) server_notify.setDisable(true); diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/DuplicatePVAction.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/DuplicatePVAction.java index c447a0373b..5fff9d078a 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/DuplicatePVAction.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/DuplicatePVAction.java @@ -7,18 +7,23 @@ *******************************************************************************/ package org.phoebus.applications.alarm.ui.tree; +import javafx.application.Platform; import org.phoebus.applications.alarm.AlarmSystem; import org.phoebus.applications.alarm.client.AlarmClient; import org.phoebus.applications.alarm.client.AlarmClientLeaf; import org.phoebus.applications.alarm.model.AlarmTreePath; import org.phoebus.framework.jobs.JobManager; import org.phoebus.ui.dialog.DialogHelper; +import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; import org.phoebus.ui.javafx.ImageCache; import javafx.scene.Node; import javafx.scene.control.MenuItem; import javafx.scene.control.TextInputDialog; +import java.util.logging.Level; +import java.util.logging.Logger; + /** Action that adds duplicate of PV to alarm tree configuration * @author Kay Kasemir */ @@ -27,7 +32,7 @@ class DuplicatePVAction extends MenuItem { /** @param node Node to position dialog * @param model Model where new component is added - * @param parent Parent item in alarm tree + * @param original Item subject to copy */ public DuplicatePVAction(final Node node, final AlarmClient model, final AlarmClientLeaf original) { @@ -59,7 +64,14 @@ public DuplicatePVAction(final Node node, final AlarmClient model, final AlarmCl // Request adding new PV final String new_path = AlarmTreePath.makePath(original.getParent().getPathName(), new_name); - JobManager.schedule(getText(), monitor -> model.sendItemConfigurationUpdate(new_path, template)); + JobManager.schedule(getText(), monitor -> { + try { + model.sendItemConfigurationUpdate(new_path, template); + } catch (Exception e) { + Logger.getLogger(DuplicatePVAction.class.getName()).log(Level.WARNING, "Failed to send item configuration", e); + Platform.runLater(() -> ExceptionDetailsErrorDialog.openError(e.getMessage(), e)); + } + }); }); } } diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/EnableComponentAction.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/EnableComponentAction.java index 308cbe0a6a..ea64bdc4c0 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/EnableComponentAction.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/EnableComponentAction.java @@ -15,8 +15,10 @@ import org.phoebus.applications.alarm.client.AlarmClientLeaf; import org.phoebus.applications.alarm.model.AlarmTreeItem; import org.phoebus.applications.alarm.ui.AlarmUI; +import org.phoebus.applications.alarm.ui.Messages; import org.phoebus.framework.jobs.JobManager; import org.phoebus.ui.dialog.DialogHelper; +import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; import org.phoebus.ui.javafx.ImageCache; import javafx.scene.Node; @@ -84,7 +86,14 @@ public EnableComponentAction(final Node node, final AlarmClient model, final Lis { final AlarmClientLeaf copy = pv.createDetachedCopy(); if (copy.setEnabled(doEnable())) - model.sendItemConfigurationUpdate(pv.getPathName(), copy); + try { + model.sendItemConfigurationUpdate(pv.getPathName(), copy); + } catch (Exception e) { + ExceptionDetailsErrorDialog.openError(Messages.error, + copy.isEnabled() ? Messages.enableAlarmFailed : Messages.disableAlarmFailed, + e); + throw e; + } } }); }); diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/ItemConfigDialog.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/ItemConfigDialog.java index 7793c653ca..1d824953a5 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/ItemConfigDialog.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/ItemConfigDialog.java @@ -7,50 +7,40 @@ *******************************************************************************/ package org.phoebus.applications.alarm.ui.tree; -import java.time.LocalDateTime; -import java.time.temporal.TemporalAmount; - -import org.phoebus.applications.alarm.AlarmSystem; -import org.phoebus.applications.alarm.client.AlarmClient; -import org.phoebus.applications.alarm.client.AlarmClientLeaf; -import org.phoebus.applications.alarm.client.AlarmClientNode; -import org.phoebus.applications.alarm.model.AlarmTreeItem; -import org.phoebus.applications.alarm.ui.tree.datetimepicker.DateTimePicker; -import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; -import org.phoebus.util.time.SecondsParser; -import org.phoebus.util.time.TimeParser; - import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.control.ButtonType; -import javafx.scene.control.CheckBox; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Dialog; -import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.Spinner; -import javafx.scene.control.TextField; -import javafx.scene.control.Tooltip; +import javafx.scene.control.*; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.stage.Modality; import javafx.util.Duration; +import org.phoebus.applications.alarm.AlarmSystem; +import org.phoebus.applications.alarm.client.AlarmClient; +import org.phoebus.applications.alarm.client.AlarmClientLeaf; +import org.phoebus.applications.alarm.client.AlarmClientNode; +import org.phoebus.applications.alarm.model.AlarmTreeItem; +import org.phoebus.applications.alarm.ui.tree.datetimepicker.DateTimePicker; +import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; +import org.phoebus.util.time.SecondsParser; +import org.phoebus.util.time.TimeParser; +import java.time.LocalDateTime; +import java.time.temporal.TemporalAmount; -/** Dialog for editing {@link AlarmTreeItem} + +/** + * Dialog for editing {@link AlarmTreeItem} * - *

When pressing "OK", dialog sends updated - * configuration. + *

When pressing "OK", dialog sends updated + * configuration. */ @SuppressWarnings("nls") -class ItemConfigDialog extends Dialog -{ +class ItemConfigDialog extends Dialog { private TextField description; private CheckBox enabled, latching, annunciating; private DateTimePicker enabled_date_picker; @@ -60,8 +50,7 @@ class ItemConfigDialog extends Dialog private final TitleDetailTable guidance, displays, commands; private final TitleDetailDelayTable actions; - public ItemConfigDialog(final AlarmClient model, final AlarmTreeItem item) - { + public ItemConfigDialog(final AlarmClient model, final AlarmTreeItem item) { // Allow multiple instances initModality(Modality.NONE); setTitle("Configure " + item.getName()); @@ -87,8 +76,7 @@ public ItemConfigDialog(final AlarmClient model, final AlarmTreeItem item) path.setEditable(false); layout.add(path, 1, row++); - if (item instanceof AlarmClientLeaf) - { + if (item instanceof AlarmClientLeaf) { final AlarmClientLeaf leaf = (AlarmClientLeaf) item; layout.add(new Label("Description:"), 0, row); @@ -126,7 +114,7 @@ public ItemConfigDialog(final AlarmClient model, final AlarmTreeItem item) enabled_date_picker.setDateTimeValue(leaf.getEnabledDate()); enabled_date_picker.setPrefSize(280, 25); - relative_date = new ComboBox(); + relative_date = new ComboBox<>(); relative_date.setTooltip(new Tooltip("Select a predefined duration for disabling the alarm")); relative_date.getItems().addAll(AlarmSystem.shelving_options); relative_date.setPrefSize(200, 25); @@ -142,15 +130,14 @@ public ItemConfigDialog(final AlarmClient model, final AlarmTreeItem item) // setOnAction for relative date must be set to null as to not trigger event when setting value enabled_date_picker.setOnAction((ActionEvent e) -> { - if (enabled_date_picker.getDateTimeValue() != null) - { + if (enabled_date_picker.getDateTimeValue() != null) { relative_date.setOnAction(null); enabled.setSelected(false); enabled_date_picker.getEditor().commitValue(); relative_date.getSelectionModel().clearSelection(); relative_date.setValue(null); relative_date.setOnAction(relative_event_handler); - }; + } }); final HBox until_box = new HBox(10, enabled_date_picker, relative_date); @@ -168,8 +155,7 @@ public ItemConfigDialog(final AlarmClient model, final AlarmTreeItem item) final String detail; if (seconds <= 0) detail = "With the current delay of 0 seconds, alarms trigger immediately"; - else - { + else { final String hhmmss = SecondsParser.formatSeconds(seconds); detail = "With the current delay of " + seconds + " seconds, alarms trigger after " + hhmmss + " hours:minutes:seconds"; } @@ -247,7 +233,7 @@ public ItemConfigDialog(final AlarmClient model, final AlarmTreeItem item) // Scroll pane stops the content from resizing, // so tell content to use the widths of the scroll pane // minus 40 to provide space for the scroll bar, and suggest minimum width - scroll.widthProperty().addListener((p, old, width) -> layout.setPrefWidth(Math.max(width.doubleValue()-40, 450))); + scroll.widthProperty().addListener((p, old, width) -> layout.setPrefWidth(Math.max(width.doubleValue() - 40, 450))); getDialogPane().setContent(scroll); setResizable(true); @@ -256,25 +242,22 @@ public ItemConfigDialog(final AlarmClient model, final AlarmTreeItem item) final Button ok = (Button) getDialogPane().lookupButton(ButtonType.OK); ok.addEventFilter(ActionEvent.ACTION, event -> - { - if (!validateAndStore(model, item)) - event.consume(); - }); + validateAndStore(model, item, event)); setResultConverter(button -> button == ButtonType.OK); } - /** Send requested configuration - * @param model {@link AlarmClient} - * @param item Original item - * @return true on success + /** + * Send requested configuration + * + * @param model {@link AlarmClient} + * @param item Original item + * @param event Button click event, consumed if save action fails (e.g. Kafka not reachable) */ - private boolean validateAndStore(final AlarmClient model, final AlarmTreeItem item) - { + private void validateAndStore(final AlarmClient model, final AlarmTreeItem item, ActionEvent event) { final AlarmTreeItem config; - if (item instanceof AlarmClientLeaf) - { + if (item instanceof AlarmClientLeaf) { final AlarmClientLeaf pv = new AlarmClientLeaf(null, item.getName()); pv.setDescription(description.getText().trim()); pv.setEnabled(enabled.isSelected()); @@ -294,31 +277,25 @@ private boolean validateAndStore(final AlarmClient model, final AlarmTreeItem else pv.setEnabled(true); - if (relative_enable_date != null) - { + if (relative_enable_date != null) { final TemporalAmount amount = TimeParser.parseTemporalAmount(relative_enable_date); final LocalDateTime update_date = LocalDateTime.now().plus(amount); pv.setEnabledDate(update_date); - }; + } + ; config = pv; - } - else + } else config = new AlarmClientNode(null, item.getName()); config.setGuidance(guidance.getItems()); config.setDisplays(displays.getItems()); config.setCommands(commands.getItems()); config.setActions(actions.getItems()); - try - { + try { model.sendItemConfigurationUpdate(item.getPathName(), config); - } - catch (Exception ex) - { + } catch (Exception ex) { ExceptionDetailsErrorDialog.openError("Error", "Cannot update item", ex); - return false; + event.consume(); } - - return true; } } \ No newline at end of file diff --git a/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/messages.properties b/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/messages.properties index 998f2ba791..2ed7ab7472 100644 --- a/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/messages.properties +++ b/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/messages.properties @@ -20,6 +20,8 @@ error=Error acknowledgeFailed=Failed to acknowledge alarm(s) addComponentFailed=Failed to add component +disableAlarmFailed=Failed to disable alarm +enableAlarmFailed=Failed to enable alarm moveItemFailed=Failed to move item removeComponentFailed=Failed to remove component renameItemFailed=Failed to rename item diff --git a/app/channel/views/src/main/java/org/phoebus/channel/views/ChannelInfo.java b/app/channel/views/src/main/java/org/phoebus/channel/views/ChannelInfo.java index 842941e42a..8275180642 100644 --- a/app/channel/views/src/main/java/org/phoebus/channel/views/ChannelInfo.java +++ b/app/channel/views/src/main/java/org/phoebus/channel/views/ChannelInfo.java @@ -99,7 +99,7 @@ public void call(Selection selection) throws Exception result -> Platform.runLater(() -> { controller.addChannels(result); }), - (url, ex) -> ExceptionDetailsErrorDialog.openError("ChannelFinder Query Error", ex.getMessage(), ex)); + (url, ex) -> ExceptionDetailsErrorDialog.openError("ChannelFinder Query Error", ex)); }); diff --git a/app/channel/views/src/main/java/org/phoebus/channel/views/ui/ChannelFinderController.java b/app/channel/views/src/main/java/org/phoebus/channel/views/ui/ChannelFinderController.java index 16520f7489..67d056b6c0 100644 --- a/app/channel/views/src/main/java/org/phoebus/channel/views/ui/ChannelFinderController.java +++ b/app/channel/views/src/main/java/org/phoebus/channel/views/ui/ChannelFinderController.java @@ -41,7 +41,7 @@ public void search(String searchString) { } channelSearchJob = ChannelSearchJob.submit(this.client, searchString, channels -> Platform.runLater(() -> setChannels(channels)), - (url, ex) -> ExceptionDetailsErrorDialog.openError("ChannelFinder Query Error", ex.getMessage(), ex)); + (url, ex) -> ExceptionDetailsErrorDialog.openError("ChannelFinder Query Error", ex)); } diff --git a/app/display/editor/doc/widgets_properties.rst b/app/display/editor/doc/widgets_properties.rst index d047b05d5d..ab5c268207 100644 --- a/app/display/editor/doc/widgets_properties.rst +++ b/app/display/editor/doc/widgets_properties.rst @@ -26,4 +26,14 @@ is ``$(pv_name)\n$(pv_value)``. In case a PV is not defined by the user for such anything useful. In such cases user should consider to change the tool tip property value, or set it to an empty value. An empty tool tip property will suppress rendering of a widget tool tip, even if the value for the tool tip text is -set by a rule. \ No newline at end of file +set by a rule. + +File Property +------------- + +Some widget use a File property to identify a resource, local or remote. If such a reference is a local file, then +user is advised to use relative a path as an absolute +path may not be portable between hosts, in particular not between Windows and Linux/Mac hosts. + +File paths may be specified using both forward slash (/) or backslash (\\) as path separator as both will work interchangeably +between Windows and Linux/Mac. \ No newline at end of file diff --git a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/EditorGUI.java b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/EditorGUI.java index afee17c772..504ba339c5 100644 --- a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/EditorGUI.java +++ b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/EditorGUI.java @@ -22,6 +22,7 @@ import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; +import javafx.scene.control.*; import org.csstudio.display.builder.editor.actions.ActionDescription; import org.csstudio.display.builder.editor.app.CreateGroupAction; import org.csstudio.display.builder.editor.app.DisplayEditorInstance; @@ -49,12 +50,6 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.Parent; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.Control; -import javafx.scene.control.Label; -import javafx.scene.control.MenuItem; -import javafx.scene.control.SeparatorMenuItem; -import javafx.scene.control.SplitPane; import javafx.scene.control.SplitPane.Divider; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; @@ -507,8 +502,8 @@ public void loadModel(final File file) "saving the display in this state might lead to losing those widgets or some of their properties." + "\nPlease check the log for details.", null); } - }); + property_panel.setFile(file); } /** Save model to file diff --git a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/Messages.java b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/Messages.java index 1e774c9945..c73eb7dbd9 100644 --- a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/Messages.java +++ b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/Messages.java @@ -47,8 +47,10 @@ public class Messages DownloadTitle, EditEmbededDisplay, ExpandTree, + FileBrowserToolTip, FileChangedHdr, FileChangedDlg, + FilePath, FindWidget, Grid, LoadDisplay, diff --git a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/PropertyPanel.java b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/PropertyPanel.java index caac164498..9f1171d135 100644 --- a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/PropertyPanel.java +++ b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/PropertyPanel.java @@ -8,27 +8,28 @@ package org.csstudio.display.builder.editor.properties; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - +import javafx.beans.property.SimpleStringProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; import org.csstudio.display.builder.editor.DisplayEditor; import org.csstudio.display.builder.editor.Messages; import org.csstudio.display.builder.model.DisplayModel; import org.csstudio.display.builder.model.Widget; import org.csstudio.display.builder.model.WidgetProperty; +import org.phoebus.framework.workbench.ApplicationService; import org.phoebus.ui.javafx.ClearingTextField; +import org.phoebus.ui.javafx.PlatformInfo; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.TextField; -import javafx.scene.control.Tooltip; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; +import java.io.File; +import java.net.URI; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; /** Property UI * @@ -42,6 +43,10 @@ public class PropertyPanel extends BorderPane private final PropertyPanelSection section; private final TextField searchField = new ClearingTextField(); + private File file; + + private final SimpleStringProperty filePathProperty = new SimpleStringProperty(); + /** @param editor {@link DisplayEditor} */ public PropertyPanel (final DisplayEditor editor) @@ -60,12 +65,49 @@ public PropertyPanel (final DisplayEditor editor) toolsPane.setPadding(new Insets(6)); toolsPane.getChildren().add(searchField); + Label filePathCategoryLabel = new Label(Messages.FilePath); + filePathCategoryLabel.getStyleClass().add("property_category"); + filePathCategoryLabel.setMaxWidth(Double.MAX_VALUE); + + final HBox filePathPane = new HBox(); + filePathPane.getStyleClass().add("file_path_pane"); + Label filePathLabel = new Label(); + filePathLabel.setMaxWidth(Double.MAX_VALUE); + filePathLabel.getStyleClass().add("file_path"); + filePathLabel.textProperty().bind(filePathProperty); + filePathLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS); + HBox.setHgrow(filePathLabel, Priority.ALWAYS); + + Button openFileBrowser = new Button("..."); + openFileBrowser.setOnAction(e -> { + try { + String absolutePath = this.file.getParentFile().getAbsolutePath(); + String uriString; + if(PlatformInfo.isWindows){ + uriString = "file:/" + absolutePath.replace('\\', '/'); + } + else { + uriString = "file:" + absolutePath; + } + ApplicationService.createInstance("file_browser", new URI(uriString)); + } catch (Exception ex) { + Logger.getLogger(PropertyPanel.class.getName()) + .log(Level.WARNING, "Unable to launch file browser app", ex); + } + }); + openFileBrowser.setTooltip(new Tooltip(Messages.FileBrowserToolTip)); + filePathPane.getChildren().addAll(filePathLabel, openFileBrowser); + + final VBox topPane = new VBox(); + topPane.setAlignment(Pos.CENTER_LEFT); + topPane.getChildren().addAll(toolsPane, filePathCategoryLabel, filePathPane); + final ScrollPane scrollPane = new ScrollPane(); scrollPane.setFitToWidth(true); scrollPane.setContent(section); scrollPane.setMinHeight(0); - setTop(toolsPane); + setTop(topPane); setCenter(scrollPane); // Track currently selected widgets @@ -147,4 +189,14 @@ private void updatePropertiesView(final Set> properties, final section.setClassMode(model != null && model.isClassModel()); section.fill(editor.getUndoableActionManager(), filtered, other); } + + /** + * Sets the absolute file path text + * + * @param file OPI file being edited + */ + public void setFile(File file) { + this.file = file; + filePathProperty.set(file.getAbsolutePath()); + } } diff --git a/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/messages.properties b/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/messages.properties index d5f7d25c16..aba46f9d24 100644 --- a/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/messages.properties +++ b/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/messages.properties @@ -29,8 +29,10 @@ DownloadTitle=Download remote file Duplicate=Duplicate Widgets EditEmbededDisplay=Edit Embedded Display ExpandTree=Expand All Tree Items +FileBrowserToolTip=Open parent dir in File Browser FileChangedHdr=File has changed FileChangedDlg=The file\n {0}\nhas been changed while you were editing it.\n\n'OK' to save and thus overwrite what somebody else has written,\nor\n'Cancel' and then re-load the file or save it under a different name. +FilePath=File Path FindWidget=Find Widget... Grid=Align widgets to Grid LoadDisplay=Load display diff --git a/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/opieditor.css b/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/opieditor.css index 7aa8799e43..de31739148 100644 --- a/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/opieditor.css +++ b/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/opieditor.css @@ -82,6 +82,16 @@ -fx-background-radius: 0; } +.file_path +{ + -fx-padding: 3 5 0 5; +} + +.file_path_pane +{ + -fx-padding: 2 0 1 0; +} + .structure_property_name { -fx-font-weight: bold; diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/macros/MacroXMLUtil.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/macros/MacroXMLUtil.java index f18b4301fc..e7bedde541 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/macros/MacroXMLUtil.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/macros/MacroXMLUtil.java @@ -11,6 +11,8 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Level; import javax.xml.stream.XMLOutputFactory; @@ -118,4 +120,17 @@ public static String toString(final Macros macros) } return xml.toString(); } + + /** @deprecated Use macros.getValue("M") or macros.getNames() + * @param macros Macros to represent as Map + * @return Map representation for macros + */ + public static Map toMap(final Macros macros) + { + Map readMacroMap = new HashMap<>(); + macros.forEach((s, s2) -> { + readMacroMap.put(s,s2); + }); + return readMacroMap; + } } diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java index 57c50db3e2..20530f9750 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderViewController.java @@ -256,7 +256,7 @@ public void search() { }, (msg, ex) -> { searchInProgress.set(false); - ExceptionDetailsErrorDialog.openError("Logbook Search Error", ex.getMessage(), ex); + ExceptionDetailsErrorDialog.openError("Logbook Search Error", ex); }); } diff --git a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookSearchController.java b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookSearchController.java index 662a9ec3f4..dc3aa4f06d 100644 --- a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookSearchController.java +++ b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookSearchController.java @@ -39,7 +39,7 @@ public void search(Map map) { logbookSearchJob = LogbookSearchJob.submit(this.client, map, logs -> Platform.runLater(() -> setLogs(logs)), - (url, ex) -> ExceptionDetailsErrorDialog.openError("Logbook Search Error", ex.getMessage(), ex)); + (url, ex) -> ExceptionDetailsErrorDialog.openError("Logbook Search Error", ex)); } public abstract void setLogs(List logs); diff --git a/core/pv/src/main/java/org/phoebus/pv/PV.java b/core/pv/src/main/java/org/phoebus/pv/PV.java index 8a8e00270d..cb7eba1ed4 100644 --- a/core/pv/src/main/java/org/phoebus/pv/PV.java +++ b/core/pv/src/main/java/org/phoebus/pv/PV.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -236,17 +235,17 @@ public VType read() /** Issue a read request * - *

{@link Future} allows waiting for + *

{@link CompletableFuture} allows waiting for * and obtaining the result, or its get() * calls will provide an error. * *

As a side effect, registered listeners will * also receive the value obtained by this call. * - * @return {@link Future} for obtaining the result or Exception + * @return {@link CompletableFuture} for obtaining the result or Exception * @exception Exception on error */ - public Future asyncRead() throws Exception + public CompletableFuture asyncRead() throws Exception { // Default: Return last known value return CompletableFuture.completedFuture(last_value); @@ -270,17 +269,17 @@ public void write(final Object new_value) throws Exception /** Write value with confirmation * - *

{@link Future} can be used to await completion + *

{@link CompletableFuture} can be used to await completion * of the write. * The get() will not return a useful value (null), * but they will throw an error if the write failed. * * @param new_value Value to write to the PV - * @return {@link Future} for awaiting completion or exception + * @return {@link CompletableFuture} for awaiting completion or exception * @exception Exception on error * @see #write(Object) */ - public Future asyncWrite(final Object new_value) throws Exception + public CompletableFuture asyncWrite(final Object new_value) throws Exception { // Default: Normal write, declare 'done' right away write(new_value); return CompletableFuture.completedFuture(null); diff --git a/core/pv/src/main/java/org/phoebus/pv/ca/JCA_PV.java b/core/pv/src/main/java/org/phoebus/pv/ca/JCA_PV.java index 7464e31dc0..a43480db21 100644 --- a/core/pv/src/main/java/org/phoebus/pv/ca/JCA_PV.java +++ b/core/pv/src/main/java/org/phoebus/pv/ca/JCA_PV.java @@ -420,7 +420,7 @@ public void getCompleted(final GetEvent ev) } @Override - public Future asyncRead() throws Exception + public CompletableFuture asyncRead() throws Exception { final DBRType type = channel.getFieldType(); if (type == null || type == DBRType.UNKNOWN) @@ -453,7 +453,7 @@ public void write(final Object new_value) throws Exception } @Override - public Future asyncWrite(final Object new_value) throws Exception + public CompletableFuture asyncWrite(final Object new_value) throws Exception { final PutCallbackFuture result = new PutCallbackFuture(); performWrite(new_value, result); diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVA_PV.java b/core/pv/src/main/java/org/phoebus/pv/opva/PVA_PV.java index d8067b12a1..9ac8d7f892 100644 --- a/core/pv/src/main/java/org/phoebus/pv/opva/PVA_PV.java +++ b/core/pv/src/main/java/org/phoebus/pv/opva/PVA_PV.java @@ -8,6 +8,7 @@ package org.phoebus.pv.opva; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.logging.Level; @@ -249,7 +250,7 @@ public void unlisten(final Monitor monitor) /** {@inheritDoc} */ @Override - public Future asyncRead() throws Exception + public CompletableFuture asyncRead() throws Exception { final PVGetHandler result = new PVGetHandler(this); channel.createChannelGet(result, read_request); @@ -265,7 +266,7 @@ public void write(final Object new_value) throws Exception /** {@inheritDoc} */ @Override - public Future asyncWrite(Object new_value) throws Exception + public CompletableFuture asyncWrite(Object new_value) throws Exception { if (enum_labels != null && new_value instanceof String) { // Convert string-for-enum into index of corresponding label diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVGetHandler.java b/core/pv/src/main/java/org/phoebus/pv/opva/PVGetHandler.java index d5d825efbc..b41b143216 100644 --- a/core/pv/src/main/java/org/phoebus/pv/opva/PVGetHandler.java +++ b/core/pv/src/main/java/org/phoebus/pv/opva/PVGetHandler.java @@ -27,7 +27,7 @@ * @author Kay Kasemir */ @SuppressWarnings("nls") -class PVGetHandler extends PVRequester implements ChannelGetRequester, Future +class PVGetHandler extends PVRequester implements ChannelGetRequester { final private PVA_PV pv; diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVPutHandler.java b/core/pv/src/main/java/org/phoebus/pv/opva/PVPutHandler.java index ed254ecf73..c59bc5301e 100644 --- a/core/pv/src/main/java/org/phoebus/pv/opva/PVPutHandler.java +++ b/core/pv/src/main/java/org/phoebus/pv/opva/PVPutHandler.java @@ -25,6 +25,7 @@ import org.epics.pvdata.pv.Status; import org.epics.pvdata.pv.Structure; import org.phoebus.pv.PV; +import org.epics.vtype.VType; /** A {@link ChannelPutRequester} for writing a value to a {@link PVA_PV}, * indicating completion via a {@link Future} @@ -32,7 +33,7 @@ * @author Kay Kasemir */ @SuppressWarnings("nls") -class PVPutHandler extends PVRequester implements ChannelPutRequester, Future +class PVPutHandler extends PVRequester implements ChannelPutRequester { final private PV pv; final private Object new_value; @@ -136,7 +137,7 @@ public boolean isDone() // Future @Override - public Object get() throws InterruptedException, ExecutionException + public VType get() throws InterruptedException, ExecutionException { updates.await(); if (error != null) @@ -146,7 +147,7 @@ public Object get() throws InterruptedException, ExecutionException // Future @Override - public Object get(final long timeout, final TimeUnit unit) throws InterruptedException, + public VType get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { if (! updates.await(timeout, unit)) diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVRequester.java b/core/pv/src/main/java/org/phoebus/pv/opva/PVRequester.java index c2849c0f65..929d883ce8 100644 --- a/core/pv/src/main/java/org/phoebus/pv/opva/PVRequester.java +++ b/core/pv/src/main/java/org/phoebus/pv/opva/PVRequester.java @@ -9,15 +9,17 @@ import static org.phoebus.pv.PV.logger; +import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import org.epics.pvdata.pv.MessageType; import org.epics.pvdata.pv.Requester; +import org.epics.vtype.VType; /** Base for PVAccess {@link Requester} * @author Kay Kasemir */ -class PVRequester implements Requester +class PVRequester extends CompletableFuture implements Requester { @Override public String getRequesterName() diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/PVA_PV.java b/core/pv/src/main/java/org/phoebus/pv/pva/PVA_PV.java index 7f22f22395..a34180c2b3 100644 --- a/core/pv/src/main/java/org/phoebus/pv/pva/PVA_PV.java +++ b/core/pv/src/main/java/org/phoebus/pv/pva/PVA_PV.java @@ -8,6 +8,7 @@ package org.phoebus.pv.pva; import java.util.BitSet; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -96,11 +97,11 @@ private void handleMonitor(final PVAChannel channel, } @Override - public Future asyncRead() throws Exception + public CompletableFuture asyncRead() throws Exception { final Future data = channel.read(name_helper.getRequest()); // Wrap into Future that converts PVAStructure into VType - return new Future<>() + return new CompletableFuture<>() { @Override public boolean cancel(final boolean mayInterruptIfRunning) @@ -190,7 +191,7 @@ public void write(final Object new_value) throws Exception } @Override - public Future asyncWrite(final Object new_value) throws Exception + public CompletableFuture asyncWrite(final Object new_value) throws Exception { // Perform a put with completion, // i.e., process target and block until processing completes, diff --git a/core/pva/src/main/java/org/epics/pva/client/PVAChannel.java b/core/pva/src/main/java/org/epics/pva/client/PVAChannel.java index ea405cafca..610ab5fc5b 100644 --- a/core/pva/src/main/java/org/epics/pva/client/PVAChannel.java +++ b/core/pva/src/main/java/org/epics/pva/client/PVAChannel.java @@ -11,7 +11,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -39,6 +38,13 @@ * *

When no longer in use, the channel should be {@link #close()}d. * + * + * Note that several methods return a CompletableFuture. + * This has been done because at this time the Futures used internally are indeed CompletableFutures + * and this type offers an extensive API for composition and chaining of futures. + * But note that user code must never call 'complete(..)' nor 'completeExceptionally()' + * on the provided CompletableFutures. + * * @author Kay Kasemir */ @SuppressWarnings("nls") @@ -116,7 +122,7 @@ public boolean isConnected() } /** Wait for channel to connect - * @return {@link Future} to await connection. + * @return {@link CompletableFuture} to await connection. * true on success, * {@link TimeoutException} on timeout. */ @@ -207,18 +213,18 @@ boolean resetConnection() * the values are not set. * * @param subfield Sub field to get, "" for complete data type - * @return {@link Future} for fetching the result + * @return {@link CompletableFuture} for fetching the result */ - public Future info(final String subfield) + public CompletableFuture info(final String subfield) { return new GetTypeRequest(this, subfield); } /** Read (get) channel's value from server * @param request Request, "" for all fields, or "field_a, field_b.subfield" - * @return {@link Future} for fetching the result + * @return {@link CompletableFuture} for fetching the result */ - public Future read(final String request) + public CompletableFuture read(final String request) { return new GetRequest(this, request); } @@ -243,11 +249,11 @@ public Future read(final String request) * @param request Request for element to write, e.g. "field(value)" * @param new_value New value: Number, String * @throws Exception on error - * @return {@link Future} for awaiting completion and getting Exception in case of error + * @return {@link CompletableFuture} for awaiting completion and getting Exception in case of error * @deprecated Use {@link #write(boolean, String, Object)} */ @Deprecated - public Future write(final String request, final Object new_value) throws Exception + public CompletableFuture write(final String request, final Object new_value) throws Exception { return write(false, request, new_value); } @@ -273,9 +279,9 @@ public Future write(final String request, final Object new_value) throws E * @param request Request for element to write, e.g. "field(value)" * @param new_value New value: Number, String * @throws Exception on error - * @return {@link Future} for awaiting completion and getting Exception in case of error + * @return {@link CompletableFuture} for awaiting completion and getting Exception in case of error */ - public Future write(final boolean completion, final String request, final Object new_value) throws Exception + public CompletableFuture write(final boolean completion, final String request, final Object new_value) throws Exception { return new PutRequest(this, completion, request, new_value); } @@ -317,9 +323,9 @@ public AutoCloseable subscribe(final String request, final int pipeline, final M /** Invoke remote procedure call (RPC) * @param request Request, i.e. parameters sent to the RPC call - * @return {@link Future} for fetching the result returned by the RPC call + * @return {@link CompletableFuture} for fetching the result returned by the RPC call */ - public Future invoke(final PVAStructure request) + public CompletableFuture invoke(final PVAStructure request) { return new RPCRequest(this, request); } diff --git a/core/pva/src/main/java/org/epics/pva/server/PVASearchMonitorMain.java b/core/pva/src/main/java/org/epics/pva/server/PVASearchMonitorMain.java index 772df9cf13..230b1def61 100644 --- a/core/pva/src/main/java/org/epics/pva/server/PVASearchMonitorMain.java +++ b/core/pva/src/main/java/org/epics/pva/server/PVASearchMonitorMain.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 Oak Ridge National Laboratory. + * Copyright (c) 2022-2023 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.LogManager; @@ -50,6 +51,7 @@ private static class SearchInfo implements Comparable this.name = name; } + /** Sort by search count */ @Override public int compareTo(final SearchInfo other) { @@ -93,7 +95,7 @@ public static void main(String[] args) throws Exception LogManager.getLogManager().readConfiguration(PVASettings.class.getResourceAsStream("/pva_logging.properties")); setLogLevel(Level.WARNING); long update_period = 10; - boolean once = false; + final AtomicBoolean once = new AtomicBoolean(); for (int i=0; i { - // Quit when receiving search for name "QUIT" - if (name.equals("QUIT")) + // In "continuing" mode, quit when receiving search for name "QUIT" + if (!once.get() && name.equals("QUIT")) done.countDown(); final SearchInfo search = searches.computeIfAbsent(name, n -> new SearchInfo(name)); @@ -155,7 +160,7 @@ else if (arg.startsWith("-v") && (i+1) < args.length) ( final PVAServer server = new PVAServer(search_handler) ) { System.out.println("Monitoring search requests for " + update_period + " seconds..."); - if (! once) + if (! once.get()) System.out.println("Run 'pvget QUIT' to stop"); while (! done.await(update_period, TimeUnit.SECONDS)) { @@ -172,7 +177,7 @@ else if (arg.startsWith("-v") && (i+1) < args.length) info.client.toString(), now.getEpochSecond() - info.last.getEpochSecond()); }); - if (once) + if (once.get()) break; } } diff --git a/core/ui/src/main/java/org/phoebus/ui/dialog/ExceptionDetailsErrorDialog.java b/core/ui/src/main/java/org/phoebus/ui/dialog/ExceptionDetailsErrorDialog.java index 0c269b789c..c91be76d9c 100644 --- a/core/ui/src/main/java/org/phoebus/ui/dialog/ExceptionDetailsErrorDialog.java +++ b/core/ui/src/main/java/org/phoebus/ui/dialog/ExceptionDetailsErrorDialog.java @@ -7,14 +7,23 @@ ******************************************************************************/ package org.phoebus.ui.dialog; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; - import javafx.application.Platform; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; import javafx.scene.control.TextArea; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; /** Dialog that shows error message with exception detail * @author Kay Kasemir @@ -22,6 +31,8 @@ @SuppressWarnings("nls") public class ExceptionDetailsErrorDialog { + private static final String LINE_SEPARATOR = System.lineSeparator(); + /** Open dialog that shows detail of error * *

May be called from non-UI thread @@ -33,7 +44,22 @@ public class ExceptionDetailsErrorDialog */ public static void openError(final Node node, final String title, final String message, final Exception exception) { - Platform.runLater(() -> doOpenError(node, title, message, exception)); + Platform.runLater(() -> doOpenError(node, title, message, exception, true)); + } + + /** Open dialog that shows detail of error + * + *

May be called from non-UI thread + * + * @param node Node relative to which the dialog will be positioned + * @param title Title + * @param message Message, may have multiple lines + * @param exception Exception + * @param appendStacktraceMsgs Append a summary for all the exceptions in the stacktrace + */ + public static void openError(final Node node, final String title, final String message, final Exception exception, final boolean appendStacktraceMsgs) + { + Platform.runLater(() -> doOpenError(node, title, message, exception, appendStacktraceMsgs)); } /** Open dialog that shows detail of error @@ -46,14 +72,47 @@ public static void openError(final Node node, final String title, final String m */ public static void openError(final String title, final String message, final Exception exception) { - openError(null, title, message, exception); + openError(null, title, message, exception, true); } - private static void doOpenError(final Node node, final String title, final String message, final Exception exception) + /** Open dialog that shows detail of error, the message/header text is constructed using the messages from the + * exception's stacktrace + * + *

May be called from non-UI thread + * + * @param title Title + * @param exception Exception + */ + public static void openError(final String title, final Exception exception) { + openError(null, title, "", exception, true); + } + + private static void doOpenError(final Node node, final String title, final String message, final Exception exception, final boolean append_stacktrace_msgs) + { + StringBuilder messageBuilder = new StringBuilder(message).append(LINE_SEPARATOR); + if(append_stacktrace_msgs) + { + messageBuilder.append(exception.getMessage() != null ? exception.getMessage() : exception.getClass()).append(LINE_SEPARATOR).append("Cause:").append(LINE_SEPARATOR); + Throwable cause = exception.getCause(); + int exceptionIndex = 1; + // maintain a list of 'causes' to handle CIRCULAR REFERENCE s + final List throwableCausesList = new ArrayList<>(); + while (cause != null && !throwableCausesList.contains(cause)) { + throwableCausesList.add(cause); + messageBuilder.append("[" + exceptionIndex + "] ").append(cause.getMessage() != null ? cause.getMessage() : cause.getClass().getName()).append(LINE_SEPARATOR); + exceptionIndex++; + cause = cause.getCause(); + } + } + final Alert dialog = new Alert(AlertType.ERROR); dialog.setTitle(title); - dialog.setHeaderText(message); + dialog.setHeaderText(messageBuilder.toString()); + + // A custom button which copies the message to clipboard + final ButtonType copyMessage = new ButtonType("Copy Msg & Close", ButtonBar.ButtonData.RIGHT); + dialog.getButtonTypes().add(copyMessage); if (exception != null) { @@ -78,6 +137,14 @@ private static void doOpenError(final Node node, final String title, final Strin if (node != null) DialogHelper.positionDialog(dialog, node, -400, -200); - dialog.showAndWait(); + Optional result = dialog.showAndWait(); + if (result.isPresent() && result.get() == copyMessage) { + final Clipboard clipboard = Clipboard.getSystemClipboard(); + final ClipboardContent content = new ClipboardContent(); + content.putString(message); + clipboard.setContent(content); + } } + + } diff --git a/core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionDetailsErrorDialogDemo.java b/core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionDetailsErrorDialogDemo.java index e5f4b7aa0c..a5f4dc7f2e 100644 --- a/core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionDetailsErrorDialogDemo.java +++ b/core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionDetailsErrorDialogDemo.java @@ -20,7 +20,8 @@ public class ExceptionDetailsErrorDialogDemo extends ApplicationWrapper @Override public void start(final Stage stage) { - ExceptionDetailsErrorDialog.openError("Test", "This is a test\nAnother line", new Exception("This is a test")); + Exception ex = new Exception("This is a test"); + ExceptionDetailsErrorDialog.openError("Test", "This is a test\nAnother line", ex); } public static void main(String[] args) diff --git a/core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionStacktraceDetailsErrorDialogDemo.java b/core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionStacktraceDetailsErrorDialogDemo.java new file mode 100644 index 0000000000..9c59926508 --- /dev/null +++ b/core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionStacktraceDetailsErrorDialogDemo.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2017 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ +package org.phoebus.ui.dialog; + +import javafx.stage.Stage; +import org.phoebus.ui.javafx.ApplicationWrapper; + +/** Demo of the error dialog with a summary of the stack trace + * @author Kay Kasemir, Kunal Shroff + */ +@SuppressWarnings("nls") +public class ExceptionStacktraceDetailsErrorDialogDemo extends ApplicationWrapper +{ + @Override + public void start(final Stage stage) + { + Exception rootCause = new Exception("The ROOT cause of the test exception"); + Exception cause = new Exception("The cause of the test exception", rootCause); + Exception ex = new Exception("This is a test", cause); + ExceptionDetailsErrorDialog.openError("Test", ex); + } + + public static void main(String[] args) + { + launch(ExceptionStacktraceDetailsErrorDialogDemo.class, args); + } +} diff --git a/core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionStacktraceWithNullErrorDialogDemo.java b/core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionStacktraceWithNullErrorDialogDemo.java new file mode 100644 index 0000000000..f5b37034a2 --- /dev/null +++ b/core/ui/src/test/java/org/phoebus/ui/dialog/ExceptionStacktraceWithNullErrorDialogDemo.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2017 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ +package org.phoebus.ui.dialog; + +import javafx.stage.Stage; +import org.phoebus.ui.javafx.ApplicationWrapper; + +import java.io.FileNotFoundException; +import java.io.IOException; + +/** Demo of the error dialog, with a summary of the stack trace containing exceptions with null messages and a cyclic + * chain + * @author Kay Kasemir, Kunal Shroff + */ +@SuppressWarnings("nls") +public class ExceptionStacktraceWithNullErrorDialogDemo extends ApplicationWrapper +{ + @Override + public void start(final Stage stage) + { + Exception rootCause = new FileNotFoundException(); + Exception cause = new IOException(); + cause.initCause(rootCause); + rootCause.initCause(cause); + Exception ex = new Exception("This is a test", cause); + ExceptionDetailsErrorDialog.openError(null,"Test", "", ex, true); + } + + public static void main(String[] args) + { + launch(ExceptionStacktraceWithNullErrorDialogDemo.class, args); + } +} diff --git a/docs/source/architecture.rst b/docs/source/architecture.rst index a4460ef694..191730307a 100644 --- a/docs/source/architecture.rst +++ b/docs/source/architecture.rst @@ -1,7 +1,7 @@ Architecture ============ -.. figure:: architecture.png +.. figure:: phoebus_architecture.png The fundamental phoebus architecture consists of **core** modules, user-interface related **core-ui** modules, and **app** modules. diff --git a/docs/source/phoebus_architecture.png b/docs/source/phoebus_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..0a622625ec2e19efe23d02f26ead1e7f282ad64a GIT binary patch literal 180763 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

xG+WG(C?5zW$+_wK=9YF;|q`L$` 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 2023-05-24T19:31:02Z - 7.0.9 - 1.0.6 + 7.0.10 + 1.0.7 19 2.12.3 1.14 diff --git a/services/alarm-config-logger/src/main/java/org/phoebus/alarm/logging/AlarmConfigLogger.java b/services/alarm-config-logger/src/main/java/org/phoebus/alarm/logging/AlarmConfigLogger.java index 3c30ea00b9..3d486eb269 100644 --- a/services/alarm-config-logger/src/main/java/org/phoebus/alarm/logging/AlarmConfigLogger.java +++ b/services/alarm-config-logger/src/main/java/org/phoebus/alarm/logging/AlarmConfigLogger.java @@ -115,20 +115,25 @@ private void initialize() { } } // Set up the ssh keys if used - if(Boolean.parseBoolean(props.getProperty("use_ssh_keys", "false"))) { - File sshDir = new File(FS.DETECTED.userHome(), ".ssh"); - JGitKeyCache cache = new JGitKeyCache(); - SshdSessionFactoryBuilder builder = new SshdSessionFactoryBuilder(); - if(props.containsKey("private_key")) { - File key = new File(props.getProperty("private_key")); - builder.setDefaultKeysProvider(file -> new CachingKeyPairProvider(List.of(key.getAbsoluteFile().toPath()), cache)); - } - builder.setHomeDirectory(FS.DETECTED.userHome()); - builder.setSshDirectory(sshDir); - sshdSessionFactory = builder.build(cache); + if(props.containsKey("use_ssh_keys") && Boolean.parseBoolean(props.getProperty("use_ssh_keys"))) { + try { + File sshDir = new File(FS.DETECTED.userHome(), ".ssh"); + JGitKeyCache cache = new JGitKeyCache(); + SshdSessionFactoryBuilder builder = new SshdSessionFactoryBuilder(); + if(props.containsKey("private_key")) { + File key = new File(props.getProperty("private_key")); + builder.setDefaultKeysProvider(file -> new CachingKeyPairProvider(List.of(key.getAbsoluteFile().toPath()), cache)); + } + builder.setHomeDirectory(FS.DETECTED.userHome()); + builder.setSshDirectory(sshDir); + sshdSessionFactory = builder.build(cache); + } catch (NullPointerException e) { + logger.log(Level.WARNING, "Failed to open .ssh/private_key", e); + } + } // Setup basic username/password auth - if (props.contains("username") && props.contains("password")) { + if (props.containsKey("username") && props.containsKey("password")) { usernamePasswordCredentialsProvider = new UsernamePasswordCredentialsProvider( props.getProperty("username"), props.getProperty("password")); @@ -268,7 +273,7 @@ private synchronized void processAlarmConfigMessages(String rawPath, String alar PushCommand pushCommand = git.push(); pushCommand.setRemote(REMOTE_NAME); pushCommand.setForce(true); - if (Boolean.parseBoolean(props.getProperty("use_ssh_keys", "false"))) { + if (Boolean.parseBoolean(props.getProperty("use_ssh_keys"))) { pushCommand.setTransportConfigCallback(transport -> { SshTransport sshTransport = (SshTransport) transport; sshTransport.setSshSessionFactory(sshdSessionFactory); diff --git a/services/alarm-server/src/main/java/org/phoebus/applications/alarm/server/ServerModel.java b/services/alarm-server/src/main/java/org/phoebus/applications/alarm/server/ServerModel.java index 6021d88ab9..6c6fb2ad1e 100644 --- a/services/alarm-server/src/main/java/org/phoebus/applications/alarm/server/ServerModel.java +++ b/services/alarm-server/src/main/java/org/phoebus/applications/alarm/server/ServerModel.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import org.apache.kafka.clients.consumer.Consumer; @@ -76,22 +77,23 @@ class ServerModel /** Did the last connectivity check fail? */ private boolean connection_lost = false; + /** + * Timeout in seconds waiting for response from Kafka when sending producer messages. + */ + private static final int KAFKA_CLIENT_TIMEOUT = 10; /** @param kafka_servers Servers * @param config_name Name of alarm tree root * @param initial_states * @param listener * @param kafka_properties_file Additional properties to pass to the kafka client - * @throws Exception on error */ public ServerModel(final String kafka_servers, final String config_name, final ConcurrentHashMap initial_states, final ServerModelListener listener, - final String kafka_properties_file) throws Exception + final String kafka_properties_file) { this.initial_states = initial_states; - // initial_states.entrySet().forEach(state -> - // System.out.println("Initial state for " + state.getKey() + " : " + state.getValue())); config_state_topic = Objects.requireNonNull(config_name); command_topic = config_name + AlarmSystem.COMMAND_TOPIC_SUFFIX; @@ -187,7 +189,7 @@ private void checkConnectivity(final long now) // but silently drops them, so clients will get out of sync, // and since Kafka is down, it won't track the most recent alarm state // for future clients... - if (connected == false && connection_lost == false) + if (!connected && !connection_lost) logger.log(Level.WARNING, "Lost Kafka connectitity"); else if (connected && connection_lost) { @@ -334,9 +336,8 @@ public AlarmTreeItem findNode(final String path) throws Exception * * @param name PV name * @return Node, null if model does not contain the PV - * @throws Exception on error */ - public AlarmServerPV findPV(final String name) throws Exception + public AlarmServerPV findPV(final String name) { return findPV(name, root); } @@ -471,7 +472,7 @@ public void sendStateUpdate(final String path, final BasicState new_state) { final String json = new_state == null ? null : new String(JsonModelWriter.toJsonBytes(new_state, AlarmLogic.getMaintenanceMode(), AlarmLogic.getDisableNotify())); final ProducerRecord record = new ProducerRecord<>(config_state_topic, AlarmSystem.STATE_PREFIX + path, json); - producer.send(record); + producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS); last_state_update = System.currentTimeMillis(); } catch (Throwable ex) @@ -482,7 +483,7 @@ public void sendStateUpdate(final String path, final BasicState new_state) /** Send alarm update to 'config' topic * @param path Path of item that has a new state - * @param new_state That new state + * @param config That new state */ public void sendConfigUpdate(final String path, final AlarmTreeItem config) { @@ -490,7 +491,7 @@ public void sendConfigUpdate(final String path, final AlarmTreeItem { final String json = config == null ? null : new String(JsonModelWriter.toJsonBytes(config)); final ProducerRecord record = new ProducerRecord<>(config_state_topic, AlarmSystem.CONFIG_PREFIX + path, json); - producer.send(record); + producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS); } catch (Throwable ex) { @@ -511,7 +512,7 @@ public void sendAnnunciatorMessage(final String path, final SeverityLevel severi final String json = JsonModelWriter.talkToString(severity, message); final ProducerRecord record = new ProducerRecord<>(talk_topic, AlarmSystem.TALK_PREFIX + path, json); - producer.send(record); + producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS); } catch (Throwable ex) { From 6251d2ada2fe5853cbadaabc260dfaf8058c3927 Mon Sep 17 00:00:00 2001 From: Jem Bishop Date: Mon, 15 Jan 2024 13:03:51 +0000 Subject: [PATCH 04/92] use pv pool --- .../epics/SnapshotRestorer.java | 136 ++++++++++-------- 1 file changed, 74 insertions(+), 62 deletions(-) diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java index 4d5bb04ebb..6f0ff6a2ae 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java @@ -1,8 +1,9 @@ package org.phoebus.service.saveandrestore.epics; +import java.io.File; +import java.io.FileInputStream; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -26,81 +27,92 @@ import org.epics.vtype.VUIntArray; import org.epics.vtype.VULongArray; import org.epics.vtype.VUShortArray; + +import org.phoebus.framework.preferences.PropertyPreferenceLoader; import org.phoebus.applications.saveandrestore.model.SnapshotItem; import org.phoebus.core.vtypes.VTypeHelper; -import org.phoebus.service.saveandrestore.epics.RestoreResult; -public class SnapshotRestorer { +import org.phoebus.pv.PV; +import org.phoebus.pv.PVPool; - PVAClient pva; - private final Logger LOG = Logger.getLogger(SnapshotRestorer.class.getName()); - private long timeoutMillis = 5000; +public class SnapshotRestorer { - public SnapshotRestorer() throws Exception { - pva = new PVAClient(); - } + PVAClient pva; + private final Logger LOG = Logger.getLogger(SnapshotRestorer.class.getName()); - /** Restore PV values from a list of snapshot items - * - *

Writes concurrently (with timeout) the pv value to the non null set PVs in the snapshot items. - Uses synchonized to ensure only one frontend can write at a time. - Returns a list of the snapshot items you have set, with an error message if an - error occurred. - * - * @param snapshotItems {@link SnapshotItem} - */ - public synchronized List restorePVValues(List snapshotItems) { + public SnapshotRestorer() throws Exception { + pva = new PVAClient(); + final File site_settings = new File("settings.ini"); + if (site_settings.canRead()) { + LOG.config("Loading settings from " + site_settings); + PropertyPreferenceLoader.load(new FileInputStream(site_settings)); + } + } - var futures = snapshotItems.stream().filter( - (snapshot_item) -> snapshot_item.getConfigPv().getPvName() != null) - .map((snapshotItem) -> { - var pvName = snapshotItem.getConfigPv().getPvName(); - var pvValue = snapshotItem.getValue(); - var channel = pva.getChannel(pvName); - CompletableFuture connected = channel.connect().completeOnTimeout(false, timeoutMillis, TimeUnit.MILLISECONDS); - CompletableFuture writeFuture = connected.thenComposeAsync( - (isConnected) -> { - String error; - try { - if (isConnected) { - Object rawValue = vTypeToObject(pvValue); - channel.write(true, "value", rawValue).get(timeoutMillis, TimeUnit.MILLISECONDS); - error = null; - } else { - LOG.warning(String.format("Tried to set %s but the channel is disconnnected", pvName)); - error = "PV disconnected"; - } - } catch (Exception e) { - error = e.getMessage(); - LOG.warning(String.format("Error setting PV %s", error)); - } finally { - channel.close(); - } + /** + * Restore PV values from a list of snapshot items + * + *

+ * Writes concurrently the pv value to the non null set PVs in + * the snapshot items. + * Uses synchonized to ensure only one frontend can write at a time. + * Returns a list of the snapshot items you have set, with an error message if + * an error occurred. + * + * @param snapshotItems {@link SnapshotItem} + */ + public synchronized List restorePVValues(List snapshotItems) { + var futures = snapshotItems.stream().filter( + (snapshot_item) -> snapshot_item.getConfigPv().getPvName() != null) + .map((snapshotItem) -> { + var pvName = snapshotItem.getConfigPv().getPvName(); + var pvValue = snapshotItem.getValue(); + Object rawValue = vTypeToObject(pvValue); + PV pv; + CompletableFuture future; + try { + pv = PVPool.getPV(pvName); + future = pv.asyncWrite(rawValue); + } catch (Exception e) { var restoreResult = new RestoreResult(); + var errorMsg = e.getMessage(); restoreResult.setSnapshotItem(snapshotItem); - restoreResult.setErrorMsg(error); + restoreResult.setErrorMsg(errorMsg); + LOG.warning(String.format("Error writing to channel %s %s", pvName, errorMsg)); return CompletableFuture.completedFuture(restoreResult); - }); - return writeFuture; - }) - .collect(Collectors.toList()); + } + return future.handle((result, ex) -> { + String errorMsg; + if (ex != null) { + errorMsg = ex.getMessage(); + LOG.warning(String.format("Error writing to channel %s %s", pvName, errorMsg)); + } else { + errorMsg = null; + } + var restoreResult = new RestoreResult(); + restoreResult.setSnapshotItem(snapshotItem); + restoreResult.setErrorMsg(errorMsg); + return restoreResult; + }); + }) + .collect(Collectors.toList()); - CompletableFuture all_done = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + CompletableFuture all_done = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - // Wait on the futures concurrently - all_done.join(); + // Wait on the futures concurrently + all_done.join(); - // Joins should not block as all the futures should be completed. - return futures.stream().map( - (future) -> future.join() - ).collect(Collectors.toList()); - } + // Joins should not block as all the futures should be completed. + return futures.stream().map( + (future) -> future.join()).collect(Collectors.toList()); + } /** - * Convert a vType to its Object representation - * @param type {@link VType} - */ - private Object vTypeToObject(VType type) { + * Convert a vType to its Object representation + * + * @param type {@link VType} + */ + private Object vTypeToObject(VType type) { if (type == null) { return null; } @@ -136,5 +148,5 @@ private Object vTypeToObject(VType type) { return ((VBoolean) type).getValue(); } return null; - } + } } From 413ad136709d7775e797c0c2d62882c8d4a65410 Mon Sep 17 00:00:00 2001 From: Jem Bishop Date: Mon, 22 Jan 2024 14:55:23 +0000 Subject: [PATCH 05/92] add test for snapshot restorer --- .../web/controllers/SnapshotRestorerTest.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java new file mode 100644 index 0000000000..ca8c00f012 --- /dev/null +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java @@ -0,0 +1,51 @@ +package org.phoebus.service.saveandrestore.web.controllers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Arrays; + +import org.epics.vtype.Time; +import org.epics.vtype.VFloat; +import org.epics.vtype.Alarm; +import org.epics.vtype.Display; +import org.junit.jupiter.api.Test; +import org.phoebus.applications.saveandrestore.model.ConfigPv; +import org.phoebus.applications.saveandrestore.model.SnapshotItem; +import org.phoebus.core.vtypes.VTypeHelper; +import org.phoebus.pv.PV; +import org.phoebus.pv.PVPool; +import org.phoebus.service.saveandrestore.epics.SnapshotRestorer; + +public class SnapshotRestorerTest { + + @Test + public void testRestorePVValues() throws Exception { + var snapshotRestorer = new SnapshotRestorer(); + PV pv = PVPool.getPV("loc://x(42.0)"); + var configPv = new ConfigPv(); + configPv.setPvName("loc://x"); + + var testSnapshotItem = new SnapshotItem(); + testSnapshotItem.setConfigPv(configPv); + testSnapshotItem.setValue(VFloat.of(1.0, Alarm.noValue(), Time.now(), Display.none())); + snapshotRestorer.restorePVValues( + Arrays.asList(testSnapshotItem)); + var pvValue = pv.asyncRead().get(); + assertEquals(VTypeHelper.toObject(pvValue), 1.0); + } + + @Test + public void testCannotConnectPV() throws Exception { + var snapshotRestorer = new SnapshotRestorer(); + var configPv = new ConfigPv(); + configPv.setPvName("pva://x"); + + var testSnapshotItem = new SnapshotItem(); + testSnapshotItem.setConfigPv(configPv); + testSnapshotItem.setValue(VFloat.of(1.0, Alarm.noValue(), Time.now(), Display.none())); + var result = snapshotRestorer.restorePVValues( + Arrays.asList(testSnapshotItem)); + assertNotNull(result.get(0).getErrorMsg()); + } +} From 6ccbf4fff77d07a0d6f67957585885963e3b32d0 Mon Sep 17 00:00:00 2001 From: Jem Bishop Date: Fri, 26 Jan 2024 10:56:47 +0000 Subject: [PATCH 06/92] update snapshot version of core-pva --- services/save-and-restore/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml index 246fb018ee..1a17eb5e06 100644 --- a/services/save-and-restore/pom.xml +++ b/services/save-and-restore/pom.xml @@ -67,12 +67,12 @@ org.phoebus core-pva - 4.7.3-SNAPSHOT + 4.7.4-SNAPSHOT org.phoebus core-pv - 4.7.3-SNAPSHOT + 4.7.4-SNAPSHOT From 3bc5db5ba1ec40b0e0f5a9ba1755d80a1718aa8d Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 30 Jan 2024 13:18:47 +0100 Subject: [PATCH 07/92] Automatic purge of alarm log indices --- .../logging/AlarmLoggingConfiguration.java | 36 ++++++ .../alarm/logging/AlarmLoggingService.java | 3 + .../logging/purge/ElasticIndexPurger.java | 116 ++++++++++++++++++ .../src/main/resources/application.properties | 14 ++- .../resources/test_application_1.properties | 58 +++++++++ 5 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmLoggingConfiguration.java create mode 100644 services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java create mode 100644 services/alarm-logger/src/test/resources/test_application_1.properties diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmLoggingConfiguration.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmLoggingConfiguration.java new file mode 100644 index 0000000000..81cae7eed1 --- /dev/null +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmLoggingConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +package org.phoebus.alarm.logging; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AlarmLoggingConfiguration { + + @Value("${days:150}") + public int days; + + @Bean + public int getDays(){ + return days; + } +} diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmLoggingService.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmLoggingService.java index 5c6fd41aeb..d6aa744200 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmLoggingService.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmLoggingService.java @@ -20,8 +20,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class AlarmLoggingService { /** Alarm system logger */ diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java new file mode 100644 index 0000000000..b79d00304c --- /dev/null +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2023 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +package org.phoebus.alarm.logging.purge; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.FieldSort; +import co.elastic.clients.elasticsearch._types.SortOptions; +import co.elastic.clients.elasticsearch._types.SortOrder; +import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery; +import co.elastic.clients.elasticsearch.cat.IndicesResponse; +import co.elastic.clients.elasticsearch.cat.indices.IndicesRecord; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest; +import co.elastic.clients.elasticsearch.indices.DeleteIndexResponse; +import org.phoebus.alarm.logging.ElasticClientHelper; +import org.phoebus.alarm.logging.rest.AlarmLogMessage; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Utility class purging Elasticsearch from indices considered obsolete based on the retention_period_time + * application property. Any value below 100 (days) suppresses instantiation of this {@link Component}. + * To determine last updated date of an index, each Elasticsearch index considered related to alarms is queried for last + * inserted document. The message_time field of that document is compared to the retention period to determine + * if the index should be deleted. + * A cron expression application property is used to define when to run the purging process. + */ +@Component +// Enable only of retention period is >= 100 days +@ConditionalOnExpression("#{T(java.lang.Integer).parseInt('${retention_period_days}') >= 100}") +public class ElasticIndexPurger { + + private static final Logger logger = Logger.getLogger(ElasticIndexPurger.class.getName()); + + private ElasticsearchClient elasticsearchClient; + + @SuppressWarnings("unused") + @Value("${retention_period_days:0}") + private int retentionPeriod; + + @SuppressWarnings("unused") + @PostConstruct + public void init() { + elasticsearchClient = ElasticClientHelper.getInstance().getClient(); + } + + /** + * Deletes Elasticsearch indices based on the {@link AlarmLogMessage#getMessage_time()} for each index found + * by the client. The message time {@link Instant} is compared to current time minus the number of days specified as + * application property. + */ + @SuppressWarnings("unused") + @Scheduled(cron = "${purge_cron_expr}") + public void purgeElasticIndices() { + try { + IndicesResponse indicesResponse = elasticsearchClient.cat().indices(); + List indicesRecords = indicesResponse.valueBody(); + Instant toInstant = Instant.now().minus(retentionPeriod, ChronoUnit.DAYS); + for (IndicesRecord indicesRecord : indicesRecords) { + // Elasticsearch may contain indices other than alarm indices... + String indexName = indicesRecord.index(); + if (indexName != null && !indexName.startsWith("_alarms") && (indexName.contains("_alarms_state") || + indexName.contains("_alarms_cmd") || + indexName.contains("_alarms_config"))) { + // Find most recent document - based on message_time - in the alarm index. + SearchRequest searchRequest = SearchRequest.of(s -> + s.index(indexName) + .query(new MatchAllQuery.Builder().build()._toQuery()) + .size(1) + .sort(SortOptions.of(so -> so.field(FieldSort.of(f -> f.field("message_time").order(SortOrder.Desc)))))); + SearchResponse searchResponse = elasticsearchClient.search(searchRequest, AlarmLogMessage.class); + if (!searchResponse.hits().hits().isEmpty()) { + AlarmLogMessage alarmLogMessage = searchResponse.hits().hits().get(0).source(); + if (alarmLogMessage != null && alarmLogMessage.getMessage_time().isBefore(toInstant)) { + DeleteIndexRequest deleteIndexRequest = DeleteIndexRequest.of(d -> d.index(indexName)); + DeleteIndexResponse deleteIndexResponse = elasticsearchClient.indices().delete(deleteIndexRequest); + logger.log(Level.INFO, "Delete index " + indexName + " acknowledged: " + deleteIndexResponse.acknowledged()); + } + } else { + logger.log(Level.WARNING, "Index " + indexName + " cannot be evaluated for removal as document count is zero."); + } + } + } + } catch (IOException e) { + logger.log(Level.WARNING, "Elastic query failed", e); + } + } +} diff --git a/services/alarm-logger/src/main/resources/application.properties b/services/alarm-logger/src/main/resources/application.properties index d35c1565dd..d2be0a9eaf 100644 --- a/services/alarm-logger/src/main/resources/application.properties +++ b/services/alarm-logger/src/main/resources/application.properties @@ -43,4 +43,16 @@ thread_pool_size=4 ############################## REST Logging ############################### # DEBUG level will log all requests and responses to and from the REST end points -logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=INFO \ No newline at end of file +logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=INFO + +############################## Index purge settings ####################### +# Minimum age (in days) of Elasticsearch indices eligible for automatic index purge. +# Any value below 100 will disable automatic purge. +# Non-numeric value will fail service startup. +retention_period_days=0 + +# Cron expression used by Spring scheduler running automatic purge, default every Sunday at midnight. +# See https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html +# Incorrect syntax will fail service startup if retention_period_days >= 100. +purge_cron_expr=0 0 0 * * SUN +############################################################################## diff --git a/services/alarm-logger/src/test/resources/test_application_1.properties b/services/alarm-logger/src/test/resources/test_application_1.properties new file mode 100644 index 0000000000..d2be0a9eaf --- /dev/null +++ b/services/alarm-logger/src/test/resources/test_application_1.properties @@ -0,0 +1,58 @@ +version=@project.version@ + +# The server port for the rest service +server.port=8080 + +# Disable the spring banner +spring.main.banner-mode=off + +# Disable the auto configured springboot elastic client +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration + +# Suppress the logging from spring boot during debugging this should be set to DEBUG +logging.level.root=WARN + +# Alarm topics to be logged, they can be defined as a comma separated list +alarm_topics=Accelerator + +# Location of elastic node/s +es_host=localhost +es_port=9200 +# Max default size for es queries +es_max_size=1000 +# Set to 'true' if sniffing to be enabled to discover other cluster nodes +es_sniff=false + +# When set to true, the service will automatically create the index templates needed +es_create_templates=true + +# Kafka server location +bootstrap.servers=localhost:9092 + +# Kafka client properties file +kafka_properties= + +# A flag indicating if the service should use index names based on date/time generated periodically based on date_span_units +use_dated_index_names=true + +# The units of the indices date span: Days (D), Weeks(W), Months(M), Years(Y). +date_span_units=M + +# Size of the thread pool for message and command loggers. Two threads per topic/configuration are required +thread_pool_size=4 + +############################## REST Logging ############################### +# DEBUG level will log all requests and responses to and from the REST end points +logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=INFO + +############################## Index purge settings ####################### +# Minimum age (in days) of Elasticsearch indices eligible for automatic index purge. +# Any value below 100 will disable automatic purge. +# Non-numeric value will fail service startup. +retention_period_days=0 + +# Cron expression used by Spring scheduler running automatic purge, default every Sunday at midnight. +# See https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html +# Incorrect syntax will fail service startup if retention_period_days >= 100. +purge_cron_expr=0 0 0 * * SUN +############################################################################## From 2295dbe75f0746edcbe435cce1d083c5660c5809 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 30 Jan 2024 13:19:30 +0100 Subject: [PATCH 08/92] Removed unused properties file --- .../resources/test_application_1.properties | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 services/alarm-logger/src/test/resources/test_application_1.properties diff --git a/services/alarm-logger/src/test/resources/test_application_1.properties b/services/alarm-logger/src/test/resources/test_application_1.properties deleted file mode 100644 index d2be0a9eaf..0000000000 --- a/services/alarm-logger/src/test/resources/test_application_1.properties +++ /dev/null @@ -1,58 +0,0 @@ -version=@project.version@ - -# The server port for the rest service -server.port=8080 - -# Disable the spring banner -spring.main.banner-mode=off - -# Disable the auto configured springboot elastic client -spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration - -# Suppress the logging from spring boot during debugging this should be set to DEBUG -logging.level.root=WARN - -# Alarm topics to be logged, they can be defined as a comma separated list -alarm_topics=Accelerator - -# Location of elastic node/s -es_host=localhost -es_port=9200 -# Max default size for es queries -es_max_size=1000 -# Set to 'true' if sniffing to be enabled to discover other cluster nodes -es_sniff=false - -# When set to true, the service will automatically create the index templates needed -es_create_templates=true - -# Kafka server location -bootstrap.servers=localhost:9092 - -# Kafka client properties file -kafka_properties= - -# A flag indicating if the service should use index names based on date/time generated periodically based on date_span_units -use_dated_index_names=true - -# The units of the indices date span: Days (D), Weeks(W), Months(M), Years(Y). -date_span_units=M - -# Size of the thread pool for message and command loggers. Two threads per topic/configuration are required -thread_pool_size=4 - -############################## REST Logging ############################### -# DEBUG level will log all requests and responses to and from the REST end points -logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=INFO - -############################## Index purge settings ####################### -# Minimum age (in days) of Elasticsearch indices eligible for automatic index purge. -# Any value below 100 will disable automatic purge. -# Non-numeric value will fail service startup. -retention_period_days=0 - -# Cron expression used by Spring scheduler running automatic purge, default every Sunday at midnight. -# See https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html -# Incorrect syntax will fail service startup if retention_period_days >= 100. -purge_cron_expr=0 0 0 * * SUN -############################################################################## From 996f4552eecbcd261337a987c5174d981f6ebec2 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 30 Jan 2024 13:41:40 +0100 Subject: [PATCH 09/92] Adding documentation on automatic purge --- services/alarm-logger/doc/index.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/services/alarm-logger/doc/index.rst b/services/alarm-logger/doc/index.rst index f825904256..a23dca6833 100644 --- a/services/alarm-logger/doc/index.rst +++ b/services/alarm-logger/doc/index.rst @@ -32,3 +32,19 @@ Examples: * **Commands** e.g. a user actions to *Acknowledge* an alarm + +**************************************** +Automatic purge of Elasticsearch indices +**************************************** + +To avoid issues related to a high number of Elasticsearch indices, automatic purge can be enabled in order to delete +indices considered obsolete. This is done by setting the preference ``retention_period_days`` to a number larger +or equal to 100. The default value is 0, i.e. automatic purge is disabled by default. + +The automatic purge is run using a cron expression defined in preference ``purge_cron_expr``, default is +``0 0 0 * * SUN``, i.e. midnight each Sunday. See the SpringDocumentation_ on how to define the cron expression. + +An Elasticsearch index is considered eligible for deletion if the last inserted message date is before current time +minus ``retention_period_days``. + +.. _SpringDocumentation: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html \ No newline at end of file From 3e3be186f334d92ddac79ddd6fb4a51bec00fdaf Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 31 Jan 2024 14:21:58 +0100 Subject: [PATCH 10/92] Updated logic evaluating enable/disable index purge --- services/alarm-logger/doc/index.rst | 6 +-- .../logging/purge/ElasticIndexPurger.java | 34 +++++++++++++++-- .../logging/rest/AlarmLogSearchUtil.java | 37 +++++++++++-------- .../src/main/resources/application.properties | 9 +++-- 4 files changed, 61 insertions(+), 25 deletions(-) diff --git a/services/alarm-logger/doc/index.rst b/services/alarm-logger/doc/index.rst index a23dca6833..12a6bfa5f0 100644 --- a/services/alarm-logger/doc/index.rst +++ b/services/alarm-logger/doc/index.rst @@ -38,13 +38,13 @@ Automatic purge of Elasticsearch indices **************************************** To avoid issues related to a high number of Elasticsearch indices, automatic purge can be enabled in order to delete -indices considered obsolete. This is done by setting the preference ``retention_period_days`` to a number larger -or equal to 100. The default value is 0, i.e. automatic purge is disabled by default. +indices considered obsolete. This is done by setting the preferences ``date_span_units`` and ``retain_indices_count`` such +that they evaluate to a number larger or equal to 100. The default ``retain_indices_count`` is 0, i.e. automatic purge is disabled by default. The automatic purge is run using a cron expression defined in preference ``purge_cron_expr``, default is ``0 0 0 * * SUN``, i.e. midnight each Sunday. See the SpringDocumentation_ on how to define the cron expression. An Elasticsearch index is considered eligible for deletion if the last inserted message date is before current time -minus ``retention_period_days``. +minus the number of days computed from ``date_span_units`` and ``retain_indices_count``. .. _SpringDocumentation: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html \ No newline at end of file diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java index b79d00304c..9b2a9fbfaf 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java @@ -32,6 +32,7 @@ import co.elastic.clients.elasticsearch.indices.DeleteIndexResponse; import org.phoebus.alarm.logging.ElasticClientHelper; import org.phoebus.alarm.logging.rest.AlarmLogMessage; +import org.phoebus.alarm.logging.rest.AlarmLogSearchUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.scheduling.annotation.Scheduled; @@ -46,8 +47,8 @@ import java.util.logging.Logger; /** - * Utility class purging Elasticsearch from indices considered obsolete based on the retention_period_time - * application property. Any value below 100 (days) suppresses instantiation of this {@link Component}. + * Utility class purging Elasticsearch from indices considered obsolete based on the date_span_units and retain_indices_count + * application properties. If these result in a value below 100 (days), this {@link Component} will not be instantiated. * To determine last updated date of an index, each Elasticsearch index considered related to alarms is queried for last * inserted document. The message_time field of that document is compared to the retention period to determine * if the index should be deleted. @@ -55,7 +56,7 @@ */ @Component // Enable only of retention period is >= 100 days -@ConditionalOnExpression("#{T(java.lang.Integer).parseInt('${retention_period_days}') >= 100}") +@ConditionalOnExpression("#{T(org.phoebus.alarm.logging.purge.ElasticIndexPurger.EnableCondition).getRetentionDays('${date_span_units}', '${retain_indices_count}') >= 100}") public class ElasticIndexPurger { private static final Logger logger = Logger.getLogger(ElasticIndexPurger.class.getName()); @@ -113,4 +114,31 @@ public void purgeElasticIndices() { logger.log(Level.WARNING, "Elastic query failed", e); } } + + /** + * Helper class used to determine whether this service should be enabled or not + */ + public static class EnableCondition { + + /** + * + * @param dateSpanUnits Any of the values Y, M, W, D + * @param retainIndicesCountString String value of the retain_indices_count preference + * @return A number computed from input. In case input arguments are invalid (e.g. non-numerical value + * for retain_indices_coun), then 0 is returned to indicate that this {@link Component} should not be enabled. + */ + @SuppressWarnings("unused") + public static int getRetentionDays(String dateSpanUnits, String retainIndicesCountString) { + int days = AlarmLogSearchUtil.getDateSpanInDays(dateSpanUnits); + if (days == -1) { + return 0; + } + try { + int retainIndicesCount = Integer.parseInt(retainIndicesCountString); + return days * retainIndicesCount; + } catch (NumberFormatException e) { + return 0; + } + } + } } diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java index 841a21d6a1..dedf7bfe33 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java @@ -360,21 +360,7 @@ public static List findIndexNames(String baseIndexName, Instant fromInst if (fromIndex.equalsIgnoreCase(toIndex)) { indexList.add(fromIndex); } else { - int indexDateSpanDayValue = -1; - switch (indexDateSpanUnits) { - case "Y": - indexDateSpanDayValue = 365; - break; - case "M": - indexDateSpanDayValue = 30; - break; - case "W": - indexDateSpanDayValue = 7; - break; - case "D": - indexDateSpanDayValue = 1; - break; - } + int indexDateSpanDayValue = getDateSpanInDays(indexDateSpanUnits); indexList.add(fromIndex); while (!fromIndex.equalsIgnoreCase(toIndex)) { fromInstant = fromInstant.plus(indexDateSpanDayValue, ChronoUnit.DAYS); @@ -386,4 +372,25 @@ public static List findIndexNames(String baseIndexName, Instant fromInst return indexList; } + + /** + * + * @param indexDateSpanUnits A single char string from [Y, M, W, D] + * @return Number of days corresponding to the unit, or -1 if the input does not match + * supported chars. + */ + public static int getDateSpanInDays(String indexDateSpanUnits){ + switch (indexDateSpanUnits) { + case "Y": + return 365; + case "M": + return 30; + case "W": + return 7; + case "D": + return 1; + default: + return -1; + } + } } diff --git a/services/alarm-logger/src/main/resources/application.properties b/services/alarm-logger/src/main/resources/application.properties index d2be0a9eaf..e5da002c53 100644 --- a/services/alarm-logger/src/main/resources/application.properties +++ b/services/alarm-logger/src/main/resources/application.properties @@ -46,10 +46,11 @@ thread_pool_size=4 logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=INFO ############################## Index purge settings ####################### -# Minimum age (in days) of Elasticsearch indices eligible for automatic index purge. -# Any value below 100 will disable automatic purge. -# Non-numeric value will fail service startup. -retention_period_days=0 +# How many indices to retain (e.g. if you have selected date_span_units as M and set the retain_indices_count to 6, then indices +# older than 6 months will be deleted. +# Number of days computed form this setting and date_span_units must be greater or equal to 100 +# for automatic purge to be enabled. +retain_indices_count=0 # Cron expression used by Spring scheduler running automatic purge, default every Sunday at midnight. # See https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html From 66cee14ea7855e7a540e6074e711b2717ea86d27 Mon Sep 17 00:00:00 2001 From: Jem Bishop Date: Wed, 31 Jan 2024 15:16:34 +0000 Subject: [PATCH 11/92] fix assertions --- .../web/controllers/SnapshotRestorerTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java index ca8c00f012..8ef2b834d6 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java @@ -1,7 +1,6 @@ package org.phoebus.service.saveandrestore.web.controllers; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import org.junit.jupiter.api.Assertions; import java.util.Arrays; @@ -32,7 +31,7 @@ public void testRestorePVValues() throws Exception { snapshotRestorer.restorePVValues( Arrays.asList(testSnapshotItem)); var pvValue = pv.asyncRead().get(); - assertEquals(VTypeHelper.toObject(pvValue), 1.0); + Assertions.assertEquals(VTypeHelper.toObject(pvValue), 1.0); } @Test @@ -46,6 +45,6 @@ public void testCannotConnectPV() throws Exception { testSnapshotItem.setValue(VFloat.of(1.0, Alarm.noValue(), Time.now(), Display.none())); var result = snapshotRestorer.restorePVValues( Arrays.asList(testSnapshotItem)); - assertNotNull(result.get(0).getErrorMsg()); + Assertions.assertNotNull(result.get(0).getErrorMsg()); } } From 344442b7891f66095bec0dd0cde78edc0953c5a4 Mon Sep 17 00:00:00 2001 From: Jem Bishop Date: Wed, 31 Jan 2024 15:40:06 +0000 Subject: [PATCH 12/92] add test for snapshot restore endpoint results --- .../SnapshotRestorerControllerTest.java | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java new file mode 100644 index 0000000000..4182abedaf --- /dev/null +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java @@ -0,0 +1,112 @@ +package org.phoebus.service.saveandrestore.web.controllers; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.epics.vtype.Alarm; +import org.epics.vtype.Display; +import org.epics.vtype.Time; +import org.epics.vtype.VFloat; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.phoebus.applications.saveandrestore.model.ConfigPv; +import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.NodeType; +import org.phoebus.applications.saveandrestore.model.Snapshot; +import org.phoebus.applications.saveandrestore.model.SnapshotData; +import org.phoebus.applications.saveandrestore.model.SnapshotItem; +import org.phoebus.service.saveandrestore.epics.RestoreResult; +import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; +import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.HttpHeaders; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; + +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = ControllersTestConfig.class) +@TestPropertySource(locations = "classpath:test_application_permit_all.properties") +@WebMvcTest(NodeController.class) +public class SnapshotRestorerControllerTest { + + @Autowired + private NodeDAO nodeDAO; + + @Autowired + private String userAuthorization; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Autowired + private MockMvc mockMvc; + + @Autowired + private String demoUser; + + @Test + public void testRestoreFromSnapshotNode() throws Exception { + + Node node = Node.builder().uniqueId("uniqueId").nodeType(NodeType.SNAPSHOT).userName(demoUser).build(); + Snapshot snapshot = new Snapshot(); + SnapshotData snapshotData = new SnapshotData(); + SnapshotItem item = new SnapshotItem(); + ConfigPv configPv = new ConfigPv(); + configPv.setPvName("loc://x"); + item.setValue(VFloat.of(1.0, Alarm.none(), Time.now(), Display.none())); + item.setConfigPv(configPv); + snapshotData.setSnapshotItems(List.of(item)); + snapshot.setSnapshotData(snapshotData); + snapshot.setSnapshotNode(node); + + when(nodeDAO.getSnapshotData("uniqueId")).thenReturn(snapshotData); + + MockHttpServletRequestBuilder request = post("/restore/node?parentNodeId=uniqueId") + .header(HttpHeaders.AUTHORIZATION, userAuthorization); + + MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON)) + .andReturn(); + + // Make sure response is in the Restore Result json format + objectMapper.readValue( + result.getResponse().getContentAsString(), + new TypeReference>() { + }); + } + + @Test + public void testRestoreFromSnapshotItems() throws Exception { + + SnapshotItem item = new SnapshotItem(); + ConfigPv configPv = new ConfigPv(); + configPv.setPvName("loc://x"); + item.setValue(VFloat.of(1.0, Alarm.none(), Time.now(), Display.none())); + item.setConfigPv(configPv); + + MockHttpServletRequestBuilder request = post("/restore/items") + .header(HttpHeaders.AUTHORIZATION, userAuthorization) + .contentType(JSON) + .content(objectMapper.writeValueAsString(List.of(item))); + + MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON)) + .andReturn(); + + // Make sure response is in the Restore Result json format + objectMapper.readValue( + result.getResponse().getContentAsString(), + new TypeReference>() { + }); + } +} From 1628a7a1c1720aa9f58992122b9f41270b3aa1fa Mon Sep 17 00:00:00 2001 From: Sebastian Marsching Date: Mon, 12 Feb 2024 18:03:16 +0100 Subject: [PATCH 13/92] Split core-pv module (see #2939). The core-pv module is split into separate modules, only leaving the core classes and the internal PV sources in the original module. PV sources external to Phoebus are separated, adding one module for each PV source type. The example Phoebus product now has explicit dependencies on all these modules, so that it offers the same range of PV sources that it did before. --- app/databrowser/pom.xml | 5 ++ .../appliance/ApplianceValueIterator.java | 5 +- .../file/ArchiveFileSampleReader.java | 14 +---- .../reader/util/ChannelAccessStatusUtil.java | 56 +++++++++++++++++ app/display/representation-javafx/pom.xml | 5 ++ core/pom.xml | 5 ++ core/pv-ca/pom.xml | 48 +++++++++++++++ .../java/org/phoebus/pv/ca/DBRHelper.java | 0 .../java/org/phoebus/pv/ca/JCAContext.java | 0 .../main/java/org/phoebus/pv/ca/JCA_PV.java | 0 .../java/org/phoebus/pv/ca/JCA_PVFactory.java | 0 .../org/phoebus/pv/ca/JCA_Preferences.java | 0 .../services/org.phoebus.pv.PVFactory | 1 + .../resources/pv_ca_preferences.properties | 0 .../test/java/org/phoebus/pv/ca}/PVDemo.java | 4 +- core/pv-mqtt/pom.xml | 40 ++++++++++++ .../java/org/phoebus/pv/mqtt/MQTT_PV.java | 0 .../java/org/phoebus/pv/mqtt/MQTT_PVConn.java | 0 .../org/phoebus/pv/mqtt/MQTT_PVFactory.java | 0 .../org/phoebus/pv/mqtt/MQTT_Preferences.java | 0 .../phoebus/pv/mqtt/VTypeToFromString.java | 0 .../services/org.phoebus.pv.PVFactory | 1 + .../resources/pv_mqtt_preferences.properties | 0 .../src/test/python/DemoMQTTPublish.py | 0 .../src/test/python/DemoMQTTSubscribe.py | 0 core/pv-opva/pom.xml | 29 +++++++++ .../java/org/phoebus/pv/opva/Decoders.java | 0 .../org/phoebus/pv/opva/ImageDecoder.java | 0 .../java/org/phoebus/pv/opva/PVA_Context.java | 0 .../main/java/org/phoebus/pv/opva/PVA_PV.java | 1 - .../org/phoebus/pv/opva/PVA_PVFactory.java | 0 .../org/phoebus/pv/opva/PVGetHandler.java | 0 .../org/phoebus/pv/opva/PVNameHelper.java | 0 .../org/phoebus/pv/opva/PVPutHandler.java | 0 .../java/org/phoebus/pv/opva/PVRequester.java | 0 .../phoebus/pv/opva/PVStructureHelper.java | 0 .../services/org.phoebus.pv.PVFactory | 1 + core/pv-pva/pom.xml | 61 +++++++++++++++++++ .../main/java/org/phoebus/pv/pva/Codec.java | 0 .../java/org/phoebus/pv/pva/Decoders.java | 6 +- .../java/org/phoebus/pv/pva/ImageDecoder.java | 0 .../java/org/phoebus/pv/pva/JPEGCodec.java | 0 .../java/org/phoebus/pv/pva/LZ4Codec.java | 0 .../phoebus/pv/pva/PVAStructureHelper.java | 0 .../java/org/phoebus/pv/pva/PVA_Context.java | 0 .../main/java/org/phoebus/pv/pva/PVA_PV.java | 0 .../org/phoebus/pv/pva/PVA_PVFactory.java | 0 .../org/phoebus/pv/pva/PVA_Preferences.java | 0 .../java/org/phoebus/pv/pva/PVNameHelper.java | 0 .../services/org.phoebus.pv.PVFactory | 1 + .../resources/pv_pva_preferences.properties | 0 .../test/java/org/phoebus/pv/pva}/Demo.java | 2 +- .../phoebus/pv/pva}/PVACustomStructDemo.java | 5 +- core/pv-tango/pom.xml | 35 +++++++++++ .../org/phoebus/pv/tga/TangoAttrContext.java | 0 .../java/org/phoebus/pv/tga/TangoAttr_PV.java | 0 .../phoebus/pv/tga/TangoAttr_PVFactory.java | 0 .../org/phoebus/pv/tga/TangoTypeUtil.java | 0 .../org/phoebus/pv/tgc/TangoCmdContext.java | 0 .../java/org/phoebus/pv/tgc/TangoCmd_PV.java | 0 .../phoebus/pv/tgc/TangoCmd_PVFactory.java | 0 .../services/org.phoebus.pv.PVFactory | 2 + core/pv/pom.xml | 35 +---------- .../services/org.phoebus.pv.PVFactory | 6 -- .../test/java/org/phoebus/pv/FormulaTest.java | 8 +-- .../test/java/org/phoebus/pv/PVPoolTest.java | 6 +- .../pv/disconnected/DisconnectedPV.java | 22 +++++++ .../disconnected/DisconnectedPVFactory.java | 32 ++++++++++ .../services/org.phoebus.pv.PVFactory | 1 + phoebus-product/pom.xml | 25 ++++++++ pom.xml | 1 + 71 files changed, 396 insertions(+), 67 deletions(-) create mode 100644 app/databrowser/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java create mode 100644 core/pv-ca/pom.xml rename core/{pv => pv-ca}/src/main/java/org/phoebus/pv/ca/DBRHelper.java (100%) rename core/{pv => pv-ca}/src/main/java/org/phoebus/pv/ca/JCAContext.java (100%) rename core/{pv => pv-ca}/src/main/java/org/phoebus/pv/ca/JCA_PV.java (100%) rename core/{pv => pv-ca}/src/main/java/org/phoebus/pv/ca/JCA_PVFactory.java (100%) rename core/{pv => pv-ca}/src/main/java/org/phoebus/pv/ca/JCA_Preferences.java (100%) create mode 100644 core/pv-ca/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory rename core/{pv => pv-ca}/src/main/resources/pv_ca_preferences.properties (100%) rename core/{pv/src/test/java/org/phoebus/pv => pv-ca/src/test/java/org/phoebus/pv/ca}/PVDemo.java (97%) create mode 100644 core/pv-mqtt/pom.xml rename core/{pv => pv-mqtt}/src/main/java/org/phoebus/pv/mqtt/MQTT_PV.java (100%) rename core/{pv => pv-mqtt}/src/main/java/org/phoebus/pv/mqtt/MQTT_PVConn.java (100%) rename core/{pv => pv-mqtt}/src/main/java/org/phoebus/pv/mqtt/MQTT_PVFactory.java (100%) rename core/{pv => pv-mqtt}/src/main/java/org/phoebus/pv/mqtt/MQTT_Preferences.java (100%) rename core/{pv => pv-mqtt}/src/main/java/org/phoebus/pv/mqtt/VTypeToFromString.java (100%) create mode 100644 core/pv-mqtt/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory rename core/{pv => pv-mqtt}/src/main/resources/pv_mqtt_preferences.properties (100%) rename core/{pv => pv-mqtt}/src/test/python/DemoMQTTPublish.py (100%) rename core/{pv => pv-mqtt}/src/test/python/DemoMQTTSubscribe.py (100%) create mode 100644 core/pv-opva/pom.xml rename core/{pv => pv-opva}/src/main/java/org/phoebus/pv/opva/Decoders.java (100%) rename core/{pv => pv-opva}/src/main/java/org/phoebus/pv/opva/ImageDecoder.java (100%) rename core/{pv => pv-opva}/src/main/java/org/phoebus/pv/opva/PVA_Context.java (100%) rename core/{pv => pv-opva}/src/main/java/org/phoebus/pv/opva/PVA_PV.java (99%) rename core/{pv => pv-opva}/src/main/java/org/phoebus/pv/opva/PVA_PVFactory.java (100%) rename core/{pv => pv-opva}/src/main/java/org/phoebus/pv/opva/PVGetHandler.java (100%) rename core/{pv => pv-opva}/src/main/java/org/phoebus/pv/opva/PVNameHelper.java (100%) rename core/{pv => pv-opva}/src/main/java/org/phoebus/pv/opva/PVPutHandler.java (100%) rename core/{pv => pv-opva}/src/main/java/org/phoebus/pv/opva/PVRequester.java (100%) rename core/{pv => pv-opva}/src/main/java/org/phoebus/pv/opva/PVStructureHelper.java (100%) create mode 100644 core/pv-opva/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory create mode 100644 core/pv-pva/pom.xml rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/Codec.java (100%) rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/Decoders.java (99%) rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/ImageDecoder.java (100%) rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/JPEGCodec.java (100%) rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/LZ4Codec.java (100%) rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/PVAStructureHelper.java (100%) rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/PVA_Context.java (100%) rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/PVA_PV.java (100%) rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/PVA_PVFactory.java (100%) rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/PVA_Preferences.java (100%) rename core/{pv => pv-pva}/src/main/java/org/phoebus/pv/pva/PVNameHelper.java (100%) create mode 100644 core/pv-pva/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory rename core/{pv => pv-pva}/src/main/resources/pv_pva_preferences.properties (100%) rename core/{pv/src/test/java/org/phoebus/pv => pv-pva/src/test/java/org/phoebus/pv/pva}/Demo.java (99%) rename core/{pv/src/test/java/org/phoebus/pv => pv-pva/src/test/java/org/phoebus/pv/pva}/PVACustomStructDemo.java (93%) create mode 100644 core/pv-tango/pom.xml rename core/{pv => pv-tango}/src/main/java/org/phoebus/pv/tga/TangoAttrContext.java (100%) rename core/{pv => pv-tango}/src/main/java/org/phoebus/pv/tga/TangoAttr_PV.java (100%) rename core/{pv => pv-tango}/src/main/java/org/phoebus/pv/tga/TangoAttr_PVFactory.java (100%) rename core/{pv => pv-tango}/src/main/java/org/phoebus/pv/tga/TangoTypeUtil.java (100%) rename core/{pv => pv-tango}/src/main/java/org/phoebus/pv/tgc/TangoCmdContext.java (100%) rename core/{pv => pv-tango}/src/main/java/org/phoebus/pv/tgc/TangoCmd_PV.java (100%) rename core/{pv => pv-tango}/src/main/java/org/phoebus/pv/tgc/TangoCmd_PVFactory.java (100%) create mode 100644 core/pv-tango/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory create mode 100644 core/pv/src/test/java/org/phoebus/pv/disconnected/DisconnectedPV.java create mode 100644 core/pv/src/test/java/org/phoebus/pv/disconnected/DisconnectedPVFactory.java create mode 100644 core/pv/src/test/resources/META-INF/services/org.phoebus.pv.PVFactory diff --git a/app/databrowser/pom.xml b/app/databrowser/pom.xml index dfe4a0bffd..d0139b4640 100644 --- a/app/databrowser/pom.xml +++ b/app/databrowser/pom.xml @@ -64,6 +64,11 @@ protobuf-java 3.21.9 + + org.epics + epics-util + ${epics.util.version} + org.epics pbrawclient diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java b/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java index a3270f5a3e..c070522fe2 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java +++ b/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java @@ -31,6 +31,7 @@ import org.epics.vtype.VString; import org.epics.vtype.VType; import org.phoebus.archive.reader.ValueIterator; +import org.phoebus.archive.reader.util.ChannelAccessStatusUtil; import org.phoebus.archive.vtype.TimestampHelper; import org.phoebus.pv.TimeHelper; @@ -41,8 +42,6 @@ import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadType; -import gov.aps.jca.dbr.Status; - /** * * ApplianceValueIterator is the base class for different value iterators. @@ -359,6 +358,6 @@ protected static AlarmSeverity getSeverity(int severity) { * @return alarm status */ protected static String getStatus(int status) { - return Status.forValue(status).getName(); + return ChannelAccessStatusUtil.idToName(status); } } diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java b/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java index b0535e7047..632003cecd 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java +++ b/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java @@ -34,10 +34,9 @@ import org.epics.vtype.VString; import org.epics.vtype.VType; import org.phoebus.archive.reader.ValueIterator; +import org.phoebus.archive.reader.util.ChannelAccessStatusUtil; import org.phoebus.pv.TimeHelper; -import gov.aps.jca.dbr.Status; - /** Obtains channel archiver samples from channel archiver * data files, and translates them to ArchiveVTypes. * @@ -394,15 +393,6 @@ private static String getStatus(final short severity, final short status) if (severity == 0x0f02) return "Change Sampling Period"; - try - { - final Status stat = Status.forValue(status); - // stat.toString()? - return stat.getName(); - } - catch (Exception ex) - { - return "<" + status + ">"; - } + return ChannelAccessStatusUtil.idToName(status); } } \ No newline at end of file diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java b/app/databrowser/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java new file mode 100644 index 0000000000..4d01e3093d --- /dev/null +++ b/app/databrowser/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.archive.reader.util; + +/** + * Utility class for dealing with CA status codes. + */ +@SuppressWarnings("nls") +public final class ChannelAccessStatusUtil { + + private static String[] names = { + "NO_ALARM", + "READ_ALARM", + "WRITE_ALARM", + "HIHI_ALARM", + "HIGH_ALARM", + "LOLO_ALARM", + "LOW_ALARM", + "STATE_ALARM", + "COS_ALARM", + "COMM_ALARM", + "TIMEOUT_ALARM", + "HW_LIMIT_ALARM", + "CALC_ALARM", + "SCAN_ALARM", + "LINK_ALARM", + "SOFT_ALARM", + "BAD_SUB_ALARM", + "UDF_ALARM", + "DISABLE_ALARM", + "SIMM_ALARM", + "READ_ACCESS_ALARM", + "WRITE_ACCESS_ALARM", + }; + + /** + * Translates a numeric Channel Access status code to a name. + * + * @param id numeric identifier, as used by the over-the-wire protocol. + * @return string representing the status code. + */ + public static String idToName(int id) { + try { + return names[id]; + } catch (IndexOutOfBoundsException e) { + return "<" + id + ">"; + } + } + +} diff --git a/app/display/representation-javafx/pom.xml b/app/display/representation-javafx/pom.xml index ad7f49551e..64bd456960 100644 --- a/app/display/representation-javafx/pom.xml +++ b/app/display/representation-javafx/pom.xml @@ -19,6 +19,11 @@ 1.3 test + + org.apache.commons + commons-lang3 + 3.5 + org.controlsfx controlsfx diff --git a/core/pom.xml b/core/pom.xml index cc0da07e87..31378d8915 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -8,6 +8,11 @@ util pva pv + pv-ca + pv-mqtt + pv-opva + pv-pva + pv-tango security formula ui diff --git a/core/pv-ca/pom.xml b/core/pv-ca/pom.xml new file mode 100644 index 0000000000..f46686bf0e --- /dev/null +++ b/core/pv-ca/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + core-pv-ca + + org.phoebus + core + 4.7.4-SNAPSHOT + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.hamcrest + hamcrest-all + 1.3 + test + + + + org.epics + epics-core + ${epics.version} + pom + + + + org.epics + vtype + ${vtype.version} + + + + org.phoebus + core-pv + 4.7.4-SNAPSHOT + + + + org.phoebus + core-framework + 4.7.4-SNAPSHOT + + + diff --git a/core/pv/src/main/java/org/phoebus/pv/ca/DBRHelper.java b/core/pv-ca/src/main/java/org/phoebus/pv/ca/DBRHelper.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/ca/DBRHelper.java rename to core/pv-ca/src/main/java/org/phoebus/pv/ca/DBRHelper.java diff --git a/core/pv/src/main/java/org/phoebus/pv/ca/JCAContext.java b/core/pv-ca/src/main/java/org/phoebus/pv/ca/JCAContext.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/ca/JCAContext.java rename to core/pv-ca/src/main/java/org/phoebus/pv/ca/JCAContext.java diff --git a/core/pv/src/main/java/org/phoebus/pv/ca/JCA_PV.java b/core/pv-ca/src/main/java/org/phoebus/pv/ca/JCA_PV.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/ca/JCA_PV.java rename to core/pv-ca/src/main/java/org/phoebus/pv/ca/JCA_PV.java diff --git a/core/pv/src/main/java/org/phoebus/pv/ca/JCA_PVFactory.java b/core/pv-ca/src/main/java/org/phoebus/pv/ca/JCA_PVFactory.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/ca/JCA_PVFactory.java rename to core/pv-ca/src/main/java/org/phoebus/pv/ca/JCA_PVFactory.java diff --git a/core/pv/src/main/java/org/phoebus/pv/ca/JCA_Preferences.java b/core/pv-ca/src/main/java/org/phoebus/pv/ca/JCA_Preferences.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/ca/JCA_Preferences.java rename to core/pv-ca/src/main/java/org/phoebus/pv/ca/JCA_Preferences.java diff --git a/core/pv-ca/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory b/core/pv-ca/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory new file mode 100644 index 0000000000..1ec275f931 --- /dev/null +++ b/core/pv-ca/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory @@ -0,0 +1 @@ +org.phoebus.pv.ca.JCA_PVFactory diff --git a/core/pv/src/main/resources/pv_ca_preferences.properties b/core/pv-ca/src/main/resources/pv_ca_preferences.properties similarity index 100% rename from core/pv/src/main/resources/pv_ca_preferences.properties rename to core/pv-ca/src/main/resources/pv_ca_preferences.properties diff --git a/core/pv/src/test/java/org/phoebus/pv/PVDemo.java b/core/pv-ca/src/test/java/org/phoebus/pv/ca/PVDemo.java similarity index 97% rename from core/pv/src/test/java/org/phoebus/pv/PVDemo.java rename to core/pv-ca/src/test/java/org/phoebus/pv/ca/PVDemo.java index 40cd82ce29..97da6af4f4 100644 --- a/core/pv/src/test/java/org/phoebus/pv/PVDemo.java +++ b/core/pv-ca/src/test/java/org/phoebus/pv/ca/PVDemo.java @@ -5,10 +5,12 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ -package org.phoebus.pv; +package org.phoebus.pv.ca; import org.epics.vtype.VType; import org.junit.jupiter.api.Test; +import org.phoebus.pv.PV; +import org.phoebus.pv.PVPool; import java.util.ArrayList; import java.util.List; diff --git a/core/pv-mqtt/pom.xml b/core/pv-mqtt/pom.xml new file mode 100644 index 0000000000..f542751c81 --- /dev/null +++ b/core/pv-mqtt/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + core-pv-mqtt + + org.phoebus + core + 4.7.4-SNAPSHOT + + + + org.epics + epics-util + ${epics.util.version} + + + + org.epics + vtype + ${vtype.version} + + + + org.phoebus + core-pv + 4.7.4-SNAPSHOT + + + + org.phoebus + core-framework + 4.7.4-SNAPSHOT + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.2 + + + diff --git a/core/pv/src/main/java/org/phoebus/pv/mqtt/MQTT_PV.java b/core/pv-mqtt/src/main/java/org/phoebus/pv/mqtt/MQTT_PV.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/mqtt/MQTT_PV.java rename to core/pv-mqtt/src/main/java/org/phoebus/pv/mqtt/MQTT_PV.java diff --git a/core/pv/src/main/java/org/phoebus/pv/mqtt/MQTT_PVConn.java b/core/pv-mqtt/src/main/java/org/phoebus/pv/mqtt/MQTT_PVConn.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/mqtt/MQTT_PVConn.java rename to core/pv-mqtt/src/main/java/org/phoebus/pv/mqtt/MQTT_PVConn.java diff --git a/core/pv/src/main/java/org/phoebus/pv/mqtt/MQTT_PVFactory.java b/core/pv-mqtt/src/main/java/org/phoebus/pv/mqtt/MQTT_PVFactory.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/mqtt/MQTT_PVFactory.java rename to core/pv-mqtt/src/main/java/org/phoebus/pv/mqtt/MQTT_PVFactory.java diff --git a/core/pv/src/main/java/org/phoebus/pv/mqtt/MQTT_Preferences.java b/core/pv-mqtt/src/main/java/org/phoebus/pv/mqtt/MQTT_Preferences.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/mqtt/MQTT_Preferences.java rename to core/pv-mqtt/src/main/java/org/phoebus/pv/mqtt/MQTT_Preferences.java diff --git a/core/pv/src/main/java/org/phoebus/pv/mqtt/VTypeToFromString.java b/core/pv-mqtt/src/main/java/org/phoebus/pv/mqtt/VTypeToFromString.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/mqtt/VTypeToFromString.java rename to core/pv-mqtt/src/main/java/org/phoebus/pv/mqtt/VTypeToFromString.java diff --git a/core/pv-mqtt/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory b/core/pv-mqtt/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory new file mode 100644 index 0000000000..d7bc6f4a06 --- /dev/null +++ b/core/pv-mqtt/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory @@ -0,0 +1 @@ +org.phoebus.pv.mqtt.MQTT_PVFactory diff --git a/core/pv/src/main/resources/pv_mqtt_preferences.properties b/core/pv-mqtt/src/main/resources/pv_mqtt_preferences.properties similarity index 100% rename from core/pv/src/main/resources/pv_mqtt_preferences.properties rename to core/pv-mqtt/src/main/resources/pv_mqtt_preferences.properties diff --git a/core/pv/src/test/python/DemoMQTTPublish.py b/core/pv-mqtt/src/test/python/DemoMQTTPublish.py similarity index 100% rename from core/pv/src/test/python/DemoMQTTPublish.py rename to core/pv-mqtt/src/test/python/DemoMQTTPublish.py diff --git a/core/pv/src/test/python/DemoMQTTSubscribe.py b/core/pv-mqtt/src/test/python/DemoMQTTSubscribe.py similarity index 100% rename from core/pv/src/test/python/DemoMQTTSubscribe.py rename to core/pv-mqtt/src/test/python/DemoMQTTSubscribe.py diff --git a/core/pv-opva/pom.xml b/core/pv-opva/pom.xml new file mode 100644 index 0000000000..869602ee86 --- /dev/null +++ b/core/pv-opva/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + core-pv-opva + + org.phoebus + core + 4.7.4-SNAPSHOT + + + + org.epics + epics-core + ${epics.version} + pom + + + + org.epics + vtype + ${vtype.version} + + + + org.phoebus + core-pv + 4.7.4-SNAPSHOT + + + diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/Decoders.java b/core/pv-opva/src/main/java/org/phoebus/pv/opva/Decoders.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/opva/Decoders.java rename to core/pv-opva/src/main/java/org/phoebus/pv/opva/Decoders.java diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/ImageDecoder.java b/core/pv-opva/src/main/java/org/phoebus/pv/opva/ImageDecoder.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/opva/ImageDecoder.java rename to core/pv-opva/src/main/java/org/phoebus/pv/opva/ImageDecoder.java diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVA_Context.java b/core/pv-opva/src/main/java/org/phoebus/pv/opva/PVA_Context.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/opva/PVA_Context.java rename to core/pv-opva/src/main/java/org/phoebus/pv/opva/PVA_Context.java diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVA_PV.java b/core/pv-opva/src/main/java/org/phoebus/pv/opva/PVA_PV.java similarity index 99% rename from core/pv/src/main/java/org/phoebus/pv/opva/PVA_PV.java rename to core/pv-opva/src/main/java/org/phoebus/pv/opva/PVA_PV.java index 9ac8d7f892..048f46be83 100644 --- a/core/pv/src/main/java/org/phoebus/pv/opva/PVA_PV.java +++ b/core/pv-opva/src/main/java/org/phoebus/pv/opva/PVA_PV.java @@ -9,7 +9,6 @@ import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; import java.util.logging.Level; import org.epics.pvaccess.client.Channel; diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVA_PVFactory.java b/core/pv-opva/src/main/java/org/phoebus/pv/opva/PVA_PVFactory.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/opva/PVA_PVFactory.java rename to core/pv-opva/src/main/java/org/phoebus/pv/opva/PVA_PVFactory.java diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVGetHandler.java b/core/pv-opva/src/main/java/org/phoebus/pv/opva/PVGetHandler.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/opva/PVGetHandler.java rename to core/pv-opva/src/main/java/org/phoebus/pv/opva/PVGetHandler.java diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVNameHelper.java b/core/pv-opva/src/main/java/org/phoebus/pv/opva/PVNameHelper.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/opva/PVNameHelper.java rename to core/pv-opva/src/main/java/org/phoebus/pv/opva/PVNameHelper.java diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVPutHandler.java b/core/pv-opva/src/main/java/org/phoebus/pv/opva/PVPutHandler.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/opva/PVPutHandler.java rename to core/pv-opva/src/main/java/org/phoebus/pv/opva/PVPutHandler.java diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVRequester.java b/core/pv-opva/src/main/java/org/phoebus/pv/opva/PVRequester.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/opva/PVRequester.java rename to core/pv-opva/src/main/java/org/phoebus/pv/opva/PVRequester.java diff --git a/core/pv/src/main/java/org/phoebus/pv/opva/PVStructureHelper.java b/core/pv-opva/src/main/java/org/phoebus/pv/opva/PVStructureHelper.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/opva/PVStructureHelper.java rename to core/pv-opva/src/main/java/org/phoebus/pv/opva/PVStructureHelper.java diff --git a/core/pv-opva/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory b/core/pv-opva/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory new file mode 100644 index 0000000000..af96e49d30 --- /dev/null +++ b/core/pv-opva/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory @@ -0,0 +1 @@ +org.phoebus.pv.opva.PVA_PVFactory diff --git a/core/pv-pva/pom.xml b/core/pv-pva/pom.xml new file mode 100644 index 0000000000..899926433f --- /dev/null +++ b/core/pv-pva/pom.xml @@ -0,0 +1,61 @@ + + 4.0.0 + core-pv-pva + + org.phoebus + core + 4.7.4-SNAPSHOT + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.hamcrest + hamcrest-all + 1.3 + test + + + + org.epics + epics-core + ${epics.version} + pom + + + + + org.apache.commons + commons-compress + 1.21 + + + + org.epics + vtype + ${vtype.version} + + + + org.phoebus + core-pv + 4.7.4-SNAPSHOT + + + + org.phoebus + core-pva + 4.7.4-SNAPSHOT + + + + org.phoebus + core-framework + 4.7.4-SNAPSHOT + + + diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/Codec.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/Codec.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/pva/Codec.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/Codec.java diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/Decoders.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/Decoders.java similarity index 99% rename from core/pv/src/main/java/org/phoebus/pv/pva/Decoders.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/Decoders.java index 3e8d6928f5..11c78f3e3a 100644 --- a/core/pv/src/main/java/org/phoebus/pv/pva/Decoders.java +++ b/core/pv-pva/src/main/java/org/phoebus/pv/pva/Decoders.java @@ -76,7 +76,6 @@ import org.epics.vtype.VULongArray; import org.epics.vtype.VUShort; import org.epics.vtype.VUShortArray; -import org.phoebus.pv.ca.DBRHelper; /** Decodes {@link Time}, {@link Alarm}, {@link Display}, ... * @author Kay Kasemir @@ -84,6 +83,9 @@ @SuppressWarnings("nls") public class Decoders { + /** 1990/01/01 00:00:00 epoch used by Channel Access and records on IOC */ + private static final long EPICS_EPOCH = 631152000L; + private static final Instant NO_TIME = Instant.ofEpochSecond(0, 0); private static final Integer NO_USERTAG = Integer.valueOf(0); @@ -171,7 +173,7 @@ static Time decodeTime(final PVAStructure struct) // as used for the Channel Access and IOC time stamp epoch // is considered invalid because IOCs send it for never processed records final boolean valid = timestamp.getNano() != 0 && - (timestamp.getEpochSecond() > 0 && timestamp.getEpochSecond() != DBRHelper.EPICS_EPOCH); + (timestamp.getEpochSecond() > 0 && timestamp.getEpochSecond() != EPICS_EPOCH); return Time.of(timestamp, usertag, valid); } diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/ImageDecoder.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/ImageDecoder.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/pva/ImageDecoder.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/ImageDecoder.java diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/JPEGCodec.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/JPEGCodec.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/pva/JPEGCodec.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/JPEGCodec.java diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/LZ4Codec.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/LZ4Codec.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/pva/LZ4Codec.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/LZ4Codec.java diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/PVAStructureHelper.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/PVAStructureHelper.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/pva/PVAStructureHelper.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/PVAStructureHelper.java diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/PVA_Context.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/PVA_Context.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/pva/PVA_Context.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/PVA_Context.java diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/PVA_PV.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/PVA_PV.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/pva/PVA_PV.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/PVA_PV.java diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/PVA_PVFactory.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/PVA_PVFactory.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/pva/PVA_PVFactory.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/PVA_PVFactory.java diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/PVA_Preferences.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/PVA_Preferences.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/pva/PVA_Preferences.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/PVA_Preferences.java diff --git a/core/pv/src/main/java/org/phoebus/pv/pva/PVNameHelper.java b/core/pv-pva/src/main/java/org/phoebus/pv/pva/PVNameHelper.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/pva/PVNameHelper.java rename to core/pv-pva/src/main/java/org/phoebus/pv/pva/PVNameHelper.java diff --git a/core/pv-pva/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory b/core/pv-pva/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory new file mode 100644 index 0000000000..5b7f0e46fa --- /dev/null +++ b/core/pv-pva/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory @@ -0,0 +1 @@ +org.phoebus.pv.pva.PVA_PVFactory diff --git a/core/pv/src/main/resources/pv_pva_preferences.properties b/core/pv-pva/src/main/resources/pv_pva_preferences.properties similarity index 100% rename from core/pv/src/main/resources/pv_pva_preferences.properties rename to core/pv-pva/src/main/resources/pv_pva_preferences.properties diff --git a/core/pv/src/test/java/org/phoebus/pv/Demo.java b/core/pv-pva/src/test/java/org/phoebus/pv/pva/Demo.java similarity index 99% rename from core/pv/src/test/java/org/phoebus/pv/Demo.java rename to core/pv-pva/src/test/java/org/phoebus/pv/pva/Demo.java index f0721fb609..114e6819a9 100644 --- a/core/pv/src/test/java/org/phoebus/pv/Demo.java +++ b/core/pv-pva/src/test/java/org/phoebus/pv/pva/Demo.java @@ -5,7 +5,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ -package org.phoebus.pv; +package org.phoebus.pv.pva; import java.util.concurrent.TimeUnit; diff --git a/core/pv/src/test/java/org/phoebus/pv/PVACustomStructDemo.java b/core/pv-pva/src/test/java/org/phoebus/pv/pva/PVACustomStructDemo.java similarity index 93% rename from core/pv/src/test/java/org/phoebus/pv/PVACustomStructDemo.java rename to core/pv-pva/src/test/java/org/phoebus/pv/pva/PVACustomStructDemo.java index bcd2e1de23..e3d19fdea1 100644 --- a/core/pv/src/test/java/org/phoebus/pv/PVACustomStructDemo.java +++ b/core/pv-pva/src/test/java/org/phoebus/pv/pva/PVACustomStructDemo.java @@ -5,7 +5,10 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ -package org.phoebus.pv; +package org.phoebus.pv.pva; + +import org.phoebus.pv.PV; +import org.phoebus.pv.PVPool; import java.util.concurrent.CountDownLatch; diff --git a/core/pv-tango/pom.xml b/core/pv-tango/pom.xml new file mode 100644 index 0000000000..6f82c23113 --- /dev/null +++ b/core/pv-tango/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + core-pv-tango + + org.phoebus + core + 4.7.4-SNAPSHOT + + + + org.epics + vtype + ${vtype.version} + + + + org.phoebus + core-pv + 4.7.4-SNAPSHOT + + + + org.tango-controls + JTango + 9.7.0 + pom + + + ch.qos.logback + logback-classic + + + + + diff --git a/core/pv/src/main/java/org/phoebus/pv/tga/TangoAttrContext.java b/core/pv-tango/src/main/java/org/phoebus/pv/tga/TangoAttrContext.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/tga/TangoAttrContext.java rename to core/pv-tango/src/main/java/org/phoebus/pv/tga/TangoAttrContext.java diff --git a/core/pv/src/main/java/org/phoebus/pv/tga/TangoAttr_PV.java b/core/pv-tango/src/main/java/org/phoebus/pv/tga/TangoAttr_PV.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/tga/TangoAttr_PV.java rename to core/pv-tango/src/main/java/org/phoebus/pv/tga/TangoAttr_PV.java diff --git a/core/pv/src/main/java/org/phoebus/pv/tga/TangoAttr_PVFactory.java b/core/pv-tango/src/main/java/org/phoebus/pv/tga/TangoAttr_PVFactory.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/tga/TangoAttr_PVFactory.java rename to core/pv-tango/src/main/java/org/phoebus/pv/tga/TangoAttr_PVFactory.java diff --git a/core/pv/src/main/java/org/phoebus/pv/tga/TangoTypeUtil.java b/core/pv-tango/src/main/java/org/phoebus/pv/tga/TangoTypeUtil.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/tga/TangoTypeUtil.java rename to core/pv-tango/src/main/java/org/phoebus/pv/tga/TangoTypeUtil.java diff --git a/core/pv/src/main/java/org/phoebus/pv/tgc/TangoCmdContext.java b/core/pv-tango/src/main/java/org/phoebus/pv/tgc/TangoCmdContext.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/tgc/TangoCmdContext.java rename to core/pv-tango/src/main/java/org/phoebus/pv/tgc/TangoCmdContext.java diff --git a/core/pv/src/main/java/org/phoebus/pv/tgc/TangoCmd_PV.java b/core/pv-tango/src/main/java/org/phoebus/pv/tgc/TangoCmd_PV.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/tgc/TangoCmd_PV.java rename to core/pv-tango/src/main/java/org/phoebus/pv/tgc/TangoCmd_PV.java diff --git a/core/pv/src/main/java/org/phoebus/pv/tgc/TangoCmd_PVFactory.java b/core/pv-tango/src/main/java/org/phoebus/pv/tgc/TangoCmd_PVFactory.java similarity index 100% rename from core/pv/src/main/java/org/phoebus/pv/tgc/TangoCmd_PVFactory.java rename to core/pv-tango/src/main/java/org/phoebus/pv/tgc/TangoCmd_PVFactory.java diff --git a/core/pv-tango/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory b/core/pv-tango/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory new file mode 100644 index 0000000000..a65725b0a7 --- /dev/null +++ b/core/pv-tango/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory @@ -0,0 +1,2 @@ +org.phoebus.pv.tga.TangoAttr_PVFactory +org.phoebus.pv.tgc.TangoCmd_PVFactory diff --git a/core/pv/pom.xml b/core/pv/pom.xml index d3cbdbc16b..bfe684f22b 100644 --- a/core/pv/pom.xml +++ b/core/pv/pom.xml @@ -27,30 +27,16 @@ org.epics - epics-core - ${epics.version} - pom + epics-util + ${epics.util.version} - - - org.apache.commons - commons-compress - 1.21 - - org.epics vtype ${vtype.version} - - org.phoebus - core-pva - 4.7.4-SNAPSHOT - - org.phoebus core-framework @@ -66,22 +52,5 @@ core-util 4.7.4-SNAPSHOT - - org.eclipse.paho - org.eclipse.paho.client.mqttv3 - 1.2.2 - - - org.tango-controls - JTango - 9.7.0 - pom - - - ch.qos.logback - logback-classic - - - diff --git a/core/pv/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory b/core/pv/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory index a63b206b0a..5a4daf856f 100644 --- a/core/pv/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory +++ b/core/pv/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory @@ -1,10 +1,4 @@ -org.phoebus.pv.ca.JCA_PVFactory org.phoebus.pv.sim.SimPVFactory org.phoebus.pv.sys.SysPVFactory org.phoebus.pv.loc.LocalPVFactory -org.phoebus.pv.pva.PVA_PVFactory -org.phoebus.pv.opva.PVA_PVFactory -org.phoebus.pv.mqtt.MQTT_PVFactory org.phoebus.pv.formula.FormulaPVFactory -org.phoebus.pv.tga.TangoAttr_PVFactory -org.phoebus.pv.tgc.TangoCmd_PVFactory \ No newline at end of file diff --git a/core/pv/src/test/java/org/phoebus/pv/FormulaTest.java b/core/pv/src/test/java/org/phoebus/pv/FormulaTest.java index 11b0c34243..5d87030769 100644 --- a/core/pv/src/test/java/org/phoebus/pv/FormulaTest.java +++ b/core/pv/src/test/java/org/phoebus/pv/FormulaTest.java @@ -137,7 +137,7 @@ public void concurrentInputs() throws Exception public void initialDisconnect() throws Exception { // Formula with missing PV needs to be 'disconnected' - PV pv = PVPool.getPV("= `missing_PV` + 5"); + PV pv = PVPool.getPV("= `disconnected://missing_PV` + 5"); VType value = pv.read(); System.out.println(pv.getName() + " = " + value); @@ -147,7 +147,7 @@ public void initialDisconnect() throws Exception // 'if' still evaluates OK, since the missing PV is not used - pv = PVPool.getPV("= 1 ? 42 : `missing_PV`"); + pv = PVPool.getPV("= 1 ? 42 : `disconnected://missing_PV`"); value = pv.read(); System.out.println(pv.getName() + " = " + value); @@ -158,7 +158,7 @@ public void initialDisconnect() throws Exception // This gets an error because the missing PV _is_ used - pv = PVPool.getPV("= 0 ? 42 : `missing_PV`"); + pv = PVPool.getPV("= 0 ? 42 : `disconnected://missing_PV`"); value = pv.read(); System.out.println(pv.getName() + " = " + value); @@ -167,7 +167,7 @@ public void initialDisconnect() throws Exception PVPool.releasePV(pv); // Error because missing PV is needed for the condition - pv = PVPool.getPV("=`missing_PV` ? 0 : 1"); + pv = PVPool.getPV("=`disconnected://missing_PV` ? 0 : 1"); value = pv.read(); System.out.println(pv.getName() + " = " + value); diff --git a/core/pv/src/test/java/org/phoebus/pv/PVPoolTest.java b/core/pv/src/test/java/org/phoebus/pv/PVPoolTest.java index de52894590..c45a5ac3b9 100644 --- a/core/pv/src/test/java/org/phoebus/pv/PVPoolTest.java +++ b/core/pv/src/test/java/org/phoebus/pv/PVPoolTest.java @@ -17,7 +17,6 @@ import org.junit.jupiter.api.Test; import org.phoebus.pv.PVPool.TypedName; -import org.phoebus.pv.ca.JCA_Preferences; /** @author Kay Kasemir */ @SuppressWarnings("nls") @@ -28,8 +27,10 @@ public void listPrefixes() { final Collection prefs = PVPool.getSupportedPrefixes(); System.out.println("Prefixes: " + prefs); - assertThat(prefs, hasItem("ca")); + assertThat(prefs, hasItem("eq")); + assertThat(prefs, hasItem("loc")); assertThat(prefs, hasItem("sim")); + assertThat(prefs, hasItem("sys")); } @Test @@ -104,7 +105,6 @@ public void equivalentPVs() @Test public void dumpPreferences() throws Exception { - JCA_Preferences.getInstance(); final Preferences prefs = Preferences.userNodeForPackage(PV.class); prefs.exportSubtree(System.out); } diff --git a/core/pv/src/test/java/org/phoebus/pv/disconnected/DisconnectedPV.java b/core/pv/src/test/java/org/phoebus/pv/disconnected/DisconnectedPV.java new file mode 100644 index 0000000000..96b9827398 --- /dev/null +++ b/core/pv/src/test/java/org/phoebus/pv/disconnected/DisconnectedPV.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.pv.disconnected; + +import org.phoebus.pv.PV; + +/** + * Dummy PV implementation that is never going to connect. + */ +public class DisconnectedPV extends PV { + + protected DisconnectedPV(String name) { + super(name); + } + +} diff --git a/core/pv/src/test/java/org/phoebus/pv/disconnected/DisconnectedPVFactory.java b/core/pv/src/test/java/org/phoebus/pv/disconnected/DisconnectedPVFactory.java new file mode 100644 index 0000000000..08aa403149 --- /dev/null +++ b/core/pv/src/test/java/org/phoebus/pv/disconnected/DisconnectedPVFactory.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.pv.disconnected; + +import org.phoebus.pv.PV; +import org.phoebus.pv.PVFactory; + +/** + * Creates dummy PVs for use inside tests. + * + * The PVs created by this factory will never connect. + */ +public class DisconnectedPVFactory implements PVFactory { + + @Override + @SuppressWarnings("nls") + public String getType() { + return "disconnected"; + } + + @Override + public PV createPV(String name, String base_name) throws Exception { + return new DisconnectedPV(base_name); + } + +} diff --git a/core/pv/src/test/resources/META-INF/services/org.phoebus.pv.PVFactory b/core/pv/src/test/resources/META-INF/services/org.phoebus.pv.PVFactory new file mode 100644 index 0000000000..058f5f8561 --- /dev/null +++ b/core/pv/src/test/resources/META-INF/services/org.phoebus.pv.PVFactory @@ -0,0 +1 @@ +org.phoebus.pv.disconnected.DisconnectedPVFactory diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index c9091a0e73..3a61dfc036 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -12,6 +12,31 @@ core-launcher 4.7.4-SNAPSHOT + + org.phoebus + core-pv-ca + 4.7.4-SNAPSHOT + + + org.phoebus + core-pv-mqtt + 4.7.4-SNAPSHOT + + + org.phoebus + core-pv-opva + 4.7.4-SNAPSHOT + + + org.phoebus + core-pv-pva + 4.7.4-SNAPSHOT + + + org.phoebus + core-pv-tango + 4.7.4-SNAPSHOT + org.phoebus app-diag diff --git a/pom.xml b/pom.xml index 6cf4b237af..9af7e5aeb6 100644 --- a/pom.xml +++ b/pom.xml @@ -66,6 +66,7 @@ 2024-01-10T19:23:58Z 7.0.10 + 1.0.7 1.0.7 19 2.12.3 From a50e333407938df09bcefee279627904335d238a Mon Sep 17 00:00:00 2001 From: Sebastian Marsching Date: Mon, 12 Feb 2024 21:50:16 +0100 Subject: [PATCH 14/92] Add PV source modules as service dependencies. --- services/alarm-server/pom.xml | 10 ++++++++++ services/archive-engine/pom.xml | 10 ++++++++++ services/scan-server/pom.xml | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/services/alarm-server/pom.xml b/services/alarm-server/pom.xml index 0d47cbd28d..ec1909dbc7 100644 --- a/services/alarm-server/pom.xml +++ b/services/alarm-server/pom.xml @@ -36,6 +36,16 @@ core-pv 4.7.4-SNAPSHOT + + org.phoebus + core-pv-ca + 4.7.4-SNAPSHOT + + + org.phoebus + core-pv-pva + 4.7.4-SNAPSHOT + org.phoebus core-formula diff --git a/services/archive-engine/pom.xml b/services/archive-engine/pom.xml index c5f5bd0eeb..9423589014 100644 --- a/services/archive-engine/pom.xml +++ b/services/archive-engine/pom.xml @@ -94,6 +94,16 @@ core-pv 4.7.4-SNAPSHOT + + org.phoebus + core-pv-ca + 4.7.4-SNAPSHOT + + + org.phoebus + core-pv-pva + 4.7.4-SNAPSHOT + diff --git a/services/scan-server/pom.xml b/services/scan-server/pom.xml index fdc4ab1f81..bec0312a58 100644 --- a/services/scan-server/pom.xml +++ b/services/scan-server/pom.xml @@ -78,6 +78,16 @@ core-pv 4.7.4-SNAPSHOT + + org.phoebus + core-pv-ca + 4.7.4-SNAPSHOT + + + org.phoebus + core-pv-pva + 4.7.4-SNAPSHOT + org.phoebus app-scan-model From d7e6a2e31ad8f11967384553946bafcf453255e6 Mon Sep 17 00:00:00 2001 From: kasemir Date: Wed, 21 Feb 2024 13:45:31 -0500 Subject: [PATCH 15/92] Choice Button: Smaller to avoid layout spillover --- .../resources/examples/controls_radio.bob | 162 ++++++++++++------ .../widgets/ChoiceButtonRepresentation.java | 9 +- 2 files changed, 113 insertions(+), 58 deletions(-) diff --git a/app/display/model/src/main/resources/examples/controls_radio.bob b/app/display/model/src/main/resources/examples/controls_radio.bob index ab2b904b5d..e6543a345e 100644 --- a/app/display/model/src/main/resources/examples/controls_radio.bob +++ b/app/display/model/src/main/resources/examples/controls_radio.bob @@ -6,47 +6,8 @@ Macro "three" Macro "two" - - Text Update - loc://test<String> - 448 - 150 - 25 - - - Label - Items from property - 341 - 221 - - - - - - - Text Update_2 - loc://test3<VEnum>(0, "High", "Medium", "Low") - 291 - 190 - 25 - - - Text Update_3 - loc://test2<String> - 548 - 150 - 25 - - - Label - Items with macros - 491 - 150 - - - - - + 803 + 573 Label_5 Radio Button Widget @@ -71,14 +32,6 @@ is written. 431 150 - - Label_9 - By default, the property "Items From PV" is set -and the widget obtains its items from an enumerated PV. - 210 - 378 - 61 - Label_10 Items from PV @@ -89,6 +42,14 @@ and the widget obtains its items from an enumerated PV. + + Label_9 + By default, the property "Items From PV" is set +and the widget obtains its items from an enumerated PV. + 210 + 378 + 61 + Combo_ItemsFromPV loc://test3<VEnum>(0, "High", "Medium", "Low") @@ -100,6 +61,50 @@ and the widget obtains its items from an enumerated PV. Item 2 + + Text Update_2 + loc://test3<VEnum>(0, "High", "Medium", "Low") + 291 + 190 + 25 + + + Label + Items from property + 341 + 221 + + + + + + + Label_1 + Choice Button Widget + 420 + 341 + 221 + + + + + + + Label_11 + Alternatively, the items can be configured on the widget, +not reading them from a PV. + 371 + 378 + 40 + + + Label_12 + The choice button widget is very similar to radio buttons. + 420 + 371 + 378 + 40 + Combo loc://test<String> @@ -113,13 +118,53 @@ and the widget obtains its items from an enumerated PV. false + + Combo_1 + loc://test<String> + 420 + 421 + 383 + 27 + false + + One Item + Another Item + Yet another + + false + + + Text Update + loc://test<String> + 448 + 150 + 25 + + + Combo_2 + loc://test<String> + 550 + 460 + 130 + 73 + false + false + + One Item + Another Item + Yet another + + false + - Label_11 - Alternatively, the items can be configured on the widget, -not reading them from a PV. - 371 - 378 - 40 + Label + Items with macros + 491 + 150 + + + + Combo_Macros @@ -134,4 +179,11 @@ not reading them from a PV. false + + Text Update_3 + loc://test2<String> + 548 + 150 + 25 + diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ChoiceButtonRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ChoiceButtonRepresentation.java index cc73220c03..08ca32577a 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ChoiceButtonRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ChoiceButtonRepresentation.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019-2023 Oak Ridge National Laboratory. + * Copyright (c) 2019-2024 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -354,17 +354,20 @@ private void sizeButtons() final int N = Math.max(1, jfx_node.getChildren().size()); final int width, height; + // For the exact size, we should only consider (N-1) gaps, the gaps between buttons. + // Add one more gap to avoid layout rounding errors that lead to occasional spill-over + // https://github.com/ControlSystemStudio/phoebus/issues/2783 if (model_widget.propHorizontal().getValue()) { jfx_node.setPrefColumns(N); - width = (int) ((model_widget.propWidth().getValue() - (N-1)*jfx_node.getHgap()) / N); + width = (int) (model_widget.propWidth().getValue() / N - jfx_node.getHgap()); height = model_widget.propHeight().getValue(); } else { jfx_node.setPrefRows(N); width = model_widget.propWidth().getValue(); - height = (int) ((model_widget.propHeight().getValue() - (N-1)*jfx_node.getVgap()) / N); + height = (int) (model_widget.propHeight().getValue() / N - jfx_node.getVgap()); } for (Node node : jfx_node.getChildren()) { From 48e08142b945316bcf78d9117aac403d35a215b8 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Wed, 21 Feb 2024 14:01:48 -0500 Subject: [PATCH 16/92] upgrade Kafka client and stream dep to 3.6.x --- app/alarm/Readme.md | 6 +++--- app/alarm/model/pom.xml | 4 ++-- dependencies/phoebus-target/pom.xml | 4 ++-- pom.xml | 1 + services/alarm-config-logger/pom.xml | 4 ++-- services/alarm-logger/pom.xml | 4 ++-- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/alarm/Readme.md b/app/alarm/Readme.md index 95e91fedc2..407259877f 100644 --- a/app/alarm/Readme.md +++ b/app/alarm/Readme.md @@ -25,9 +25,9 @@ kafka in `/opt/kafka`. cd examples # Use wget, 'curl -O', or web browser to fetch a recent version of kafka - wget https://downloads.apache.org/kafka/3.3.1/kafka_2.13-3.3.1.tgz - tar vzxf kafka_2.13-3.3.1.tgz - ln -s kafka_2.13-3.3.1 kafka + wget https://downloads.apache.org/kafka/3.3.1/kafka_2.13-3.6.1.tgz + tar vzxf kafka_2.13-3.6.1.tgz + ln -s kafka_2.13-3.6.1 kafka Check `config/zookeeper.properties` and `config/server.properties`. By default these contain settings for keeping data in `/tmp/`, which works for initial tests, diff --git a/app/alarm/model/pom.xml b/app/alarm/model/pom.xml index d117bd7883..f11efb84c3 100644 --- a/app/alarm/model/pom.xml +++ b/app/alarm/model/pom.xml @@ -37,12 +37,12 @@ org.apache.kafka kafka-clients - 2.0.0 + ${kafka.version} org.apache.kafka kafka-streams - 2.0.0 + ${kafka.version} org.phoebus diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml index b7df3148d1..66dd4a1afd 100644 --- a/dependencies/phoebus-target/pom.xml +++ b/dependencies/phoebus-target/pom.xml @@ -282,12 +282,12 @@ org.apache.kafka kafka-streams - 2.0.0 + ${kafka.version} org.apache.kafka kafka-clients - 2.0.0 + ${kafka.version} diff --git a/pom.xml b/pom.xml index b118f42d76..3812c70f51 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,7 @@ 3.6.1 5.8.2 8.2.0 + 3.6.1 UTF-8 UTF-8 diff --git a/services/alarm-config-logger/pom.xml b/services/alarm-config-logger/pom.xml index e921b0fec3..a999cfaef0 100644 --- a/services/alarm-config-logger/pom.xml +++ b/services/alarm-config-logger/pom.xml @@ -49,12 +49,12 @@ org.apache.kafka kafka-streams - 2.0.0 + ${kafka.version} org.apache.kafka kafka-clients - 2.0.0 + ${kafka.version} org.eclipse.jgit diff --git a/services/alarm-logger/pom.xml b/services/alarm-logger/pom.xml index 7f014d6b3f..7007ddef65 100644 --- a/services/alarm-logger/pom.xml +++ b/services/alarm-logger/pom.xml @@ -50,12 +50,12 @@ org.apache.kafka kafka-streams - 2.0.0 + ${kafka.version} org.apache.kafka kafka-clients - 2.0.0 + ${kafka.version} org.elasticsearch.client From df627b641dc627b3d55060a90e9d41f75981e2c8 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Wed, 21 Feb 2024 16:27:11 -0500 Subject: [PATCH 17/92] Add missing dependency 4 MultivaluedHashMap (different solution sought) --- app/alarm/logging-ui/pom.xml | 5 +++++ .../applications/alarm/logging/ui/AlarmLogSearchJob.java | 1 - dependencies/phoebus-target/pom.xml | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/alarm/logging-ui/pom.xml b/app/alarm/logging-ui/pom.xml index 8b503e9519..31c26eae33 100644 --- a/app/alarm/logging-ui/pom.xml +++ b/app/alarm/logging-ui/pom.xml @@ -63,5 +63,10 @@ jackson-datatype-jsr310 ${jackson.version} + + javax.ws.rs + javax.ws.rs-api + 2.1 + diff --git a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogSearchJob.java b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogSearchJob.java index 0367b3f36d..b6d4849bbe 100644 --- a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogSearchJob.java +++ b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogSearchJob.java @@ -1,6 +1,5 @@ package org.phoebus.applications.alarm.logging.ui; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml index 2ddb2bdc18..0bc47543a7 100644 --- a/dependencies/phoebus-target/pom.xml +++ b/dependencies/phoebus-target/pom.xml @@ -581,6 +581,12 @@ 1.1.4 + + javax.ws.rs + javax.ws.rs-api + 2.1 + + org.apache.activemq From 3539ba9f5ab1c181ceb94bed43e75071194d0136 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 22 Feb 2024 09:30:07 -0500 Subject: [PATCH 18/92] Update stream close to new kafka 3.6 API --- .../main/java/org/phoebus/alarm/logging/AlarmCmdLogger.java | 5 ++++- .../java/org/phoebus/alarm/logging/AlarmMessageLogger.java | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmCmdLogger.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmCmdLogger.java index 9b7807d574..9878516e7f 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmCmdLogger.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmCmdLogger.java @@ -2,7 +2,10 @@ import static org.phoebus.alarm.logging.AlarmLoggingService.logger; +import java.time.Duration; import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalUnit; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -136,7 +139,7 @@ public void close() { Runtime.getRuntime().addShutdownHook(new Thread("streams-" + topic + "-alarm-cmd-shutdown-hook") { @Override public void run() { - streams.close(10, TimeUnit.SECONDS); + streams.close(Duration.of(10, ChronoUnit.SECONDS)); System.out.println("\nShutting cmd streams Done."); latch.countDown(); } diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmMessageLogger.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmMessageLogger.java index b83a7b99a2..73ab6a7cdb 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmMessageLogger.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmMessageLogger.java @@ -2,7 +2,9 @@ import static org.phoebus.alarm.logging.AlarmLoggingService.logger; +import java.time.Duration; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -126,7 +128,7 @@ public long extract(ConsumerRecord record, long previousTimestam Runtime.getRuntime().addShutdownHook(new Thread("streams-"+topic+"-alarm-messages-shutdown-hook") { @Override public void run() { - streams.close(10, TimeUnit.SECONDS); + streams.close(Duration.of(10, ChronoUnit.SECONDS)); System.out.println("\nShutting streams Done."); latch.countDown(); } From 1fb24f67acf670c7cbfaa80c116475c0980a2d86 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 22 Feb 2024 10:23:17 -0500 Subject: [PATCH 19/92] Issue #2947 use the existing utility class for manipulating focus --- .../logging/ui/AlarmLogTableController.java | 4 +-- .../alarm/ui/AlarmContextMenuHelper.java | 6 ++-- .../databrowser3/ui/properties/TracesTab.java | 4 +-- .../databrowser3/ui/search/SearchView.java | 4 +-- .../org/csstudio/display/pace/gui/GUI.java | 4 +-- .../probe/view/ProbeController.java | 4 +-- .../applications/pvtable/ui/PVTable.java | 4 +-- .../applications/pvtree/ui/FXTree.java | 4 +-- .../ConfigurationController.java | 4 +-- .../BaseSnapshotTableViewController.java | 4 +-- .../org/phoebus/ui/focus/FocusUtility.java | 36 ------------------- .../java/org/phoebus/ui/javafx/FocusUtil.java | 29 ++++++++++++++- .../main/java/org/phoebus/ui/pv/PVList.java | 4 +-- 13 files changed, 51 insertions(+), 60 deletions(-) delete mode 100644 core/ui/src/main/java/org/phoebus/ui/focus/FocusUtility.java diff --git a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableController.java b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableController.java index 8843345dbe..e0fc658613 100644 --- a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableController.java +++ b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableController.java @@ -45,7 +45,7 @@ import org.phoebus.framework.selection.SelectionService; import org.phoebus.ui.application.ContextMenuHelper; import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.ui.javafx.ImageCache; import org.phoebus.ui.javafx.JFXUtil; import org.phoebus.util.time.TimeParser; @@ -572,7 +572,7 @@ public void createContextMenu() { // search for other context menu actions registered for AlarmLogTableType SelectionService.getInstance().setSelection("AlarmLogTable", tableView.getSelectionModel().getSelectedItems()); - ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(tableView), contextMenu); + ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(tableView), contextMenu); tableView.setContextMenu(contextMenu); diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmContextMenuHelper.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmContextMenuHelper.java index d37ffb0047..193db49185 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmContextMenuHelper.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmContextMenuHelper.java @@ -24,7 +24,7 @@ import org.phoebus.framework.selection.SelectionService; import org.phoebus.ui.application.ContextMenuHelper; import org.phoebus.ui.dialog.DialogHelper; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; import java.util.ArrayList; import java.util.HashSet; @@ -132,13 +132,13 @@ public void addSupportedEntries(final Node node, { menu_items.add(new SeparatorMenuItem()); SelectionService.getInstance().setSelection("AlarmUI", pvnames); - ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(node), menu); + ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(node), menu); } else { // search for other context menu actions registered for AlarmTreeItem SelectionService.getInstance().setSelection("AlarmUI", selection); - ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(node), menu); + ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(node), menu); } } diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/properties/TracesTab.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/properties/TracesTab.java index 78a7437c42..ed8d995ae0 100644 --- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/properties/TracesTab.java +++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/properties/TracesTab.java @@ -37,7 +37,7 @@ import org.phoebus.ui.application.ContextMenuHelper; import org.phoebus.ui.dialog.AlertWithToggle; import org.phoebus.ui.dialog.DialogHelper; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.ui.undo.UndoableActionManager; import org.phoebus.util.time.SecondsParser; @@ -742,7 +742,7 @@ private void createContextMenu() if (pvs.size() > 0) { SelectionService.getInstance().setSelection(this, pvs); - ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(trace_table), menu); + ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(trace_table), menu); } menu.show(trace_table.getScene().getWindow(), event.getScreenX(), event.getScreenY()); diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/search/SearchView.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/search/SearchView.java index a58f3d108d..57c84f2744 100644 --- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/search/SearchView.java +++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/search/SearchView.java @@ -22,7 +22,7 @@ import org.phoebus.framework.selection.SelectionService; import org.phoebus.ui.application.ContextMenuHelper; import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.ui.undo.UndoableActionManager; import javafx.application.Platform; @@ -165,7 +165,7 @@ private void updateContextMenu(final ContextMenuEvent event) menu.getItems().setAll(new AddToPlotAction(channel_table, model, undo, selection), new SeparatorMenuItem()); SelectionService.getInstance().setSelection(channel_table, selection); - ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(channel_table), menu); + ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(channel_table), menu); menu.show(channel_table.getScene().getWindow(), event.getScreenX(), event.getScreenY()); } } diff --git a/app/pace/src/main/java/org/csstudio/display/pace/gui/GUI.java b/app/pace/src/main/java/org/csstudio/display/pace/gui/GUI.java index 2a1c323ea6..ff7e66ab25 100644 --- a/app/pace/src/main/java/org/csstudio/display/pace/gui/GUI.java +++ b/app/pace/src/main/java/org/csstudio/display/pace/gui/GUI.java @@ -38,7 +38,7 @@ import javafx.scene.control.TablePosition; import javafx.scene.control.TableView; import javafx.scene.layout.BorderPane; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; /** GUI for PACE {@link Model} * @author Kay Kasemir @@ -201,7 +201,7 @@ private void createContextMenu() { items.add(new SeparatorMenuItem()); SelectionService.getInstance().setSelection("AlarmUI", pvnames); - ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(table), menu); + ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(table), menu); } }); diff --git a/app/probe/src/main/java/org/phoebus/applications/probe/view/ProbeController.java b/app/probe/src/main/java/org/phoebus/applications/probe/view/ProbeController.java index 51103248da..69984304f4 100644 --- a/app/probe/src/main/java/org/phoebus/applications/probe/view/ProbeController.java +++ b/app/probe/src/main/java/org/phoebus/applications/probe/view/ProbeController.java @@ -25,7 +25,7 @@ import org.phoebus.ui.application.ContextMenuHelper; import org.phoebus.ui.application.PhoebusApplication; import org.phoebus.ui.docking.DockStage; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.ui.javafx.JFXUtil; import org.phoebus.ui.pv.SeverityColors; @@ -179,7 +179,7 @@ public void initialize() { List.of(new ProcessVariable(txtPVName.getText().trim()))); } - ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(txtAlarm), menu); + ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(txtAlarm), menu); menu.show(txtPVName.getScene().getWindow(), event.getScreenX(), event.getScreenY()); }); diff --git a/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/PVTable.java b/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/PVTable.java index b4d448cd46..9bbfef2466 100644 --- a/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/PVTable.java +++ b/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/PVTable.java @@ -35,7 +35,7 @@ import org.phoebus.ui.dialog.DialogHelper; import org.phoebus.ui.dialog.NumericInputDialog; import org.phoebus.ui.dnd.DataFormats; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.ui.javafx.PrintAction; import org.phoebus.ui.javafx.Screenshot; import org.phoebus.ui.javafx.ToolbarHelper; @@ -692,7 +692,7 @@ private void createContextMenu() } // Add PV entries - if (ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(table), menu)) + if (ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(table), menu)) menu.getItems().add(new SeparatorMenuItem()); menu.getItems().add(new PrintAction(this)); diff --git a/app/pvtree/src/main/java/org/phoebus/applications/pvtree/ui/FXTree.java b/app/pvtree/src/main/java/org/phoebus/applications/pvtree/ui/FXTree.java index 030c02b95b..252ead6c9c 100644 --- a/app/pvtree/src/main/java/org/phoebus/applications/pvtree/ui/FXTree.java +++ b/app/pvtree/src/main/java/org/phoebus/applications/pvtree/ui/FXTree.java @@ -28,7 +28,7 @@ import org.phoebus.ui.application.ContextMenuHelper; import org.phoebus.ui.application.ContextMenuService; import org.phoebus.ui.application.SaveSnapshotAction; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.ui.javafx.PrintAction; import org.phoebus.ui.javafx.Screenshot; import org.phoebus.ui.javafx.TreeHelper; @@ -139,7 +139,7 @@ private void createContextMenu() { menu.getItems().clear(); - if (ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(tree_view), menu)) + if (ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(tree_view), menu)) menu.getItems().add(new SeparatorMenuItem()); menu.getItems().add(new PrintAction(tree_view)); menu.getItems().add(new SaveSnapshotAction(tree_view)); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java index b1667d607d..f167d70c9d 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java @@ -45,7 +45,7 @@ import org.phoebus.framework.selection.SelectionService; import org.phoebus.ui.application.ContextMenuHelper; import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.ui.javafx.ImageCache; import org.phoebus.util.time.TimestampFormats; @@ -191,7 +191,7 @@ public void updateItem(String item, boolean empty) { .collect(Collectors.toList()); SelectionService.getInstance().setSelection(SaveAndRestoreApplication.NAME, selectedPVList); - ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(cell), pvNameContextMenu); + ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(cell), pvNameContextMenu); } pvNameContextMenu.show(cell, event.getScreenX(), event.getScreenY()); }); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java index 57b91e1c94..035c832d79 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java @@ -39,7 +39,7 @@ import org.phoebus.core.types.TimeStampedProcessVariable; import org.phoebus.framework.selection.SelectionService; import org.phoebus.ui.application.ContextMenuHelper; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.util.time.TimestampFormats; import java.lang.reflect.Field; @@ -199,7 +199,7 @@ protected void updateItem(TableEntry item, boolean empty) { contextMenu.getItems().clear(); SelectionService.getInstance().setSelection(SaveAndRestoreApplication.NAME, selectedPVList); - ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(this), contextMenu); + ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(this), contextMenu); contextMenu.getItems().add(new SeparatorMenuItem()); MenuItem toggle = new MenuItem(); toggle.setText(item.readOnlyProperty().get() ? Messages.makeRestorable : Messages.makeReadOnly); diff --git a/core/ui/src/main/java/org/phoebus/ui/focus/FocusUtility.java b/core/ui/src/main/java/org/phoebus/ui/focus/FocusUtility.java deleted file mode 100644 index 9ad58d6226..0000000000 --- a/core/ui/src/main/java/org/phoebus/ui/focus/FocusUtility.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.phoebus.ui.focus; - -import javafx.scene.Node; -import javafx.stage.Stage; -import javafx.stage.Window; -import org.phoebus.ui.application.PhoebusApplication; -import org.phoebus.ui.docking.DockStage; - -import java.util.logging.Level; - -/** - * A utility class which provides support for handling Focus - */ -public class FocusUtility { - - /** - * Create a Runnable which when called sets the focus on the first DockPane of the Stage hosting the provided Node - * @param node A node - * @return A Runnable to set the Focus on the first DockPane of the Stage which holds the Node - */ - public static Runnable setFocusOn(final Node node){ - { - Window window = node.getScene().getWindow(); - if (window instanceof Stage) - { - final Stage stage = (Stage) window; - return () -> DockStage.setActiveDockStage(stage); - } else - { - PhoebusApplication.logger.log(Level.WARNING, "Expected 'Stage' for context menu, got " + window); - return () -> { - }; - } - } - } -} diff --git a/core/ui/src/main/java/org/phoebus/ui/javafx/FocusUtil.java b/core/ui/src/main/java/org/phoebus/ui/javafx/FocusUtil.java index fdf827a59b..b8dc46778b 100644 --- a/core/ui/src/main/java/org/phoebus/ui/javafx/FocusUtil.java +++ b/core/ui/src/main/java/org/phoebus/ui/javafx/FocusUtil.java @@ -8,9 +8,15 @@ package org.phoebus.ui.javafx; import javafx.scene.Node; +import javafx.stage.Stage; +import javafx.stage.Window; +import org.phoebus.ui.application.PhoebusApplication; +import org.phoebus.ui.docking.DockStage; + +import java.util.logging.Level; /** Helper for handling focus - * @author Kay Kasemir + * @author Kay Kasemir, Kunal Shroff, Abraham Wolk */ public class FocusUtil { @@ -26,4 +32,25 @@ public static void removeFocus(Node node) parent = parent.getParent(); parent.requestFocus(); } + + /** + * Create a Runnable which when called sets the focus on the first DockPane of the Stage hosting the provided Node + * @param node A node + * @return A Runnable to set the Focus on the first DockPane of the Stage which holds the Node + */ + public static Runnable setFocusOn(final Node node){ + { + Window window = node.getScene().getWindow(); + if (window instanceof Stage) + { + final Stage stage = (Stage) window; + return () -> DockStage.setActiveDockStage(stage); + } else + { + PhoebusApplication.logger.log(Level.WARNING, "Expected 'Stage' for context menu, got " + window); + return () -> { + }; + } + } + } } diff --git a/core/ui/src/main/java/org/phoebus/ui/pv/PVList.java b/core/ui/src/main/java/org/phoebus/ui/pv/PVList.java index 94999d105e..b89c2a1126 100644 --- a/core/ui/src/main/java/org/phoebus/ui/pv/PVList.java +++ b/core/ui/src/main/java/org/phoebus/ui/pv/PVList.java @@ -25,7 +25,7 @@ import org.phoebus.pv.RefCountMap.ReferencedEntry; import org.phoebus.ui.application.ContextMenuHelper; import org.phoebus.ui.application.Messages; -import org.phoebus.ui.focus.FocusUtility; +import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.ui.javafx.ImageCache; import javafx.application.Platform; @@ -191,7 +191,7 @@ private void createContextMenu() { menu.getItems().clear(); - ContextMenuHelper.addSupportedEntries(FocusUtility.setFocusOn(table), menu); + ContextMenuHelper.addSupportedEntries(FocusUtil.setFocusOn(table), menu); menu.show(table.getScene().getWindow()); }); table.setContextMenu(menu); From 439ab4092a1b1c4ce98e4135caf323b1454c271a Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 22 Feb 2024 12:10:47 -0500 Subject: [PATCH 20/92] make formatting consistent --- .../java/org/phoebus/ui/javafx/FocusUtil.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/javafx/FocusUtil.java b/core/ui/src/main/java/org/phoebus/ui/javafx/FocusUtil.java index b8dc46778b..e92e2ba441 100644 --- a/core/ui/src/main/java/org/phoebus/ui/javafx/FocusUtil.java +++ b/core/ui/src/main/java/org/phoebus/ui/javafx/FocusUtil.java @@ -38,19 +38,19 @@ public static void removeFocus(Node node) * @param node A node * @return A Runnable to set the Focus on the first DockPane of the Stage which holds the Node */ - public static Runnable setFocusOn(final Node node){ + public static Runnable setFocusOn(final Node node) + { + Window window = node.getScene().getWindow(); + if (window instanceof Stage) + { + final Stage stage = (Stage) window; + return () -> DockStage.setActiveDockStage(stage); + } + else { - Window window = node.getScene().getWindow(); - if (window instanceof Stage) - { - final Stage stage = (Stage) window; - return () -> DockStage.setActiveDockStage(stage); - } else - { - PhoebusApplication.logger.log(Level.WARNING, "Expected 'Stage' for context menu, got " + window); - return () -> { - }; - } + PhoebusApplication.logger.log(Level.WARNING, "Expected 'Stage' for context menu, got " + window); + return () -> { + }; } } } From 5b7e091b0cb6bcc444f6af42a9ac46991ba85f02 Mon Sep 17 00:00:00 2001 From: Sebastian Marsching Date: Wed, 21 Feb 2024 21:40:32 +0100 Subject: [PATCH 21/92] Add EPICS Jackie PV source. --- core/pom.xml | 1 + core/pv-jackie/pom.xml | 48 ++ .../java/org/phoebus/pv/jackie/JackiePV.java | 758 +++++++++++++++++ .../phoebus/pv/jackie/JackiePVFactory.java | 96 +++ .../phoebus/pv/jackie/JackiePreferences.java | 585 +++++++++++++ .../pv/jackie/util/SimpleJsonParser.java | 578 +++++++++++++ .../pv/jackie/util/ValueConverter.java | 556 +++++++++++++ .../services/org.phoebus.pv.PVFactory | 1 + .../pv_jackie_preferences.properties | 148 ++++ .../pv/jackie/util/SimpleJsonParserTest.java | 215 +++++ .../pv/jackie/util/ValueConverterTest.java | 767 ++++++++++++++++++ dependencies/phoebus-target/pom.xml | 7 + phoebus-product/pom.xml | 5 + 13 files changed, 3765 insertions(+) create mode 100644 core/pv-jackie/pom.xml create mode 100644 core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePV.java create mode 100644 core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePVFactory.java create mode 100644 core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePreferences.java create mode 100644 core/pv-jackie/src/main/java/org/phoebus/pv/jackie/util/SimpleJsonParser.java create mode 100644 core/pv-jackie/src/main/java/org/phoebus/pv/jackie/util/ValueConverter.java create mode 100644 core/pv-jackie/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory create mode 100644 core/pv-jackie/src/main/resources/pv_jackie_preferences.properties create mode 100644 core/pv-jackie/src/test/java/org/phoebus/pv/jackie/util/SimpleJsonParserTest.java create mode 100644 core/pv-jackie/src/test/java/org/phoebus/pv/jackie/util/ValueConverterTest.java diff --git a/core/pom.xml b/core/pom.xml index 31378d8915..117a37f443 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -9,6 +9,7 @@ pva pv pv-ca + pv-jackie pv-mqtt pv-opva pv-pva diff --git a/core/pv-jackie/pom.xml b/core/pv-jackie/pom.xml new file mode 100644 index 0000000000..a3a6c58228 --- /dev/null +++ b/core/pv-jackie/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + core-pv-jackie + + org.phoebus + core + 4.7.4-SNAPSHOT + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + org.hamcrest + hamcrest-all + 1.3 + test + + + + org.epics + vtype + ${vtype.version} + + + + org.phoebus + core-framework + 4.7.4-SNAPSHOT + + + + org.phoebus + core-pv + 4.7.4-SNAPSHOT + + + + com.aquenos.epics.jackie + epics-jackie-client + 3.1.0 + + + diff --git a/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePV.java b/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePV.java new file mode 100644 index 0000000000..fd8b14aa7e --- /dev/null +++ b/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePV.java @@ -0,0 +1,758 @@ +/******************************************************************************* + * Copyright (c) 2017-2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.pv.jackie; + +import com.aquenos.epics.jackie.client.ChannelAccessChannel; +import com.aquenos.epics.jackie.client.ChannelAccessClient; +import com.aquenos.epics.jackie.client.ChannelAccessMonitor; +import com.aquenos.epics.jackie.client.ChannelAccessMonitorListener; +import com.aquenos.epics.jackie.common.exception.ChannelAccessException; +import com.aquenos.epics.jackie.common.protocol.ChannelAccessEventMask; +import com.aquenos.epics.jackie.common.protocol.ChannelAccessStatus; +import com.aquenos.epics.jackie.common.value.ChannelAccessAlarmSeverity; +import com.aquenos.epics.jackie.common.value.ChannelAccessAlarmStatus; +import com.aquenos.epics.jackie.common.value.ChannelAccessControlsValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessGettableValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessString; +import com.aquenos.epics.jackie.common.value.ChannelAccessTimeValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessValueFactory; +import com.aquenos.epics.jackie.common.value.ChannelAccessValueType; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.epics.vtype.VType; +import org.phoebus.pv.PV; +import org.phoebus.pv.jackie.util.SimpleJsonParser; +import org.phoebus.pv.jackie.util.ValueConverter; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.regex.Pattern; + +/** + * Process variable representing a Channel Access channel. + */ +public class JackiePV extends PV { + + private record ParsedChannelName( + String ca_name, + boolean treat_char_as_long_string, + UsePutCallback use_put_callback) { + } + + private enum UsePutCallback { + NO, + YES, + AUTO, + } + + private static final Pattern RECORD_FIELD_AS_LONG_STRING_PATTERN = Pattern + .compile(".+\\.[A-Z][A-Z0-9]*\\$"); + + private final String ca_name; + + private final ChannelAccessChannel channel; + + private ChannelAccessMonitor> controls_monitor; + + private final ChannelAccessMonitorListener> controls_monitor_listener = new ChannelAccessMonitorListener<>() { + @Override + public void monitorError( + ChannelAccessMonitor> monitor, + ChannelAccessStatus status, String message) { + controlsMonitorException(monitor, + new ChannelAccessException(status, message)); + } + + @Override + public void monitorEvent( + ChannelAccessMonitor> monitor, + ChannelAccessControlsValue value) { + controlsMonitorEvent(monitor, value); + } + }; + + private boolean controls_value_expected; + + private ChannelAccessControlsValue last_controls_value; + + private ChannelAccessTimeValue last_time_value; + + private final Object lock = new Object(); + + private final JackiePreferences preferences; + + private ChannelAccessMonitor> time_monitor; + + private final ChannelAccessMonitorListener> time_monitor_listener = new ChannelAccessMonitorListener<>() { + @Override + public void monitorError( + ChannelAccessMonitor> monitor, + ChannelAccessStatus status, String message) { + timeMonitorException(monitor, + new ChannelAccessException(status, message)); + } + + @Override + public void monitorEvent( + ChannelAccessMonitor> monitor, + ChannelAccessGettableValue value) { + if (value.getType().isTimeType()) { + timeMonitorEvent(monitor, (ChannelAccessTimeValue) value); + } else if (value.getType() == ChannelAccessValueType.DBR_STRING) { + // We might receive a DBR_STRING if this channel uses the + // special RTYP handling. In this case, we use the local time + // and assume that there is no alarm. As an alternative, we + // could create a value without an alarm status and time stamp, + // but some application code might expect that there is always + // this meta-data, so we rather generate it here. + var string_value = (ChannelAccessString) value; + var now = System.currentTimeMillis(); + var time_string = ChannelAccessValueFactory + .createTimeString(string_value.getValue(), + channel.getClient().getConfiguration().getCharset(), + ChannelAccessAlarmSeverity.NO_ALARM, + ChannelAccessAlarmStatus.NO_ALARM, + (int) (now / 1000L + - ValueConverter.OFFSET_EPICS_TO_UNIX_EPOCH_SECONDS), + (int) (now % 1000L * 1000000L)); + timeMonitorEvent(monitor, time_string); + } else { + timeMonitorException(monitor, new RuntimeException( + "Received a monitor event with an value of the " + + "unexpected type " + + value.getType().name() + + ".")); + } + } + }; + + private final boolean treat_char_as_long_string; + + private final UsePutCallback use_put_callback; + + /** + * Create a PV backed by a Channel Access channel. + *

+ * Typically, this constructor should not be used directly. Instances + * should be received from {@link JackiePVFactory} through the + * {@link org.phoebus.pv.PVPool} instead. + * + * @param client CA client that is used for connecting the PV to the + * CA channel. + * @param preferences preferences for the Jackie client. This should be the + * same preferences that were also used when creating + * the client. + * @param name name of the PV (possibly including a prefix). + * @param base_name name of the PV without the prefix. + */ + public JackiePV( + ChannelAccessClient client, + JackiePreferences preferences, + String name, + String base_name) { + super(name); + logger.fine(getName() + " creating EPICS Jackie PV."); + var parse_name_result = parseName(base_name); + this.ca_name = parse_name_result.ca_name; + this.treat_char_as_long_string = parse_name_result.treat_char_as_long_string; + this.use_put_callback = parse_name_result.use_put_callback; + this.preferences = preferences; + // The PV base class starts of in a read-write state. We cannot know + // whether the PV is actually writable before the connection has been + // established, so we rather start in the read-only state. + this.notifyListenersOfPermissions(true); + this.channel = client.getChannel(this.ca_name); + this.channel.addConnectionListener(this::connectionEvent); + } + + @Override + public CompletableFuture asyncRead() throws Exception { + final var force_array = channel.getNativeCount() != 1; + final var listenable_future = channel.get( + timeTypeForNativeType(channel.getNativeDataType())); + logger.fine(getName() + " reading asynchronously."); + final var completable_future = new CompletableFuture(); + listenable_future.addCompletionListener((future) -> { + final ChannelAccessTimeValue value; + try { + // We know that we requested a time value, so we can be sure + // that we get one and can cast without further checks. + value = (ChannelAccessTimeValue) future.get(); + logger.fine( + getName() + + " asynchronous read completed successfully: " + + value); + } catch (InterruptedException e) { + // The listener is only called when the future has completed, + // so we should never receive such an exception. + Thread.currentThread().interrupt(); + completable_future.completeExceptionally( + new RuntimeException( + "Unexpected InterruptedException", e)); + return; + } catch (ExecutionException e) { + logger.log( + Level.FINE, + getName() + + " asynchronous read failed: " + + e.getMessage(), + e.getCause()); + completable_future.completeExceptionally(e.getCause()); + return; + } + ChannelAccessControlsValue controls_value; + final boolean controls_value_expected; + synchronized (lock) { + controls_value = this.last_controls_value; + controls_value_expected = this.controls_value_expected; + // We only save the value that we received if it matches + // the type of the stored controls value of if we did not + // receive a control value yet. Conversely, we do not use + // the controls value if its type does not match. + if (controls_value == null + || controls_value.getType().toSimpleType().equals( + value.getType().toSimpleType())) { + this.last_time_value = value; + } else { + controls_value = null; + } + } + // We do the conversion in a try-catch block because we have to + // ensure that the future always completes (otherwise, a thread + // waiting for it might be blocked indefinitely). + final VType vtype; + try { + vtype = ValueConverter.channelAccessToVType( + controls_value, + value, + channel.getClient().getConfiguration().getCharset(), + force_array, + preferences.honor_zero_precision(), + treat_char_as_long_string); + completable_future.complete(vtype); + } catch (Throwable e) { + completable_future.completeExceptionally(e); + return; + } + // The description in the API documentation states that the + // listeners are notified when a value is received through the use + // of asyncRead(). However, if we have not received a controls + // value yet, we cannot construct a VType with meta-data. In this + // case, we do not notify the listeners now. They are notified when + // we receive the controls value. + if (!controls_value_expected || controls_value != null) { + notifyListenersOfValue(vtype); + } + }); + return completable_future; + } + + @Override + public CompletableFuture asyncWrite(Object new_value) throws Exception { + return switch (use_put_callback) { + case AUTO, YES -> { + // Use ca_put_callback. + final var listenable_future = channel.put( + ValueConverter.objectToChannelAccessSimpleOnlyValue( + new_value, + channel.getClient().getConfiguration() + .getCharset(), + treat_char_as_long_string)); + final var completable_future = new CompletableFuture(); + listenable_future.addCompletionListener((future) -> { + try { + future.get(); + completable_future.complete(null); + } catch (InterruptedException e) { + // The listener is only called when the future has + // completed, so we should never receive such an + // exception. + Thread.currentThread().interrupt(); + completable_future.completeExceptionally( + new RuntimeException( + "Unexpected InterruptedException", e)); + } catch (ExecutionException e) { + completable_future.completeExceptionally(e.getCause()); + } + }); + yield completable_future; + } + case NO -> { + // Do not wait for the write operation to complete and instead + // report completion immediately. This allows code that does + // not have direct access to the API to avoid the use of + // ca_put_callback, which can have side effects on the server. + write(new_value); + var future = new CompletableFuture(); + future.complete(null); + yield future; + } + }; + } + + @Override + public void write(Object new_value) throws Exception { + switch (use_put_callback) { + case AUTO, NO -> { + // Use ca_put without a callback. + channel.putNoCallback( + ValueConverter.objectToChannelAccessSimpleOnlyValue( + new_value, + channel.getClient().getConfiguration() + .getCharset(), + treat_char_as_long_string)); + } + case YES -> { + // Wait for the write operation to complete. This allows code + // (e.g. OPIs) that does not have direct access to the API to + // wait for the write operation to complete. + var future = asyncWrite(new_value); + try { + future.get(); + } catch (ExecutionException e) { + var cause = e.getCause(); + try { + throw cause; + } catch (Error | Exception nested_e) { + throw nested_e; + } catch (Throwable nested_e) { + throw ExceptionUtils.asRuntimeException(nested_e); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + } + + @Override + protected void close() { + logger.fine(getName() + " closing PV."); + super.close(); + channel.destroy(); + // Destroying the channel implicitly destroys the monitors associated + // with it, so we can simply set them to null. + synchronized (lock) { + controls_monitor = null; + time_monitor = null; + } + } + + private void connectionEvent(ChannelAccessChannel channel, boolean now_connected) { + if (now_connected) { + logger.fine(getName() + " connected."); + // Let the listeners now whether the channel is writable. + boolean may_write; + // This event handler is called in the same thread that changes the + // connection state, so the channel cannot get disconnected while + // we are inside the handler. However, it can be destroyed + // asynchronously. Therefore, we simply return when we encounter an + // IllegalStateException while calling one of the methods that only + // work for connected channels. + ChannelAccessValueType native_data_type; + try { + may_write = channel.isMayWrite(); + native_data_type = channel.getNativeDataType(); + } catch (IllegalStateException e) { + return; + } + this.notifyListenersOfPermissions(!may_write); + var controls_type = controlsTypeForNativeType(native_data_type); + var time_type = timeTypeForNativeType(native_data_type); + if (time_type == null) { + // If we cannot convert the native type to a time type, we + // cannot meaningfully register a monitor, so we keep the PV + // disconnected. + return; + } + // We have to set the controls_value_expected flag before + // registering the monitor for time values. Otherwise, we might use + // a wrong value when receiving the first time-value event. + var controls_value_expected = (controls_type != null); + // We always create the monitors, even if the channel is not + // readable. In this case, the monitors will trigger an error which + // will be passed on to code trying to read this PV. + ChannelAccessMonitor> controls_monitor = null; + ChannelAccessMonitor time_monitor; + try { + time_monitor = channel.monitor( + time_type, preferences.monitor_mask()); + } catch (IllegalStateException e) { + return; + } + time_monitor.addMonitorListener(time_monitor_listener); + if (controls_type != null) { + if (preferences.dbe_property_supported()) { + try { + controls_monitor = createControlsMonitor( + channel, controls_type); + } catch (IllegalStateException e) { + time_monitor.destroy(); + return; + } + controls_monitor.addMonitorListener(controls_monitor_listener); + } else { + try { + channel.get(controls_type, 1) + .addCompletionListener((future) -> { + ChannelAccessGettableValue value; + try { + value = future.get(); + } catch (ExecutionException e) { + if (e.getCause() != null) { + controlsGetException(e.getCause()); + } else { + controlsGetException(e); + } + return; + } catch (Throwable e) { + controlsGetException(e); + return; + } + // We know that we requested a DBR_CTRL_* + // value, so we can safely cast here. + controlsGetSuccess( + (ChannelAccessControlsValue) value); + }); + } catch (Throwable e) { + controlsGetException(e); + } + } + } + synchronized (lock) { + this.controls_value_expected = controls_value_expected; + this.controls_monitor = controls_monitor; + this.time_monitor = time_monitor; + } + } else { + logger.fine(getName() + " disconnected."); + // When the PV is closed asynchronously while we are in this event + // handler, the references to the monitors might suddenly become + // null, so we have to handle this situation. + ChannelAccessMonitor controls_monitor; + ChannelAccessMonitor time_monitor; + synchronized (lock) { + controls_monitor = this.controls_monitor; + time_monitor = this.time_monitor; + this.controls_monitor = null; + this.time_monitor = null; + // Delete last values, so that we do not accidentally use them + // in event notifications when the channel gets connected + // again. + this.last_controls_value = null; + this.last_time_value = null; + } + if (time_monitor != null) { + time_monitor.destroy(); + } + if (controls_monitor != null) { + controls_monitor.destroy(); + } + // Let the listeners now that the PV is no longer connected. + this.notifyListenersOfDisconnect(); + // As the channel is disconnected now, we consider it to not be + // writable. + this.notifyListenersOfPermissions(true); + } + } + + private void controlsGetException(Throwable e) { + // This method is only called if the controls_monitor is null, so we + // can simply pass null to controlsMonitorEvent. + controlsMonitorException(null, e); + } + + private void controlsGetSuccess(ChannelAccessControlsValue value) { + // This method is only called if the controlsMonitor is null, so we can + // simply pass null to controlsMonitorEvent. + controlsMonitorEvent(null, value); + } + + private void controlsMonitorEvent( + ChannelAccessMonitor> monitor_from_listener, + ChannelAccessControlsValue controls_value) { + logger.fine(getName() + " received controls value: " + controls_value); + // If the monitor instance passed to the listener is not the same + // instance that we have here, we ignore the event. This can happen if + // a late notification arrives after destroying the monitor. In this + // case, controls_monitor is going to be null or a new monitor instance + // while monitor_from_listener is going to be an old monitor instance. + ChannelAccessTimeValue time_value; + synchronized (lock) { + if (controls_monitor != monitor_from_listener) { + return; + } + last_controls_value = controls_value; + time_value = last_time_value; + } + // If we previously received a time value, we can notify the listeners + // now. We do this without holding the lock in order to avoid potential + // deadlocks. There is a very small chance that due to not holding the + // lock, we might send an old value, but this should only happen when + // the channel has been disconnected to being destroyed, and in this + // case it should not matter any longer. + if (time_value != null) { + notifyListenersOfValue(controls_value, time_value); + } + } + + private void controlsMonitorException( + ChannelAccessMonitor> monitor_from_listener, + Throwable e) { + // If the monitor instance passed to the listener is not the same + // instance that we have here, we ignore the event. This can happen if + // a late notification arrives after destroying the monitor. In this + // case, controls_monitor is going to be null or a new monitor instance + // while monitor_from_listener is going to be an old monitor instance. + synchronized (lock) { + if (controls_monitor != monitor_from_listener) { + return; + } + } + logger.log( + Level.WARNING, + getName() + " monitor for DBR_CTRL_* value raised an exception.", + e); + } + + private ChannelAccessValueType controlsTypeForNativeType( + ChannelAccessValueType native_data_type) { + // Strings do not have additional meta-data, so registering a controls + // monitor does not make sense. + // If this channel is configured for long-string mode and we have a + // DBR_CHAR, there is no sense in requesting the meta-data either + // because we are not going to use it anyway. + if (native_data_type == ChannelAccessValueType.DBR_STRING) { + return null; + } else if (treat_char_as_long_string + && native_data_type == ChannelAccessValueType.DBR_CHAR) { + return null; + } else { + return native_data_type.toControlsType(); + } + } + + @SuppressWarnings("unchecked") + private ChannelAccessMonitor> createControlsMonitor( + ChannelAccessChannel channel, + ChannelAccessValueType controls_type + ) { + // We do not use the value received via this monitor, so requesting + // more than a single element would be a waste of bandwidth. + // We always request a DBR_CTRL_* type, so we can safely cast the + // monitor. + return (ChannelAccessMonitor>) channel + .monitor(controls_type, 1, ChannelAccessEventMask.DBE_PROPERTY); + } + + private ParsedChannelName parseName(String pv_name) { + // A PV name might consist of the actual CA channel name followed by + // optional parameters that configure the behavior of this PV source. + // In order to be compatible with the format used by the older DIIRT + // integration of EPICS Jackie, we use the same format. + // This means that these options are enclosed in curly braces and + // follow a JSON-style syntax. We also use the same option names. + // Extracting the JSON-string is a bit tricky: A valid channel name + // might contain a space and curly braces, so we cannot simply cut at + // the first combination of space and opening curly brace. JSON, on the + // other hand, might contain objects within the object, so cutting at + // the last combination of a space and opening curly brace is not + // necessarily correct either. + // However, channel names rarely contain spaces, so cutting at the + // first occurrence of a space and an opening curly brace is a pretty + // good assumption. If this does not work (the resulting string is not + // valid JSON), we simply look for other places where we can cut. + // If the string does not end with a closing curly brace, our life is + // much simpler, and we can simply assume that there is no JSON string + // at the end of the channel name. + pv_name = pv_name.trim(); + String ca_name = null; + var force_no_long_string = false; + var use_put_callback = UsePutCallback.AUTO; + var treat_char_as_long_string = false; + if (pv_name.endsWith("}")) { + var space_index = pv_name.indexOf(" {"); + Object json_obj = null; + // We remember the first exception because the first place where we + // cut the string is most likely the right place. + IllegalArgumentException first_exception = null; + while (space_index != -1) { + try { + json_obj = SimpleJsonParser.parse(pv_name.substring( + space_index + 1)); + first_exception = null; + break; + } catch (IllegalArgumentException e) { + // We try a larger portion of the string, but we save the + // exception in case the other attempts fail as well. + if (first_exception == null) { + first_exception = e; + } + } + space_index = pv_name.indexOf(" {", space_index + 2); + } + if (first_exception != null) { + logger.warning( + getName() + + " Ignoring JSON options in PV name because " + + "they cannot be parsed: " + + first_exception.getMessage()); + } else if (json_obj != null) { + // json_obj must be a map because we know that the string + // represents a JSON object (because of the curly braces). + @SuppressWarnings("unchecked") + var options = (Map) json_obj; + var long_string_option = options.get("longString"); + if (Boolean.TRUE.equals(long_string_option)) { + treat_char_as_long_string = true; + } else if (Boolean.FALSE.equals(long_string_option)) { + force_no_long_string = true; + } else if (options.containsKey("longString")) { + logger.warning( + getName() + + " illegal value for \"longString\" " + + "option (true or false was expected). " + + "Option is going to be ignored."); + } + var put_callback_option = options.get("putCallback"); + if (Boolean.TRUE.equals(put_callback_option)) { + use_put_callback = UsePutCallback.YES; + } else if (Boolean.FALSE.equals(put_callback_option)) { + use_put_callback = UsePutCallback.NO; + } else if (options.containsKey("putCallback")) { + logger.warning( + getName() + + " illegal value for \"putCallback\" " + + "option (true or false was expected). " + + "Option is going to be ignored."); + } + ca_name = pv_name.substring(0, space_index); + } + } + // If the ca_name has not been set yet, there is no valid JSON options + // part and the full channel name is the actual channel name. + if (ca_name == null) { + ca_name = pv_name; + } + // When reading fields from an IOC's record, one can read them as long + // strings (arrays of chars) by appending a dollar sign to the end of + // their names. If we find a channel name that matches this scheme, we + // assume that the array of chars should actually be treated as a + // string. + // We do not automatically set the treat_char_as_long_string option if + // it has been explicitly set to false by the user. + if (!treat_char_as_long_string && !force_no_long_string + && RECORD_FIELD_AS_LONG_STRING_PATTERN + .matcher(ca_name).matches()) { + treat_char_as_long_string = true; + } + return new ParsedChannelName( + ca_name, treat_char_as_long_string, use_put_callback); + } + + private void notifyListenersOfValue( + ChannelAccessControlsValue controls_value, + ChannelAccessTimeValue time_value) { + boolean force_array; + try { + force_array = channel.getNativeCount() != 1; + } catch (IllegalStateException e) { + // If the channel has been disconnected in the meantime, we skip + // the notification. + return; + } + var vtype = ValueConverter.channelAccessToVType( + controls_value, + time_value, + channel.getClient().getConfiguration().getCharset(), + force_array, + preferences.honor_zero_precision(), + treat_char_as_long_string); + notifyListenersOfValue(vtype); + } + + private void timeMonitorEvent( + ChannelAccessMonitor> monitor_from_listener, + ChannelAccessTimeValue time_value) { + logger.fine(getName() + " received time value: " + time_value); + // If the monitor instance passed to the listener is not the same + // instance that we have here, we ignore the event. This can happen if + // a late notification arrives after destroying the monitor. In this + // case, time_monitor is going to be null or a new monitor instance + // while monitor_from_listener is going to be an old monitor instance. + ChannelAccessControlsValue controls_value; + synchronized (lock) { + if (time_monitor != monitor_from_listener) { + return; + } + last_time_value = time_value; + controls_value = last_controls_value; + } + // If we previously received a time value, we can notify the listeners + // now. We do this without holding the lock in order to avoid potential + // deadlocks. There is a very small chance that due to not holding the + // lock, we might send an old value, but this should only happen when + // the channel has been disconnected to being destroyed, and in this + // case it should not matter any longer. + if (controls_value != null || !controls_value_expected) { + notifyListenersOfValue(controls_value, time_value); + } + } + + private void timeMonitorException( + ChannelAccessMonitor> monitor_from_listener, + Throwable e) { + // If the monitor instance passed to the listener is not the same + // instance that we have here, we ignore the event. This can happen if + // a late notification arrives after destroying the monitor. In this + // case, time_monitor is going to be null or a new monitor instance + // while monitor_from_listener is going to be an old monitor instance. + synchronized (lock) { + if (time_monitor != monitor_from_listener) { + return; + } + } + logger.log( + Level.WARNING, + getName() + " monitor for DBR_TIME_* value raised an exception.", + e); + } + + private ChannelAccessValueType timeTypeForNativeType( + ChannelAccessValueType native_data_type) { + // If the corresponding configuration flag is enabled, we want to handle + // the RTYP field in a special way. + if (preferences.rtyp_value_only() + && native_data_type == ChannelAccessValueType.DBR_STRING + && ca_name.endsWith(".RTYP")) { + return native_data_type; + } + // In theory, it is possible that the server sends a data-type that has + // no corresponding DBR_TIME_* type. In particular, this happens if it + // sends a DBR_PUT_ACKT, DBR_PUT_ACKS, DBR_STSACK_STRING, or + // DBR_CLASS_NAME. Sending a DBR_PUT_ACKT or DBR_PUT_ACKS are only used + // in write operations and DBR_STSACK_STRING and DBR_CLASS_NAME are + // only used in read operations when specifically requested. In fact, + // the CA server of EPICS Base will never report such a native type and + // as it does not make much sense, it is unlikely any other + // implementation will. Thus, we log an error and simply keep the + // channel disconnected. + try { + return native_data_type.toTimeType(); + } catch (IllegalArgumentException e) { + logger.severe( + getName() + + " server returned unexpected native type: " + + native_data_type.name()); + return null; + } + } + +} diff --git a/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePVFactory.java b/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePVFactory.java new file mode 100644 index 0000000000..572b90f1c8 --- /dev/null +++ b/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePVFactory.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.pv.jackie; + +import com.aquenos.epics.jackie.client.ChannelAccessClient; +import com.aquenos.epics.jackie.client.ChannelAccessClientConfiguration; +import com.aquenos.epics.jackie.client.DefaultChannelAccessClient; +import com.aquenos.epics.jackie.client.beacon.BeaconDetectorConfiguration; +import com.aquenos.epics.jackie.client.resolver.ChannelNameResolverConfiguration; +import com.aquenos.epics.jackie.common.exception.JavaUtilLoggingErrorHandler; +import com.aquenos.epics.jackie.common.util.ListenerLockPolicy; +import org.phoebus.pv.PV; +import org.phoebus.pv.PVFactory; + +import java.util.logging.Level; + +/** + *

+ * Factory for instances of {@link JackiePV}. + *

+ *

+ * Typically, this factory should not be used directly but through + * {@link org.phoebus.pv.PVPool}. There is no need to create more than one + * instance of this class, because all its state is static anyway. + *

+ *

+ * This class statically creates an instance of EPICS Jackie’s + * {@link DefaultChannelAccessClient}, which is configured using the default + * instance of {@link JackiePreferences}. + *

+ */ +public class JackiePVFactory implements PVFactory { + + private final static ChannelAccessClient CLIENT; + private final static JackiePreferences PREFERENCES; + private final static String TYPE = "jackie"; + + static { + PREFERENCES = JackiePreferences.getDefaultInstance(); + // We want to use a higher log-level for errors, so that we can be sure + // that they are reported, even if INFO logging is not enabled. + var error_handler = new JavaUtilLoggingErrorHandler( + Level.SEVERE, Level.WARNING); + var beacon_detector_config = new BeaconDetectorConfiguration( + error_handler, + PREFERENCES.ca_server_port(), + PREFERENCES.ca_repeater_port()); + var resolver_config = new ChannelNameResolverConfiguration( + PREFERENCES.charset(), + error_handler, + PREFERENCES.hostname(), + PREFERENCES.username(), + PREFERENCES.ca_server_port(), + PREFERENCES.ca_name_servers(), + PREFERENCES.ca_address_list(), + PREFERENCES.ca_auto_address_list(), + PREFERENCES.ca_max_search_period(), + PREFERENCES.ca_echo_interval(), + PREFERENCES.ca_multicast_ttl()); + var client_config = new ChannelAccessClientConfiguration( + PREFERENCES.charset(), + PREFERENCES.hostname(), + PREFERENCES.username(), + PREFERENCES.ca_max_array_bytes(), + PREFERENCES.ca_max_array_bytes(), + PREFERENCES.ca_echo_interval(), + PREFERENCES.cid_block_reuse_time(), + null, + Boolean.TRUE, + error_handler, + beacon_detector_config, + resolver_config); + // We use ListenerLockPolicy.IGNORE, because we call listeners from our + // code, and we cannot be sure whether these listeners might acquire + // locks, so the BLOCK policy could result in deadlocks. + CLIENT = new DefaultChannelAccessClient( + client_config, ListenerLockPolicy.IGNORE); + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public PV createPV(String name, String base_name) throws Exception { + return new JackiePV(CLIENT, PREFERENCES, name, base_name); + } + +} diff --git a/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePreferences.java b/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePreferences.java new file mode 100644 index 0000000000..6c678cd961 --- /dev/null +++ b/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/JackiePreferences.java @@ -0,0 +1,585 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.pv.jackie; + +import com.aquenos.epics.jackie.common.exception.ErrorHandler; +import com.aquenos.epics.jackie.common.protocol.ChannelAccessConstants; +import com.aquenos.epics.jackie.common.protocol.ChannelAccessEventMask; +import com.aquenos.epics.jackie.common.util.Inet4AddressUtil; +import org.apache.commons.lang3.tuple.Pair; +import org.phoebus.framework.preferences.PreferencesReader; + +import java.net.Inet4Address; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + *

+ * Preferences used by the {@link JackiePV} and {@link JackiePVFactory}. + *

+ *

+ * Each of the parameters corresponds to a property in the preferences system, + * using the org.phoebus.pv.jackie namespace. In addition to that, + * there is the use_env property, which controls whether the + * ca_* properties are actually used or whether the corresponding + * environment variables are preferred. + *

+ *

+ * Please refer to the pv_jackie_preferences.properties file for a + * full list of available properties and their meanings. + *

+ * + * @param ca_address_list + * EPICS servers that are contacted via UDP when resolving channel names. + * null means that the EPICS_CA_ADDR_LIST + * environment variable shall be used instead. + * @param ca_auto_address_list + * flag indicating whether the broadcast addresses of local interfaces shall + * be automatically added to the ca_address_list. + * null means that the EPICS_CA_AUTO_ADDR_LIST + * environment variable shall be used instead. + * @param ca_auto_array_bytes + * flag indicating whether the ca_max_array_bytes setting shall + * be discarded. null means that the + * EPICS_CA_AUTO_ARRAY_BYTES environment variable shall be used + * instead. + * @param ca_echo_interval + * time interval (in seconds) between sending echo requests to Channel Access + * servers. null means that the EPICS_CA_CONN_TMO + * environment variable shall be used instead. + * @param ca_max_array_bytes + * maximum size (in bytes) of a serialized value that can be transferred via + * Channel Access. This is not used when ca_auto_array_bytes is + * true. null means that the + * EPICS_CA_MAX_ARRAY_BYTES environment variable shall be used + * instead. + * @param ca_max_search_period + * time interval (in seconds) for that is used for the highest search period + * when resolving channel names. null means that the + * EPICS_CA_MAX_SEARCH_PERIOD environment variable shall be used + * instead. + * @param ca_multicast_ttl + * TTL used when sending multicast UDP packets. null means that + * the EPICS_CA_MCAST_TTL environment variable shall be used + * instead. + * @param ca_name_servers + * EPICS servers that are contacted via TCP when resolving channel names. + * null means that the EPICS_CA_NAME_SERVERS + * environment variable shall be used instead. + * @param ca_repeater_port + * UDP port used by the CA repeater. null means that the + * EPICS_CA_REPEATER_PORT environment variable shall be used + * instead. + * @param ca_server_port + * TCP and UDP port used when connecting to CA servers and the port is not + * known. null means that theEPICS_CA_SERVER_PORT + * environment variable shall be used instead. + * @param charset + * charset used when encoding or decoding Channel Access string values. + * @param cid_block_reuse_time + * time (in milliseconds) after which a CID (identifying a certain channel on + * the client side) may be reused. + * @param dbe_property_supported + * flag indicating whether a monitor using the DBE_PROPERTY event + * code shall be registered in order to be notified of meta-data changes. + * @param honor_zero_precision + * flag indicating whether a floating-point value specifying a precision of + * zero shall be printed without any fractional digits (true) or + * whether such a value should be printed using a default format + * (false). + * @param hostname + * hostname that is sent to the Channel Access server. null means + * that the hostname should be determined automatically. + * @param monitor_mask + * event mask used for the regular monitor. This mask should typically include + * DBE_ALARM and one of DBE_VALUE or + * DBE_ARCHIVE. + * @param rtyp_value_only + * flag indicating whether a value of type DBR_STRING instead of + * DBR_TIME_STRING should be requested when monitoring a channel + * with a name ending with .RTYP. + * @param username + * username that is sent to the Channel Access server. null means + * that the hostname should be determined automatically. + */ +public record JackiePreferences( + Set> ca_address_list, + Boolean ca_auto_address_list, + Boolean ca_auto_array_bytes, + Double ca_echo_interval, + Integer ca_max_array_bytes, + Double ca_max_search_period, + Integer ca_multicast_ttl, + Set> ca_name_servers, + Integer ca_repeater_port, + Integer ca_server_port, + Charset charset, + long cid_block_reuse_time, + boolean dbe_property_supported, + boolean honor_zero_precision, + String hostname, + ChannelAccessEventMask monitor_mask, + boolean rtyp_value_only, + String username) { + + private final static JackiePreferences DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = loadPreferences(); + } + + /** + * Returns the default instance of the preferences. This is the instance + * that is automatically configured through Phoebus’s + * {@link PreferencesReader}. + * + * @return preference instance created using the {@link PreferencesReader}. + */ + public static JackiePreferences getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static JackiePreferences loadPreferences() { + final var logger = Logger.getLogger( + JackiePreferences.class.getName()); + final var preference_reader = new PreferencesReader( + JackiePreferences.class, + "/pv_jackie_preferences.properties"); + Set> ca_address_list = null; + final var ca_address_list_string = preference_reader.get( + "ca_address_list"); + Boolean ca_auto_address_list = null; + final var ca_auto_address_list_string = preference_reader.get( + "ca_auto_address_list"); + Boolean ca_auto_array_bytes = null; + final var ca_auto_array_bytes_string = preference_reader.get( + "ca_auto_array_bytes"); + Double ca_echo_interval = null; + final var ca_echo_interval_string = preference_reader.get( + "ca_echo_interval"); + Integer ca_max_array_bytes = null; + final var ca_max_array_bytes_string = preference_reader.get( + "ca_max_array_bytes"); + Double ca_max_search_period = null; + final var ca_max_search_period_string = preference_reader.get( + "ca_max_search_period"); + Integer ca_multicast_ttl = null; + final var ca_multicast_ttl_string = preference_reader.get( + "ca_multicast_ttl"); + Set> ca_name_servers = null; + final var ca_name_servers_string = preference_reader.get( + "ca_name_servers"); + Integer ca_repeater_port = null; + final var ca_repeater_port_string = preference_reader.get( + "ca_repeater_port"); + Integer ca_server_port = null; + final var ca_server_port_string = preference_reader.get( + "ca_server_port"); + Charset charset = null; + final var charset_string = preference_reader.get("charset"); + if (!charset_string.isEmpty()) { + try { + charset = Charset.forName(charset_string); + } catch (IllegalCharsetNameException + | UnsupportedCharsetException e) { + logger.warning( + "Using UTF-8 charset because specified charset is " + + "invalid: " + + charset_string); + } + } + if (charset == null) { + charset = StandardCharsets.UTF_8; + } + final var cid_block_reuse_time = preference_reader.getLong( + "cid_block_reuse_time"); + final var dbe_property_supported = preference_reader.getBoolean( + "dbe_property_supported"); + final var honor_zero_precision = preference_reader.getBoolean( + "honor_zero_precision"); + var hostname = preference_reader.get("hostname"); + if (hostname.isEmpty()) { + hostname = null; + } + final var monitor_mask_string = preference_reader.get("monitor_mask"); + ChannelAccessEventMask monitor_mask; + try { + monitor_mask = parseMonitorMask(monitor_mask_string); + } catch (IllegalArgumentException e) { + logger.severe("Invalid monitor mask: " + monitor_mask_string); + monitor_mask = ChannelAccessEventMask.DBE_VALUE.or( + ChannelAccessEventMask.DBE_ALARM); + } + final var rtyp_value_only = preference_reader.getBoolean( + "rtyp_value_only"); + final var use_env = preference_reader.getBoolean("use_env"); + var username = preference_reader.get("username"); + if (username.isEmpty()) { + username = null; + } + if (use_env) { + if (!ca_address_list_string.isEmpty()) { + logger.warning( + "use_env = true, ca_address_list setting is ignored."); + } + if (!ca_auto_address_list_string.isEmpty()) { + logger.warning( + "use_env = true, ca_auto_address_list setting is " + + "ignored."); + } + if (!ca_auto_array_bytes_string.isEmpty()) { + logger.warning( + "use_env = true, ca_auto_array_bytes setting is " + + "ignored."); + } + if (!ca_echo_interval_string.isEmpty()) { + logger.warning( + "use_env = true, ca_echo_interval setting is " + + "ignored."); + } + if (!ca_max_array_bytes_string.isEmpty()) { + logger.warning( + "use_env = true, ca_max_array_bytes setting is " + + "ignored."); + } + if (!ca_max_search_period_string.isEmpty()) { + logger.warning( + "use_env = true, ca_max_search_period setting is " + + "ignored."); + } + if (!ca_multicast_ttl_string.isEmpty()) { + logger.warning( + "use_env = true, ca_multicast_ttl setting is " + + "ignored."); + } + if (!ca_name_servers_string.isEmpty()) { + logger.warning( + "use_env = true, ca_name_servers setting is ignored."); + } + if (!ca_repeater_port_string.isEmpty()) { + logger.warning( + "use_env = true, ca_repeater_port setting is " + + "ignored."); + } + if (!ca_server_port_string.isEmpty()) { + logger.warning( + "use_env = true, ca_server_port setting is ignored."); + } + } else { + if (ca_auto_address_list_string.isEmpty()) { + ca_auto_address_list = Boolean.TRUE; + } else { + ca_auto_address_list = Boolean.valueOf( + ca_auto_address_list_string); + } + if (ca_auto_array_bytes_string.isEmpty()) { + ca_auto_array_bytes = Boolean.TRUE; + } else { + ca_auto_array_bytes = Boolean.valueOf( + ca_auto_array_bytes_string); + } + if (!ca_echo_interval_string.isEmpty()) { + ca_echo_interval = 30.0; + } else { + try { + ca_echo_interval = Double.valueOf(ca_echo_interval_string); + } catch (NumberFormatException e) { + logger.warning( + "Using ca_echo_interval = 30.0 because specified " + + "value is invalid: " + + ca_echo_interval_string); + ca_echo_interval = 30.0; + } + if (ca_echo_interval < 0.1) { + logger.warning( + "ca_echo_interval = " + + ca_echo_interval + + " is too small. Using ca_echo_inteval = " + + "0.1 instead."); + ca_echo_interval = 0.1; + } + if (!Double.isFinite(ca_echo_interval)) { + logger.warning( + "Using ca_echo_interval = 30.0 because specified " + + "value is invalid: " + + ca_echo_interval); + ca_echo_interval = 30.0; + } + } + if (ca_max_array_bytes_string.isEmpty()) { + ca_max_array_bytes = 16384; + } else { + try { + ca_max_array_bytes = Integer.valueOf( + ca_max_array_bytes_string); + } catch (NumberFormatException e) { + logger.warning( + "Using ca_max_array_bytes = 16384 because " + + "specified value is invalid: " + + ca_max_array_bytes_string); + ca_max_array_bytes = 16384; + } + if (ca_max_array_bytes < 16384) { + logger.warning( + "ca_max_array_bytes = " + + ca_max_array_bytes + + " is too small. Using " + + "ca_max_array_bytes = 16384 instead."); + ca_max_array_bytes = 16384; + } + } + if (ca_max_search_period_string.isEmpty()) { + ca_max_search_period = 60.0; + } else { + try { + ca_max_search_period = Double.valueOf( + ca_max_search_period_string); + } catch (NumberFormatException e) { + logger.warning( + "Using ca_max_search_period = 60.0 because " + + "specified value is invalid: " + + ca_max_search_period_string); + ca_max_search_period = 60.0; + } + if (ca_max_search_period < 60.0) { + logger.warning( + "ca_max_search_period = " + + ca_max_search_period + + " is too small. Using " + + "ca_max_search_period = 60.0 instead."); + ca_max_search_period = 60.0; + } + if (!Double.isFinite(ca_max_search_period)) { + logger.warning( + "Using ca_max_search_period = 30.0 because " + + "specified value is invalid: " + + ca_max_search_period); + ca_max_search_period = 60.0; + } + } + if (ca_multicast_ttl_string.isEmpty()) { + ca_multicast_ttl = 1; + } else { + try { + ca_multicast_ttl = Integer.valueOf(ca_multicast_ttl_string); + } catch (NumberFormatException e) { + logger.warning( + "Using ca_multicast_ttl = 1 because specified " + + "value is invalid: " + + ca_multicast_ttl_string); + ca_multicast_ttl = 1; + } + if (ca_multicast_ttl < 1) { + logger.warning( + "ca_multicast_ttl = " + + ca_multicast_ttl + + " is too small. Using ca_multicast_ttl " + + "= 1 instead."); + ca_multicast_ttl = 1; + } + if (ca_multicast_ttl > 255) { + logger.warning( + "ca_multicast_ttl = " + + ca_multicast_ttl + + " is too large. Using ca_multicast_ttl " + + "= 255 instead."); + ca_multicast_ttl = 255; + } + } + if (ca_repeater_port_string.isEmpty()) { + ca_repeater_port = ( + ChannelAccessConstants.DEFAULT_REPEATER_PORT); + } else { + try { + ca_repeater_port = Integer.valueOf(ca_repeater_port_string); + } catch (NumberFormatException e) { + logger.warning( + "Using ca_repeater_port = " + + ChannelAccessConstants.DEFAULT_REPEATER_PORT + + " because specified value is invalid: " + + ca_repeater_port_string); + ca_repeater_port = ( + ChannelAccessConstants.DEFAULT_REPEATER_PORT); + } + if (ca_repeater_port < 1 || ca_repeater_port > 65535) { + logger.warning( + "Using ca_repeater_port = " + + ChannelAccessConstants.DEFAULT_REPEATER_PORT + + " because specified value is invalid: " + + ca_repeater_port); + ca_repeater_port = ( + ChannelAccessConstants.DEFAULT_REPEATER_PORT); + } + } + if (ca_server_port_string.isEmpty()) { + ca_server_port = ( + ChannelAccessConstants.DEFAULT_SERVER_PORT); + } else { + try { + ca_server_port = Integer.valueOf(ca_server_port_string); + } catch (NumberFormatException e) { + logger.warning( + "Using ca_server_port = " + + ChannelAccessConstants.DEFAULT_SERVER_PORT + + " because specified value is invalid: " + + ca_server_port_string); + ca_server_port = ( + ChannelAccessConstants.DEFAULT_SERVER_PORT); + } + if (ca_server_port < 1 || ca_server_port > 65535) { + logger.warning( + "Using ca_server_port = " + + ChannelAccessConstants.DEFAULT_SERVER_PORT + + " because specified value is invalid: " + + ca_server_port); + ca_server_port = ( + ChannelAccessConstants.DEFAULT_SERVER_PORT); + } + } + // We need the server port setting in order to process the address + // lists, so we process them last. + if (ca_address_list_string.isEmpty()) { + ca_address_list = Collections.emptySet(); + } else { + ca_address_list = parseAddressList( + ca_address_list_string, + ca_server_port, + "ca_address_list", + logger); + } + if (ca_name_servers_string.isEmpty()) { + ca_name_servers = Collections.emptySet(); + } else { + ca_name_servers = parseAddressList( + ca_name_servers_string, + ca_server_port, + "ca_name_servers", + logger); + } + // Log all CA related settings. We only do this if use_env is + // false, because these settings are not used when use_env is true. + logger.config( + "ca_address_list = " + serializeAddressList( + ca_address_list, ca_server_port)); + logger.config("ca_auto_address_list = " + ca_auto_address_list); + logger.config("ca_auto_array_bytes = " + ca_auto_array_bytes); + logger.config("ca_echo_interval = " + ca_echo_interval); + logger.config("ca_max_array_bytes = " + ca_max_array_bytes); + logger.config("ca_max_search_period = " + ca_max_search_period); + logger.config("ca_multicast_ttl = " + ca_multicast_ttl); + logger.config( + "ca_name_servers = " + serializeAddressList( + ca_name_servers, ca_server_port)); + logger.config("ca_repeater_port = " + ca_repeater_port); + logger.config("ca_server_port = " + ca_server_port); + } + logger.config("charset = " + charset.name()); + logger.config("cid_block_reuse_time = " + cid_block_reuse_time); + logger.config("dbe_property_supported = " + dbe_property_supported); + logger.config("honor_zero_precision = " + honor_zero_precision); + logger.config("hostname = " + hostname); + logger.config("monitor_mask = " + monitor_mask); + logger.config("rtyp_value_only = " + rtyp_value_only); + logger.config("use_env = " + use_env); + logger.config("username = " + username); + return new JackiePreferences( + ca_address_list, + ca_auto_address_list, + ca_auto_array_bytes, + ca_echo_interval, + ca_max_array_bytes, + ca_max_search_period, + ca_multicast_ttl, + ca_name_servers, + ca_repeater_port, + ca_server_port, + charset, + cid_block_reuse_time, + dbe_property_supported, + honor_zero_precision, + hostname, + monitor_mask, + rtyp_value_only, + username); + } + + private static Set> parseAddressList( + final String address_list_string, + final int default_port, + final String setting_name, + final Logger logger) { + final ErrorHandler error_handler = (context, e, description) -> { + final String message; + if (description == null) { + message = "Error while parsing address list in " + setting_name + + "."; + } else { + message = "Error while parsing address list in " + setting_name + + ": " + description; + } + if (e != null) { + logger.log(Level.WARNING, message, e); + } else { + logger.log(Level.WARNING, message); + } + }; + final var socket_address_list = Inet4AddressUtil.stringToInet4SocketAddressList( + address_list_string, default_port, false, error_handler); + final Set> addresses = new LinkedHashSet<>(); + for (final var socket_address : socket_address_list) { + var address = socket_address.getAddress(); + var port = socket_address.getPort(); + // We know that the socket addresses returned by + // stringToInet4SocketAddressList only use instances of + // Inet4Address, so we can cast without checking. + addresses.add(Pair.of((Inet4Address) address, port)); + } + return addresses; + } + + private static ChannelAccessEventMask parseMonitorMask(final String mask_string) { + ChannelAccessEventMask mask = ChannelAccessEventMask.DBE_NONE; + for (final var token : mask_string.split("\\|")) { + switch (token.trim()) { + case "DBE_ALARM" -> mask = mask.setAlarm(true); + case "DBE_ARCHIVE" -> mask = mask.setArchive(true); + case "DBE_PROPERTY" -> mask = mask.setProperty(true); + case "DBE_VALUE" -> mask = mask.setValue(true); + default -> throw new IllegalArgumentException(); + } + } + return mask; + } + + private static String serializeAddressList( + final Set> address_list, + final int default_port) { + Function, String> entry_to_string = (entry) -> { + var address = entry.getLeft(); + var port = entry.getRight(); + if (port == default_port) { + return address.getHostAddress(); + } else { + return address.getHostAddress() + ":" + port; + } + }; + return address_list.stream().map(entry_to_string).collect( + Collectors.joining(" ")); + } + +} diff --git a/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/util/SimpleJsonParser.java b/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/util/SimpleJsonParser.java new file mode 100644 index 0000000000..1e720101a9 --- /dev/null +++ b/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/util/SimpleJsonParser.java @@ -0,0 +1,578 @@ +/******************************************************************************* + * Copyright (c) 2017-2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.pv.jackie.util; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.PrimitiveIterator; + +/** + *

+ * Simple JSON parser. This parser is optimized for simplicity, not + * performance, so code that wants to parse large or complex JSON documents + * should use a different JSON parser. + *

+ * + *

+ * This parser has specifically been written in order to minimize the + * dependencies needed for parsing JSON document. It only uses the Java 17 SE + * API and the Apache Commons Lang 3 library. + *

+ * + *

+ * This parser is able to parse any document that complies with the JSON + * (ECMA-404) standard. Compared to many other parsers, this parser is very + * strict about compliance and will typically refuse any input that is not + * strictly compliant. + *

+ * + *

+ * This parser converts JSON objects to Java objects using the following rules: + *

+ * + *
    + *
  • A JSON object is converted to a {@link Map Map<String, Object>}. + * The order of the members is preserved in the map. The parser does not allow + * duplicate member keys in objects. If a member using the same key as an + * earlier member is found, the parser throws an exception.
  • + *
  • A JSON array is converted to a {@link List List<Object>}.
  • + *
  • A JSON string is converted to a {@link String}.
  • + *
  • A JSON number is converted to a {@link Number}. The actual type of the + * {@code Number} depends on the number's value and should be regarded as an + * implementation detail that might change in the future.
  • + *
  • A JSON boolean value is converted to a {@link Boolean}.
  • + *
  • A JSON value of null is converted to + * null.
  • + *
+ */ +public class SimpleJsonParser { + + /** + * Parses the specified string into a Java object. Please refer to the + * {@linkplain SimpleJsonParser class description} for details. + * + * @param json_string + * string that represents a valid JSON document. + * @return object that is the result of converting the string from JSON + * into a Java object. null if and only if the + * json_string is the literal string "null". + * @throws IllegalArgumentException + * if the json_string cannot be parsed because it + * is either invalid, or there is an object with duplicate + * member keys. + */ + public static Object parse(String json_string) { + // If json_string is null, fail early. + if (json_string == null) { + throw new NullPointerException(); + } + return new SimpleJsonParser(json_string).parse(); + } + + private static String escapeString(String s) { + return s.codePoints().collect( + StringBuilder::new, + (sb, code_point) -> { + switch (code_point) { + case 8: // \b + case 9: // \t + case 10: // \n + case 12: // \f + case 13: // \r + case 34: // \" + case 92: // \\ + sb.append('\\'); + } + sb.appendCodePoint(code_point); + }, + StringBuilder::append).toString(); + } + + private final String parsed_string; + private int position; + + private SimpleJsonParser(String json_string) { + this.parsed_string = json_string; + this.position = 0; + } + + private boolean accept(int code_point) { + if (isNext(code_point)) { + consumeCodePoint(); + return true; + } else { + return false; + } + } + + private boolean accept(String accepted_string) { + if (parsed_string.startsWith(accepted_string, position)) { + position += accepted_string.length(); + return true; + } else { + return false; + } + } + + private Optional acceptAnyOf(String options) { + if (exhausted()) { + return Optional.empty(); + } + int actual_code_point = peek(); + int index = 0; + while (index < options.length()) { + int expected_code_point = options.codePointAt(index); + index += Character.charCount(expected_code_point); + if (actual_code_point == expected_code_point) { + return Optional.of(consumeCodePoint()); + } + } + return Optional.empty(); + } + + private void acceptWhitespace() { + boolean is_whitespace = true; + while (!exhausted() && is_whitespace) { + int code_point = peek(); + switch (code_point) { + case '\t': + case '\n': + case '\r': + case ' ': + consumeCodePoint(); + break; + default: + is_whitespace = false; + break; + } + } + } + + private int consumeCodePoint() { + // We assume that this method is only called after checking that we + // have not reached the end of the string. + int code_point = parsed_string.codePointAt(position); + position += Character.charCount(code_point); + return code_point; + } + + private String escapeAndShorten() { + return escapeAndShorten(parsed_string.substring(position)); + } + + private String escapeAndShorten(CharSequence cs) { + int max_length = 12; + if (cs.length() < max_length) { + return escapeString(cs.toString()); + } else { + return escapeString(cs.subSequence(0, max_length - 3) + "..."); + } + } + + private boolean exhausted() { + return position >= parsed_string.length(); + } + + private void expect(int expected_code_point) { + if (exhausted()) { + throw new IllegalArgumentException("Expected '" + + new String(Character.toChars(expected_code_point)) + + "', but found end-of-string."); + } + int actual_code_point = consumeCodePoint(); + if (actual_code_point != expected_code_point) { + throw new IllegalArgumentException("Expected '" + + new String(Character.toChars(expected_code_point)) + + "', but found '" + + new String(Character.toChars(actual_code_point)) + "'."); + } + } + + private int expectAny(String description) { + if (exhausted()) { + throw new IllegalArgumentException( + "Expected " + description + ", but found end-of-string."); + } + int code_point = peek(); + if (!Character.isValidCodePoint(code_point)) { + throw new IllegalArgumentException( + "Expected " + description + + ", but found invalid code point \\u" + + StringUtils.leftPad(Integer.toString( + code_point, 16), 4) + + "."); + } + consumeCodePoint(); + return code_point; + } + + private int expectAnyOf(String options, String description) { + if (exhausted()) { + throw new IllegalArgumentException( + "Expected " + description + ", but found end-of-string."); + } + int actual_code_point = peek(); + int index = 0; + while (index < options.length()) { + int expected_code_point = options.codePointAt(index); + index += Character.charCount(expected_code_point); + if (actual_code_point == expected_code_point) { + return consumeCodePoint(); + } + } + throw new IllegalArgumentException("Expected " + description + + ", but found '" + + new String(Character.toChars(actual_code_point)) + "'."); + } + + private int expectDecimalDigit() { + return expectAnyOf("0123456789", + "'0', '1', '2', '3', '4', '5', '6', '7', or '9'"); + } + + private int fourHexDigits() { + StringBuilder four_digits = new StringBuilder(4); + while (four_digits.length() < 4) { + four_digits.appendCodePoint( + expectAnyOf( + "0123456789ABCDEFabcdef", + "hexadecimal digit")); + } + return Integer.valueOf(four_digits.toString(), 16); + } + + private boolean isNext(int code_point) { + return !exhausted() && peek() == code_point; + } + + private boolean isNextAnyOf(String options) { + if (exhausted()) { + return false; + } + int actual_code_point = peek(); + int index = 0; + while (index < options.length()) { + int expected_code_point = options.codePointAt(index); + index += Character.charCount(expected_code_point); + if (actual_code_point == expected_code_point) { + return true; + } + } + return false; + } + + private List jsonArray() { + // We use an ArrayList because in general, it performs better than a + // LinkedList. + expect('['); + acceptWhitespace(); + if (accept(']')) { + return Collections.emptyList(); + } + ArrayList members = new ArrayList<>(); + boolean array_closed = false; + while (!array_closed) { + members.add(jsonValue()); + acceptWhitespace(); + if (accept(']')) { + array_closed = true; + } else { + expect(','); + acceptWhitespace(); + } + } + return members; + } + + private Number jsonNumber() { + // First, we copy the number into a string builder. This way, we know + // that we have a valid number, and we know where it ends. + StringBuilder sb = new StringBuilder(); + sb.append(jsonNumberIntPart()); + if (accept('.')) { + sb.appendCodePoint('.'); + sb.append(jsonNumberDigitsPart(false)); + } + Optional e_code_point = acceptAnyOf("eE"); + if (e_code_point.isPresent()) { + sb.appendCodePoint(e_code_point.get()); + if (accept('+')) { + sb.appendCodePoint('+'); + } else if (accept('-')) { + sb.appendCodePoint('-'); + } + sb.append(jsonNumberDigitsPart(false)); + } + BigDecimal number = new BigDecimal(sb.toString()); + try { + return number.byteValueExact(); + } catch (ArithmeticException e) { + // Ignore any exception that might occur here, we simply continue + // with other conversions. + } + try { + return number.shortValueExact(); + } catch (ArithmeticException e) { + // Ignore any exception that might occur here, we simply continue + // with other conversions. + } + try { + return number.intValueExact(); + } catch (ArithmeticException e) { + // Ignore any exception that might occur here, we simply continue + // with other conversions. + } + try { + return number.longValueExact(); + } catch (ArithmeticException e) { + // Ignore any exception that might occur here, we simply continue + // with other conversions. + } + float number_as_float = number.floatValue(); + if (Float.isFinite(number_as_float) + && BigDecimal.valueOf(number_as_float).equals(number)) { + return number_as_float; + } + double number_as_double = number.doubleValue(); + if (Double.isFinite(number_as_double) + && BigDecimal.valueOf(number_as_double).equals(number)) { + return number_as_double; + } + try { + return number.toBigIntegerExact(); + } catch (ArithmeticException e) { + // Ignore any exception that might occur here, we simply return the + // BigDecimal. + } + return number; + } + + private CharSequence jsonNumberDigitsPart(boolean optional) { + StringBuilder sb = new StringBuilder(); + if (!optional) { + int digitCodePoint = expectDecimalDigit(); + sb.appendCodePoint(digitCodePoint); + } + Optional next_digit_code_point = acceptAnyOf("0123456789"); + while (next_digit_code_point.isPresent()) { + sb.appendCodePoint(next_digit_code_point.get()); + next_digit_code_point = acceptAnyOf("0123456789"); + } + return sb; + } + + private CharSequence jsonNumberIntPart() { + StringBuilder sb = new StringBuilder(); + if (accept('-')) { + sb.appendCodePoint('-'); + } + int digit_code_point = expectDecimalDigit(); + sb.appendCodePoint(digit_code_point); + if (digit_code_point == '0') { + return sb; + } + sb.append(jsonNumberDigitsPart(true)); + return sb; + } + + private Map jsonObject() { + expect('{'); + acceptWhitespace(); + if (accept('}')) { + return Collections.emptyMap(); + } + // We use a linked hash-map so that the order of members is + // preserved. + LinkedHashMap members = new LinkedHashMap<>(); + boolean object_closed = false; + while (!object_closed) { + Pair member = jsonObjectMember(); + // This is a SIMPLE parser, so we do not support duplicate keys + // (even though the JSON specification basically allows them). + if (members.put(member.getLeft(), member.getRight()) != null) { + throw new IllegalArgumentException( + "Found duplicate key \"" + + escapeAndShorten(member.getLeft()) + + "\" in object."); + } + acceptWhitespace(); + if (accept('}')) { + object_closed = true; + } else { + expect(','); + acceptWhitespace(); + } + } + return members; + } + + private Pair jsonObjectMember() { + String key = jsonString(); + acceptWhitespace(); + expect(':'); + acceptWhitespace(); + Object value = jsonValue(); + return Pair.of(key, value); + } + + private String jsonString() { + expect('"'); + StringBuilder content = new StringBuilder(); + boolean string_closed = false; + while (!string_closed) { + if (accept('"')) { + string_closed = true; + } else if (accept('\\')) { + int codePoint = expectAnyOf( + "\"\\/bfnrtu", + "any of '\"', '\\', '/', 'b', 'f', 'n', 'r', 't', or 'u'"); + switch (codePoint) { + case '"': + case '\\': + case '/': + content.appendCodePoint(codePoint); + break; + case 'b': + content.appendCodePoint('\b'); + break; + case 'f': + content.appendCodePoint('\f'); + break; + case 'n': + content.appendCodePoint('\n'); + break; + case 'r': + content.appendCodePoint('\r'); + break; + case 't': + content.appendCodePoint('\t'); + break; + case 'u': + // Unicode sequence. + int hex_code_point = fourHexDigits(); + if (!Character.isValidCodePoint(hex_code_point)) { + String hex_code_point_as_string = StringUtils.leftPad( + Integer.toString(hex_code_point, 16), + 4); + throw new IllegalArgumentException( + "Illegal code point specified in unicode " + + "sequence \\u" + + hex_code_point_as_string + + "."); + } + content.appendCodePoint(hex_code_point); + break; + default: + // We matched all characters that we passed to the expect + // method, so we really should not find any other ones. + throw new RuntimeException("Internal logic error."); + } + } else { + int code_point = expectAny("valid string content"); + if (code_point > 0 && code_point < 0x20) { + String code_point_as_string = StringUtils.leftPad( + Integer.toString(code_point), 2, '0'); + throw new IllegalArgumentException( + "Expected valid string content, but found invalid " + + "control character 0x" + + code_point_as_string + + "."); + } + content.appendCodePoint(code_point); + } + } + return content.toString(); + } + + private Object jsonValue() { + if (isNext('"')) { + return jsonString(); + } else if (isNext('{')) { + return jsonObject(); + } else if (isNext('[')) { + return jsonArray(); + } else if (accept("true")) { + return Boolean.TRUE; + } else if (accept("false")) { + return Boolean.FALSE; + } else if (accept("null")) { + return null; + } else if (isNextAnyOf("-0123456789")) { + return jsonNumber(); + } else { + throw new IllegalArgumentException( + "Expected JSON value, but found \"" + escapeAndShorten() + + "\"."); + } + } + + private Object parse() { + // We always throw an IllegalArgumentException, so we can specifically + // catch it. + String error_message; + try { + Object obj = jsonValue(); + if (position < parsed_string.length()) { + throw new IllegalArgumentException( + "Expected end-of-string, but found \"" + + escapeAndShorten() + "\"."); + } + return obj; + } catch (IllegalArgumentException e) { + error_message = e.getMessage(); + } + // We use the position information to determine where the problem + // happened. This can help the user to find the problem in the document. + int line = 0; + int column = 0; + boolean last_char_was_cr = false; + + PrimitiveIterator.OfInt code_point_iterator = ( + parsed_string.codePoints().iterator()); + while (code_point_iterator.hasNext()) { + int code_point = code_point_iterator.nextInt(); + // We ignore a newline directly after a carriage return, if we did + // not, we would mess up our line count for documents using CR LF as + // the end-of-line sequence. + if (last_char_was_cr && code_point == '\n') { + last_char_was_cr = false; + continue; + } + last_char_was_cr = false; + if (code_point == '\r') { + last_char_was_cr = true; + ++line; + column = 0; + } else if (code_point == '\n') { + ++line; + column = 0; + } else { + ++column; + } + } + // Most users expect one-based line and column numbers, so we add one + // when including them in the error message. + throw new IllegalArgumentException("Error at line " + (line + 1) + + " column " + (column + 1) + ": " + error_message); + } + + private int peek() { + // We assume that this method is only called after checking that we have + // not reached the end of the string. + return parsed_string.codePointAt(position); + } +} diff --git a/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/util/ValueConverter.java b/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/util/ValueConverter.java new file mode 100644 index 0000000000..ec2733b5ef --- /dev/null +++ b/core/pv-jackie/src/main/java/org/phoebus/pv/jackie/util/ValueConverter.java @@ -0,0 +1,556 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.pv.jackie.util; + +import com.aquenos.epics.jackie.common.util.NullTerminatedStringUtil; +import com.aquenos.epics.jackie.common.value.ChannelAccessControlsValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessFloatingPointControlsValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessGraphicsEnum; +import com.aquenos.epics.jackie.common.value.ChannelAccessNumericControlsValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessSimpleOnlyValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessTimeChar; +import com.aquenos.epics.jackie.common.value.ChannelAccessTimeDouble; +import com.aquenos.epics.jackie.common.value.ChannelAccessTimeEnum; +import com.aquenos.epics.jackie.common.value.ChannelAccessTimeFloat; +import com.aquenos.epics.jackie.common.value.ChannelAccessTimeLong; +import com.aquenos.epics.jackie.common.value.ChannelAccessTimeShort; +import com.aquenos.epics.jackie.common.value.ChannelAccessTimeString; +import com.aquenos.epics.jackie.common.value.ChannelAccessTimeValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessValueFactory; +import org.epics.util.array.ListByte; +import org.epics.util.array.ListDouble; +import org.epics.util.array.ListFloat; +import org.epics.util.array.ListInteger; +import org.epics.util.array.ListShort; +import org.epics.util.stats.Range; +import org.epics.util.text.NumberFormats; +import org.epics.vtype.Alarm; +import org.epics.vtype.AlarmSeverity; +import org.epics.vtype.AlarmStatus; +import org.epics.vtype.Display; +import org.epics.vtype.EnumDisplay; +import org.epics.vtype.Time; +import org.epics.vtype.VByte; +import org.epics.vtype.VByteArray; +import org.epics.vtype.VDouble; +import org.epics.vtype.VDoubleArray; +import org.epics.vtype.VEnum; +import org.epics.vtype.VEnumArray; +import org.epics.vtype.VFloat; +import org.epics.vtype.VFloatArray; +import org.epics.vtype.VInt; +import org.epics.vtype.VIntArray; +import org.epics.vtype.VShort; +import org.epics.vtype.VShortArray; +import org.epics.vtype.VString; +import org.epics.vtype.VStringArray; +import org.epics.vtype.VType; +import org.phoebus.core.vtypes.VTypeHelper; + +import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +/** + * Converts between VTypes and Channel Access values. + */ +public final class ValueConverter { + + /** + * Offset between the UNIX and EPICS epoch in seconds. + */ + public static final long OFFSET_EPICS_TO_UNIX_EPOCH_SECONDS = 631152000L; + + private ValueConverter() { + } + + /** + * Converts a Channel Access value to a VType. + *

+ * The underlying types of the controls_value and the + * time_value must match. + * + * @param controls_value + * CA value from which the meta-data is used. May be null>. + * If null the resulting value is constructed without + * meta-data. + * @param time_value + * CA value from which the value, alarm severity and status, and time + * stamp are used. + * @param charset + * charset that is used to convert arrays of bytes to strings (only + * relevant if treat_char_as_long_string is + * true). + * @param force_array + * whether values with a single element should be converted to array + * VTypes. + * @param honor_zero_precision + * whether floating-point values specifying a zero-precision should be + * rendered without any fractional digits. If true, they are + * rendered without fractional digits. If false, they are + * rendered using a default format. + * @param treat_char_as_long_string + * whether values of type DBR_CHAR_* should be converted to + * strings. + * @return + * VType representing the combination of controls_value and + * time_value. + * @throws IllegalArgumentException + * if the underlying base types of controls_value and + * time_value do not match. + */ + public static VType channelAccessToVType( + ChannelAccessControlsValue controls_value, + ChannelAccessTimeValue time_value, + Charset charset, + boolean force_array, + boolean honor_zero_precision, + boolean treat_char_as_long_string) { + if (time_value == null) { + throw new NullPointerException("time_value must not be null."); + } + // The controls and time values must have compatible types. + if (controls_value != null + && controls_value.getType().toSimpleType() + != time_value.getType().toSimpleType()) { + throw new IllegalArgumentException( + "Value of type " + controls_value.getType() + + " is not compatible with value of type " + + time_value.getType() + "."); + } + Alarm alarm = convertAlarm(time_value); + Time time = convertTime(time_value); + Display display = convertDisplay(controls_value, honor_zero_precision); + return switch (time_value.getType()) { + case DBR_TIME_CHAR -> { + ChannelAccessTimeChar typed_time_value = (ChannelAccessTimeChar) time_value; + if (treat_char_as_long_string) { + ByteBuffer buffer = typed_time_value.getValue(); + byte[] bytes; + if (buffer.hasArray()) { + bytes = buffer.array(); + } else { + bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + } + String stringValue = NullTerminatedStringUtil.nullTerminatedBytesToString( + bytes, + charset); + yield VString.of(stringValue, alarm, time); + } else if (typed_time_value.getValue().remaining() == 1 && !force_array) { + yield VByte.of( + typed_time_value.getValue().get(0), + alarm, + time, + display); + } else { + yield VByteArray.of( + byteBufferToListByte(typed_time_value.getValue()), + alarm, + time, + display); + } + } + case DBR_TIME_DOUBLE -> { + ChannelAccessTimeDouble typed_time_value = (ChannelAccessTimeDouble) time_value; + if (typed_time_value.getValue().remaining() == 1 && !force_array) { + yield VDouble.of( + typed_time_value.getValue().get(0), + alarm, + time, + display); + } else { + yield VDoubleArray.of( + doubleBufferToListDouble(typed_time_value.getValue()), + alarm, + time, + display); + } + } + case DBR_TIME_ENUM -> { + final var typed_time_value = (ChannelAccessTimeEnum) time_value; + if (typed_time_value.getValue().remaining() == 1 && !force_array) { + final var value = typed_time_value.getValue().get(0); + // If the value is in a reasonable range ([0, 15]), we + // generate the enum display and return a VEnum, Otherwise, + // we return a VShort. We also do this if we cannot + // generate the enum display for some other reason. + final EnumDisplay enum_display; + if (value >= 0 && value <= 15) { + enum_display = convertEnumDisplay( + controls_value, value + 1); + } else { + enum_display = null; + } + if (enum_display == null) { + yield VShort.of(value, alarm, time, Display.none()); + } + yield VEnum.of( + typed_time_value.getValue().get(0), + enum_display, + alarm, + time); + } else { + final var list_short = shortBufferToListShort( + typed_time_value.getValue()); + var min_value = Short.MAX_VALUE; + var max_value = Short.MIN_VALUE; + final var list_short_iterator = list_short.iterator(); + while (list_short_iterator.hasNext()) { + final var value = list_short_iterator.nextShort(); + min_value = (min_value > value) ? value : min_value; + max_value = (max_value < value) ? value : max_value; + } + // If all values are in a reasonable range ([0, 15]), we + // generate the enum display and return a VEnum, Otherwise, + // we return a VShort. We also do this if we cannot + // generate the enum display for some other reason. + final EnumDisplay enum_display; + if (min_value >= 0 && max_value <= 15) { + enum_display = convertEnumDisplay( + controls_value, max_value + 1); + } else { + enum_display = null; + } + if (enum_display == null) { + yield VShortArray.of( + list_short, alarm, time, Display.none()); + } + yield VEnumArray.of( + list_short, + enum_display, + alarm, + time); + } + } + case DBR_TIME_FLOAT -> { + ChannelAccessTimeFloat typed_time_value = (ChannelAccessTimeFloat) time_value; + if (typed_time_value.getValue().remaining() == 1 && !force_array) { + yield VFloat.of( + typed_time_value.getValue().get(0), + alarm, + time, + display); + } else { + yield VFloatArray.of( + floatBufferToListFloat(typed_time_value.getValue()), + alarm, + time, + display); + } + } + case DBR_TIME_LONG -> { + ChannelAccessTimeLong typed_time_value = (ChannelAccessTimeLong) time_value; + if (typed_time_value.getValue().remaining() == 1 && !force_array) { + yield VInt.of( + typed_time_value.getValue().get(0), + alarm, + time, + display); + } else { + yield VIntArray.of( + intBufferToListInteger(typed_time_value.getValue()), + alarm, + time, + display); + } + } + case DBR_TIME_SHORT -> { + ChannelAccessTimeShort typed_time_value = (ChannelAccessTimeShort) time_value; + if (typed_time_value.getValue().remaining() == 1 && !force_array) { + yield VShort.of( + typed_time_value.getValue().get(0), + alarm, + time, + display); + } else { + yield VShortArray.of( + shortBufferToListShort(typed_time_value.getValue()), + alarm, + time, + display); + } + } + case DBR_TIME_STRING -> { + ChannelAccessTimeString typed_time_value = (ChannelAccessTimeString) time_value; + if (typed_time_value.getValue().size() == 1 && !force_array) { + yield VString.of( + typed_time_value.getValue().get(0), + alarm, + time); + } else { + yield VStringArray.of( + typed_time_value.getValue(), + alarm, + time); + } + } + default -> + // This should never happen and indicates a bug in EPICS + // Jackie. + throw new RuntimeException( + "Instance of ChannelAccessTimeValue has unexpected type " + + time_value.getType() + ": " + time_value); + }; + } + + /** + * Converts an object to a value that can be sent via Channel access. This + * method supports most {@link VType} objects (through the help of + * {@link VTypeHelper#toObject(VType)}), the primitive Java types + * byte, double, float, + * int, and short, arrays of these primitive + * types, {@link String}, and arrays of {@link String}. + * + * @param object + * object to be converted. + * @param charset + * charset to be used when converting strings. + * @param convert_string_as_long_string + * indicates whether a {@link String} or single element + * String[] array should be converted to a + * DBR_CHAR instead of a DBR_STRING. + * @return + * the converted value. + * @throws IllegalArgumentException + * if object cannot be converted. + */ + public static ChannelAccessSimpleOnlyValue objectToChannelAccessSimpleOnlyValue( + Object object, + Charset charset, + boolean convert_string_as_long_string) { + if (object instanceof VType vtype) { + var converted_object = VTypeHelper.toObject(vtype); + // VTypeHelper.toObject returns null if it does not know how to + // convert the object. In this case, we rather want to keep the + // original object that we got, so that the resulting error message + // is more specific. + if (converted_object != null) { + object = converted_object; + } + } + if (object instanceof Byte value) { + return ChannelAccessValueFactory.createChar(new byte[] {value}); + } + if (object instanceof byte[] value) { + return ChannelAccessValueFactory.createChar(value); + } + if (object instanceof Double value) { + return ChannelAccessValueFactory.createDouble(new double[] {value}); + } + if (object instanceof double[] value) { + return ChannelAccessValueFactory.createDouble(value); + } + if (object instanceof Float value) { + return ChannelAccessValueFactory.createFloat(new float[] {value}); + } + if (object instanceof float[] value) { + return ChannelAccessValueFactory.createFloat(value); + } + if (object instanceof Integer value) { + return ChannelAccessValueFactory.createLong(new int[] {value}); + } + if (object instanceof int[] value) { + return ChannelAccessValueFactory.createLong(value); + } + if (object instanceof Short value) { + return ChannelAccessValueFactory.createShort(new short[] {value}); + } + if (object instanceof short[] value) { + return ChannelAccessValueFactory.createShort(value); + } + if (object instanceof String value) { + if (convert_string_as_long_string) { + // Convert string to an array of bytes. + final var byte_buffer = charset.encode(value); + final var byte_array = new byte[byte_buffer.remaining()]; + byte_buffer.get(byte_array); + return ChannelAccessValueFactory.createChar(byte_array); + } + return ChannelAccessValueFactory.createString( + Collections.singleton(value), charset); + } + if (object instanceof String[] value) { + // In case of a string array, we can only use the long-string + // conversion if the array has a single element. + if (value.length == 1 && convert_string_as_long_string) { + return objectToChannelAccessSimpleOnlyValue( + value[0], charset, true); + } + return ChannelAccessValueFactory.createString( + Arrays.asList(value), charset); + } + throw new IllegalArgumentException( + "Cannot convert object of type " + + object.getClass().getName() + + ": " + + object); + } + + private static ListByte byteBufferToListByte(ByteBuffer buffer) { + return new ListByte() { + @Override + public byte getByte(int index) { + return buffer.get(index); + } + + @Override + public int size() { + return buffer.remaining(); + } + }; + } + + private static Alarm convertAlarm(ChannelAccessTimeValue time_value) { + AlarmSeverity severity = switch (time_value.getAlarmSeverity()) { + case NO_ALARM -> AlarmSeverity.NONE; + case MINOR_ALARM -> AlarmSeverity.MINOR; + case MAJOR_ALARM -> AlarmSeverity.MAJOR; + case INVALID_ALARM -> AlarmSeverity.INVALID; + }; + return Alarm.of( + severity, + AlarmStatus.NONE, + time_value.getAlarmStatus().toString()); + } + + private static Display convertDisplay( + ChannelAccessControlsValue controls_value, + boolean honor_zero_precision) { + if (controls_value instanceof ChannelAccessNumericControlsValue numeric_value) { + Range alarm_range = Range.of( + numeric_value.getGenericLowerAlarmLimit().doubleValue(), + numeric_value.getGenericUpperAlarmLimit().doubleValue()); + Range control_range = Range.of( + numeric_value.getGenericLowerControlLimit().doubleValue(), + numeric_value.getGenericUpperControlLimit().doubleValue()); + Range display_range = Range.of( + numeric_value.getGenericLowerDisplayLimit().doubleValue(), + numeric_value.getGenericUpperDisplayLimit().doubleValue()); + Range warning_range = Range.of( + numeric_value.getGenericLowerWarningLimit().doubleValue(), + numeric_value.getGenericUpperWarningLimit().doubleValue()); + String units = numeric_value.getUnits(); + short precision = 0; + if (numeric_value instanceof ChannelAccessFloatingPointControlsValue fp_value) { + precision = fp_value.getPrecision(); + } + NumberFormat number_format; + if (precision > 0 || (honor_zero_precision && precision == 0)) { + number_format = NumberFormats.precisionFormat(precision); + } else { + number_format = NumberFormats.toStringFormat(); + } + return Display.of( + display_range, + alarm_range, + warning_range, + control_range, + units, + number_format); + } + return Display.none(); + } + + private static EnumDisplay convertEnumDisplay( + ChannelAccessControlsValue controls_value, + int min_number_of_labels) { + if (controls_value instanceof ChannelAccessGraphicsEnum enum_value) { + final var original_labels = enum_value.getLabels(); + // If the highest does not have a label in the meta-data, we have + // to generate such a label. Otherwise, we would get an + // IndexOutOfBoundsError when trying to create the VEnum. + if (min_number_of_labels <= original_labels.size()) { + return EnumDisplay.of(original_labels); + } + var labels = new ArrayList(min_number_of_labels); + for (int index = 0; index < min_number_of_labels; ++index) { + if (index < original_labels.size()) { + labels.add(original_labels.get(index)); + } else { + labels.add("Index " + index); + } + } + return EnumDisplay.of(labels); + } + return null; + } + + private static Time convertTime(ChannelAccessTimeValue time_value) { + return Time.of(Instant.ofEpochSecond( + time_value.getTimeSeconds() + + OFFSET_EPICS_TO_UNIX_EPOCH_SECONDS, + time_value.getTimeNanoseconds())); + } + + private static ListDouble doubleBufferToListDouble(DoubleBuffer buffer) { + return new ListDouble() { + @Override + public double getDouble(int index) { + return buffer.get(index); + } + + @Override + public int size() { + return buffer.remaining(); + } + }; + } + + private static ListFloat floatBufferToListFloat(FloatBuffer buffer) { + return new ListFloat() { + @Override + public float getFloat(int index) { + return buffer.get(index); + } + + @Override + public int size() { + return buffer.remaining(); + } + }; + } + + private static ListInteger intBufferToListInteger(IntBuffer buffer) { + return new ListInteger() { + @Override + public int getInt(int index) { + return buffer.get(index); + } + + @Override + public int size() { + return buffer.remaining(); + } + }; + } + + private static ListShort shortBufferToListShort(ShortBuffer buffer) { + return new ListShort() { + @Override + public short getShort(int index) { + return buffer.get(index); + } + + @Override + public int size() { + return buffer.remaining(); + } + }; + } + +} diff --git a/core/pv-jackie/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory b/core/pv-jackie/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory new file mode 100644 index 0000000000..4203eaf84f --- /dev/null +++ b/core/pv-jackie/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory @@ -0,0 +1 @@ +org.phoebus.pv.jackie.JackiePVFactory diff --git a/core/pv-jackie/src/main/resources/pv_jackie_preferences.properties b/core/pv-jackie/src/main/resources/pv_jackie_preferences.properties new file mode 100644 index 0000000000..4937eea24b --- /dev/null +++ b/core/pv-jackie/src/main/resources/pv_jackie_preferences.properties @@ -0,0 +1,148 @@ +# ----------------------------- +# Package org.phoebus.pv.jackie +# ----------------------------- + +# List of servers that shall be queried via UDP when looking for channels. +# +# This setting is equivalent to the EPICS_CA_ADDR_LIST environment variable. It +# is only used when use_env is false. +ca_address_list= + +# Shall the broadcast addresses of local interfaces automatically be added to +# the list of addresses that shall be used when looking for a channel? +# +# This setting is equivalent to the EPICS_CA_AUTO_ADDR_LIST environment +# variable, but expects a value of true or false instead of YES or NO. It is +# only used when use_env is false. +# +# The default value is true. +ca_auto_address_list= + +# Shall the size of values transferred via Channel Access be limited (false) or +# not (true)? +# +# If false, the value of ca_max_array_bytes limits the size of serialized +# values that are transferred via Channel Access. +# +# This setting is equivalent to the EPICS_CA_AUTO_ARRAY_BYTES environment +# variable, but expects a value of true or false instead of YES or NO. This +# setting is only used when use_env is false. +# +# The default value is true. +ca_auto_array_bytes= + +# Interval between sending echo packages to a Channel Access server (in +# seconds). +# +# This setting is equivalent to the EPICS_CA_CONN_TMO environment variable. It +# is only used when use_env is false. +# +# The default value is 30. +ca_echo_interval= + +# Maximum size (in bytes) of a value that can be transferred via Channel +# Access. +# +# This setting is equivalent to the EPICS_CA_MAX_ARRAY_BYTES environment +# variable. It is only used when use_env is false. and ca_auto_array_bytes is +# false. +# +# The default value is 16384. +ca_max_array_bytes= + +# Interval of the longest search period (in seconds). +# +# This setting is equivalent to the EPICS_CA_MAX_SEARCH_PERIOD environment +# variable. It is only used when use_env is false. +# +# The default value (and smallest allowed value) is 60. +ca_max_search_period= + +# TTL for UDP packets that are sent to multicast addresses. +# +# This setting is equivalent to the EPICS_CA_MCAST_TTL environment variable. It +# is only used when use_env is false. +# +# The default value (and smallest allowed value) is 1. The greatest allowed +# value is 255. +ca_multicast_ttl= + +# List of servers that shall be queried via UDP when looking for channels. +# +# This setting is equivalent to the EPICS_CA_NAME_SERVERS environment variable. +# It is only used when use_env is false. +ca_name_servers= + +# UDP port that is used when connecting to the Channel Access repeater. +# +# This setting is equivalent to the EPICS_CA_REPEATER_PORT environment +# variable. It is only used when use_env is false. +# +# The default value is 5065. +ca_repeater_port= + +# UDP and TCP port on which Channel Access servers are expected to listen. +# +# This setting is used when sending search requests and when connecting to +# serves that did not explicitly specify a port in search responses. It is +# only used when use_env is false. +# +# The default value is 5064. +ca_server_port= + +# Charset to use when encoding and decoding strings. +# +# The default value is UTF-8. +charset= + +# Time that a CID is blocked from being used again in milliseconds. +# After destroying a channel, the CID may not be reused for some time because +# there might still be late responses to old search requests, which would be +# used for the wrong channel if the CID was reused too early. A value of 0 (or +# a negative value) means that CIDs can be reused immediately. +cid_block_reuse_time=900000 + +# Shall meta-data monitors using DBE_PROPERTY be created? +# +# This ensures that the meta-data for PVs is updated when it changes on the +# server, but some servers do not correctly support using DBE_PROPERTY. When +# experiencing problems with such a server, try setting this to false. +dbe_property_supported=true + +# Shall a precision of zero for a floating-point value result in this value +# being rendered without a fractional digits (true) or shall it be treated as +# an indication that the value should be rendered with a default number of +# fractional digits (false)? +honor_zero_precision=true + +# Hostname that is sent to the Channel Access server. If empty, the system?s +# hostname is determined automatically. +hostname= + +# Mask that shall be used when registering monitors for DBR_TIME_* values. +# +# This can be a combination of DBE_ALARM, DBE_ARCHIVE, DBE_PROPERTY, and +# DBE_VALUE, where multiple flags can be combined using the ?|? character. +monitor_mask=DBE_VALUE|DBE_ALARM + +# Shall PVs referencing a record?s RTYP field be treated like any other PV +# (false) or shall the monitor registered for the channel request the value +# only, without any meta-data like a time-stamp (true)? +# +# In general, setting this to false is preferred, but there are certain +# versions of EPICS where requesting a DBR_TIME_STRING for the RTYP field +# results in invalid data being returned by the server. In this case, this +# setting should be changed to true. +rtyp_value_only=false + +# Shall Channel Access client settings be read from the CA_* environment +# variables? +# +# If true, the ca_* settings from the preferences are ignored and the values +# from the process?s environment are used instead. If false, the preferences +# are used and the environment variables are ignored. +use_env=true + +# Username that is sent to the Channel Access server. If empty, the username +# for the current process is determined automatically. +username= diff --git a/core/pv-jackie/src/test/java/org/phoebus/pv/jackie/util/SimpleJsonParserTest.java b/core/pv-jackie/src/test/java/org/phoebus/pv/jackie/util/SimpleJsonParserTest.java new file mode 100644 index 0000000000..cc85c4a81e --- /dev/null +++ b/core/pv-jackie/src/test/java/org/phoebus/pv/jackie/util/SimpleJsonParserTest.java @@ -0,0 +1,215 @@ +/******************************************************************************* + * Copyright (c) 2017-2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.pv.jackie.util; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the {@link SimpleJsonParser}. + */ +public class SimpleJsonParserTest { + + private static Object parse(String json_string) { + return SimpleJsonParser.parse(json_string); + } + + private static void testNumber(String number) { + assertEquals(Double.parseDouble(number), + ((Number) parse(number)).doubleValue(), 0.00001); + } + + /** + * Tests that JSON arrays are parsed correctly. + */ + @Test + public void arrays() { + assertEquals(Collections.emptyList(), parse("[]")); + assertEquals(Collections.emptyList(), parse("[\t]")); + assertEquals(Collections.singletonList(true), parse("[true]")); + assertEquals(Collections.singletonList("abc"), parse("[ \"abc\"]")); + assertEquals(Collections.singletonList(null), parse("[null ]")); + assertEquals(Arrays.asList("abc", null, "def", false), + parse("[ \"abc\", null,\"def\" , false ]")); + } + + /** + * Test that the parsing fails if there is a comma in an empty array. + */ + @Test + public void commaInEmptyArrayNotAllowed() { + assertThrows(IllegalArgumentException.class, () -> parse("[,]")); + } + + /** + * Test that the parsing fails if there is a comma in an empty array. + */ + @Test + public void commaInEmptyObjectNotAllowed() { + assertThrows(IllegalArgumentException.class, () -> parse("{,}")); + } + + /** + * Tests that the JSON document "false" is parsed correctly. + */ + @Test + public void falseValue() { + assertEquals(Boolean.FALSE, parse("false")); + } + + /** + * Test that the parsing fails if there is leading comma in an array. + */ + @Test + public void leadingCommaInArrayNotAllowed() { + assertThrows(IllegalArgumentException.class, () -> { + parse("[,\"abc\"]"); + }); + } + + /** + * Test that the parsing fails if there is leading comma in an object. + */ + @Test + public void leadingCommaInObjectNotAllowed() { + assertThrows(IllegalArgumentException.class, () -> { + parse("{,\"a\": 5}"); + }); + } + + /** + * Test that the parsing fails if there is leading whitespace. + */ + @Test + public void leadingWhitespaceNotAllowed() { + assertThrows(IllegalArgumentException.class, () -> parse(" 5")); + } + + /** + * Tests that the JSON document "null" is parsed correctly. + */ + @Test + public void nullValue() { + assertNull(parse("null")); + } + + /** + * Tests that JSON numbers are parsed correctly. + */ + @Test + public void numberValues() { + testNumber("5.384"); + testNumber("-7.384"); + testNumber("2.0e-3"); + testNumber("-5e22"); + testNumber("1234567890"); + testNumber("0"); + testNumber("0.00"); + testNumber("-48"); + testNumber("1e50000"); + } + + /** + * Tests that JSON objects are parsed correctly. + */ + @Test + public void objects() { + assertEquals(Collections.emptyMap(), parse("{}")); + assertEquals(Collections.emptyMap(), parse("{ \n}")); + assertEquals(Collections.singletonMap("boolean", true), + parse("{\"boolean\":true}")); + assertEquals(Collections.singletonMap("string", "abc"), + parse("{ \"string\" : \"abc\" }")); + assertEquals(Collections.singletonMap("null", null), + parse("{\"null\": null }")); + assertEquals( + Collections.singletonMap("nested", + Collections.singletonMap("test", true)), + parse("{\"nested\":{\"test\":true}}")); + LinkedHashMap test_map = new LinkedHashMap<>(); + test_map.put("k1", "abc"); + test_map.put("k2", null); + test_map.put("k3", "def"); + test_map.put("k4", false); + String test_json = "{ \"k1\": \"abc\", \"k2\":null,\"k3\": \"def\" , \"k4\" : false }"; + @SuppressWarnings("unchecked") + Map result_map = (Map) parse(test_json); + // We want to be sure that the result map has the right order, so we + // cannot simply use assertEquals(). + assertEquals(test_map.size(), result_map.size()); + Iterator> i1 = test_map.entrySet().iterator(); + Iterator> i2 = result_map.entrySet() + .iterator(); + while (i1.hasNext()) { + assertEquals(i1.next(), i2.next()); + } + } + + /** + * Test that the parsing fails if there is an object that has a key without + * an associated value. + */ + @Test + public void objectWithKeyAndNoValueNotAllowed() { + assertThrows(IllegalArgumentException.class, () -> parse("{\"a\"}")); + } + + /** + * Tests that JSON strings are parsed correctly. + */ + @Test + public void stringValues() { + assertEquals("a\"b\\c\näöü", parse("\"a\\\"b\\\\c\\näöü\"")); + assertEquals("", parse("\"\"")); + assertEquals("\"", parse("\"\\\"\"")); + assertEquals(" \n@>", parse("\" \\n\\u0040\\u003e\"")); + } + + /** + * Test that the parsing fails if there is trailing comma in an array. + */ + @Test + public void trailingCommaInArrayNotAllowed() { + assertThrows(IllegalArgumentException.class, () -> parse("[5,]")); + } + + /** + * Test that the parsing fails if there is trailing comma in an object. + */ + @Test + public void trailingCommaInObjectNotAllowed() { + assertThrows(IllegalArgumentException.class, () -> { + parse("{\"a\": 5,}"); + }); + } + + /** + * Test that the parsing fails if there is trailing whitespace. + */ + @Test + public void trailingWhitespaceNotAllowed() { + assertThrows(IllegalArgumentException.class, () -> parse("48\t")); + } + + /** + * Tests that the JSON document "true" is parsed correctly. + */ + @Test + public void trueValue() { + assertEquals(Boolean.TRUE, parse("true")); + } + +} diff --git a/core/pv-jackie/src/test/java/org/phoebus/pv/jackie/util/ValueConverterTest.java b/core/pv-jackie/src/test/java/org/phoebus/pv/jackie/util/ValueConverterTest.java new file mode 100644 index 0000000000..fad6381333 --- /dev/null +++ b/core/pv-jackie/src/test/java/org/phoebus/pv/jackie/util/ValueConverterTest.java @@ -0,0 +1,767 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.pv.jackie.util; + +import com.aquenos.epics.jackie.common.value.ChannelAccessAlarmSeverity; +import com.aquenos.epics.jackie.common.value.ChannelAccessAlarmStatus; +import com.aquenos.epics.jackie.common.value.ChannelAccessFloatingPointControlsValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessGraphicsEnum; +import com.aquenos.epics.jackie.common.value.ChannelAccessNumericControlsValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessSimpleOnlyChar; +import com.aquenos.epics.jackie.common.value.ChannelAccessSimpleOnlyDouble; +import com.aquenos.epics.jackie.common.value.ChannelAccessSimpleOnlyFloat; +import com.aquenos.epics.jackie.common.value.ChannelAccessSimpleOnlyLong; +import com.aquenos.epics.jackie.common.value.ChannelAccessSimpleOnlyShort; +import com.aquenos.epics.jackie.common.value.ChannelAccessSimpleOnlyString; +import com.aquenos.epics.jackie.common.value.ChannelAccessTimeValue; +import com.aquenos.epics.jackie.common.value.ChannelAccessValueFactory; +import org.apache.commons.lang3.ArrayUtils; +import org.epics.vtype.AlarmProvider; +import org.epics.vtype.AlarmSeverity; +import org.epics.vtype.DisplayProvider; +import org.epics.vtype.EnumDisplay; +import org.epics.vtype.TimeProvider; +import org.epics.vtype.VByte; +import org.epics.vtype.VByteArray; +import org.epics.vtype.VDouble; +import org.epics.vtype.VDoubleArray; +import org.epics.vtype.VEnum; +import org.epics.vtype.VEnumArray; +import org.epics.vtype.VFloat; +import org.epics.vtype.VFloatArray; +import org.epics.vtype.VInt; +import org.epics.vtype.VIntArray; +import org.epics.vtype.VShort; +import org.epics.vtype.VShortArray; +import org.epics.vtype.VString; +import org.epics.vtype.VStringArray; +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the {@link ValueConverter}. + */ +public class ValueConverterTest { + + private final static Charset UTF_8 = StandardCharsets.UTF_8; + + /** + * Test conversion of a byte[] to a CA value. + */ + @Test + public void byteArrayToChannelAccessValue() { + final byte[] value = new byte[] {2, 4}; + final var ca_value = (ChannelAccessSimpleOnlyChar) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(ByteBuffer.wrap(value), ca_value.getValue()); + } + + /** + * Test conversion of a {@link Byte} to a CA value. + */ + @Test + public void byteToChannelAccessValue() { + final byte value = 3; + final var ca_value = (ChannelAccessSimpleOnlyChar) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(value, ca_value.getValue().get(0)); + } + + /** + * Test conversion of a char CA value representing a long string to a VType. + */ + @Test + public void caCharAsStringToVType() { + final var value = "This is a string."; + final var byte_buffer = UTF_8.encode(value); + final var bytes_value = new byte[byte_buffer.remaining()]; + byte_buffer.get(bytes_value); + final var time_value = ChannelAccessValueFactory.createTimeChar( + bytes_value, + ChannelAccessAlarmSeverity.NO_ALARM, + ChannelAccessAlarmStatus.NO_ALARM, + 789, + 132); + final var vtype = (VString) ValueConverter.channelAccessToVType( + null, time_value, UTF_8, false, false, true); + assertEquals(value, vtype.getValue()); + checkAlarm(time_value, vtype); + checkTime(time_value, vtype); + } + + /** + * Test conversion of a char CA value to a VString. + */ + @Test + public void caCharToVType() { + final var controls_value = ChannelAccessValueFactory.createControlsChar( + ArrayUtils.EMPTY_BYTE_ARRAY, + ChannelAccessAlarmSeverity.MAJOR_ALARM, + ChannelAccessAlarmStatus.LOLO, + (byte) -15, + (byte) 5, + (byte) -5, + (byte) 40, + (byte) -50, + (byte) 50, + "some unit", + UTF_8, + (byte) -10, + (byte) 10); + var value = new byte[] {1, 2}; + final var time_value = ChannelAccessValueFactory.createTimeChar( + value, + ChannelAccessAlarmSeverity.NO_ALARM, + ChannelAccessAlarmStatus.NO_ALARM, + 789, + 132); + var vtype_array = (VByteArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertArrayEquals( + value, + vtype_array.getData().toArray(ArrayUtils.EMPTY_BYTE_ARRAY)); + checkAlarm(time_value, vtype_array); + checkDisplay(controls_value, vtype_array); + checkTime(time_value, vtype_array); + // Test a single-element value. + value = new byte[] {3}; + time_value.setValue(value); + vtype_array = (VByteArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, true, false, false); + assertArrayEquals( + value, + vtype_array.getData().toArray(ArrayUtils.EMPTY_BYTE_ARRAY)); + var vtype_single = (VByte) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertEquals((byte) 3, vtype_single.getValue().byteValue()); + } + + /** + * Test conversion of a double CA value to a VType. + */ + @Test + public void caDoubleToVType() { + final var controls_value = ChannelAccessValueFactory.createControlsDouble( + ArrayUtils.EMPTY_DOUBLE_ARRAY, + ChannelAccessAlarmSeverity.MINOR_ALARM, + ChannelAccessAlarmStatus.HIGH, + -15.0, + 500.0, + -5.0, + 400.0, + -1000.0, + 1000.0, + "V", + UTF_8, + (short) 3, + -10.0, + 10.0); + var value = new double[] {1.0, 2.0}; + final var time_value = ChannelAccessValueFactory.createTimeDouble( + value, + ChannelAccessAlarmSeverity.MINOR_ALARM, + ChannelAccessAlarmStatus.LOW, + 123, + 456); + var vtype_array = (VDoubleArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertArrayEquals( + value, + vtype_array.getData().toArray(ArrayUtils.EMPTY_DOUBLE_ARRAY)); + checkAlarm(time_value, vtype_array); + checkDisplay(controls_value, vtype_array); + checkTime(time_value, vtype_array); + // Test a single-element value. + value = new double[] {3.1}; + time_value.setValue(value); + vtype_array = (VDoubleArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, true, false, false); + assertArrayEquals( + value, + vtype_array.getData().toArray(ArrayUtils.EMPTY_DOUBLE_ARRAY)); + var vtype_single = (VDouble) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertEquals(3.1, vtype_single.getValue().doubleValue()); + } + + /** + * Test conversion of a double CA value to a VType. + */ + @Test + public void caEnumToVType() { + var labels = List.of("a", "b", "c", "d"); + final var controls_value = ChannelAccessValueFactory.createControlsEnum( + ArrayUtils.EMPTY_SHORT_ARRAY, + ChannelAccessAlarmSeverity.NO_ALARM, + ChannelAccessAlarmStatus.NO_ALARM, + labels, + UTF_8); + var value = new short[] {1, 2}; + final var time_value = ChannelAccessValueFactory.createTimeEnum( + value, + ChannelAccessAlarmSeverity.MINOR_ALARM, + ChannelAccessAlarmStatus.STATE, + 1234, + 4567); + var vtype_array = (VEnumArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertArrayEquals( + value, + vtype_array.getIndexes().toArray( + ArrayUtils.EMPTY_SHORT_ARRAY)); + assertEquals(List.of("b", "c"), vtype_array.getData()); + checkAlarm(time_value, vtype_array); + checkEnumDisplay(controls_value, vtype_array.getDisplay()); + checkTime(time_value, vtype_array); + // Test a single-element value. + value = new short[] {3}; + time_value.setValue(value); + vtype_array = (VEnumArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, true, false, false); + assertArrayEquals( + value, + vtype_array.getIndexes().toArray( + ArrayUtils.EMPTY_SHORT_ARRAY)); + assertEquals(List.of("d"), vtype_array.getData()); + var vtype_single = (VEnum) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertEquals(3, vtype_single.getIndex()); + assertEquals("d", vtype_single.getValue()); + // Test an array with a value for which there is no label, but which is + // reasonably small (less than 16). + value = new short[] {1, 14}; + time_value.setValue(value); + vtype_array = (VEnumArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertArrayEquals( + value, + vtype_array.getIndexes().toArray( + ArrayUtils.EMPTY_SHORT_ARRAY)); + assertEquals(List.of("b", "Index 14"), vtype_array.getData()); + assertEquals( + List.of( + "a", + "b", + "c", + "d", + "Index 4", + "Index 5", + "Index 6", + "Index 7", + "Index 8", + "Index 9", + "Index 10", + "Index 11", + "Index 12", + "Index 13", + "Index 14"), + vtype_array.getDisplay().getChoices()); + // Repeat the test with a single element. + value = new short[] {15}; + time_value.setValue(value); + vtype_single = (VEnum) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertEquals(15, vtype_single.getIndex()); + assertEquals("Index 15", vtype_single.getValue()); + assertEquals( + List.of( + "a", + "b", + "c", + "d", + "Index 4", + "Index 5", + "Index 6", + "Index 7", + "Index 8", + "Index 9", + "Index 10", + "Index 11", + "Index 12", + "Index 13", + "Index 14", + "Index 15"), + vtype_single.getDisplay().getChoices()); + // Test an array with a value for which there is no label and which has + // a value greater than 15. In this case, we expect a VShortArray + // instead of a VEnumArray. + value = new short[] {0, 42}; + time_value.setValue(value); + var vshort_array = (VShortArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, true, false, false); + assertArrayEquals( + value, + vshort_array.getData().toArray(ArrayUtils.EMPTY_SHORT_ARRAY)); + // Repeat the test with a single element. + value = new short[] {16}; + time_value.setValue(value); + var vshort_single = (VShort) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertEquals((short) 16, vshort_single.getValue().shortValue()); + // Finally, we repeat the test with a negative number. + value = new short[] {-5, 2}; + time_value.setValue(value); + vshort_array = (VShortArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, true, false, false); + assertArrayEquals( + value, + vshort_array.getData().toArray(ArrayUtils.EMPTY_SHORT_ARRAY)); + value = new short[] {-1}; + time_value.setValue(value); + vshort_single = (VShort) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertEquals((short) -1, vshort_single.getValue().shortValue()); + } + + /** + * Test conversion of a float CA value to a VType. + */ + @Test + public void caFloatToVType() { + final var controls_value = ChannelAccessValueFactory.createControlsFloat( + ArrayUtils.EMPTY_FLOAT_ARRAY, + ChannelAccessAlarmSeverity.INVALID_ALARM, + ChannelAccessAlarmStatus.BAD_SUB, + -15.0f, + 500.0f, + -5.0f, + 400.0f, + -1000.0f, + 1000.0f, + "A", + UTF_8, + (short) 2, + -10.0f, + 10.0f); + var value = new float[] {1.0f, 2.0f}; + final var time_value = ChannelAccessValueFactory.createTimeFloat( + value, + ChannelAccessAlarmSeverity.MAJOR_ALARM, + ChannelAccessAlarmStatus.HIHI, + 123, + 456); + var vtype_array = (VFloatArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertArrayEquals( + value, + vtype_array.getData().toArray(ArrayUtils.EMPTY_FLOAT_ARRAY)); + checkAlarm(time_value, vtype_array); + checkDisplay(controls_value, vtype_array); + checkTime(time_value, vtype_array); + // Test a single-element value. + value = new float[] {3.1f}; + time_value.setValue(value); + vtype_array = (VFloatArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, true, false, false); + assertArrayEquals( + value, + vtype_array.getData().toArray(ArrayUtils.EMPTY_FLOAT_ARRAY)); + var vtype_single = (VFloat) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertEquals(3.1f, vtype_single.getValue().floatValue()); + } + + /** + * Test conversion of a long CA value to a VType. + */ + @Test + public void caLongToVType() { + final var controls_value = ChannelAccessValueFactory.createControlsLong( + ArrayUtils.EMPTY_INT_ARRAY, + ChannelAccessAlarmSeverity.MINOR_ALARM, + ChannelAccessAlarmStatus.HIGH, + -15, + 500, + -5, + 400, + -1000, + 1000, + "V", + UTF_8, + -10, + 10); + var value = new int[] {1, 2}; + final var time_value = ChannelAccessValueFactory.createTimeLong( + value, + ChannelAccessAlarmSeverity.INVALID_ALARM, + ChannelAccessAlarmStatus.CALC, + 123, + 456); + var vtype_array = (VIntArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertArrayEquals( + value, + vtype_array.getData().toArray(ArrayUtils.EMPTY_INT_ARRAY)); + checkAlarm(time_value, vtype_array); + checkDisplay(controls_value, vtype_array); + checkTime(time_value, vtype_array); + // Test a single-element value. + value = new int[] {3}; + time_value.setValue(value); + vtype_array = (VIntArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, true, false, false); + assertArrayEquals( + value, + vtype_array.getData().toArray(ArrayUtils.EMPTY_INT_ARRAY)); + var vtype_single = (VInt) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertEquals(3, vtype_single.getValue().intValue()); + } + + /** + * Test conversion of a short CA value to a VType. + */ + @Test + public void caShortToVType() { + final var controls_value = ChannelAccessValueFactory.createControlsShort( + ArrayUtils.EMPTY_SHORT_ARRAY, + ChannelAccessAlarmSeverity.MINOR_ALARM, + ChannelAccessAlarmStatus.HIGH, + (short) -15, + (short) 500, + (short) -5, + (short) 400, + (short) -1000, + (short) 1000, + "V", + UTF_8, + (short) -10, + (short) 10); + var value = new short[] {1, 2}; + final var time_value = ChannelAccessValueFactory.createTimeShort( + value, + ChannelAccessAlarmSeverity.NO_ALARM, + ChannelAccessAlarmStatus.NO_ALARM, + 123, + 456); + var vtype_array = (VShortArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertArrayEquals( + value, + vtype_array.getData().toArray(ArrayUtils.EMPTY_SHORT_ARRAY)); + checkAlarm(time_value, vtype_array); + checkDisplay(controls_value, vtype_array); + checkTime(time_value, vtype_array); + // Test a single-element value. + value = new short[] {3}; + time_value.setValue(value); + vtype_array = (VShortArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, true, false, false); + assertArrayEquals( + value, + vtype_array.getData().toArray(ArrayUtils.EMPTY_SHORT_ARRAY)); + var vtype_single = (VShort) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + assertEquals((short) 3, vtype_single.getValue().shortValue()); + } + + /** + * Test conversion of a string CA value to a VType. + */ + @Test + public void caStringToVType() { + var value = List.of("abc", "def"); + final var time_value = ChannelAccessValueFactory.createTimeString( + value, + UTF_8, + ChannelAccessAlarmSeverity.MAJOR_ALARM, + ChannelAccessAlarmStatus.STATE, + 123, + 456); + var vtype_array = (VStringArray) ValueConverter.channelAccessToVType( + null, time_value, UTF_8, false, false, false); + assertEquals(value, vtype_array.getData()); + checkAlarm(time_value, vtype_array); + checkTime(time_value, vtype_array); + // Test a single-element value. + value = Collections.singletonList("some string"); + time_value.setValue(value); + vtype_array = (VStringArray) ValueConverter.channelAccessToVType( + null, time_value, UTF_8, true, false, false); + assertEquals(value, vtype_array.getData()); + var vtype_single = (VString) ValueConverter.channelAccessToVType( + null, time_value, UTF_8, false, false, false); + assertEquals("some string", vtype_single.getValue()); + } + + /** + * Test the honor_zero_precision flag when converting from a + * CA value to a VType. + */ + @Test + public void caToVTypeHonorZeroPrecision() { + final var controls_value = ChannelAccessValueFactory.createControlsDouble( + ArrayUtils.EMPTY_DOUBLE_ARRAY, + ChannelAccessAlarmSeverity.MINOR_ALARM, + ChannelAccessAlarmStatus.HIGH, + -15.0, + 500.0, + -5.0, + 400.0, + -1000.0, + 1000.0, + "V", + UTF_8, + (short) 0, + -10.0, + 10.0); + var value = new double[] {1.0, 2.0}; + final var time_value = ChannelAccessValueFactory.createTimeDouble( + value, + ChannelAccessAlarmSeverity.NO_ALARM, + ChannelAccessAlarmStatus.NO_ALARM, + 123, + 456); + // If honor_zero_precision is set to false, the display format should + // include fractional digits, even if the precision is zero. We do not + // check the minimum fraction digits here because due to using a + // default format, those might well be zero. + var vtype = (VDoubleArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, false, false); + var format = vtype.getDisplay().getFormat(); + assertNotEquals(0, format.getMaximumFractionDigits()); + // If honor_zero_precision is true, the display format should not + // include any fractional digits if the precision is zero. + vtype = (VDoubleArray) ValueConverter.channelAccessToVType( + controls_value, time_value, UTF_8, false, true, false); + format = vtype.getDisplay().getFormat(); + assertEquals(0, format.getMaximumFractionDigits()); + assertEquals(0, format.getMinimumFractionDigits()); + } + + /** + * Test conversion of a double[] to a CA value. + */ + @Test + public void doubleArrayToChannelAccessValue() { + final double[] value = new double[] {2.0, 4.0}; + final var ca_value = (ChannelAccessSimpleOnlyDouble) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(DoubleBuffer.wrap(value), ca_value.getValue()); + } + + /** + * Test conversion of a {@link Double} to a CA value. + */ + @Test + public void doubleToChannelAccessValue() { + final double value = 3.0; + final var ca_value = (ChannelAccessSimpleOnlyDouble) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(value, ca_value.getValue().get(0)); + } + + /** + * Test conversion of a float[] to a CA value. + */ + @Test + public void floatArrayToChannelAccessValue() { + final float[] value = new float[] {2.0f, 4.0f}; + final var ca_value = (ChannelAccessSimpleOnlyFloat) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(FloatBuffer.wrap(value), ca_value.getValue()); + } + + /** + * Test conversion of a {@link Float} to a CA value. + */ + @Test + public void floatToChannelAccessValue() { + final float value = 3.0f; + final var ca_value = (ChannelAccessSimpleOnlyFloat) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(value, ca_value.getValue().get(0)); + } + + /** + * Test conversion of an int[] to a CA value. + */ + @Test + public void intArrayToChannelAccessValue() { + final int[] value = new int[] {2, 4}; + final var ca_value = (ChannelAccessSimpleOnlyLong) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(IntBuffer.wrap(value), ca_value.getValue()); + } + + /** + * Test conversion of an {@link Integer} to a CA value. + */ + @Test + public void intToChannelAccessValue() { + final int value = 3; + final var ca_value = (ChannelAccessSimpleOnlyLong) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(value, ca_value.getValue().get(0)); + } + + /** + * Test conversion of a short[] to a CA value. + */ + @Test + public void shortArrayToChannelAccessValue() { + final short[] value = new short[] {2, 4}; + final var ca_value = (ChannelAccessSimpleOnlyShort) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(ShortBuffer.wrap(value), ca_value.getValue()); + } + + /** + * Test conversion of a {@link Short} to a CA value. + */ + @Test + public void shortToChannelAccessValue() { + final short value = 3; + final var ca_value = (ChannelAccessSimpleOnlyShort) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(value, ca_value.getValue().get(0)); + } + + /** + * Test conversion of a String[] to a CA value. + */ + @Test + public void stringArrayToChannelAccessValue() { + var value = new String[] {"abc", "123"}; + var ca_value = (ChannelAccessSimpleOnlyString) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(Arrays.asList(value), ca_value.getValue()); + // For an array with multiple elements, it should not make a difference + // if we enable the convert_string_as_long_string option. + ca_value = (ChannelAccessSimpleOnlyString) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, true); + assertEquals(Arrays.asList(value), ca_value.getValue()); + // For a single-element array, we expect a different result. + value = new String[] {"a single string"}; + final var ca_char_array = (ChannelAccessSimpleOnlyChar) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, true); + assertEquals( + value[0], UTF_8.decode(ca_char_array.getValue()).toString()); + } + + /** + * Test conversion of a {@link String} to a CA value. + */ + @Test + public void stringToChannelAccessValue() { + final String value = "some string"; + final var ca_value = (ChannelAccessSimpleOnlyString) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, false); + assertEquals(value, ca_value.getValue().get(0)); + // When setting convert_string_as_long_string to true, we expect a + // ChannelAccessSimpleOnlyChar instead. + final var ca_char_array = (ChannelAccessSimpleOnlyChar) ValueConverter + .objectToChannelAccessSimpleOnlyValue( + value, UTF_8, true); + assertEquals(value, UTF_8.decode(ca_char_array.getValue()).toString()); + } + + private static void checkAlarm( + ChannelAccessTimeValue time_value, + AlarmProvider alarmProvider_provider) { + final var alarm = alarmProvider_provider.getAlarm(); + switch (time_value.getAlarmSeverity()) { + case NO_ALARM -> { + assertEquals(AlarmSeverity.NONE, alarm.getSeverity()); + } + case MINOR_ALARM -> { + assertEquals(AlarmSeverity.MINOR, alarm.getSeverity()); + } + case MAJOR_ALARM -> { + assertEquals(AlarmSeverity.MAJOR, alarm.getSeverity()); + } + case INVALID_ALARM -> { + assertEquals(AlarmSeverity.INVALID, alarm.getSeverity()); + } + } + } + + private static void checkDisplay( + ChannelAccessNumericControlsValue controls_value, + DisplayProvider display_provider) { + final var display = display_provider.getDisplay(); + assertEquals( + controls_value.getGenericLowerAlarmLimit().doubleValue(), + display.getAlarmRange().getMinimum()); + assertEquals( + controls_value.getGenericUpperAlarmLimit().doubleValue(), + display.getAlarmRange().getMaximum()); + assertEquals( + controls_value.getGenericLowerDisplayLimit().doubleValue(), + display.getDisplayRange().getMinimum()); + assertEquals( + controls_value.getGenericUpperDisplayLimit().doubleValue(), + display.getDisplayRange().getMaximum()); + assertEquals( + controls_value.getGenericLowerControlLimit().doubleValue(), + display.getControlRange().getMinimum()); + assertEquals( + controls_value.getGenericUpperControlLimit().doubleValue(), + display.getControlRange().getMaximum()); + assertEquals( + controls_value.getGenericLowerWarningLimit().doubleValue(), + display.getWarningRange().getMinimum()); + assertEquals( + controls_value.getGenericUpperWarningLimit().doubleValue(), + display.getWarningRange().getMaximum()); + assertEquals(controls_value.getUnits(), display.getUnit()); + if (controls_value instanceof ChannelAccessFloatingPointControlsValue fp_value) { + final var precision = fp_value.getPrecision(); + if (precision != 0) { + final var format = display.getFormat(); + assertEquals(precision, format.getMinimumFractionDigits()); + assertEquals(precision, format.getMaximumFractionDigits()); + } + } + } + + private static void checkEnumDisplay( + ChannelAccessGraphicsEnum controls_value, + EnumDisplay display) { + assertEquals(controls_value.getLabels(),display.getChoices()); + } + + private static void checkTime( + ChannelAccessTimeValue time_value, + TimeProvider time_provider) { + final var instant = time_provider.getTime().getTimestamp(); + assertEquals( + time_value.getTimeSeconds() + + ValueConverter.OFFSET_EPICS_TO_UNIX_EPOCH_SECONDS, + instant.getEpochSecond()); + assertEquals(time_value.getTimeNanoseconds(), instant.getNano()); + } + +} diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml index ea4f5e146b..79499f62fc 100644 --- a/dependencies/phoebus-target/pom.xml +++ b/dependencies/phoebus-target/pom.xml @@ -588,6 +588,13 @@ 5.18.2 + + + com.aquenos.epics.jackie + epics-jackie-client + 3.1.0 + + diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index 3a61dfc036..694c33c2e0 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -259,6 +259,11 @@ phoebus-target ${project.version} + + org.phoebus + core-pv-jackie + 4.7.4-SNAPSHOT + From d1fd756c5219cb19bbe525764f317336f0ab2932 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Fri, 23 Feb 2024 15:53:14 +0100 Subject: [PATCH 22/92] Bug fix Olog client: maintain log entry on background refresh --- .../olog/ui/LogEntryDisplayController.java | 14 +++- .../olog/ui/LogEntryTableViewController.java | 66 ++++++++++--------- .../ui/MergedLogEntryDisplayController.java | 36 +++++----- 3 files changed, 62 insertions(+), 54 deletions(-) diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryDisplayController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryDisplayController.java index 87e10f73f5..a259a46ee8 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryDisplayController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryDisplayController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 European Spallation Source ERIC. + * Copyright (C) 2024 European Spallation Source ERIC. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -43,6 +43,8 @@ public class LogEntryDisplayController { @FXML @SuppressWarnings("unused") private MergedLogEntryDisplayController mergedLogEntryDisplayController; + + private LogEntryTableViewController logEntryTableViewController; @FXML private ToggleButton showHideLogEntryGroupButton; @FXML @@ -90,7 +92,9 @@ public void showHideLogEntryGroup() { mergedLogEntryDisplayController.setLogSelectionHandler((logEntry) -> { Platform.runLater(() -> { currentViewProperty.set(SINGLE); - setLogEntry(logEntry); + if(logEntryTableViewController.selectLogEntry(logEntry)){ + singleLogEntryDisplayController.setLogEntry(logEntry); + } }); return null; }); @@ -136,8 +140,12 @@ public LogEntry getLogEntry() { */ public void updateLogEntry(LogEntry logEntry){ // Log entry display may be "empty", i.e. logEntryProperty not set yet - if(!logEntryProperty.isNull().get() && logEntryProperty.get().getId() == logEntry.getId()){ + if(!logEntryProperty.isNull().get() && logEntryProperty.get().getId().equals(logEntry.getId())){ setLogEntry(logEntry); } } + + public void setLogEntryTableViewController(LogEntryTableViewController logEntryTableViewController){ + this.logEntryTableViewController = logEntryTableViewController; + } } diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java index 3211a1458a..9cb6a534f8 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableViewController.java @@ -14,21 +14,8 @@ import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; -import javafx.scene.control.Alert; +import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.ComboBox; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.Label; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.control.MenuItem; -import javafx.scene.control.Pagination; -import javafx.scene.control.ProgressIndicator; -import javafx.scene.control.SelectionMode; -import javafx.scene.control.TableCell; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.TextField; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; @@ -39,12 +26,7 @@ import javafx.util.Duration; import javafx.util.StringConverter; import org.phoebus.framework.jobs.JobManager; -import org.phoebus.logbook.LogClient; -import org.phoebus.logbook.LogEntry; -import org.phoebus.logbook.LogService; -import org.phoebus.logbook.LogbookException; -import org.phoebus.logbook.LogbookPreferences; -import org.phoebus.logbook.SearchResult; +import org.phoebus.logbook.*; import org.phoebus.logbook.olog.ui.query.OlogQuery; import org.phoebus.logbook.olog.ui.query.OlogQueryManager; import org.phoebus.logbook.olog.ui.write.LogEntryEditorStage; @@ -58,8 +40,10 @@ import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -137,11 +121,12 @@ public LogEntryTableViewController(LogClient logClient, OlogQueryManager ologQue private final SearchParameters searchParameters; - private LogEntry selectedLogEntry; @FXML public void initialize() { + logEntryDisplayController.setLogEntryTableViewController(this); + advancedSearchViewController.setSearchCallback(this::search); configureComboBox(); @@ -171,7 +156,7 @@ public void initialize() { menuItemNewLogEntry.setOnAction(ae -> new LogEntryEditorStage(new OlogLog(), null, null).show()); MenuItem menuItemUpdateLogEntry = new MenuItem(Messages.UpdateLogEntry); - menuItemUpdateLogEntry.visibleProperty().bind(Bindings.createBooleanBinding(()-> selectedLogEntries.size() == 1, selectedLogEntries)); + menuItemUpdateLogEntry.visibleProperty().bind(Bindings.createBooleanBinding(() -> selectedLogEntries.size() == 1, selectedLogEntries)); menuItemUpdateLogEntry.acceleratorProperty().setValue(new KeyCodeCombination(KeyCode.U, KeyCombination.CONTROL_DOWN)); menuItemUpdateLogEntry.setOnAction(ae -> new LogEntryUpdateStage(selectedLogEntries.get(0), null).show()); @@ -195,8 +180,8 @@ public void initialize() { tableView.getColumns().clear(); tableView.setEditable(false); tableView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + // Update detailed view, but only if selection contains a single item. if (newValue != null && tableView.getSelectionModel().getSelectedItems().size() == 1) { - selectedLogEntry = newValue.getLogEntry(); logEntryDisplayController.setLogEntry(newValue.getLogEntry()); } List logEntries = tableView.getSelectionModel().getSelectedItems() @@ -398,16 +383,16 @@ public String getQuery() { private void refresh() { if (this.searchResult != null) { + List selectedLogEntries = new ArrayList<>(tableView.getSelectionModel().getSelectedItems()); ObservableList logsList = FXCollections.observableArrayList(); - logsList.addAll(searchResult.getLogs().stream().map(le -> new TableViewListItem(le, showDetails.get())).collect(Collectors.toList())); + logsList.addAll(searchResult.getLogs().stream().map(le -> new TableViewListItem(le, showDetails.get())).toList()); tableView.setItems(logsList); - // This will ensure that if an entry was selected, it stays selected after the list has been - // updated from the search result, even if it is empty. - if (selectedLogEntry != null) { + // This will ensure that selected entries stay selected after the list has been + // updated from the search result. + for (TableViewListItem selectedItem : selectedLogEntries) { for (TableViewListItem item : tableView.getItems()) { - if (item.getLogEntry().getId().equals(selectedLogEntry.getId())) { + if (item.getLogEntry().getId().equals(selectedItem.getLogEntry().getId())) { Platform.runLater(() -> tableView.getSelectionModel().select(item)); - break; } } } @@ -537,10 +522,29 @@ public void showHelp() { * Handler for a {@link LogEntry} change, new or updated. * A search is triggered to make sure the result list reflects the change, and * the detail view controller is called to refresh, if applicable. - * @param logEntry + * + * @param logEntry A {@link LogEntry} */ - public void logEntryChanged(LogEntry logEntry){ + public void logEntryChanged(LogEntry logEntry) { search(); logEntryDisplayController.updateLogEntry(logEntry); } + + /** + * Selects a log entry as a result of an action outside the {@link TreeView}, but selection happens on the + * {@link TreeView} item, if it exists (match on log entry id). If it does not exist, selection is cleared + * anyway to indicate that user selected log entry is not visible in {@link TreeView}. + * @param logEntry User selected log entry. + * @return true if user selected log entry is present in {@link TreeView}, otherwise + * false. + */ + public boolean selectLogEntry(LogEntry logEntry){ + tableView.getSelectionModel().clearSelection(); + Optional optional = tableView.getItems().stream().filter(i -> i.getLogEntry().getId().equals(logEntry.getId())).findFirst(); + if(optional.isPresent()){ + tableView.getSelectionModel().select(optional.get()); + return true; + } + return false; + } } diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/MergedLogEntryDisplayController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/MergedLogEntryDisplayController.java index 572d2263d5..06acca79bd 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/MergedLogEntryDisplayController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/MergedLogEntryDisplayController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 European Spallation Source ERIC. + * Copyright (C) 2024 European Spallation Source ERIC. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -104,20 +104,7 @@ public void setLogSelectionHandler(Function handler){ * @param logEntry The log entry selected by user in the table/list view. */ public void setLogEntry(LogEntry logEntry) { - getLogEntries(logEntry); - } - - private void mergeAndRender() { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(""); - logEntries.forEach(l -> { - stringBuilder.append(createSeparator(l)); - stringBuilder.append("

"); - stringBuilder.append(toHtml(l.getSource())); - stringBuilder.append("
"); - }); - stringBuilder.append(""); - webEngine.loadContent(stringBuilder.toString()); + getLogEntriesAndMerge(logEntry); } /** @@ -129,7 +116,7 @@ private void mergeAndRender() { */ private String createSeparator(LogEntry logEntry) { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("
"); + stringBuilder.append("
"); stringBuilder.append(SECONDS_FORMAT.format(logEntry.getCreatedDate())).append(", "); stringBuilder.append(logEntry.getOwner()).append(", "); stringBuilder.append(logEntry.getTitle()); @@ -137,14 +124,14 @@ private String createSeparator(LogEntry logEntry) { stringBuilder.append(" *"); } stringBuilder.append("
").append(logEntry.getId()).append("
"); - if(logEntry.getAttachments().size() > 0){ + if(!logEntry.getAttachments().isEmpty()){ stringBuilder.append("
 
"); } stringBuilder.append("
"); return stringBuilder.toString(); } - private void getLogEntries(LogEntry logEntry) { + private void getLogEntriesAndMerge(LogEntry logEntry) { Optional property = logEntry.getProperties().stream() @@ -164,7 +151,16 @@ private void getLogEntries(LogEntry logEntry) { logger.log(Level.SEVERE, "Unable to locate log entry items using log entry group id " + id, e); } - mergeAndRender(); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(""); + logEntries.forEach(l -> { + stringBuilder.append(createSeparator(l)); + stringBuilder.append("
"); + stringBuilder.append(toHtml(l.getSource())); + stringBuilder.append("
"); + }); + stringBuilder.append(""); + webEngine.loadContent(stringBuilder.toString()); } public class JavaConnector { @@ -176,7 +172,7 @@ public class JavaConnector { * the String to convert */ @SuppressWarnings("unused") - public void toLowerCase(String value) { + public void select(String value) { Optional logEntry = logEntries.stream().filter(l -> Long.toString(l.getId()).equals(value)).findFirst(); if(logEntry.isEmpty()){ return; From 84386c13aba0dfc2f7dc020fedf99b3171b1c72d Mon Sep 17 00:00:00 2001 From: kasemir Date: Fri, 23 Feb 2024 10:06:16 -0500 Subject: [PATCH 23/92] Ant and Eclipse settings for core-pv-* split --- app/display/model/.classpath | 2 -- app/rtplot/.classpath | 3 +-- build.xml | 8 +++++++ core/pv-ca/.classpath | 11 ++++++++++ core/pv-ca/.project | 22 +++++++++++++++++++ core/pv-ca/build.xml | 25 +++++++++++++++++++++ core/pv-jackie/.classpath | 12 +++++++++++ core/pv-jackie/.project | 17 +++++++++++++++ core/pv-jackie/build.xml | 24 +++++++++++++++++++++ core/pv-mqtt/.classpath | 11 ++++++++++ core/pv-mqtt/.project | 17 +++++++++++++++ core/pv-mqtt/build.xml | 24 +++++++++++++++++++++ core/pv-pva/.classpath | 14 ++++++++++++ core/pv-pva/.project | 17 +++++++++++++++ core/pv-pva/build.xml | 26 ++++++++++++++++++++++ core/pv/.classpath | 4 ---- core/pv/build.xml | 3 +-- core/vtype/.classpath | 1 + dependencies/phoebus-target/.classpath | 30 ++++++++++++++------------ phoebus-product/.classpath | 4 ++++ 20 files changed, 251 insertions(+), 24 deletions(-) create mode 100644 core/pv-ca/.classpath create mode 100644 core/pv-ca/.project create mode 100644 core/pv-ca/build.xml create mode 100644 core/pv-jackie/.classpath create mode 100644 core/pv-jackie/.project create mode 100644 core/pv-jackie/build.xml create mode 100644 core/pv-mqtt/.classpath create mode 100644 core/pv-mqtt/.project create mode 100644 core/pv-mqtt/build.xml create mode 100644 core/pv-pva/.classpath create mode 100644 core/pv-pva/.project create mode 100644 core/pv-pva/build.xml diff --git a/app/display/model/.classpath b/app/display/model/.classpath index d2913cb056..6b36eb4869 100644 --- a/app/display/model/.classpath +++ b/app/display/model/.classpath @@ -8,7 +8,5 @@ - - diff --git a/app/rtplot/.classpath b/app/rtplot/.classpath index 3c6a73abf5..69a191f71b 100644 --- a/app/rtplot/.classpath +++ b/app/rtplot/.classpath @@ -8,7 +8,6 @@ - - + diff --git a/build.xml b/build.xml index 2ee89e1c67..fee79775d5 100644 --- a/build.xml +++ b/build.xml @@ -13,6 +13,10 @@ + + + + @@ -89,6 +93,10 @@ + + + + diff --git a/core/pv-ca/.classpath b/core/pv-ca/.classpath new file mode 100644 index 0000000000..4105812a7e --- /dev/null +++ b/core/pv-ca/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/pv-ca/.project b/core/pv-ca/.project new file mode 100644 index 0000000000..22b16ecce3 --- /dev/null +++ b/core/pv-ca/.project @@ -0,0 +1,22 @@ + + + core-pv-ca + + + + + + org.python.pydev.PyDevBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/core/pv-ca/build.xml b/core/pv-ca/build.xml new file mode 100644 index 0000000000..92444e9781 --- /dev/null +++ b/core/pv-ca/build.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/pv-jackie/.classpath b/core/pv-jackie/.classpath new file mode 100644 index 0000000000..a6f19cb98b --- /dev/null +++ b/core/pv-jackie/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/core/pv-jackie/.project b/core/pv-jackie/.project new file mode 100644 index 0000000000..248eaa1078 --- /dev/null +++ b/core/pv-jackie/.project @@ -0,0 +1,17 @@ + + + core-pv-jackie + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/core/pv-jackie/build.xml b/core/pv-jackie/build.xml new file mode 100644 index 0000000000..20c1a1c634 --- /dev/null +++ b/core/pv-jackie/build.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/pv-mqtt/.classpath b/core/pv-mqtt/.classpath new file mode 100644 index 0000000000..bd9e37ce77 --- /dev/null +++ b/core/pv-mqtt/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/pv-mqtt/.project b/core/pv-mqtt/.project new file mode 100644 index 0000000000..1a1ef492d8 --- /dev/null +++ b/core/pv-mqtt/.project @@ -0,0 +1,17 @@ + + + core-pv-mqtt + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/core/pv-mqtt/build.xml b/core/pv-mqtt/build.xml new file mode 100644 index 0000000000..52d285ec9b --- /dev/null +++ b/core/pv-mqtt/build.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/pv-pva/.classpath b/core/pv-pva/.classpath new file mode 100644 index 0000000000..7e8bb20ca8 --- /dev/null +++ b/core/pv-pva/.classpath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/core/pv-pva/.project b/core/pv-pva/.project new file mode 100644 index 0000000000..38c8f0c94f --- /dev/null +++ b/core/pv-pva/.project @@ -0,0 +1,17 @@ + + + core-pv-pva + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/core/pv-pva/build.xml b/core/pv-pva/build.xml new file mode 100644 index 0000000000..60b39b6c9a --- /dev/null +++ b/core/pv-pva/build.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/pv/.classpath b/core/pv/.classpath index 4d3e6523d6..60d99b64ad 100644 --- a/core/pv/.classpath +++ b/core/pv/.classpath @@ -4,15 +4,11 @@ - - - - diff --git a/core/pv/build.xml b/core/pv/build.xml index 9707051080..c5b3c00db3 100644 --- a/core/pv/build.xml +++ b/core/pv/build.xml @@ -13,7 +13,6 @@ - @@ -37,4 +36,4 @@ - \ No newline at end of file + diff --git a/core/vtype/.classpath b/core/vtype/.classpath index b9dfd896cf..b748db33a5 100644 --- a/core/vtype/.classpath +++ b/core/vtype/.classpath @@ -2,6 +2,7 @@ + diff --git a/dependencies/phoebus-target/.classpath b/dependencies/phoebus-target/.classpath index 82bcca0210..5e9a04d34b 100644 --- a/dependencies/phoebus-target/.classpath +++ b/dependencies/phoebus-target/.classpath @@ -1,13 +1,11 @@ - - - + @@ -41,7 +39,7 @@ - + @@ -50,14 +48,17 @@ + - - - - + + + + + + @@ -70,6 +71,7 @@ + @@ -95,7 +97,7 @@ - + @@ -115,7 +117,7 @@ - + @@ -128,7 +130,7 @@ - + @@ -145,7 +147,7 @@ - + @@ -172,8 +174,8 @@ - - + + diff --git a/phoebus-product/.classpath b/phoebus-product/.classpath index ffb088f71b..d4138ff3a5 100644 --- a/phoebus-product/.classpath +++ b/phoebus-product/.classpath @@ -11,6 +11,10 @@ + + + + From 6d9150655642a54311fb8687940c20695e6dee00 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Mon, 26 Feb 2024 14:27:24 +0100 Subject: [PATCH 24/92] CSSTUDIO-1774 Change positionAndSize() to not remember the last position, and instead open the dialog to the left of the "owner" of the dialog. --- .../org/phoebus/ui/dialog/DialogHelper.java | 74 ++++--------------- 1 file changed, 16 insertions(+), 58 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/dialog/DialogHelper.java b/core/ui/src/main/java/org/phoebus/ui/dialog/DialogHelper.java index 8f50b4fbe5..d1b156246c 100644 --- a/core/ui/src/main/java/org/phoebus/ui/dialog/DialogHelper.java +++ b/core/ui/src/main/java/org/phoebus/ui/dialog/DialogHelper.java @@ -171,18 +171,16 @@ public static void positionAndSize(final Dialog dialog, final Node owner, fin positionAndSize(dialog, owner, prefs, initialWidth, initialHeight, null, null); } - /** Position the given {@code dialog} initially relative to {@code owner}, - * then it saves/restore the dialog's position and size into/from the - * provided {@link Preferences}. + /** Position the given {@code dialog}. Saves/restores the dialog's + * position and size into/from the provided {@link Preferences}. * - *

{@code "dialog.x"} and {@code "dialog.y"} will be the preferences names - * used to save and restore the dialog's location. {@code "content.width"} - * and {@code "content.height"} the ones used for saving the size of the - * dialog's pane ({@link Dialog#getDialogPane()}). + *

{@code "content.width"} and {@code "content.height"} are the + * preference names used for saving the size of the dialog's pane + * ({@link Dialog#getDialogPane()}). * * @param dialog The dialog to be positioned and sized. * @param owner The node starting this dialog. - * @param prefs The {@link Preferences} used to save/restore position and size. + * @param prefs The {@link Preferences} used to save/restore size. * @param initialWidth The (very) initial width. {@link Double#NaN} must be * used if the default, automatically computed width and height * should be used instead. @@ -197,37 +195,26 @@ public static void positionAndSize(final Dialog dialog, final Node owner, fin public static void positionAndSize(final Dialog dialog, final Node owner, final Preferences prefs, final double initialWidth, final double initialHeight, final Consumer injector, - final Consumer projector) - { + final Consumer projector) { Objects.requireNonNull(dialog, "Null dialog."); - if (injector != null && prefs != null) + if (injector != null && prefs != null) injector.accept(prefs); if (owner != null) dialog.initOwner(owner.getScene().getWindow()); - double prefX, prefY; final double prefWidth, prefHeight; - if (prefs == null) - { // Use available defaults - prefX = Double.NaN; - prefY = Double.NaN; + if (prefs == null) { // Use available defaults prefWidth = initialWidth; prefHeight = initialHeight; - } - else - { // Read preferences - prefX = prefs.getDouble("dialog.x", Double.NaN); - prefY = prefs.getDouble("dialog.y", Double.NaN); + } else { // Read preferences prefWidth = prefs.getDouble("content.width", initialWidth); prefHeight = prefs.getDouble("content.height", initialHeight); // .. and arrange for saving location to prefs on close dialog.setOnHidden(event -> { - prefs.putDouble("dialog.x", dialog.getX()); - prefs.putDouble("dialog.y", dialog.getY()); prefs.putDouble("content.width", dialog.getDialogPane().getWidth()); prefs.putDouble("content.height", dialog.getDialogPane().getHeight()); @@ -235,52 +222,23 @@ public static void positionAndSize(final Dialog dialog, final Node owner, fin projector.accept(prefs); // TODO Flush prefs in background thread? - try - { + try { prefs.flush(); - } - catch (BackingStoreException ex) - { + } catch (BackingStoreException ex) { logger.log(Level.WARNING, "Unable to flush preferences", ex); } }); } - if (!Double.isNaN(prefX) && !Double.isNaN(prefY)) - { - // Check if prefX, Y are inside available screens - // Find bounds of all screens together, assuming same display size - // Can be enhanced, checking all displays individually - // Finding maxX,Y, while minX,Y = 0. is constant so no need to check - List screens = Screen.getScreens(); - double maxX = 0.; - double maxY = 0.; - for (Screen screen : screens) - { - Rectangle2D sb = screen.getVisualBounds(); - maxX = Math.max(sb.getMaxX(), maxX); - maxY = Math.max(sb.getMaxY(), maxY); - } - // When no width/height available, set a reasonable - // default to take dialog to screen but not influence small dialog windows - final double dw = Double.isNaN(prefWidth) ? 100 : prefWidth; - final double dh = Double.isNaN(prefHeight) ? 100 : prefHeight; - prefX = prefX + dw > maxX ? maxX - dw : prefX; - prefY = prefY + dh > maxY ? maxY - dh : prefY; - - dialog.setX(prefX); - dialog.setY(prefY); - } - else if (owner != null) - { + if (owner != null) { // Position relative to owner final Bounds pos = owner.localToScreen(owner.getBoundsInLocal()); - dialog.setX(pos.getMinX()); - dialog.setY(pos.getMinY() + pos.getHeight()/3); + dialog.setX(pos.getMinX() - prefWidth); + dialog.setY(pos.getMinY() - prefHeight/3); } - if (!Double.isNaN(prefWidth) && !Double.isNaN(prefHeight)) + if (!Double.isNaN(prefWidth) && !Double.isNaN(prefHeight)) dialog.getDialogPane().setPrefSize(prefWidth, prefHeight); } } From dafd807422dfc3d3a848b7505c93d276742cb9be Mon Sep 17 00:00:00 2001 From: Sebastian Marsching Date: Fri, 23 Feb 2024 23:45:36 +0100 Subject: [PATCH 25/92] Add JSON archive reader. This archive reader can read archived data from any HTTP server implementing the JSON archive access protocol as described at https://oss.aquenos.com/cassandra-pv-archiver/docs/3.2.6/manual/html/apb.html. In particular, it can retrieve data from the Cassandra PV Archiver and the JSON Archive Proxy. --- app/databrowser-json/pom.xml | 60 + .../reader/json/JsonArchivePreferences.java | 66 + .../reader/json/JsonArchiveReader.java | 501 +++++++ .../reader/json/JsonArchiveReaderFactory.java | 43 + .../json/internal/JsonArchiveInfoReader.java | 180 +++ .../reader/json/internal/JsonVTypeReader.java | 933 +++++++++++++ .../json/internal/JsonValueIterator.java | 224 ++++ ...us.archive.reader.spi.ArchiveReaderFactory | 1 + ...archive_reader_json_preferences.properties | 5 + .../reader/json/HttpServerTestBase.java | 211 +++ .../json/JsonArchiveReaderFactoryTest.java | 53 + .../reader/json/JsonArchiveReaderTest.java | 1166 +++++++++++++++++ app/pom.xml | 1 + phoebus-product/pom.xml | 5 + 14 files changed, 3449 insertions(+) create mode 100644 app/databrowser-json/pom.xml create mode 100644 app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchivePreferences.java create mode 100644 app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchiveReader.java create mode 100644 app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchiveReaderFactory.java create mode 100644 app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonArchiveInfoReader.java create mode 100644 app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonVTypeReader.java create mode 100644 app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonValueIterator.java create mode 100644 app/databrowser-json/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory create mode 100644 app/databrowser-json/src/main/resources/archive_reader_json_preferences.properties create mode 100644 app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/HttpServerTestBase.java create mode 100644 app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/JsonArchiveReaderFactoryTest.java create mode 100644 app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/JsonArchiveReaderTest.java diff --git a/app/databrowser-json/pom.xml b/app/databrowser-json/pom.xml new file mode 100644 index 0000000000..9fffffa15d --- /dev/null +++ b/app/databrowser-json/pom.xml @@ -0,0 +1,60 @@ + + 4.0.0 + + org.phoebus + app + 4.7.4-SNAPSHOT + + app-databrowser-json + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + org.hamcrest + hamcrest-all + 1.3 + test + + + + org.phoebus + app-databrowser + 4.7.4-SNAPSHOT + + + + org.phoebus + core-framework + 4.7.4-SNAPSHOT + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + + com.google.guava + guava + ${guava.version} + + + + org.epics + epics-util + ${epics.util.version} + + + + org.epics + vtype + ${vtype.version} + + + diff --git a/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchivePreferences.java b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchivePreferences.java new file mode 100644 index 0000000000..16405f75c3 --- /dev/null +++ b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchivePreferences.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.archive.reader.json; + +import org.phoebus.framework.preferences.PreferencesReader; + +import java.util.logging.Logger; + +/** + *

+ * Preferences used by the {@link JsonArchiveReader}. + *

+ *

+ * Each of the parameters corresponds to a property in the preferences system, + * using the org.phoebus.archive.reader.json namespace. + *

+ *

+ * Please refer to the archive_reader_json_preferences.properties + * file for a full list of available properties and their meanings. + *

+ * + * @param honor_zero_precision + * flag indicating whether a floating-point value specifying a precision of + * zero shall be printed without any fractional digits (true) or + * whether such a value should be printed using a default format + * (false). + */ +public record JsonArchivePreferences( + boolean honor_zero_precision) { + + private final static JsonArchivePreferences DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = loadPreferences(); + } + + /** + * Returns the default instance of the preferences. This is the instance + * that is automatically configured through Phoebus’s + * {@link PreferencesReader}. + * + * @return preference instance created using the {@link PreferencesReader}. + */ + public static JsonArchivePreferences getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static JsonArchivePreferences loadPreferences() { + final var logger = Logger.getLogger( + JsonArchivePreferences.class.getName()); + final var preference_reader = new PreferencesReader( + JsonArchivePreferences.class, + "/archive_reader_json_preferences.properties"); + final var honor_zero_precision = preference_reader.getBoolean( + "honor_zero_precision"); + logger.config("honor_zero_precision = " + honor_zero_precision); + return new JsonArchivePreferences(honor_zero_precision); + } + +} diff --git a/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchiveReader.java b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchiveReader.java new file mode 100644 index 0000000000..9c55f4283c --- /dev/null +++ b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchiveReader.java @@ -0,0 +1,501 @@ +/******************************************************************************* + * Copyright (c) 2013-2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.archive.reader.json; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import org.phoebus.archive.reader.ArchiveReader; +import org.phoebus.archive.reader.UnknownChannelException; +import org.phoebus.archive.reader.ValueIterator; +import org.phoebus.archive.reader.json.internal.JsonArchiveInfoReader; +import org.phoebus.archive.reader.json.internal.JsonValueIterator; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.Cleaner; +import java.math.BigInteger; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Map; +import java.util.Objects; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.DeflaterInputStream; +import java.util.zip.GZIPInputStream; + +/** + *

+ * Archive reader implementation that connects to an archive server using an + * HTTP / JSON based protocol. Typically, this reader is used together with the + * JSON archive server. However, it will work with any compliant HTTP server. + *

+ * + *

+ * Instances of this class are thread-safe. + *

+ */ +public class JsonArchiveReader implements ArchiveReader { + + private final static BigInteger ONE_BILLION = BigInteger + .valueOf(1000000000L); + + private final Cleaner cleaner; + private final String description; + private final String http_url; + private final Map iterators; + private final JsonFactory json_factory; + private final int key; + private final Logger logger; + private final JsonArchivePreferences preferences; + + /** + *

+ * Creates an archive reader that requests samples from the specified URL. + * The URL must start with the scheme "json" followed by the HTTP or + * HTTPS URL of the archive server. The URL must include the context path, + * but not include the servlet path. + *

+ * + *

+ * For example, the URL json:http://localhost:8080/ will + * expect the archive server to run on port 8080 of the same computer and + * will use the URL + * http://localhost:8080/archive/<key>/channels-by-pattern/<pattern> + * when searching for channels. + *

+ * + *

+ * If not specified, the key is assumes to be 1. + * The key can be specified by adding ;key=<key> to the + * archive URL (e.g. json:http://localhost:8080/;key=2). + *

+ * + * @param url + * archive URL with the scheme "json" followed by a valid HTTP HTTPS URL. + * @param preferences + * preferences that are used by this archive reader. + * @throws IllegalArgumentException + * if the specified URL is invalid. + */ + public JsonArchiveReader(String url, JsonArchivePreferences preferences) { + // Initialize the logger first. + this.logger = Logger.getLogger(getClass().getName()); + // The URL must start with the json: prefix. + if (!url.startsWith("json:")) { + throw new IllegalArgumentException( + "The URL \"" + + url + + "\" is not a valid archive URL, because it does " + + "not start with \"json:\"."); + } + // Remove the prefix. + var http_url = url.substring(5); + // Extract the key=… part, if present. + var key = 1; + var semicolon_index = http_url.indexOf(';'); + if (semicolon_index != -1) { + final var args_part = http_url.substring(semicolon_index + 1); + http_url = http_url.substring(0, semicolon_index); + if (args_part.startsWith("key=")) { + try { + key = Integer.parseInt(args_part.substring(4)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "The URL \"" + + url + + "\" is not a valid archive URL, because " + + "the argument \";" + + args_part + + "\" is invalid."); + } + } + } + // We want the base URL to always have a trailing slash, so that we + // have a common basis for constructing specific URLs. + if (!http_url.endsWith("/")) { + http_url = http_url + "/"; + } + // Initialize the class fields. + this.cleaner = Cleaner.create(); + this.http_url = http_url; + this.iterators = new WeakHashMap<>(); + this.json_factory = JsonFactory.builder() + .enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build(); + // We want to ensure that the underlying input stream is closed when + // closing a parser. This should be the default, but it is better to be + // sure. + this.json_factory.enable(JsonParser.Feature.AUTO_CLOSE_SOURCE); + this.key = key; + this.preferences = Objects.requireNonNull(preferences); + // We have to initialize most fields before we can retrieve the + // description. + this.description = retrieveArchiveDescription(); + } + + @Override + public void cancel() { + synchronized (iterators) { + for (JsonValueIterator i : iterators.keySet()) { + // We only call cancel. The iterator is going to be removed + // from the map when it is closed. + i.cancel(); + } + } + } + + @Override + public void close() { + // We do nothing here, because we do not hold any expensive resources + // that need to be closed. + } + + @Override + public String getDescription() { + return description; + } + + @Override + public Collection getNamesByPattern(String glob_pattern) + throws Exception { + final var url = "/" + key + "/channels-by-pattern/" + + URLEncoder.encode(glob_pattern, StandardCharsets.UTF_8); + try (final var parser = doGetJson(url)) { + var token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token != JsonToken.START_ARRAY) { + throw new JsonParseException( + parser, + "Expected START_ARRAY but got " + token, + parser.getTokenLocation()); + } + final var channel_names = new LinkedList(); + while (true) { + token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token == JsonToken.END_ARRAY) { + break; + } + if (token == JsonToken.VALUE_STRING) { + String channel_name = parser.getText(); + channel_names.add(channel_name); + } else { + throw new JsonParseException( + parser, + "Expected VALUE_STRING but got " + token, + parser.getTokenLocation()); + } + } + return channel_names; + } + } + + @Override + public ValueIterator getOptimizedValues( + String name, Instant start, Instant end, int count) + throws UnknownChannelException, Exception { + return getValues(name, start, end, count); + } + + @Override + public ValueIterator getRawValues( + String name, Instant start, Instant end) + throws UnknownChannelException, Exception { + return getValues(name, start, end, null); + } + + /** + * Converts a {@link BigInteger} representing the number of nanoseconds + * since epoch to an {@link Instant}. + * + * @param timestamp + * number of nanoseconds since UNIX epoch (January 1st, 1970, + * 00:00:00 UTC). + * @return + * instant representing the timestamp. + */ + private static BigInteger timestampToBigInteger(final Instant timestamp) { + return BigInteger.valueOf(timestamp.getNano()).add( + BigInteger.valueOf(timestamp.getEpochSecond()).multiply( + ONE_BILLION)); + } + + /** + *

+ * Sends a GET request to the archive source and returns the + * response. + *

+ * + * @param url + * URL which shall be requested. Must start with a forward slash and be + * relative to the base HTTP url configured for this reader. + * @return + * input stream that provides the HTTP server’s response. + * @throws IOException + * if the URL is malformed, the connection cannot be opened, or the input + * stream cannot be retrieved. + */ + private InputStream doGet(String url) throws IOException { + final var request_url = this.http_url + "archive" + url; + final var connection = new URL(request_url).openConnection(); + connection.addRequestProperty("Accept-Encoding", "gzip, deflate"); + connection.connect(); + final var content_encoding = connection.getHeaderField( + "Content-Encoding"); + final var input_stream = connection.getInputStream(); + try { + if (content_encoding != null) { + if (content_encoding.equals("gzip")) { + return new GZIPInputStream(input_stream); + } else if (content_encoding.equals("deflate")) { + return new DeflaterInputStream(input_stream); + } + } + return input_stream; + } catch (IOException | RuntimeException e) { + input_stream.close(); + throw e; + } + } + + /** + *

+ * Sends a GET request to the archive source and returns a + * JSON parser for the response. + *

+ * + * @param url + * URL which shall be requested. Must start with a forward slash and be + * relative to the base HTTP url configured for this reader. + * @return + * JSON parser that parses HTTP server’s response. + * @throws IOException + * if the URL is malformed, the connection cannot be opened, or the JSON + * parser cannot be created. + */ + private JsonParser doGetJson(String url) throws IOException { + final var input_stream = doGet(url); + try { + return json_factory.createParser(input_stream); + } catch (IOException | RuntimeException e) { + // If we could not create the parser, we have to close the input + // stream. Otherwise, the input stream is going to be closed when + // the parser is closed. + input_stream.close(); + throw e; + } + } + + /** + * Sends a request for samples to the archive server and returns an + * iterator providing the samples. + * + * @param name + * channel name in the archive. + * @param start + * beginning of the time period for which samples shall be retrieved. + * @param end + * end of the time period for which samples shall be retrieved. + * @param count + * approximate number of samples that shall be retrieved. If + * null raw samples shall be retrieved. + * @return + * iterator iterating over the samples for the specified time period in + * ascending order by time. + * @throws IOException + * if there is an error while requesting the samples. If an error occurs + * later, while using the iterator, no exception is thrown and the + * iterator’s hasNext() method simply returns false. + * @throws UnknownChannelException + * if the specified channel is not present in the archive. + */ + private JsonValueIterator getValues( + final String name, + final Instant start, + final Instant end, + final Integer count) + throws IOException, UnknownChannelException { + // Construct the request URL. + final var sb = new StringBuilder(); + sb.append("/"); + sb.append(key); + sb.append("/samples/"); + sb.append(URLEncoder.encode(name, StandardCharsets.UTF_8)); + sb.append("?start="); + sb.append(timestampToBigInteger(start)); + sb.append("&end="); + sb.append(timestampToBigInteger(end)); + if (count != null) { + sb.append("&count="); + sb.append(count); + } + final var request_url = sb.toString(); + // Send the request and create the JSON parser for the response. + final JsonParser parser; + try { + parser = doGetJson(request_url); + } catch (FileNotFoundException e) { + throw new UnknownChannelException(name); + } + // Before creating the iterator, we have to advance the parser to the + // first token. + try { + parser.nextToken(); + } catch (IOException | RuntimeException e) { + parser.close(); + throw e; + } + // Prepare the cleanup action. This action is executed when the + // iterator is closed or garbage collected. + final Runnable iterator_cleanup_action = () -> { + try { + parser.close(); + } catch (IOException e) { + // We ignore an exception that happens on cleanup. + } + }; + // Create an iterator based on the JSON parser. + try { + final var iterator = new JsonValueIterator( + parser, + this::unregisterValueIterator, + request_url, + preferences.honor_zero_precision()); + // We register the iterator. This has two purposes: First, we have to + // be able to call its cancel() method. Second, we need to close the + // parser when the iterator is closed or garbage collected. We do + // not register the iterator if it has no more elements. In this + // case, it might already be closed (and if it is not, we close it + // now), so we do not have run any cleanup actions either and if we + // registered it, it would never be unregistered because it is + // already closed. + if (iterator.hasNext()) { + registerValueIterator(iterator, iterator_cleanup_action); + } else { + // The iterator should already be closed, but calling the + // close() method anyway does not hurt. + iterator.close(); + } + return iterator; + } catch (IOException | RuntimeException e) { + // If we cannot create the iterator, we have to close the parser + // now. First, it is not going to be used for anything else. + // Second, the iterator does not exist, so it will not be closed + // when the iterator is closed. + parser.close(); + throw e; + } + } + + /** + * Registers a value iterator with this reader. This method is only + * intended for use by the {@link JsonValueIterator} constructor. + * + * @param iterator + * iterator that is calling this method. + * @param cleanup_action + * cleanup action that shall be run when the iterator is garbage + * collected or when {@link #unregisterValueIterator(JsonValueIterator)} + * is called for the iterator. + */ + private void registerValueIterator( + JsonValueIterator iterator, Runnable cleanup_action) { + // If the iterator has not been closed properly, we have to ensure that + // we close the JSON parser and input stream. Usually, this will happen + // when unregisterValueIterator is called, which is called by the + // iterator’s close method. However, if close is never called for some + // reason, registering the cleanup action ensures that the external + // resources are freed. We cannot explicitly remove the iterator from + // our iterators map in this case, but this is not a problem because + // the WeakHashMap will automatically remove entries when the key is + // garbage collected. + final var cleanable = cleaner.register(iterator, cleanup_action); + synchronized (iterators) { + iterators.put(iterator, cleanable); + } + } + + /** + * Retrieves the archive description from the archive server. If the + * description cannot be received, a warning is logged and a generic + * description is returned. + * + * @return + * the description for the archive specified by the URL and archive key or + * a generic description if the archive information cannot be retrieved + * from the server. + * @throws IllegalArgumentException + * if the server sends valid archive information, but it does not contain + * any information for the specified archive key. + */ + private String retrieveArchiveDescription() { + try (final var parser = doGetJson("/")) { + // We have to advance to the first token before calling + // readArchiveInfos(…). + parser.nextToken(); + final var archive_infos = JsonArchiveInfoReader + .readArchiveInfos(parser); + for (final var archive_info : archive_infos) { + if (archive_info.archive_key() == key) { + return archive_info.archive_description(); + } + } + throw new IllegalArgumentException( + "The server at \"" + + http_url + + "\" does not provide an archive with the key " + + key + + "."); + } catch (IOException e) { + logger.log( + Level.WARNING, + "Could not load archive information from server for URL \"" + + http_url + + "\"."); + // If we cannot get the archive description, we still want to + // initialize the archive reader. Maybe there is a temporary + // network problem and the archive reader will work correctly + // later. So, instead of throwing an exception, we rather use a + // generic description instead of the one retrieved from the + // server. + return "Provides archive access over HTTP/JSON."; + } + } + + /** + * Unregister an iterator that has previously been registered. This method + * is called when the iterator is closed. + * + * @param iterator + * iterator that was previously registered using + * {@link #registerValueIterator(JsonValueIterator, Runnable)}. + */ + private void unregisterValueIterator(JsonValueIterator iterator) { + final Cleaner.Cleanable cleanable; + synchronized (iterators) { + cleanable = iterators.remove(iterator); + } + if (cleanable != null) { + cleanable.clean(); + } + } + +} diff --git a/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchiveReaderFactory.java b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchiveReaderFactory.java new file mode 100644 index 0000000000..9263c14c53 --- /dev/null +++ b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/JsonArchiveReaderFactory.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2013-2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.archive.reader.json; + +import org.phoebus.archive.reader.ArchiveReader; +import org.phoebus.archive.reader.spi.ArchiveReaderFactory; + +/** + *

+ * Factory for {@link JsonArchiveReader} instances. This type of archive reader + * handles archive URLs starting with json: and implements the + * + * JSON archive access protocol 1.0. + *

+ * + *

+ * Instances of this class are thread-safe. + *

+ */ +public class JsonArchiveReaderFactory implements ArchiveReaderFactory { + + @Override + public ArchiveReader createReader(String url) throws Exception { + if (!url.startsWith("json:")) { + throw new IllegalArgumentException( + "URL must start with scheme \"json:\"."); + } + return new JsonArchiveReader( + url, JsonArchivePreferences.getDefaultInstance()); + } + + @Override + public String getPrefix() { + return "json"; + } + +} diff --git a/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonArchiveInfoReader.java b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonArchiveInfoReader.java new file mode 100644 index 0000000000..3a5074df13 --- /dev/null +++ b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonArchiveInfoReader.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2013-2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.archive.reader.json.internal; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +/** + * Reads a {@link ArchiveInfo} objects from a {@link JsonParser}. + */ +public final class JsonArchiveInfoReader { + + /** + * Information about an archive that is available on the server. + * + * @param archive_description the archive’s description. + * @param archive_key key identifying the archive on the server. + * @param archive_name the archive’s name. + */ + public record ArchiveInfo( + String archive_description, + int archive_key, + String archive_name) { + } + + private JsonArchiveInfoReader() { + } + + /** + * Reads a {@link ArchiveInfo} value from a {@link JsonParser}. When + * calling this method, the parser’s current token must be + * {@link JsonToken#START_ARRAY START_ARRAY} and when the method returns + * successfully, the parser’s current token is the corresponding + * {@link JsonToken#END_ARRAY END_ARRAY}. + * + * @param parser JSON parser from which the tokens are read. + * @return list representing the parsed JSON array. + * @throws IOException + * if the JSON data is malformed or there is an I/O problem. + */ + public static List readArchiveInfos(JsonParser parser) + throws IOException { + var token = parser.currentToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token != JsonToken.START_ARRAY) { + throw new JsonParseException( + parser, + "Expected START_ARRAY but got " + token, + parser.getTokenLocation()); + } + final var archive_infos = new LinkedList(); + while (true) { + token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token == JsonToken.END_ARRAY) { + break; + } + archive_infos.add(readArchiveInfo(parser)); + } + return archive_infos; + } + + private static void duplicateFieldIfNotNull( + final JsonParser parser, + final String field_name, + final Object field_value) + throws JsonParseException { + if (field_value != null) { + throw new JsonParseException( + parser, + "Field \"" + field_name + "\" occurs twice.", + parser.getTokenLocation()); + } + } + + private static ArchiveInfo readArchiveInfo(JsonParser parser) + throws IOException { + JsonToken token = parser.getCurrentToken(); + if (token != JsonToken.START_OBJECT) { + throw new JsonParseException( + parser, + "Expected START_OBJECT but got " + token, + parser.getTokenLocation()); + } + Integer archive_key = null; + String archive_name = null; + String archive_description = null; + String field_name = null; + while (true) { + token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token == JsonToken.END_OBJECT) { + break; + } + if (field_name == null) { + if (token == JsonToken.FIELD_NAME) { + field_name = parser.getCurrentName(); + continue; + } else { + throw new JsonParseException( + parser, + "Expected FIELD_NAME but got " + token, + parser.getTokenLocation()); + } + } + switch (field_name) { + case "description" -> { + duplicateFieldIfNotNull( + parser, field_name, archive_description); + archive_description = readStringValue(parser); + } + case "key" -> { + duplicateFieldIfNotNull(parser, field_name, archive_key); + archive_key = readIntValue(parser); + } + case "name" -> { + duplicateFieldIfNotNull(parser, field_name, archive_name); + archive_name = readStringValue(parser); + } + default -> throw new JsonParseException( + parser, + "Found unknown field \"" + field_name + "\".", + parser.getTokenLocation()); + } + field_name = null; + } + if (archive_description == null + || archive_key == null + || archive_name == null) { + throw new JsonParseException( + parser, + "Mandatory field is missing in object.", + parser.getTokenLocation()); + } + return new ArchiveInfo(archive_description, archive_key, archive_name); + } + + private static int readIntValue(final JsonParser parser) + throws IOException { + final var token = parser.getCurrentToken(); + if (token != JsonToken.VALUE_NUMBER_INT) { + throw new JsonParseException( + parser, + "Expected VALUE_NUMBER_INT but got " + + token, + parser.getTokenLocation()); + } + return parser.getIntValue(); + } + + private static String readStringValue(final JsonParser parser) + throws IOException { + final var token = parser.currentToken(); + if (token != JsonToken.VALUE_STRING) { + throw new JsonParseException( + parser, + "Expected VALUE_STRING but got " + token, + parser.getTokenLocation()); + } + return parser.getText(); + } + +} diff --git a/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonVTypeReader.java b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonVTypeReader.java new file mode 100644 index 0000000000..4febe23c57 --- /dev/null +++ b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonVTypeReader.java @@ -0,0 +1,933 @@ +/******************************************************************************* + * Copyright (c) 2013-2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.archive.reader.json.internal; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.google.common.primitives.ImmutableDoubleArray; +import com.google.common.primitives.ImmutableIntArray; +import com.google.common.primitives.ImmutableLongArray; +import org.epics.util.array.CollectionNumbers; +import org.epics.util.array.ListDouble; +import org.epics.util.array.ListInteger; +import org.epics.util.array.ListLong; +import org.epics.util.stats.Range; +import org.epics.util.text.NumberFormats; +import org.epics.vtype.Alarm; +import org.epics.vtype.AlarmSeverity; +import org.epics.vtype.AlarmStatus; +import org.epics.vtype.Display; +import org.epics.vtype.EnumDisplay; +import org.epics.vtype.Time; +import org.epics.vtype.VDouble; +import org.epics.vtype.VDoubleArray; +import org.epics.vtype.VEnum; +import org.epics.vtype.VEnumArray; +import org.epics.vtype.VInt; +import org.epics.vtype.VIntArray; +import org.epics.vtype.VLong; +import org.epics.vtype.VLongArray; +import org.epics.vtype.VStatistics; +import org.epics.vtype.VString; +import org.epics.vtype.VStringArray; +import org.epics.vtype.VType; + +import java.io.IOException; +import java.math.BigInteger; +import java.text.NumberFormat; +import java.time.Instant; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +/** + * Reads a {@link org.epics.vtype.VType} from a {@link JsonParser}. + */ +public final class JsonVTypeReader { + + private enum ValueType { + DOUBLE("double"), + ENUM("enum"), + LONG("long"), + MIN_MAX_DOUBLE("minMaxDouble"), + STRING("string"); + + public final String name; + + ValueType(String name) { + this.name = name; + } + + } + + private final static BigInteger ONE_BILLION = BigInteger + .valueOf(1000000000L); + + private JsonVTypeReader() { + } + + /** + * Reads a {@link VType} value from a {@link JsonParser}. When calling this + * method, the parser’s current token must be {@link JsonToken#START_OBJECT + * START_OBJECT} and when the method returns successfully, the parser’s + * current token is the corresponding {@link JsonToken#END_OBJECT + * END_OBJECT}. + * + * @param parser + * JSON parser from which the tokens are read. + * @param honor_zero_precision + * whether a precision of zero should result in no fractional digits being + * used in the number format (true) or a default number + * format should be used when the precision is zero (false). + * This only applies to floating-point values. Integer values always use + * a number format that does not include fractional digits. + * @return value representing the parsed JSON object. + * @throws IOException + * if the JSON data is malformed or there is an I/O problem. + */ + public static VType readValue( + final JsonParser parser, boolean honor_zero_precision) + throws IOException { + JsonToken token = parser.getCurrentToken(); + if (token != JsonToken.START_OBJECT) { + throw new JsonParseException( + parser, + "Expected START_OBJECT but got " + token, + parser.getTokenLocation()); + } + Display display = null; + ImmutableDoubleArray double_value = null; + EnumDisplay enum_display = null; + ImmutableIntArray enum_value = null; + String field_name = null; + boolean found_value = false; + ImmutableLongArray long_value = null; + Double maximum = null; + Double minimum = null; + String quality = null; + AlarmSeverity severity = null; + String status = null; + Instant timestamp = null; + ValueType type = null; + List string_value = null; + while (true) { + token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token == JsonToken.END_OBJECT) { + break; + } + if (field_name == null) { + if (token != JsonToken.FIELD_NAME) { + throw new JsonParseException( + parser, + "Expected FIELD_NAME but got " + token, + parser.getTokenLocation()); + } + field_name = parser.getCurrentName(); + continue; + } + switch (field_name) { + case "maximum" -> { + duplicateFieldIfNotNull(parser, field_name, maximum); + maximum = readDoubleValue(parser); + } + case "metaData" -> { + if (enum_display != null || display != null) { + throw new JsonParseException( + parser, + "Field \"" + field_name + "\" occurs twice.", + parser.getTokenLocation()); + } + Object metaData = readMetaData( + parser, honor_zero_precision); + if (metaData instanceof Display) { + display = (Display) metaData; + } else if (metaData instanceof EnumDisplay) { + enum_display = (EnumDisplay) metaData; + } else { + throw new RuntimeException( + "Return value of internal method readMetaData " + + "has unexpected type " + + metaData.getClass().getName() + + "."); + } + } + case "minimum" -> { + duplicateFieldIfNotNull(parser, field_name, minimum); + minimum = readDoubleValue(parser); + } + case "quality" -> { + // We do not use the quality field any longer (Phoebus’s + // VType system does not support it), but we still want to + // ensure that the data is well-formed. + duplicateFieldIfNotNull(parser, field_name, quality); + quality = readStringValue(parser); + } + case "severity" -> { + duplicateFieldIfNotNull(parser, field_name, severity); + severity = readSeverity(parser); + } + case "status" -> { + duplicateFieldIfNotNull(parser, field_name, status); + status = readStringValue(parser); + } + case "time" -> { + duplicateFieldIfNotNull(parser, field_name, timestamp); + timestamp = readInstant(parser); + } + case "type" -> { + duplicateFieldIfNotNull(parser, field_name, type); + final var type_name = readStringValue(parser); + type = switch (type_name.toLowerCase(Locale.ROOT)) { + case "double" -> ValueType.DOUBLE; + case "enum" -> ValueType.ENUM; + case "long" -> ValueType.LONG; + case "minmaxdouble" -> ValueType.MIN_MAX_DOUBLE; + case "string" -> ValueType.STRING; + default -> throw new JsonParseException( + parser, + "Unknown type \"" + type_name + "\".", + parser.getTokenLocation()); + }; + } + case"value" -> { + if (found_value) { + throw new JsonParseException( + parser, + "Field \"" + field_name + "\" occurs twice.", + parser.getTokenLocation()); + } + if (type == null) { + throw new JsonParseException( + parser, + "\"value\" field must be specified after " + + "\"type\" field.", + parser.getTokenLocation()); + } + found_value = true; + switch (type) { + case DOUBLE, MIN_MAX_DOUBLE -> { + double_value = readDoubleArray(parser); + } + case ENUM -> { + enum_value = readIntArray(parser); + } + case LONG -> { + long_value = readLongArray(parser); + } + case STRING -> { + string_value = readStringArray(parser); + } + } + } + default -> throw new JsonParseException( + parser, + "Found unknown field \"" + field_name + "\".", + parser.getTokenLocation()); + } + field_name = null; + } + if (!found_value + || quality == null + || severity == null + || status == null + || timestamp == null + || type == null) { + throw new JsonParseException( + parser, + "Mandatory field is missing in object.", + parser.getTokenLocation()); + } + if (type != ValueType.ENUM && enum_display != null) { + throw new JsonParseException( + parser, + "Value of type \"" + + type.name + + "\" does not accept enum meta-data.", + parser.getTokenLocation()); + } + if (type != ValueType.MIN_MAX_DOUBLE && ( + minimum != null || maximum != null)) { + throw new JsonParseException( + parser, + "Invalid field specified for value of type\"" + + type.name + + "\".", + parser.getTokenLocation()); + } + if ((type == ValueType.ENUM || type == ValueType.STRING) + && display != null) { + throw new JsonParseException( + parser, + "Value of type \"" + + type.name + + "\" does not accept numeric meta-data.", + parser.getTokenLocation()); + } + final var alarm = Alarm.of(severity, AlarmStatus.NONE, status); + final var time = Time.of(timestamp); + switch (type) { + case DOUBLE -> { + if (display == null) { + display = Display.none(); + } + if (double_value.length() == 1) { + return VDouble.of( + double_value.get(0), alarm, time, display); + } else { + return VDoubleArray.of( + CollectionNumbers.toListDouble( + double_value.toArray()), + alarm, + time, + display); + } + } + case ENUM -> { + // Ensure that we have labels for all indices. + int min_value = Integer.MAX_VALUE; + int max_value = Integer.MIN_VALUE; + for (var i = 0; i < enum_value.length(); ++i) { + final var value = enum_value.get(i); + min_value = Math.min(min_value, value); + max_value = Math.max(max_value, value); + } + // If we have a negative value or we have a value without a + // label, we cannot use the meta-data and return a regular + // integer instead. + if (min_value < 0 + || max_value >= enum_display.getChoices().size()) { + enum_display = null; + } + // If there is no meta-data, we cannot return an enum because + // an enum must have meta-data and this meta-data must include + // labels for all values. + if (enum_display == null) { + // If there are no labels, there is no benefit in returning + // an enum, so we rather return an integer type. + display = Display.of( + Range.undefined(), + Range.undefined(), + Range.undefined(), + Range.undefined(), + "", + NumberFormats.precisionFormat(0)); + if (enum_value.length() == 1) { + return VInt.of( + enum_value.get(0), + alarm, + time, + display); + } else { + return VIntArray.of( + toListInteger(enum_value), + alarm, + time, + display); + } + } + if (enum_value.length() == 1) { + return VEnum.of( + enum_value.get(0), enum_display, alarm, time); + } else { + return VEnumArray.of( + toListInteger(enum_value), + enum_display, + alarm, + time); + } + } + case LONG -> { + if (display == null) { + display = Display.none(); + } else if (display.getFormat() + .getMaximumFractionDigits() != 0) { + // The Display instance that was generated by readMetaData + // might use a number format that includes fractional + // digits because that function does not know yet that we + // are dealing with an integer value. In this case, we + // replace the number format with one that does not include + // fractional digits. + display = Display.of( + display.getDisplayRange(), + display. getAlarmRange(), + display.getWarningRange(), + display.getControlRange(), + display.getUnit(), + NumberFormats.precisionFormat(0), + display.getDescription()); + } + if (long_value.length() == 1) { + return VLong.of(long_value.get(0), alarm, time, display); + } else { + return VLongArray.of( + toListLong(long_value), + alarm, + time, + display); + } + } + case MIN_MAX_DOUBLE -> { + if (display == null) { + display = Display.none(); + } + if (minimum == null || maximum == null) { + throw new JsonParseException( + parser, + "Mandatory field is missing in object.", + parser.getTokenLocation()); + } + if (double_value.length() == 1) { + return VStatistics.of( + double_value.get(0), + Double.NaN, + minimum, + maximum, + 0, + alarm, + time, + display); + } else { + // There is no type for arrays with statistics, so we have + // to choose between dropping statistics information and + // dropping array elements. We choose to drop statistics + // information. This is supposed to be a rare exception + // anyway, there typically is no sense in building this + // kind of statistics for arrays. + return VDoubleArray.of( + toListDouble(double_value), + alarm, + time, + display); + } + } + case STRING -> { + if (string_value.size() == 1) { + return VString.of(string_value.get(0), alarm, time); + } else { + return VStringArray.of(string_value, alarm, time); + } + } + } + throw new JsonParseException( + parser, + "Invalid value type \"" + type + "\".", + parser.getTokenLocation()); + } + + private static Instant bigIntegerToTimestamp(final BigInteger big_int) { + BigInteger[] quotient_and_remainder = big_int + .divideAndRemainder(ONE_BILLION); + return Instant.ofEpochSecond( + quotient_and_remainder[0].longValue(), + quotient_and_remainder[1].longValue()); + } + + private static void duplicateFieldIfNotNull( + final JsonParser parser, + final String field_name, + final Object field_value) + throws JsonParseException { + if (field_value != null) { + throw new JsonParseException( + parser, + "Field \"" + field_name + "\" occurs twice.", + parser.getTokenLocation()); + } + } + + private static boolean readBooleanValue(final JsonParser parser) + throws IOException { + final var token = parser.currentToken(); + if (token != JsonToken.VALUE_TRUE + && token != JsonToken.VALUE_FALSE) { + throw new JsonParseException( + parser, + "Expected VALUE_TRUE or VALUE_FALSE but got " + + token, + parser.getTokenLocation()); + } + return parser.getBooleanValue(); + } + + private static ImmutableDoubleArray readDoubleArray( + final JsonParser parser) throws IOException { + final var array_builder = ImmutableDoubleArray.builder(1); + var token = parser.getCurrentToken(); + if (token != JsonToken.START_ARRAY) { + throw new JsonParseException( + parser, + "Expected START_ARRAY but got " + token, + parser.getTokenLocation()); + } + while (true) { + token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token == JsonToken.END_ARRAY) { + break; + } + array_builder.add(readDoubleValue(parser)); + } + return array_builder.build(); + } + + private static double readDoubleValue(final JsonParser parser) + throws IOException { + final var token = parser.currentToken(); + if (token != JsonToken.VALUE_NUMBER_INT + && token != JsonToken.VALUE_NUMBER_FLOAT) { + if (token != JsonToken.VALUE_STRING) { + throw new JsonParseException( + parser, + "Expected VALUE_NUMBER_INT, VALUE_NUMBER_FLOAT, or " + + "VALUE_STRING but got " + + token, + parser.getTokenLocation()); + } + return stringToSpecialDouble(parser.getText(), + parser); + } else { + return parser.getDoubleValue(); + } + } + + private static Instant readInstant(final JsonParser parser) + throws IOException { + final var token = parser.currentToken(); + if (token != JsonToken.VALUE_NUMBER_INT) { + throw new JsonParseException( + parser, + "Expected VALUE_NUMBER_INT but got " + + token, + parser.getTokenLocation()); + } + return bigIntegerToTimestamp(parser.getBigIntegerValue()); + } + + private static ImmutableIntArray readIntArray(final JsonParser parser) + throws IOException { + final var array_builder = ImmutableIntArray.builder(1); + var token = parser.getCurrentToken(); + if (token != JsonToken.START_ARRAY) { + throw new JsonParseException( + parser, + "Expected START_ARRAY but got " + token, + parser.getTokenLocation()); + } + while (true) { + token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token == JsonToken.END_ARRAY) { + break; + } + array_builder.add(readIntValue(parser)); + } + return array_builder.build(); + } + + private static int readIntValue(final JsonParser parser) + throws IOException { + final var token = parser.getCurrentToken(); + if (token != JsonToken.VALUE_NUMBER_INT) { + throw new JsonParseException( + parser, + "Expected VALUE_NUMBER_INT but got " + + token, + parser.getTokenLocation()); + } + return parser.getIntValue(); + } + + private static ImmutableLongArray readLongArray(final JsonParser parser) + throws IOException { + final var array_builder = ImmutableLongArray.builder(1); + var token = parser.getCurrentToken(); + if (token != JsonToken.START_ARRAY) { + throw new JsonParseException( + parser, + "Expected START_ARRAY but got " + token, + parser.getTokenLocation()); + } + while (true) { + token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token == JsonToken.END_ARRAY) { + break; + } + array_builder.add(readLongValue(parser)); + } + return array_builder.build(); + } + + private static long readLongValue(final JsonParser parser) + throws IOException { + final var token = parser.getCurrentToken(); + if (token != JsonToken.VALUE_NUMBER_INT) { + throw new JsonParseException( + parser, + "Expected VALUE_NUMBER_INT but got " + + token, + parser.getTokenLocation()); + } + return parser.getLongValue(); + } + + /** + * Reads the meta-data associated with a value. There are different + * types of meta-data for numeric and enum values, therefore the type of + * the return value has to be determined at runtime. + * + * @param parser the JSON parser that is used to read the meta-data. + * @param honor_zero_precision + * whether a precision of zero should result in no fractional digits being + * used in the number format (true) or a default number + * format should be used when the precision is zero (false). + * @return + * an instance of {@link String}[] (storing the enum labels) + * or an instance of {@link Display} (storing numeric limits and number + * formatting information). + * @throws IOException + * if an error occurs while parsing the JSON input (e.g. interrupted + * stream, malformed data). + */ + private static Object readMetaData( + final JsonParser parser, boolean honor_zero_precision) + throws IOException { + JsonToken token = parser.getCurrentToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token != JsonToken.START_OBJECT) { + throw new JsonParseException( + parser, + "Expected START_OBJECT but got " + token, + parser.getTokenLocation()); + } + Double alarm_high = null; + Double alarm_low = null; + Double display_high = null; + Double display_low = null; + String field_name = null; + Integer precision = null; + List states = null; + String type = null; + String units = null; + Double warn_high = null; + Double warn_low = null; + while (true) { + token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token == JsonToken.END_OBJECT) { + break; + } + if (field_name == null) { + if (token != JsonToken.FIELD_NAME) { + throw new JsonParseException( + parser, + "Expected FIELD_NAME but got " + token, + parser.getTokenLocation()); + } + field_name = parser.getCurrentName(); + continue; + } + switch (field_name) { + case "precision" -> { + duplicateFieldIfNotNull(parser, field_name, precision); + precision = readIntValue(parser); + } + case "type" -> { + duplicateFieldIfNotNull(parser, field_name, type); + type = readStringValue(parser); + } + case "units" -> { + duplicateFieldIfNotNull(parser, field_name, units); + units = readStringValue(parser); + } + case "displayLow" -> { + duplicateFieldIfNotNull(parser, field_name, display_low); + display_low = readDoubleValue(parser); + } + case "displayHigh" -> { + duplicateFieldIfNotNull(parser, field_name, display_high); + display_high = readDoubleValue(parser); + } + case "warnLow" -> { + duplicateFieldIfNotNull(parser, field_name, warn_low); + warn_low = readDoubleValue(parser); + } + case "warnHigh" -> { + duplicateFieldIfNotNull(parser, field_name, warn_high); + warn_high = readDoubleValue(parser); + } + case "alarmLow" -> { + duplicateFieldIfNotNull(parser, field_name, alarm_low); + alarm_low = readDoubleValue(parser); + } + case "alarmHigh" -> { + duplicateFieldIfNotNull(parser, field_name, alarm_high); + alarm_high = readDoubleValue(parser); + } + case "states" -> { + duplicateFieldIfNotNull(parser, field_name, states); + states = readStringArray(parser); + } + default -> throw new JsonParseException( + parser, + "Found unknown field \"" + field_name + "\".", + parser.getTokenLocation()); + } + field_name = null; + } + if (type == null) { + throw new JsonParseException( + parser, + "Mandatory field is missing in object.", + parser.getTokenLocation()); + + } + if (type.equalsIgnoreCase("enum")) { + if (states == null) { + throw new JsonParseException( + parser, + "Mandatory field is missing in object.", + parser.getTokenLocation()); + } + if (alarm_high != null + || alarm_low != null + || display_high != null + || display_low != null + || precision != null + || units != null + || warn_high != null + || warn_low != null) { + throw new JsonParseException( + parser, + "Invalid field specified for enum meta-data.", + parser.getTokenLocation()); + } + return EnumDisplay.of(states); + } else if (type.equalsIgnoreCase("numeric")) { + if (alarm_high == null + || alarm_low == null + || display_high == null + || display_low == null + || precision == null + || units == null + || warn_high == null + || warn_low == null) { + throw new JsonParseException( + parser, + "Mandatory field is missing in object.", + parser.getTokenLocation()); + } + if (states != null) { + throw new JsonParseException( + parser, + "Invalid field specified for numeric meta-data.", + parser.getTokenLocation()); + } + final NumberFormat format; + if (precision > 0 || (precision == 0 && honor_zero_precision)) { + format = NumberFormats.precisionFormat(precision); + } else { + format = NumberFormats.toStringFormat(); + } + return Display.of( + Range.of(display_low, display_high), + Range.of(alarm_low, alarm_high), + Range.of(warn_low, warn_high), + Range.undefined(), + units, + format); + } else { + throw new JsonParseException( + parser, + "Invalid meta-data type \"" + type + "\".", + parser.getTokenLocation()); + } + } + + private static AlarmSeverity readSeverity(final JsonParser parser) + throws IOException { + var token = parser.getCurrentToken(); + if (token != JsonToken.START_OBJECT) { + throw new JsonParseException( + parser, + "Expected START_OBJECT but got " + token, + parser.getTokenLocation()); + } + String field_name = null; + Boolean has_value = null; + String level_string = null; + while (true) { + token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token == JsonToken.END_OBJECT) { + break; + } + if (field_name == null) { + if (token != JsonToken.FIELD_NAME) { + throw new JsonParseException( + parser, + "Expected FIELD_NAME but got " + token, + parser.getTokenLocation()); + } + field_name = parser.getCurrentName(); + } else { + if (field_name.equals("level")) { + duplicateFieldIfNotNull(parser, field_name, level_string); + level_string = readStringValue(parser); + } else if (field_name.equals("hasValue")) { + // We do not use the hasValue field any longer (Phoebus’s + // VType system does not support it), but we still want to + // ensure that the data is well-formed. + duplicateFieldIfNotNull(parser, field_name, has_value); + has_value = readBooleanValue(parser); + } else { + throw new JsonParseException( + parser, + "Found unknown field \"" + field_name + "\".", + parser.getTokenLocation()); + } + field_name = null; + } + } + if (has_value == null || level_string == null) { + throw new JsonParseException( + parser, + "Mandatory field is missing in object.", + parser.getTokenLocation()); + } + return switch(level_string.toUpperCase(Locale.ROOT)) { + case "OK" -> AlarmSeverity.NONE; + case "MINOR" -> AlarmSeverity.MINOR; + case "MAJOR" -> AlarmSeverity.MAJOR; + case "INVALID" -> AlarmSeverity.INVALID; + default -> throw new JsonParseException( + parser, + "Unknown severity \"" + level_string + "\".", + parser.getTokenLocation()); + }; + } + + private static List readStringArray(final JsonParser parser) + throws IOException { + final var elements = new LinkedList(); + JsonToken token = parser.getCurrentToken(); + if (token != JsonToken.START_ARRAY) { + throw new JsonParseException( + parser, + "Expected START_ARRAY but got " + token, + parser.getTokenLocation()); + } + while (true) { + token = parser.nextToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token == JsonToken.END_ARRAY) { + break; + } + if (token == JsonToken.VALUE_STRING) { + elements.add(parser.getText()); + } else { + throw new JsonParseException( + parser, + "Expected VALUE_STRING but got " + token, + parser.getTokenLocation()); + } + } + return elements; + } + + private static String readStringValue(final JsonParser parser) + throws IOException { + final var token = parser.currentToken(); + if (token != JsonToken.VALUE_STRING) { + throw new JsonParseException( + parser, + "Expected VALUE_STRING but got " + token, + parser.getTokenLocation()); + } + return parser.getText(); + } + + private static double stringToSpecialDouble( + final String value, final JsonParser parser) throws IOException { + return switch (value.toLowerCase()) { + case "inf", "infinity", "+inf", "+infinity" -> ( + Double.POSITIVE_INFINITY); + case "-inf", "-infinity" -> Double.NEGATIVE_INFINITY; + case "nan" -> Double.NaN; + default -> throw new JsonParseException( + parser, + "String \"" + + value + + "\" does not qualify as a special double " + + "number.", + parser.getTokenLocation()); + }; + } + + private static ListDouble toListDouble(final ImmutableDoubleArray array) { + return new ListDouble() { + @Override + public double getDouble(int index) { + return array.get(index); + } + + @Override + public int size() { + return array.length(); + } + }; + } + + private static ListInteger toListInteger(final ImmutableIntArray array) { + return new ListInteger() { + @Override + public int getInt(int index) { + return array.get(index); + } + + @Override + public int size() { + return array.length(); + } + }; + } + + private static ListLong toListLong(final ImmutableLongArray array) { + return new ListLong() { + @Override + public long getLong(int index) { + return array.get(index); + } + + @Override + public int size() { + return array.length(); + } + }; + } + +} diff --git a/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonValueIterator.java b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonValueIterator.java new file mode 100644 index 0000000000..d6909a4d70 --- /dev/null +++ b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonValueIterator.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2013-2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.archive.reader.json.internal; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import org.epics.vtype.VType; +import org.phoebus.archive.reader.ValueIterator; +import org.phoebus.archive.reader.json.JsonArchiveReader; + +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + *

+ * Iterator for the {@link JsonArchiveReader}. This class is only intended for + * instantiation by that class. + *

+ * + *

+ * Like most iterators, instances of this class are not thread-safe. + * The one exception is the {@link #cancel()} method, which may be called by + * any thread. In order to implement cancellation in a thread-safe way, calling + * this method only results in a flag being set. The iterator is then closed + * the next time {@link #hasNext()} is called. + *

+ */ +public class JsonValueIterator implements ValueIterator { + + private volatile boolean canceled = false; + private final boolean honor_zero_precision; + private final Logger logger; + private VType next_value; + private Consumer on_close; + private JsonParser parser; + private final String request_url; + + /** + * Create an iterator reading samples from a JSON parser. The parser is + * not closed when this iterator is closed. However, the + * on_close function is called when the iterator is closed, so + * the calling code can pass a function that closes the parser. + * + * @param parser + * JSON parser from which samples are read. The iterator expects that the + * parser’s current token is the start of an array and reads samples until + * the current token is the corresponding end of an array. + * @param on_close + * function that is called when the iterator is closed. May be + * null. + * @param request_url + * URL that was used to retrieve the JSON data. This is only used when + * logging error messages. + * @param honor_zero_precision + * whether a precision of zero should result in no fractional digits being + * used in the number format of returned values (true) or a + * default number format should be used when the precision is zero + * (false). This only applies to floating-point values. + * Integer values always use a number format that does not include + * fractional digits. + * @throws IOException + * if initial operations on the JSON parser fail or if the JSON document + * is malformed. Errors that occur later do not result in an exception + * being thrown. Instead, the error is logged and {@link #hasNext()} + * returns false. + */ + public JsonValueIterator( + final JsonParser parser, + final Consumer on_close, + final String request_url, + final boolean honor_zero_precision) + throws IOException { + this.logger = Logger.getLogger(getClass().getName()); + this.honor_zero_precision = honor_zero_precision; + this.on_close = on_close; + this.parser = parser; + this.request_url = request_url; + final var token = this.parser.currentToken(); + if (token == null) { + throw new IOException("Unexpected end of stream."); + } + if (token != JsonToken.START_ARRAY) { + // The server response is malformed, so we cannot continue. + throw new JsonParseException( + parser, + "Expected START_ARRAY but got " + token, + parser.getTokenLocation()); + } + // We try to read the first sample. If that sample is malformed, the + // exception is raised before an iterator is even returned. If it is + // well-formed, there is a good chance that the remaining samples are + // going to be well-formed as well. + hasNextInternal(); + } + + /** + * Cancels this iterator. Subsequent calls to {@link #hasNext()} return + * false. For use by {@link JsonArchiveReader} only. + */ + public void cancel() { + this.canceled = true; + } + + @Override + public void close() { + // The parser field also serves as an indicator whether this iterator + // has been closed. If the parser is null, we know that the iterator + // has already been closed. + if (parser != null) { + // We have to call the on_close callback. Besides other things, + // this ensures that the parser is closed. + if (on_close != null) { + on_close.accept(this); + } + // Give up references that are not needed any longer. Setting the + // parser reference to null also has the effect that this iterator + // is marked as closed. + next_value = null; + on_close = null; + parser = null; + } + } + + @Override + public boolean hasNext() { + final boolean has_next; + // The hasNext method is not supposed to throw an exception, so when + // there is an exception, we log it and return false. + try { + has_next = hasNextInternal(); + } catch (IOException e) { + close(); + logger.log( + Level.SEVERE, + "Error while trying to read sample from server response " + + "for URL \"" + + request_url + + "\": " + + e.getMessage(), + e); + return false; + } + return has_next; + } + + @Override + public VType next() { + // We check whether next_value is null before calling hasNext(). If we + // called hasNext() directly, this method would throw an exception when + // cancel was called between calling hasNext() and next(). As cancel() + // may be called by a different thread, this could result in an + // unexpected NoSuchElementException being thrown. Therefore, we rather + // return the already retrieved element and close the iterator on the + // next call to hasNext(). + if (next_value == null && !hasNext()) { + // If the parser is null, the last call to hasNext() might have + // returned true, but close() has been called in between. + if (parser == null) { + throw new NoSuchElementException( + "This iterator has been closed, so no more elements " + + "available."); + } + // The last call to hasNext() must have returned false, so this + // call to next clearly is a violation of the API. + throw new NoSuchElementException( + "next() called while hasNext() == false."); + } + VType returnValue = next_value; + next_value = null; + return returnValue; + } + + private boolean fetchNext() throws IOException { + if (canceled) { + return false; + } + final var token = parser.nextToken(); + if (token == null) { + throw new IOException( + "Stream ended prematurely while trying to read next " + + "sample."); + } + if (token == JsonToken.END_ARRAY) { + // There should be no data after the end of the array. + final var next_token = parser.nextToken(); + if (next_token != null) { + throw new JsonParseException( + parser, + "Expected end-of-stream but found " + next_token + ".", + parser.getTokenLocation()); + } + return false; + } + next_value = JsonVTypeReader.readValue(parser, honor_zero_precision); + return true; + } + + private boolean hasNextInternal() throws IOException { + if (next_value != null) { + // We already fetched the next value. + return true; + } + if (parser == null) { + // The iterator has been closed. + return false; + } + if (fetchNext()) { + return true; + } + close(); + return false; + } + +} diff --git a/app/databrowser-json/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory b/app/databrowser-json/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory new file mode 100644 index 0000000000..35de6f7726 --- /dev/null +++ b/app/databrowser-json/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory @@ -0,0 +1 @@ +org.phoebus.archive.reader.json.JsonArchiveReaderFactory diff --git a/app/databrowser-json/src/main/resources/archive_reader_json_preferences.properties b/app/databrowser-json/src/main/resources/archive_reader_json_preferences.properties new file mode 100644 index 0000000000..359b0f1813 --- /dev/null +++ b/app/databrowser-json/src/main/resources/archive_reader_json_preferences.properties @@ -0,0 +1,5 @@ +# Shall a precision of zero for a floating-point value result in this value +# using a number format without fractional digits (true) or shall it be treated +# as an indication that the value should be rendered with a default number of +# fractional digits (false)? +honor_zero_precision=true diff --git a/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/HttpServerTestBase.java b/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/HttpServerTestBase.java new file mode 100644 index 0000000000..afa7d7437b --- /dev/null +++ b/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/HttpServerTestBase.java @@ -0,0 +1,211 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.archive.reader.json; + +import com.google.common.base.Splitter; +import com.google.common.collect.Maps; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Base class for tests that need an HTTP server. + */ +public class HttpServerTestBase { + + /** + * Information about an HTTP request. + * + * @param headers request headers. + * @param method request method. + * @param uri request URI. + */ + public record HttpRequest( + Headers headers, + String method, + URI uri) { + } + + private static HttpServer http_server; + + /** + * Parse a query string, returning the individual parameters. This function + * cannot handle query strings with duplicate parameters or parameters that + * do not have a value. + * + * @param query_string query string that shall be parsed. + * @return + * map mapping parameter names to their respective (decoded) values. + * @throws IllegalArgumentException + * if the query string is malformed, containers value-less parameters, or + * contains duplicate parameters. + */ + public static Map parseQueryString( + final String query_string) { + return Maps.transformValues( + Splitter + .on('&') + .withKeyValueSeparator('=') + .split(query_string), + (value) -> URLDecoder.decode(value, StandardCharsets.UTF_8)); + } + + /** + * Returns the port of the HTTP server that is started for the tests. Must + * only be called after {@link #startHttpServer()} and before + * {@link #stopHttpServer()}. + * + * @return TCP port where the HTTP server is listening. + */ + protected static int getHttpServerPort() { + return http_server.getAddress().getPort(); + } + + /** + * Start the HTTP server that is needed for the tests. Must be called + * before running the tests. + */ + @BeforeAll + protected static void startHttpServer() { + try { + http_server = HttpServer.create( + new InetSocketAddress( + InetAddress.getByName("127.0.0.1"), 0), + 0); + } catch (IOException e) { + throw new RuntimeException(e); + } + http_server.start(); + } + + /** + * Start the HTTP server that is needed for the tests. Must be called + * before running the tests. + */ + @AfterAll + protected static void stopHttpServer() { + http_server.stop(1); + http_server = null; + } + + /** + * Runs a function while providing an HTTP service for the archive + * information. This only works when the HTTP server has previously been + * started and has not been stopped yet. + * + * @param archive_info_json + * content that is returned by the HTTP handler that serves the path + * /archive/ below the base URL that is passed to + * request_func. + * @param request_func + * function that is called, passing the base URL of the provided archive + * service. + */ + protected static void withArchiveInfo( + final String archive_info_json, + final Consumer request_func) { + final HttpHandler info_handler = (http_exchange) -> { + if (!http_exchange.getRequestURI().getPath().equals("/archive/")) { + http_exchange.sendResponseHeaders(404, -1); + return; + } + http_exchange.getResponseHeaders().add( + "Content-Type", "application/json;charset=UTF-8"); + http_exchange.sendResponseHeaders(200, 0); + try (final var writer = new OutputStreamWriter( + http_exchange.getResponseBody(), StandardCharsets.UTF_8)) { + writer.write(archive_info_json); + } + }; + final var info_context = http_server.createContext( + "/archive", info_handler); + try { + request_func.accept("http://127.0.0.1:" + getHttpServerPort()); + } finally { + http_server.removeContext(info_context); + } + } + + /** + * Runs a function while providing an HTTP service providing archived + * samples. This only works when the HTTP server has previously been + * started and has not been stopped yet. In addition to providing samples, + * this function also provides rudimentary archive information for the + * specified archive_key. + * + * @param archive_key + * numerical key that identifies the archive that is provided. + * @param channel_name + * channel name for which samples are provided. + * @param samples_json + * content that is returned by the HTTP handler that serves the path + * /archive/<archive_key>/samples/<channel_name> + * below the base URL that is passed to the + * @param request_func + * function that is called, passing the base URL of the provided archive + * service. + * @return + * list with information about the requests that were made to the samples + * service. Requests to the archive-info service are not included. + */ + protected static List withSamples( + final int archive_key, + final String channel_name, + final String samples_json, + final Consumer request_func) { + final LinkedList http_requests = new LinkedList<>(); + final HttpHandler samples_handler = (http_exchange) -> { + http_requests.add(new HttpRequest( + http_exchange.getRequestHeaders(), + http_exchange.getRequestMethod(), + http_exchange.getRequestURI())); + http_exchange.getResponseHeaders().add( + "Content-Type", "application/json;charset=UTF-8"); + http_exchange.sendResponseHeaders(200, 0); + try (final var writer = new OutputStreamWriter( + http_exchange.getResponseBody(), StandardCharsets.UTF_8)) { + writer.write(samples_json); + } + }; + final var samples_path = + "/archive/" + archive_key + "/samples/" + channel_name; + final var samples_context = http_server.createContext( + samples_path, samples_handler); + final var archive_info_json = + "[{\"key\":" + + archive_key + + ", \"name\": \"Test\"" + + ", \"description\":\"Test description\"}]"; + // We also provide some rudimentary archive information in order to + // avoid a warning being logged when creating the JsonArchiveReader. + withArchiveInfo(archive_info_json, (base_url) -> { + try { + request_func.accept(base_url); + } finally { + http_server.removeContext(samples_context); + } + }); + return http_requests; + } + +} diff --git a/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/JsonArchiveReaderFactoryTest.java b/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/JsonArchiveReaderFactoryTest.java new file mode 100644 index 0000000000..84decc4663 --- /dev/null +++ b/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/JsonArchiveReaderFactoryTest.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.archive.reader.json; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the {@link JsonArchiveReaderFactory}. + */ +public class JsonArchiveReaderFactoryTest extends HttpServerTestBase { + + /** + * Tests the {@link JsonArchiveReaderFactory#createReader(String)} method. + */ + @Test + public void createReader() { + var archive_info_json = """ + [ { + "key" : 1, + "name" : "", + "description" : "Dummy archive" + } ] + """; + withArchiveInfo(archive_info_json, (base_url) -> { + try { + assertEquals( + "Dummy archive", + new JsonArchiveReaderFactory() + .createReader("json:" + base_url) + .getDescription()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + /** + * Tests the {@link JsonArchiveReaderFactory#getPrefix()} method. + */ + @Test + public void getPrefix() { + assertEquals("json", new JsonArchiveReaderFactory().getPrefix()); + } + +} diff --git a/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/JsonArchiveReaderTest.java b/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/JsonArchiveReaderTest.java new file mode 100644 index 0000000000..e0f44fce2e --- /dev/null +++ b/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/JsonArchiveReaderTest.java @@ -0,0 +1,1166 @@ +/******************************************************************************* + * Copyright (c) 2024 aquenos GmbH. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ + +package org.phoebus.archive.reader.json; + +import org.epics.util.stats.Range; +import org.epics.vtype.AlarmSeverity; +import org.epics.vtype.VDouble; +import org.epics.vtype.VDoubleArray; +import org.epics.vtype.VEnum; +import org.epics.vtype.VEnumArray; +import org.epics.vtype.VInt; +import org.epics.vtype.VIntArray; +import org.epics.vtype.VLong; +import org.epics.vtype.VLongArray; +import org.epics.vtype.VStatistics; +import org.epics.vtype.VString; +import org.epics.vtype.VStringArray; +import org.junit.jupiter.api.Test; +import org.phoebus.archive.reader.UnknownChannelException; + +import java.io.IOException; +import java.time.Instant; +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the {@link JsonArchiveReader}. + */ +public class JsonArchiveReaderTest extends HttpServerTestBase { + + /** + * Tests the {@link JsonArchiveReader#cancel()} method. + */ + @Test + public void cancel() { + final var channel_name = "some-channel"; + final var start = Instant.ofEpochMilli(123L); + final var end = Instant.ofEpochMilli(456L); + final var preferences = new JsonArchivePreferences(true); + // We need two samples, so that we can cancel the iterator after + // retrieving the first one. + final var samples_json = """ + [ { + "time" : 123457000001, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 3, + "units" : "mA", + "displayLow" : 0.0, + "displayHigh" : 300.0, + "warnLow" : 5.0, + "warnHigh" : 100.0, + "alarmLow" : 2.0, + "alarmHigh" : "NaN" + }, + "type" : "double", + "value" : [ 27.2, 48.3 ] + }, { + "time_modified" : 123457000002, + "severity" : { + "level" : "MAJOR", + "hasValue" : true + }, + "status" : "TEST_STATUS", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 3, + "units" : "mA", + "displayLow" : 0.0, + "displayHigh" : 300.0, + "warnLow" : 5.0, + "warnHigh" : 100.0, + "alarmLow" : 2.0, + "alarmHigh" : "NaN" + }, + "type" : "double", + "value" : [ 31.9 ] + } ] + """; + withSamples( + 1, channel_name, samples_json, (base_url) -> { + try ( + final var reader = new JsonArchiveReader( + "json:" + base_url, preferences); + final var iterator = reader.getRawValues( + channel_name, start, end + ) + ) { + // Retrieve the first sample. + iterator.next(); + // Cancel all iterators. + reader.cancel(); + // Now, hasNext() should return false. + assertFalse(iterator.hasNext()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + /** + * Tests creating a {@link JsonArchiveReader} with an archive key which + * does not specify a valid archive on the archive server. + */ + @Test + public void createWithInvalidArchiveKey() { + final var archive_info_json = """ + [ { + "key" : 2, + "name" : "Some name", + "description" : "Some description" + } ] + """; + withArchiveInfo(archive_info_json, (base_url) -> { + assertThrows(IllegalArgumentException.class, () -> { + new JsonArchiveReader( + "json:" + base_url, + new JsonArchivePreferences(true)); + }); + }); + } + + /** + * Tests creating a {@link JsonArchiveReader} with a base URL that is + * invalid (does not start with json:). + */ + @Test + public void createWithInvalidUrl() { + assertThrows(IllegalArgumentException.class, () -> { + new JsonArchiveReader( + "http://invalid.example.com", + new JsonArchivePreferences(true)); + }); + } + + /** + * Tests the {@link JsonArchiveReader#getDescription()} function. + */ + @Test + public void getDescription() { + var archive_info_json = """ + [ { + "key" : 1, + "name" : "Some name", + "description" : "Some description" + } ] + """; + final var preferences = new JsonArchivePreferences(true); + withArchiveInfo(archive_info_json, (base_url) -> { + try (final var reader = new JsonArchiveReader( + "json:" + base_url, preferences)) { + assertEquals( + "Some description", reader.getDescription()); + } + }); + archive_info_json = """ + [ { + "key" : 1, + "name" : "Some name", + "description" : "Another description" + }, { + "key" : 3, + "name" : "Some name", + "description" : "Yet another description" + } ] + """; + withArchiveInfo(archive_info_json, (base_url) -> { + try (final var reader = new JsonArchiveReader( + "json:" + base_url, preferences)) { + assertEquals( + "Another description", reader.getDescription()); + } + try (final var reader = new JsonArchiveReader( + "json:" + base_url + ";key=3", preferences)) { + assertEquals( + "Yet another description", + reader.getDescription()); + } + }); + } + + /** + * Tests the {@link + * JsonArchiveReader#getOptimizedValues(String, Instant, Instant, int)} + * function. + */ + @Test + public void getOptimizedValues() { + final var samples_json = """ + [ { + "time" : 123, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Interpolated", + "metaData" : { + "type" : "numeric", + "precision" : 1, + "units" : "V", + "displayLow" : -100.0, + "displayHigh" : 100.0, + "warnLow" : "NaN", + "warnHigh" : "NaN", + "alarmLow" : "NaN", + "alarmHigh" : "NaN" + }, + "type" : "minMaxDouble", + "value" : [ -5.0, -1.2 ], + "minimum" : -15.1, + "maximum" : 2.7 + }, { + "time" : 456, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Interpolated", + "metaData" : { + "type" : "numeric", + "precision" : 1, + "units" : "V", + "displayLow" : -100.0, + "displayHigh" : 100.0, + "warnLow" : "NaN", + "warnHigh" : "NaN", + "alarmLow" : "NaN", + "alarmHigh" : "NaN" + }, + "type" : "minMaxDouble", + "value" : [ 4.7 ], + "minimum" : -3.9, + "maximum" : 17.1 + } ] + """; + final var channel_name = "double-channel"; + final var start = Instant.ofEpochMilli(0L); + final var end = Instant.ofEpochMilli(1L); + final var preferences = new JsonArchivePreferences(true); + var requests = withSamples( + 7, channel_name, samples_json, (base_url) -> { + try ( + final var reader = new JsonArchiveReader( + "json:" + base_url + ";key=7", + preferences); + final var iterator = reader.getOptimizedValues( + channel_name, start, end, 10) + ) { + // Check the first sample. The statistics VType does + // not support arrays, so we expect a VDoubleArray. + final var double_array = (VDoubleArray) iterator.next(); + assertEquals(2, double_array.getData().size()); + assertEquals( + -5.0, double_array.getData().getDouble(0)); + assertEquals( + -1.2, double_array.getData().getDouble(1)); + assertEquals( + "NO_ALARM", double_array.getAlarm().getName()); + assertEquals( + AlarmSeverity.NONE, + double_array.getAlarm().getSeverity()); + assertEquals( + Range.undefined(), + double_array.getDisplay().getAlarmRange()); + assertEquals( + Range.undefined(), + double_array.getDisplay().getControlRange() + ); + assertEquals( + Range.of(-100.0, 100.0), + double_array.getDisplay().getDisplayRange()); + assertEquals( + Range.undefined(), + double_array.getDisplay().getWarningRange()); + assertEquals( + 1, + double_array + .getDisplay() + .getFormat() + .getMinimumFractionDigits()); + assertEquals( + 1, + double_array + .getDisplay() + .getFormat() + .getMaximumFractionDigits()); + assertEquals( + "V", + double_array.getDisplay().getUnit()); + assertEquals( + Instant.ofEpochSecond(0, 123L), + double_array.getTime().getTimestamp()); + // Check the second sample. + final var statistics = (VStatistics) iterator.next(); + assertEquals( + 4.7, + statistics.getAverage().doubleValue()); + assertEquals( + -3.9, + statistics.getMin().doubleValue()); + assertEquals( + 17.1, + statistics.getMax().doubleValue()); + assertEquals( + 0, + statistics.getNSamples().intValue()); + assertEquals( + Double.NaN, + statistics.getStdDev().doubleValue()); + assertEquals( + "NO_ALARM", + statistics.getAlarm().getName()); + assertEquals( + AlarmSeverity.NONE, + statistics.getAlarm().getSeverity()); + assertEquals( + Range.undefined(), + statistics.getDisplay().getAlarmRange()); + assertEquals( + Range.undefined(), + statistics.getDisplay().getControlRange() + ); + assertEquals( + Range.of(-100.0, 100.0), + statistics.getDisplay().getDisplayRange()); + assertEquals( + Range.undefined(), + statistics.getDisplay().getWarningRange()); + assertEquals( + 1, + statistics + .getDisplay() + .getFormat() + .getMinimumFractionDigits()); + assertEquals( + 1, + statistics + .getDisplay() + .getFormat() + .getMaximumFractionDigits()); + assertEquals( + "V", + statistics.getDisplay().getUnit()); + assertEquals( + Instant.ofEpochSecond(0L, 456L), + statistics.getTime().getTimestamp()); + // There should be no more samples. + assertFalse(iterator.hasNext()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + assertEquals(1, requests.size()); + final var request = requests.get(0); + assertEquals("GET", request.method()); + final var query_params = parseQueryString(request.uri().getQuery()); + assertEquals("0", query_params.get("start")); + assertEquals("1000000", query_params.get("end")); + assertEquals("10", query_params.get("count")); + } + + /** + * Tests the + * {@link JsonArchiveReader#getRawValues(String, Instant, Instant)} + * function with double samples. Of the tests for numeric values, this is + * the most detailed one. + */ + @Test + public void getRawValuesWithDoubleSamples() { + final var samples_json = """ + [ { + "time" : 123457000001, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 3, + "units" : "mA", + "displayLow" : 0.0, + "displayHigh" : 300.0, + "warnLow" : 5.0, + "warnHigh" : 100.0, + "alarmLow" : 2.0, + "alarmHigh" : "NaN" + }, + "type" : "double", + "value" : [ 27.2, 48.3 ] + }, { + "time" : 123457000002, + "severity" : { + "level" : "MAJOR", + "hasValue" : true + }, + "status" : "TEST_STATUS", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 3, + "units" : "mA", + "displayLow" : 0.0, + "displayHigh" : 300.0, + "warnLow" : 5.0, + "warnHigh" : 100.0, + "alarmLow" : 2.0, + "alarmHigh" : "NaN" + }, + "type" : "double", + "value" : [ 31.9 ] + } ] + """; + final var channel_name = "double-channel"; + final var start = Instant.ofEpochMilli(123456L); + final var end = Instant.ofEpochMilli(456789L); + final var preferences = new JsonArchivePreferences(true); + var requests = withSamples( + 2, channel_name, samples_json, (base_url) -> { + try ( + final var reader = new JsonArchiveReader( + "json:" + base_url + ";key=2", + preferences); + final var iterator = reader.getRawValues( + channel_name, start, end) + ) { + // Check the first sample. + assertTrue(iterator.hasNext()); + final var double_array = (VDoubleArray) iterator.next(); + assertEquals(2, double_array.getData().size()); + assertEquals( + 27.2, double_array.getData().getDouble(0)); + assertEquals( + 48.3, double_array.getData().getDouble(1)); + assertEquals( + "NO_ALARM", double_array.getAlarm().getName()); + assertEquals( + AlarmSeverity.NONE, + double_array.getAlarm().getSeverity()); + assertEquals( + 2.0, + double_array + .getDisplay().getAlarmRange().getMinimum()); + assertEquals( + Double.POSITIVE_INFINITY, + double_array + .getDisplay().getAlarmRange().getMaximum()); + assertEquals( + Range.undefined(), + double_array.getDisplay().getControlRange() + ); + assertEquals( + 0.0, + double_array + .getDisplay().getDisplayRange().getMinimum()); + assertEquals( + 300.0, + double_array + .getDisplay().getDisplayRange().getMaximum()); + assertEquals( + 5.0, + double_array + .getDisplay().getWarningRange().getMinimum()); + assertEquals( + 100.0, + double_array + .getDisplay().getWarningRange().getMaximum()); + assertEquals( + 3, + double_array + .getDisplay() + .getFormat() + .getMinimumFractionDigits()); + assertEquals( + 3, + double_array + .getDisplay() + .getFormat() + .getMaximumFractionDigits()); + assertEquals( + "mA", + double_array.getDisplay().getUnit()); + assertEquals( + Instant.ofEpochSecond(123L, 457000001L), + double_array.getTime().getTimestamp()); + // Check the second sample (only the parts that differ + // from the first on). + assertTrue(iterator.hasNext()); + final var double_scalar = (VDouble) iterator.next(); + assertEquals( + 31.9, double_scalar.getValue().doubleValue()); + assertEquals( + "TEST_STATUS", + double_scalar.getAlarm().getName()); + assertEquals( + AlarmSeverity.MAJOR, + double_scalar.getAlarm().getSeverity()); + assertEquals( + Instant.ofEpochSecond(123L, 457000002L), + double_scalar.getTime().getTimestamp()); + // There should be no more samples. + assertFalse(iterator.hasNext()); + assertThrows(NoSuchElementException.class, iterator::next); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + assertEquals(1, requests.size()); + final var request = requests.get(0); + assertEquals("GET", request.method()); + final var query_params = parseQueryString(request.uri().getQuery()); + assertEquals("123456000000", query_params.get("start")); + assertEquals("456789000000", query_params.get("end")); + assertFalse(query_params.containsKey("count")); + } + + /** + * Tests the + * {@link JsonArchiveReader#getRawValues(String, Instant, Instant)} method + * with enum samples. + */ + @Test + public void getRawValuesWithEnumSamples() { + final var samples_json = """ + [ { + "time" : 123000000009, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "enum", + "states" : [ "High", "Low" ] + }, + "type" : "enum", + "value" : [ 1, 0 ] + }, { + "time" : 124000000011, + "severity" : { + "level" : "INVALID", + "hasValue" : true + }, + "status" : "LINK", + "quality" : "Original", + "metaData" : { + "type" : "enum", + "states" : [ "High", "Low" ] + }, + "type" : "enum", + "value" : [ 1 ] + }, { + "time" : 124000000012, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "enum", + "states" : [ "High", "Low" ] + }, + "type" : "enum", + "value" : [ 1, 2 ] + }, { + "time" : 124000000013, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "enum", + "states" : [ "High", "Low" ] + }, + "type" : "enum", + "value" : [ -1 ] + } ] + """; + final var channel_name = "enum-channel"; + final var start = Instant.ofEpochMilli(4321L); + final var end = Instant.ofEpochMilli(999999L); + final var preferences = new JsonArchivePreferences(true); + var requests = withSamples( + 1, channel_name, samples_json, (base_url) -> { + try ( + final var reader = new JsonArchiveReader( + "json:" + base_url, preferences); + final var iterator = reader.getRawValues( + channel_name, start, end) + ) { + // Check the first sample. + final var enum_array = (VEnumArray) iterator.next(); + assertEquals(2, enum_array.getIndexes().size()); + assertEquals(1, enum_array.getIndexes().getInt(0)); + assertEquals(0, enum_array.getIndexes().getInt(1)); + assertEquals( + "NO_ALARM", + enum_array.getAlarm().getName()); + assertEquals( + AlarmSeverity.NONE, + enum_array.getAlarm().getSeverity()); + assertEquals( + Arrays.asList("High", "Low"), + enum_array.getDisplay().getChoices()); + assertEquals( + Instant.ofEpochSecond(123L, 9L), + enum_array.getTime().getTimestamp()); + // Check the second sample (only the parts that differ + // from the first on). + final var enum_scalar = (VEnum) iterator.next(); + assertEquals(1, enum_scalar.getIndex()); + assertEquals( + "LINK", + enum_scalar.getAlarm().getName()); + assertEquals( + AlarmSeverity.INVALID, + enum_scalar.getAlarm().getSeverity()); + assertEquals( + Instant.ofEpochSecond(124L, 11L), + enum_scalar.getTime().getTimestamp()); + // Check the third sample. As this sample contains a + // value for which there is no label, we expect a + // VIntArray instead of a VEnumArray. + final var int_array = (VIntArray) iterator.next(); + assertEquals(2, int_array.getData().size()); + assertEquals(1, int_array.getData().getInt(0)); + assertEquals(2, int_array.getData().getInt(1)); + assertEquals( + 0, + int_array + .getDisplay() + .getFormat() + .getMaximumFractionDigits()); + // Check the fourth sample. As this sample contains a + // value for which there is no label, we expect a + // VInt instead of a VEnum. + final var int_scalar = (VInt) iterator.next(); + assertEquals(-1, int_scalar.getValue().intValue()); + // There should be no more samples. + assertFalse(iterator.hasNext()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + assertEquals(1, requests.size()); + final var request = requests.get(0); + assertEquals("GET", request.method()); + final var query_params = parseQueryString(request.uri().getQuery()); + assertEquals("4321000000", query_params.get("start")); + assertEquals("999999000000", query_params.get("end")); + assertFalse(query_params.containsKey("count")); + } + + /** + * Tests the + * {@link JsonArchiveReader#getRawValues(String, Instant, Instant)} method + * with long samples. + */ + @Test + public void getRawValuesWithLongSamples() { + final var samples_json = """ + [ { + "time" : 456000000001, + "severity" : { + "level" : "MAJOR", + "hasValue" : true + }, + "status" : "SOME_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 0, + "units" : "pcs.", + "displayLow" : 1.0, + "displayHigh" : 100.0, + "warnLow" : 0.0, + "warnHigh" : 0.0, + "alarmLow" : 0.0, + "alarmHigh" : 0.0 + }, + "type" : "long", + "value" : [ 14, 2 ] + }, { + "time" : 456000000002, + "severity" : { + "level" : "INVALID", + "hasValue" : true + }, + "status" : "INVALID_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 0, + "units" : "pcs.", + "displayLow" : 1.0, + "displayHigh" : 100.0, + "warnLow" : 0.0, + "warnHigh" : 0.0, + "alarmLow" : 0.0, + "alarmHigh" : 0.0 + }, + "type" : "long", + "value" : [ 19 ] + } ] + """; + final var channel_name = "long-channel"; + final var start = Instant.ofEpochMilli(4321L); + final var end = Instant.ofEpochMilli(999999L); + final var preferences = new JsonArchivePreferences(true); + var requests = withSamples( + 1, channel_name, samples_json, (base_url) -> { + try ( + final var reader = new JsonArchiveReader( + "json:" + base_url, preferences); + final var iterator = reader.getRawValues( + channel_name, start, end) + ) { + + // Check the first sample. We do not check the limits + // because the code parsing them is identical the same + // for the double samples, and we already check them + // there. + final var long_array = (VLongArray) iterator.next(); + assertEquals(2, long_array.getData().size()); + assertEquals(14, long_array.getData().getLong(0)); + assertEquals(2, long_array.getData().getLong(1)); + assertEquals( + "SOME_ALARM", + long_array.getAlarm().getName()); + assertEquals( + AlarmSeverity.MAJOR, + long_array.getAlarm().getSeverity()); + assertEquals( + 0, + long_array + .getDisplay() + .getFormat() + .getMaximumFractionDigits()); + assertEquals( + "pcs.", + long_array.getDisplay().getUnit()); + assertEquals( + Instant.ofEpochSecond(456L, 1L), + long_array.getTime().getTimestamp()); + // Check the second sample (only the parts that differ + // from the first on). + final var long_scalar = (VLong) iterator.next(); + assertEquals(19, long_scalar.getValue().longValue()); + assertEquals( + "INVALID_ALARM", + long_scalar.getAlarm().getName()); + assertEquals( + AlarmSeverity.INVALID, + long_scalar.getAlarm().getSeverity()); + assertEquals( + Instant.ofEpochSecond(456L, 2L), + long_scalar.getTime().getTimestamp()); + // There should be no more samples. + assertFalse(iterator.hasNext()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + assertEquals(1, requests.size()); + final var request = requests.get(0); + assertEquals("GET", request.method()); + final var query_params = parseQueryString(request.uri().getQuery()); + assertEquals("4321000000", query_params.get("start")); + assertEquals("999999000000", query_params.get("end")); + assertFalse(query_params.containsKey("count")); + } + + /** + * Tests the + * {@link JsonArchiveReader#getRawValues(String, Instant, Instant)} method + * with a malformed response. + */ + @Test + public void getRawValuesWithMalformedResponse() { + final var channel_name = "some-channel"; + final var start = Instant.ofEpochMilli(123L); + final var end = Instant.ofEpochMilli(456L); + final var preferences = new JsonArchivePreferences(true); + // First, we test that we get an immediate exception if the first + // sample is malformed. + var samples_json = """ + [ { + "time_modified" : 123457000001, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 3, + "units" : "mA", + "displayLow" : 0.0, + "displayHigh" : 300.0, + "warnLow" : 5.0, + "warnHigh" : 100.0, + "alarmLow" : 2.0, + "alarmHigh" : "NaN" + }, + "type" : "double", + "value" : [ 27.2, 48.3 ] + } ] + """; + + withSamples( + 1, channel_name, samples_json, (base_url) -> { + try (final var reader = new JsonArchiveReader( + "json:" + base_url, preferences)) { + assertThrows(IOException.class, () -> { + reader.getRawValues( + channel_name, start, end); + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + // Second, we test that we do not get an exception when a subsequent + // sample is malformed. Instead, we expect hasNext() to return false. + // As a side effect, an error message should be logged. but we cannot + // test this easily. + samples_json = """ + [ { + "time" : 123457000001, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 3, + "units" : "mA", + "displayLow" : 0.0, + "displayHigh" : 300.0, + "warnLow" : 5.0, + "warnHigh" : 100.0, + "alarmLow" : 2.0, + "alarmHigh" : "NaN" + }, + "type" : "double", + "value" : [ 27.2, 48.3 ] + }, { + "time_modified" : 123457000002, + "severity" : { + "level" : "MAJOR", + "hasValue" : true + }, + "status" : "TEST_STATUS", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 3, + "units" : "mA", + "displayLow" : 0.0, + "displayHigh" : 300.0, + "warnLow" : 5.0, + "warnHigh" : 100.0, + "alarmLow" : 2.0, + "alarmHigh" : "NaN" + }, + "type" : "double", + "value" : [ 31.9 ] + } ] + """; + withSamples(1, channel_name, samples_json, (base_url) -> { + try ( + final var reader = new JsonArchiveReader( + "json:" + base_url, preferences); + final var iterator = reader.getRawValues( + channel_name, start, end) + ) { + // We should be able to retrieve the first sample, but + // not the second one. + assertTrue(iterator.hasNext()); + iterator.next(); + // Before calling hasNext() the second time, we suppress error + // logging for the iterator. + final var iterator_logger = Logger.getLogger( + iterator.getClass().getName()); + final var log_level = iterator_logger.getLevel(); + iterator_logger.setLevel(Level.OFF); + try { + assertFalse(iterator.hasNext()); + } finally { + iterator_logger.setLevel(log_level); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + /** + * Tests the + * {@link JsonArchiveReader#getRawValues(String, Instant, Instant)} method + * with no samples. + */ + @Test + public void getRawValuesWithNoSamples() { + final var channel_name = "empty-channel"; + final var start = Instant.ofEpochMilli(456L); + final var end = Instant.ofEpochMilli(789L); + final var preferences = new JsonArchivePreferences(true); + var requests = withSamples( + 1, channel_name, "[]", (base_url) -> { + try ( + final var reader = new JsonArchiveReader( + "json:" + base_url, preferences); + final var iterator = reader.getRawValues( + channel_name, start, end) + ) { + // The iterator should be empty. + assertFalse(iterator.hasNext()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + assertEquals(1, requests.size()); + final var request = requests.get(0); + assertEquals("GET", request.method()); + final var query_params = parseQueryString(request.uri().getQuery()); + assertEquals("456000000", query_params.get("start")); + assertEquals("789000000", query_params.get("end")); + assertFalse(query_params.containsKey("count")); + } + + /** + * Tests the + * {@link JsonArchiveReader#getRawValues(String, Instant, Instant)} method + * with string samples. + */ + @Test + public void getRawValuesWithStringSamples() { + final var samples_json = """ + [ { + "time" : 123000000001, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Original", + "type" : "string", + "value" : [ "abc", "def", "ghi" ] + }, { + "time" : 123000000002, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Original", + "type" : "string", + "value" : [ "123" ] + } ] + """; + final var channel_name = "long-channel"; + final var start = Instant.ofEpochMilli(0L); + final var end = Instant.ofEpochMilli(999000L); + final var preferences = new JsonArchivePreferences(true); + var requests = withSamples( + 1, channel_name, samples_json, (base_url) -> { + try ( + final var reader = new JsonArchiveReader( + "json:" + base_url, preferences); + final var iterator = reader.getRawValues( + channel_name, start, end) + ) { + // Check the first sample. + final var string_array = (VStringArray) iterator.next(); + assertEquals(3, string_array.getData().size()); + assertEquals("abc", string_array.getData().get(0)); + assertEquals("def", string_array.getData().get(1)); + assertEquals("ghi", string_array.getData().get(2)); + assertEquals( + Instant.ofEpochSecond(123L, 1L), + string_array.getTime().getTimestamp()); + // Check the second sample. + final var string_scalar = (VString) iterator.next(); + assertEquals("123", string_scalar.getValue()); + assertEquals( + Instant.ofEpochSecond(123L, 2L), + string_scalar.getTime().getTimestamp()); + // There should be no more samples. + assertFalse(iterator.hasNext()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + assertEquals(1, requests.size()); + final var request = requests.get(0); + assertEquals("GET", request.method()); + final var query_params = parseQueryString(request.uri().getQuery()); + assertEquals("0", query_params.get("start")); + assertEquals("999000000000", query_params.get("end")); + assertFalse(query_params.containsKey("count")); + } + + /** + * Tests the + * {@link JsonArchiveReader#getRawValues(String, Instant, Instant)} method + * with a channel name that is not known by the server. + */ + @Test + public void getRawValuesWithUnknownChannel() { + final var start = Instant.ofEpochMilli(123L); + final var end = Instant.ofEpochMilli(456L); + final var preferences = new JsonArchivePreferences(true); + withSamples(1, "some-channel", "", (base_url) -> { + try (final var reader = new JsonArchiveReader( + "json:" + base_url, preferences)) { + assertThrows(UnknownChannelException.class, () -> { + reader.getRawValues("another-channel", start, end); + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + /** + * Tests the {@link JsonArchivePreferences#honor_zero_precision()} flag. + */ + @Test + public void honorZeroPrecision() { + final var samples_json = """ + [ { + "time" : 123457000001, + "severity" : { + "level" : "OK", + "hasValue" : true + }, + "status" : "NO_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 0, + "units" : "mA", + "displayLow" : 0.0, + "displayHigh" : 300.0, + "warnLow" : 5.0, + "warnHigh" : 100.0, + "alarmLow" : 2.0, + "alarmHigh" : "NaN" + }, + "type" : "double", + "value" : [ 1.5 ] + }, { + "time" : 456000000002, + "severity" : { + "level" : "INVALID", + "hasValue" : true + }, + "status" : "INVALID_ALARM", + "quality" : "Original", + "metaData" : { + "type" : "numeric", + "precision" : 0, + "units" : "pcs.", + "displayLow" : 1.0, + "displayHigh" : 100.0, + "warnLow" : 0.0, + "warnHigh" : 0.0, + "alarmLow" : 0.0, + "alarmHigh" : 0.0 + }, + "type" : "long", + "value" : [ 19 ] + } ] + """; + final var channel_name = "double-channel"; + final var start = Instant.ofEpochMilli(123456L); + final var end = Instant.ofEpochMilli(456789L); + // When honor_zero_precision is set, a sample with a precision of zero + // should have a number format that does not include fractional digits. + withSamples( + 1, channel_name, samples_json, (base_url) -> { + final var preferences = new JsonArchivePreferences(true); + try ( + final var reader = new JsonArchiveReader( + "json:" + base_url, preferences); + final var iterator = reader.getRawValues( + channel_name, start, end) + ) { + final var double_scalar = (VDouble) iterator.next(); + assertEquals( + 0, + double_scalar + .getDisplay() + .getFormat() + .getMaximumFractionDigits()); + final var long_scalar = (VLong) iterator.next(); + assertEquals( + 0, + long_scalar + .getDisplay() + .getFormat() + .getMaximumFractionDigits()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + // When honor_zero_precision is clear, a sample with a precision of + // zero should have a number format that includes fractional digits, + // but only for double samples and not for long samples. + withSamples( + 1, channel_name, samples_json, (base_url) -> { + final var preferences = new JsonArchivePreferences(false); + try ( + final var reader = new JsonArchiveReader( + "json:" + base_url, preferences); + final var iterator = reader.getRawValues( + channel_name, start, end) + ) { + final var double_scalar = (VDouble) iterator.next(); + assertNotEquals( + 0, + double_scalar + .getDisplay() + .getFormat() + .getMaximumFractionDigits()); + final var long_scalar = (VLong) iterator.next(); + assertEquals( + 0, + long_scalar + .getDisplay() + .getFormat() + .getMaximumFractionDigits()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + +} diff --git a/app/pom.xml b/app/pom.xml index 3771508034..ff5bb5eee0 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -20,6 +20,7 @@ logbook rtplot databrowser + databrowser-json databrowser-timescale display alarm diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index 694c33c2e0..66b9b5b2e5 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -116,6 +116,11 @@ app-databrowser 4.7.4-SNAPSHOT + + org.phoebus + app-databrowser-json + 4.7.4-SNAPSHOT + org.phoebus app-databrowser-timescale From 065c3476c85140436598a56fe007f849cb3bc6f9 Mon Sep 17 00:00:00 2001 From: kasemir Date: Tue, 27 Feb 2024 12:45:13 -0500 Subject: [PATCH 26/92] Trace legend: Fix layout when traces are hidden Port of update from SWT implementation https://github.com/ControlSystemStudio/cs-studio/pull/2725 --- .../org/csstudio/javafx/rtplot/internal/LegendPart.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LegendPart.java b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LegendPart.java index bc186b0f38..9887e96aae 100644 --- a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LegendPart.java +++ b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/LegendPart.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015-2016 Oak Ridge National Laboratory. + * Copyright (c) 2015-2024 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -72,7 +72,7 @@ public int getDesiredHeight(final Graphics2D gc, final int bounds_width, gc.setFont(orig_font); - final int items = traces.size(); + final int items = (int) traces.stream().filter(Trace::isVisible).count(); final int items_per_row = Math.max(1, bounds_width / grid_x); // Round down, counting full items final int rows = (items + items_per_row-1) / items_per_row; // Round up return rows * grid_y; @@ -84,9 +84,8 @@ private void computeGrid(final Graphics2D gc, final List> traces){ int max_width = 1; // Start with 1 pixel to avoid later div-by-0 for (Trace trace : traces) { - if (!trace.isVisible()) { + if (!trace.isVisible()) continue; - } final int width = metrics.stringWidth(trace.getLabel()); if (width > max_width) max_width = width; @@ -118,7 +117,7 @@ public void paint(final Graphics2D gc, final Font font, // Need to compute grid since labels may have changed in case unit string was added when PV connects. computeGrid(gc, traces); - + super.paint(gc); int x = bounds.x, y = bounds.y + base_offset; From c40eb633c9ff26259c9d1e12bc6e3536325463d6 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Tue, 27 Feb 2024 13:46:32 -0500 Subject: [PATCH 27/92] fix the kafka download link for 3.6.x --- app/alarm/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/alarm/Readme.md b/app/alarm/Readme.md index 407259877f..1d61dc27bd 100644 --- a/app/alarm/Readme.md +++ b/app/alarm/Readme.md @@ -25,7 +25,7 @@ kafka in `/opt/kafka`. cd examples # Use wget, 'curl -O', or web browser to fetch a recent version of kafka - wget https://downloads.apache.org/kafka/3.3.1/kafka_2.13-3.6.1.tgz + wget https://downloads.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgz tar vzxf kafka_2.13-3.6.1.tgz ln -s kafka_2.13-3.6.1 kafka From 9fd75f2b72b28223c612aa653ffc3dec7636dae9 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Fri, 1 Mar 2024 14:10:22 +0100 Subject: [PATCH 28/92] Fix NPE when creating log entry --- .../java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java index a7bb74de53..c50ca88d73 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java @@ -74,6 +74,10 @@ public LogClient getClient() { * @param logEntry A new or updated {@link LogEntry} */ public void handleLogEntryChange(LogEntry logEntry){ - logEntryTable.logEntryChanged(logEntry); + // At this point the logEntryTable might be null, e.g. if log entry editor is launched + // before first launch of log entry table app. + if(logEntryTable != null){ + logEntryTable.logEntryChanged(logEntry); + } } } From a777e8e3959d3cf5bb32c510244b64ba312cc331 Mon Sep 17 00:00:00 2001 From: Malte Gotz Date: Fri, 1 Mar 2024 15:02:30 +0100 Subject: [PATCH 29/92] handle file-url in openResourceStream --- .../csstudio/display/builder/model/util/ModelResourceUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/util/ModelResourceUtil.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/util/ModelResourceUtil.java index a79e8b6eac..3cc941179f 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/util/ModelResourceUtil.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/util/ModelResourceUtil.java @@ -421,7 +421,7 @@ public static InputStream openResourceStream(final String resource_name) throws // final long milli = Math.round(1000 + Math.random()*4000); // Thread.sleep(milli); // } - if (resource_name.startsWith("http")) + if (resource_name.startsWith("http") || resource_name.startsWith("file:/")) return openURL(resource_name); // Handle legacy RCP URL From 8cf2c5ec49931c2eeba9b8b9512525b376f7df9e Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 11 Mar 2024 15:55:21 -0400 Subject: [PATCH 30/92] Prepare for a Annuncitor based on SPI --- .../alarm/freetts/annunciator}/VoiceDemo.java | 2 +- .../alarm/ui/annunciator/Annunciator.java | 47 ++++--------------- 2 files changed, 9 insertions(+), 40 deletions(-) rename app/alarm/{ui/src/test/java/org/phoebus/applications/alarm => freetts-annunciator/src/test/java/org/phoebus/applications/alarm/freetts/annunciator}/VoiceDemo.java (94%) diff --git a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/VoiceDemo.java b/app/alarm/freetts-annunciator/src/test/java/org/phoebus/applications/alarm/freetts/annunciator/VoiceDemo.java similarity index 94% rename from app/alarm/ui/src/test/java/org/phoebus/applications/alarm/VoiceDemo.java rename to app/alarm/freetts-annunciator/src/test/java/org/phoebus/applications/alarm/freetts/annunciator/VoiceDemo.java index 49ba15fd7b..43e192e704 100644 --- a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/VoiceDemo.java +++ b/app/alarm/freetts-annunciator/src/test/java/org/phoebus/applications/alarm/freetts/annunciator/VoiceDemo.java @@ -5,7 +5,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ -package org.phoebus.applications.alarm; +package org.phoebus.applications.alarm.freetts.annunciator; import com.sun.speech.freetts.Voice; import com.sun.speech.freetts.VoiceManager; diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java index 2cda2a5163..e2c1498615 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java @@ -1,51 +1,20 @@ -/******************************************************************************* - * Copyright (c) 2018 Oak Ridge National Laboratory. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ package org.phoebus.applications.alarm.ui.annunciator; -import com.sun.speech.freetts.Voice; -import com.sun.speech.freetts.VoiceManager; - -/** - * Annunciator class. Uses freeTTS to annunciate passed messages. - * @author Evan Smith - */ -@SuppressWarnings("nls") -public class Annunciator +public interface Annunciator { - private final VoiceManager voiceManager; - private final Voice voice; - private static final String voice_name = "kevin16"; - - /** Constructor */ - public Annunciator() - { - // Define the voices directory. - System.setProperty("freetts.voices", "com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory"); - voiceManager = VoiceManager.getInstance(); - voice = voiceManager.getVoice(voice_name); - voice.allocate(); - } + /** + * Initialize the Annunciator resources. + */ + default void initialize(){} /** * Annunciate the message. Only returns once speaking finishes. * @param message Message text */ - public void speak(final String message) - { - if (null != message) - voice.speak(message); - } + void speak(final String message); /** - * Deallocates the voice. + * Release resources that need to be cleaned on shutdown. */ - public void shutdown() - { - voice.deallocate(); - } + default void shutdown(){} } From c3016c8062b30beedf6fac389b593e87cc37d991 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 11 Mar 2024 15:56:57 -0400 Subject: [PATCH 31/92] Cleanup dependencies, freetts needed by impl of annunciator only --- app/alarm/ui/pom.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/alarm/ui/pom.xml b/app/alarm/ui/pom.xml index d36aa6b31f..de1c177062 100644 --- a/app/alarm/ui/pom.xml +++ b/app/alarm/ui/pom.xml @@ -44,10 +44,6 @@ app-alarm-model 4.7.4-SNAPSHOT - - net.sf.sociaal - freetts - 1.2.2 - + From 114eaa3e572b236a277c970f8f2af8145461ed2a Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 11 Mar 2024 16:17:28 -0400 Subject: [PATCH 32/92] Create a new module for an annunciator based on freetts --- app/alarm/freetts-annunciator/.classpath | 21 ++++++++ app/alarm/freetts-annunciator/build.xml | 20 +++++++ app/alarm/freetts-annunciator/pom.xml | 32 +++++++++++ .../annunciator/FreeTTSAnnunciator.java | 54 +++++++++++++++++++ ...lications.alarm.ui.annunciator.Annunciator | 1 + app/alarm/pom.xml | 1 + 6 files changed, 129 insertions(+) create mode 100644 app/alarm/freetts-annunciator/.classpath create mode 100644 app/alarm/freetts-annunciator/build.xml create mode 100644 app/alarm/freetts-annunciator/pom.xml create mode 100644 app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java create mode 100644 app/alarm/freetts-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator diff --git a/app/alarm/freetts-annunciator/.classpath b/app/alarm/freetts-annunciator/.classpath new file mode 100644 index 0000000000..948256b10b --- /dev/null +++ b/app/alarm/freetts-annunciator/.classpath @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/alarm/freetts-annunciator/build.xml b/app/alarm/freetts-annunciator/build.xml new file mode 100644 index 0000000000..8f2787706f --- /dev/null +++ b/app/alarm/freetts-annunciator/build.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/alarm/freetts-annunciator/pom.xml b/app/alarm/freetts-annunciator/pom.xml new file mode 100644 index 0000000000..58b9bbd018 --- /dev/null +++ b/app/alarm/freetts-annunciator/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + org.phoebus + app-alarm + 4.7.4-SNAPSHOT + + app-alarm-freetts-annunciator + + + 17 + 17 + + + + + net.sf.sociaal + freetts + 1.2.2 + + + org.phoebus + app-alarm-ui + 4.7.4-SNAPSHOT + compile + + + + \ No newline at end of file diff --git a/app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java b/app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java new file mode 100644 index 0000000000..74478e1912 --- /dev/null +++ b/app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2018 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.phoebus.applications.alarm.freetts.annunciator; + +import com.sun.speech.freetts.Voice; +import com.sun.speech.freetts.VoiceManager; +import org.phoebus.applications.alarm.ui.annunciator.Annunciator; + +/** + * Annunciator class. Uses freeTTS to annunciate passed messages. + * @author Evan Smith, Kunal Shroff + */ +@SuppressWarnings("nls") +public class FreeTTSAnnunciator implements Annunciator +{ + private final VoiceManager voiceManager; + private final Voice voice; + private static final String voice_name = "kevin16"; + + /** Constructor */ + public FreeTTSAnnunciator() + { + // Define the voices directory. + System.setProperty("freetts.voices", "com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory"); + voiceManager = VoiceManager.getInstance(); + voice = voiceManager.getVoice(voice_name); + voice.allocate(); + } + + /** + * Annunciate the message. Only returns once speaking finishes. + * @param message Message text + */ + @Override + public void speak(final String message) + { + if (null != message) + voice.speak(message); + } + + /** + * Deallocates the voice. + */ + @Override + public void shutdown() + { + voice.deallocate(); + } +} diff --git a/app/alarm/freetts-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator b/app/alarm/freetts-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator new file mode 100644 index 0000000000..06c0cfc350 --- /dev/null +++ b/app/alarm/freetts-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator @@ -0,0 +1 @@ +org.phoebus.applications.alarm.freetts.annunciator.FreeTTSAnnunciator diff --git a/app/alarm/pom.xml b/app/alarm/pom.xml index a5b1307493..78f8ff6551 100644 --- a/app/alarm/pom.xml +++ b/app/alarm/pom.xml @@ -12,5 +12,6 @@ ui logging-ui datasource + freetts-annunciator From fc0f8c71f8cce48a2dd80214d2c9a2b15b0b3081 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 11 Mar 2024 16:18:20 -0400 Subject: [PATCH 33/92] Use the annunciator SPI --- .../ui/annunciator/AnnunciatorController.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java index 22a9a12751..c99fab65a1 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java @@ -10,11 +10,13 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.ServiceLoader; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Consumer; import org.phoebus.applications.alarm.model.SeverityLevel; +import org.phoebus.framework.adapter.AdapterFactory; /** Controller class for an annunciator. * @@ -44,7 +46,8 @@ public class AnnunciatorController private final BlockingQueue to_annunciate = new LinkedBlockingQueue<>(); - private final Annunciator annunciator = new Annunciator(); + private static ServiceLoader loader; + private final Thread process_thread = new Thread(this::processMessages, "Annunciator"); // Muted _IS_ read from multiple threads, so it should always be fetched from memory. @@ -81,6 +84,8 @@ private void processMessages() { final List batch = new ArrayList<>(); + loader = ServiceLoader.load(Annunciator.class); + // Process new messages until receiving LAST_MESSAGE while (true) { @@ -115,7 +120,9 @@ private void processMessages() { addToTable.accept(message); if (! muted) - annunciator.speak(message.message); + loader.stream().forEach(annunciatorProvider -> { + annunciatorProvider.get().speak(message.message); + }); } } else @@ -130,7 +137,9 @@ private void processMessages() { // Annunciate if marked as stand out. addToTable.accept(message); if (! muted) - annunciator.speak(message.message); + { + loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().speak(message.message)); + } } else { // Increment count of non stand out messages. @@ -144,7 +153,9 @@ private void processMessages() final AnnunciatorMessage message = new AnnunciatorMessage(false, null, earliest, "There are " + flurry + " new messages"); addToTable.accept(message); if (! muted) - annunciator.speak(message.message); + { + loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().speak(message.message)); + } } } } @@ -161,6 +172,7 @@ public void shutdown() throws InterruptedException process_thread.join(2000); // Deallocate the annunciator's voice. - annunciator.shutdown(); + loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().shutdown()); + } } From 48f4e89921c68871936b0408dc04e09c4d1ba931 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Tue, 12 Mar 2024 10:00:01 -0400 Subject: [PATCH 34/92] Add freetts annunciator as a optional dep to the common product --- phoebus-product/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index 66b9b5b2e5..4815bb3803 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -186,6 +186,12 @@ app-alarm-ui 4.7.4-SNAPSHOT + + org.phoebus + app-alarm-freetts-annunciator + 4.7.4-SNAPSHOT + true + org.phoebus app-alarm-logging-ui From 0edf629b66c42ed5f44d4bbed1db840d4fb6f1d4 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Tue, 12 Mar 2024 15:02:27 -0400 Subject: [PATCH 35/92] Annunciator interface changes: Remove initialize, pass the entire msg --- .../applications/alarm/ui/annunciator/Annunciator.java | 6 +----- .../alarm/ui/annunciator/AnnunciatorController.java | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java index e2c1498615..8a4beff8d2 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java @@ -2,16 +2,12 @@ public interface Annunciator { - /** - * Initialize the Annunciator resources. - */ - default void initialize(){} /** * Annunciate the message. Only returns once speaking finishes. * @param message Message text */ - void speak(final String message); + void speak(final AnnunciatorMessage message); /** * Release resources that need to be cleaned on shutdown. diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java index c99fab65a1..8904afbc1f 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java @@ -121,7 +121,7 @@ private void processMessages() addToTable.accept(message); if (! muted) loader.stream().forEach(annunciatorProvider -> { - annunciatorProvider.get().speak(message.message); + annunciatorProvider.get().speak(message); }); } } @@ -138,7 +138,7 @@ private void processMessages() addToTable.accept(message); if (! muted) { - loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().speak(message.message)); + loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().speak(message)); } } else @@ -154,7 +154,7 @@ private void processMessages() addToTable.accept(message); if (! muted) { - loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().speak(message.message)); + loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().speak(message)); } } } From 50a648a2dbaf82e90bc2715e87875ded75456127 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Tue, 12 Mar 2024 15:03:02 -0400 Subject: [PATCH 36/92] Use the new Annunciator interface --- .../alarm/freetts/annunciator/FreeTTSAnnunciator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java b/app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java index 74478e1912..cc5d6674f8 100644 --- a/app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java +++ b/app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java @@ -10,6 +10,7 @@ import com.sun.speech.freetts.Voice; import com.sun.speech.freetts.VoiceManager; import org.phoebus.applications.alarm.ui.annunciator.Annunciator; +import org.phoebus.applications.alarm.ui.annunciator.AnnunciatorMessage; /** * Annunciator class. Uses freeTTS to annunciate passed messages. @@ -37,10 +38,10 @@ public FreeTTSAnnunciator() * @param message Message text */ @Override - public void speak(final String message) + public void speak(final AnnunciatorMessage message) { if (null != message) - voice.speak(message); + voice.speak(message.message); } /** From a3cad8d05fef0b7f618f612bd6f9d90458d702c8 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 13 Mar 2024 13:14:17 +0100 Subject: [PATCH 37/92] CSSTUDIO-2228 Add the widget property "Min/Max tolerance" to the Linear Meter. --- .../LinearMeterRepresentation.java | 8 ++++++++ .../linearmeter/LinearMeterWidget.java | 11 ++++++++++ .../widgets/linearmeter/RTLinearMeter.java | 20 +++++++++++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterRepresentation.java b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterRepresentation.java index a7e9aca9ed..44f9fe0389 100644 --- a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterRepresentation.java +++ b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterRepresentation.java @@ -61,11 +61,14 @@ public Pane createJFXNode() hiHi = model_widget.propLevelHiHi().getValue(); } + double minMaxTolerance = model_widget.propMinMaxTolerance().getValue(); + meter = new RTLinearMeter(initialValue, model_widget.propWidth().getValue(), model_widget.propHeight().getValue(), minimum, maximum, + minMaxTolerance, loLo, low, high, @@ -182,6 +185,11 @@ protected void registerListeners() layoutChanged(null, null, null); }); + addWidgetPropertyListener(model_widget.propMinMaxTolerance(), (property, old_value, new_value) -> { + meter.setMinMaxTolerance(new_value); + layoutChanged(null, null, null); + }); + addWidgetPropertyListener(model_widget.propLevelLoLo(), (property, old_value, new_value) -> { meter.setLoLo(new_value); layoutChanged(null, null, null); diff --git a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java index cbac432785..956640037d 100644 --- a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java +++ b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java @@ -130,6 +130,11 @@ else if (xml_version.getMajor() < 3) public static WidgetPropertyDescriptor propLevelLow = newDoublePropertyDescriptor (WidgetPropertyCategory.BEHAVIOR, "level_low", Messages.WidgetProperties_LevelLow); + + /** 'min_max_tolerance' property: Treat the value range [min - min_max_tolerance, max + min_max_tolerance] as the valid value range for the widget (can be used to avoid warnings due to precision errors in cases such as when a PV sends -0.0000001 when the value is actually 0.0. */ + public static final WidgetPropertyDescriptor propMinMaxTolerance = + newDoublePropertyDescriptor(WidgetPropertyCategory.BEHAVIOR, "min_max_tolerance", "Min/Max Tolerance"); + public static StructuredWidgetProperty.Descriptor colorsStructuredWidget_descriptor = new StructuredWidgetProperty.Descriptor(WidgetPropertyCategory.DISPLAY, "colors", "Colors"); @@ -168,6 +173,7 @@ else if (xml_version.getMajor() < 3) private WidgetProperty level_hihi; private WidgetProperty level_lolo; private WidgetProperty level_low; + private WidgetProperty minMaxTolerance; private WidgetProperty displayHorizontal; private StructuredWidgetProperty colorsStructuredWidget; @@ -230,6 +236,7 @@ protected void defineProperties(List> properties) properties.add(level_low = propLevelLow.createProperty(this, 20.0)); properties.add(level_high = propLevelHigh.createProperty(this, 80.0)); properties.add(level_hihi = propLevelHiHi.createProperty(this, 90.0)); + properties.add(minMaxTolerance = propMinMaxTolerance.createProperty(this, 0.0)); } /** @return 'foreground_color' property */ @@ -324,6 +331,10 @@ public WidgetProperty propLevelLow ( ) { return level_low; } + public WidgetProperty propMinMaxTolerance ( ) { + return minMaxTolerance; + } + public WidgetProperty propIsGradientEnabled () { return isGradientEnabled; } diff --git a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java index 8e6a35f1a6..cee62591ba 100644 --- a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java +++ b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java @@ -57,6 +57,7 @@ public RTLinearMeter(double initialValue, int height, double min, double max, + double minMaxTolerance, double loLo, double low, double high, @@ -98,6 +99,7 @@ public RTLinearMeter(double initialValue, min, max); + this.minMaxTolerance = minMaxTolerance; this.loLo = loLo; this.low = low; this.high = high; @@ -177,6 +179,7 @@ public void refreshPlotPart(PlotPart plotPart) { } }; private boolean validRange; + private double minMaxTolerance; public boolean getValidRange() { return validRange; @@ -391,6 +394,12 @@ public synchronized void setRange(double minimum, double maximum, boolean validR redrawIndicator(currentValue, currentWarning); } + public synchronized void setMinMaxTolerance(double minMaxTolerance) { + this.minMaxTolerance = minMaxTolerance; + determineWarning(); + redrawIndicator(currentValue, currentWarning); + } + public double getLoLo() { return loLo; } @@ -547,6 +556,13 @@ private void drawNewValue(double newValue) { double oldValue = currentValue; currentValue = newValue; + if (newValue > linearMeterScale.getValueRange().getHigh() && newValue < linearMeterScale.getValueRange().getHigh() + minMaxTolerance) { + newValue = linearMeterScale.getValueRange().getHigh(); + } + if (newValue < linearMeterScale.getValueRange().getLow() && newValue > linearMeterScale.getValueRange().getLow() - minMaxTolerance) { + newValue = linearMeterScale.getValueRange().getLow(); + } + if (oldValue != newValue) { if (!Double.isNaN(newValue)){ int newIndicatorPosition; @@ -577,10 +593,10 @@ else if (showUnits && units == "") { else if (!validRange) { return WARNING.MIN_AND_MAX_NOT_DEFINED; } - else if (currentValue < linearMeterScale.getValueRange().getLow()) { + else if (currentValue < linearMeterScale.getValueRange().getLow() - minMaxTolerance) { return WARNING.VALUE_LESS_THAN_MIN; } - else if (currentValue > linearMeterScale.getValueRange().getHigh()) { + else if (currentValue > linearMeterScale.getValueRange().getHigh() + minMaxTolerance) { return WARNING.VALUE_GREATER_THAN_MAX; } else { From 8de967c005330ac96b03674bb8ede4971b8879fc Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 13 Mar 2024 15:10:49 +0100 Subject: [PATCH 38/92] CSSTUDIO-2228 Add label to messages.properties. --- .../display/extra/widgets/linearmeter/LinearMeterWidget.java | 2 +- .../main/java/org/csstudio/display/builder/model/Messages.java | 1 + .../org/csstudio/display/builder/model/messages.properties | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java index 956640037d..79357b4ae9 100644 --- a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java +++ b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java @@ -133,7 +133,7 @@ else if (xml_version.getMajor() < 3) /** 'min_max_tolerance' property: Treat the value range [min - min_max_tolerance, max + min_max_tolerance] as the valid value range for the widget (can be used to avoid warnings due to precision errors in cases such as when a PV sends -0.0000001 when the value is actually 0.0. */ public static final WidgetPropertyDescriptor propMinMaxTolerance = - newDoublePropertyDescriptor(WidgetPropertyCategory.BEHAVIOR, "min_max_tolerance", "Min/Max Tolerance"); + newDoublePropertyDescriptor(WidgetPropertyCategory.BEHAVIOR, "min_max_tolerance", Messages.WidgetProperties_MinMaxTolerance); public static StructuredWidgetProperty.Descriptor colorsStructuredWidget_descriptor = new StructuredWidgetProperty.Descriptor(WidgetPropertyCategory.DISPLAY, "colors", "Colors"); diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/Messages.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/Messages.java index f2c07a5a33..ebc0bbb6db 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/Messages.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/Messages.java @@ -249,6 +249,7 @@ public class Messages WidgetProperties_Maximum, WidgetProperties_MediumTickVisible, WidgetProperties_Minimum, + WidgetProperties_MinMaxTolerance, WidgetProperties_MinorTickSpace, WidgetProperties_MinorTickVisible, WidgetProperties_MinuteColor, diff --git a/app/display/model/src/main/resources/org/csstudio/display/builder/model/messages.properties b/app/display/model/src/main/resources/org/csstudio/display/builder/model/messages.properties index bc11431af0..c511703e5c 100644 --- a/app/display/model/src/main/resources/org/csstudio/display/builder/model/messages.properties +++ b/app/display/model/src/main/resources/org/csstudio/display/builder/model/messages.properties @@ -232,6 +232,7 @@ WidgetProperties_MajorTickVisible=Major Ticks Visible WidgetProperties_Maximum=Maximum WidgetProperties_MediumTickVisible=Medium Ticks Visible WidgetProperties_Minimum=Minimum +WidgetProperties_MinMaxTolerance=Min/Max Tolerance WidgetProperties_MinorTickSpace=Minor Ticks Space WidgetProperties_MinorTickVisible=Minor Ticks Visible WidgetProperties_MinuteColor=Minute Color From 9c9e4a6a716f9adc6e9de52c98f78f287ceb74be Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 13 Mar 2024 15:28:13 +0100 Subject: [PATCH 39/92] CSSTUDIO-2228 Bugfix: use String.equals() instead of '==' when comparing two strings. --- .../display/extra/widgets/linearmeter/RTLinearMeter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java index cee62591ba..05c604f1c4 100644 --- a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java +++ b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java @@ -587,7 +587,7 @@ private WARNING determineWarning() { if (lag) { return WARNING.LAG; } - else if (showUnits && units == "") { + else if (showUnits && units.equals("")) { return WARNING.NO_UNIT; } else if (!validRange) { From 466114563ccc493eb579a1d46559c929671b5af7 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Thu, 14 Mar 2024 09:56:48 +0100 Subject: [PATCH 40/92] CSSTUDIO-2228 Bugfix: use '<=' and '>=' instead of '<' and '>', respectively. --- .../display/extra/widgets/linearmeter/RTLinearMeter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java index 05c604f1c4..2658beee8b 100644 --- a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java +++ b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java @@ -556,10 +556,10 @@ private void drawNewValue(double newValue) { double oldValue = currentValue; currentValue = newValue; - if (newValue > linearMeterScale.getValueRange().getHigh() && newValue < linearMeterScale.getValueRange().getHigh() + minMaxTolerance) { + if (newValue > linearMeterScale.getValueRange().getHigh() && newValue <= linearMeterScale.getValueRange().getHigh() + minMaxTolerance) { newValue = linearMeterScale.getValueRange().getHigh(); } - if (newValue < linearMeterScale.getValueRange().getLow() && newValue > linearMeterScale.getValueRange().getLow() - minMaxTolerance) { + if (newValue < linearMeterScale.getValueRange().getLow() && newValue >= linearMeterScale.getValueRange().getLow() - minMaxTolerance) { newValue = linearMeterScale.getValueRange().getLow(); } From dd1388cb67d0ef99fc295c427eef683fea80ab2a Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 14 Mar 2024 17:12:38 +0100 Subject: [PATCH 41/92] Fix save&restore readback rendering --- .../client/SaveAndRestoreJerseyClient.java | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java index 91b2860bcc..83427c4587 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java @@ -44,22 +44,18 @@ public class SaveAndRestoreJerseyClient implements org.phoebus.applications.saveandrestore.client.SaveAndRestoreClient { private static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8"; - private final Logger logger = Logger.getLogger(SaveAndRestoreJerseyClient.class.getName()); + private static final Logger logger = Logger.getLogger(SaveAndRestoreJerseyClient.class.getName()); private static final int DEFAULT_READ_TIMEOUT = 5000; // ms private static final int DEFAULT_CONNECT_TIMEOUT = 5000; // ms - ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = new ObjectMapper(); - private HTTPBasicAuthFilter httpBasicAuthFilter; + private static final Client client; - public SaveAndRestoreJerseyClient() { - - mapper.registerModule(new JavaTimeModule()); - mapper.setSerializationInclusion(Include.NON_NULL); - } + private static HTTPBasicAuthFilter httpBasicAuthFilter; - private Client getClient() { + static { int httpClientReadTimeout = Preferences.httpClientReadTimeout > 0 ? Preferences.httpClientReadTimeout : DEFAULT_READ_TIMEOUT; logger.log(Level.INFO, "Save&restore client using read timeout " + httpClientReadTimeout + " ms"); @@ -73,7 +69,7 @@ private Client getClient() { JacksonJsonProvider jacksonJsonProvider = new JacksonJsonProvider(mapper); defaultClientConfig.getSingletons().add(jacksonJsonProvider); - Client client = Client.create(defaultClientConfig); + client = Client.create(defaultClientConfig); try { SecureStore store = new SecureStore(); @@ -89,8 +85,11 @@ private Client getClient() { } catch (Exception e) { logger.log(Level.WARNING, "Unable to retrieve credentials from secure store", e); } + } - return client; + public SaveAndRestoreJerseyClient() { + mapper.registerModule(new JavaTimeModule()); + mapper.setSerializationInclusion(Include.NON_NULL); } @Override @@ -110,7 +109,7 @@ public Node getNode(String uniqueNodeId) { @Override public List getCompositeSnapshotReferencedNodes(String uniqueNodeId) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/nodes"); + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/nodes"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -129,7 +128,7 @@ public List getCompositeSnapshotReferencedNodes(String uniqueNodeId) { @Override public List getCompositeSnapshotItems(String uniqueNodeId) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/items"); + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/items"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -160,7 +159,7 @@ public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClient @Override public Node createNewNode(String parentNodeId, Node node) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/node") + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/node") .queryParam("parentNodeId", parentNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(node, CONTENT_TYPE_JSON) @@ -184,7 +183,7 @@ public Node updateNode(Node nodeToUpdate) { @Override public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/node") + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/node") .queryParam("customTimeForMigration", customTimeForMigration ? "true" : "false"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) @@ -210,7 +209,7 @@ private T getCall(String relativeUrl, Class clazz) { } private ClientResponse getCall(String relativeUrl) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + relativeUrl); + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + relativeUrl); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -228,7 +227,7 @@ private ClientResponse getCall(String relativeUrl) { @Override public void deleteNodes(List nodeIds) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/node"); + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/node"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(nodeIds, CONTENT_TYPE_JSON) .delete(ClientResponse.class); @@ -255,7 +254,7 @@ public List getAllSnapshots() { @Override public Node moveNodes(List sourceNodeIds, String targetNodeId) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/move") + client.resource(Preferences.jmasarServiceUrl + "/move") .queryParam("to", targetNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) @@ -277,7 +276,7 @@ public Node moveNodes(List sourceNodeIds, String targetNodeId) { @Override public Node copyNodes(List sourceNodeIds, String targetNodeId) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/copy") + client.resource(Preferences.jmasarServiceUrl + "/copy") .queryParam("to", targetNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) @@ -299,7 +298,7 @@ public Node copyNodes(List sourceNodeIds, String targetNodeId) { @Override public String getFullPath(String uniqueNodeId) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/path/" + uniqueNodeId); + client.resource(Preferences.jmasarServiceUrl + "/path/" + uniqueNodeId); ClientResponse response = webResource.get(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -322,7 +321,7 @@ public ConfigurationData getConfigurationData(String nodeId) { @Override public Configuration createConfiguration(String parentNodeId, Configuration configuration) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/config") + client.resource(Preferences.jmasarServiceUrl + "/config") .queryParam("parentNodeId", parentNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(configuration, CONTENT_TYPE_JSON) @@ -341,7 +340,7 @@ public Configuration createConfiguration(String parentNodeId, Configuration conf @Override public Configuration updateConfiguration(Configuration configuration) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/config"); + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/config"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(configuration, CONTENT_TYPE_JSON) @@ -367,7 +366,7 @@ public SnapshotData getSnapshotData(String nodeId) { @Override public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/snapshot") + client.resource(Preferences.jmasarServiceUrl + "/snapshot") .queryParam("parentNodeId", parentNodeId); ClientResponse response; try { @@ -392,7 +391,7 @@ public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) { @Override public Snapshot updateSnapshot(Snapshot snapshot) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/snapshot"); + client.resource(Preferences.jmasarServiceUrl + "/snapshot"); ClientResponse response; try { response = webResource.accept(CONTENT_TYPE_JSON) @@ -417,7 +416,7 @@ public Snapshot updateSnapshot(Snapshot snapshot) { @Override public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot") + client.resource(Preferences.jmasarServiceUrl + "/composite-snapshot") .queryParam("parentNodeId", parentNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(compositeSnapshot, CONTENT_TYPE_JSON) @@ -437,7 +436,7 @@ public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeS @Override public List checkCompositeSnapshotConsistency(List snapshotNodeIds) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot-consistency-check"); + client.resource(Preferences.jmasarServiceUrl + "/composite-snapshot-consistency-check"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(snapshotNodeIds, CONTENT_TYPE_JSON) .post(ClientResponse.class); @@ -456,7 +455,7 @@ public List checkCompositeSnapshotConsistency(List snapshotNodeI @Override public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot"); + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/composite-snapshot"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(compositeSnapshot, CONTENT_TYPE_JSON) @@ -475,7 +474,7 @@ public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnap @Override public SearchResult search(MultivaluedMap searchParams) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/search") + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/search") .queryParams(searchParams); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .get(ClientResponse.class); @@ -493,7 +492,7 @@ public SearchResult search(MultivaluedMap searchParams) { @Override public Filter saveFilter(Filter filter) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/filter"); + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/filter"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(filter, CONTENT_TYPE_JSON) .put(ClientResponse.class); @@ -511,7 +510,7 @@ public Filter saveFilter(Filter filter) { @Override public List getAllFilters() { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/filters"); + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/filters"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .get(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -531,7 +530,7 @@ public List getAllFilters() { public void deleteFilter(String name) { // Filter name may contain space chars, need to URL encode these. String filterName = name.replace(" ", "%20"); - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/filter/" + filterName); + WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/filter/" + filterName); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .delete(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -555,7 +554,7 @@ public void deleteFilter(String name) { public List addTag(TagData tagData) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/tags"); + client.resource(Preferences.jmasarServiceUrl + "/tags"); ClientResponse response; try { response = webResource.accept(CONTENT_TYPE_JSON) @@ -586,7 +585,7 @@ public List addTag(TagData tagData) { */ public List deleteTag(TagData tagData) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/tags"); + client.resource(Preferences.jmasarServiceUrl + "/tags"); ClientResponse response; try { response = webResource.accept(CONTENT_TYPE_JSON) @@ -611,7 +610,7 @@ public List deleteTag(TagData tagData) { @Override public UserData authenticate(String userName, String password) { WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/login") + client.resource(Preferences.jmasarServiceUrl + "/login") .queryParam("username", userName) .queryParam("password", password); ClientResponse response; From 7e11a51551f49f893d10fce6c306ce8e1c60db2b Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Fri, 15 Mar 2024 10:04:26 +0100 Subject: [PATCH 42/92] CSSTUDIO-2044 Bugfix: run the correct search query when selecting "Alarm History" in the context menu of a widget in an OPI. --- .../applications/alarm/logging/ui/AlarmLogTable.java | 6 +++++- .../applications/alarm/logging/ui/AlarmLogTableApp.java | 4 ++-- .../alarm/logging/ui/actions/ContextMenuPVAlarmHistory.java | 3 +-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTable.java b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTable.java index 00d71a545d..9f5c368262 100644 --- a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTable.java +++ b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTable.java @@ -23,7 +23,8 @@ public class AlarmLogTable implements AppInstance { private DockItem tab; private AlarmLogTableController controller; - AlarmLogTable(final AlarmLogTableApp app) { + AlarmLogTable(final AlarmLogTableApp app, URI resource) { + // If URI resource == null, the default search query is used. this.app = app; try { ResourceBundle resourceBundle = NLS.getMessages(Messages.class); @@ -50,6 +51,9 @@ else if(clazz.isAssignableFrom(AdvancedSearchViewController.class)){ tab.setOnClosed(event -> { controller.shutdown(); }); + if (resource != null) { + setPVResource(resource); + } DockPane.getActiveDockPane().addTab(tab); } catch (IOException e) { Logger.getLogger(getClass().getName()).log(Level.WARNING, "Cannot load UI", e); diff --git a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableApp.java b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableApp.java index 2824d097cd..d993899da6 100644 --- a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableApp.java +++ b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableApp.java @@ -40,7 +40,7 @@ public String getName() { @Override public AppInstance create() { - return new AlarmLogTable(this); + return new AlarmLogTable(this, null); } /** @@ -50,7 +50,7 @@ public AppInstance create() { */ @Override public AppInstance create(URI resource) { - AlarmLogTable alarmLogTable = new AlarmLogTable(this); + AlarmLogTable alarmLogTable = new AlarmLogTable(this, resource); //alarmLogTable.s return alarmLogTable; } diff --git a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/actions/ContextMenuPVAlarmHistory.java b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/actions/ContextMenuPVAlarmHistory.java index e36018e303..0c43b460f2 100644 --- a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/actions/ContextMenuPVAlarmHistory.java +++ b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/actions/ContextMenuPVAlarmHistory.java @@ -41,9 +41,8 @@ public void call(Selection selection) throws URISyntaxException { selection.getSelections().stream().forEach(s -> { AdapterService.adapt(s, ProcessVariable.class).ifPresent(selectedPvs::add); }); - AlarmLogTable table = ApplicationService.createInstance(AlarmLogTableApp.NAME); URI uri = new URI(AlarmLogTableApp.SUPPORTED_SCHEMA, "", "", "pv="+selectedPvs.stream().map(ProcessVariable::getName).collect(Collectors.joining(",")), ""); - table.setPVResource(uri); + AlarmLogTable table = ApplicationService.createInstance(AlarmLogTableApp.NAME, uri); } @Override From eeee966f1dd912896a3d20f4debe7522a31b7d26 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Fri, 15 Mar 2024 10:05:24 +0100 Subject: [PATCH 43/92] Remove commented-out code. --- .../phoebus/applications/alarm/logging/ui/AlarmLogTableApp.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableApp.java b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableApp.java index d993899da6..c7421c9afb 100644 --- a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableApp.java +++ b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/AlarmLogTableApp.java @@ -51,7 +51,6 @@ public AppInstance create() { @Override public AppInstance create(URI resource) { AlarmLogTable alarmLogTable = new AlarmLogTable(this, resource); - //alarmLogTable.s return alarmLogTable; } From 9b99feee9b2087a188e0191d7547b077be0204db Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Fri, 15 Mar 2024 11:06:47 -0400 Subject: [PATCH 44/92] Creating a new annunciator which plays audio files --- app/alarm/audio-annunciator/.classpath | 21 ++++++ app/alarm/audio-annunciator/build.xml | 20 ++++++ app/alarm/audio-annunciator/pom.xml | 31 +++++++++ .../audio/annunciator/AudioAnnunciator.java | 60 ++++++++++++++++++ .../alarm/audio/annunciator/Preferences.java | 56 ++++++++++++++++ ...lications.alarm.ui.annunciator.Annunciator | 1 + .../audio_annunciator_preferences.properties | 14 ++++ .../sounds/mixkit-classic-alarm-995.wav | Bin 0 -> 886612 bytes 8 files changed, 203 insertions(+) create mode 100644 app/alarm/audio-annunciator/.classpath create mode 100644 app/alarm/audio-annunciator/build.xml create mode 100644 app/alarm/audio-annunciator/pom.xml create mode 100644 app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java create mode 100644 app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java create mode 100644 app/alarm/audio-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator create mode 100644 app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties create mode 100644 app/alarm/audio-annunciator/src/main/resources/sounds/mixkit-classic-alarm-995.wav diff --git a/app/alarm/audio-annunciator/.classpath b/app/alarm/audio-annunciator/.classpath new file mode 100644 index 0000000000..948256b10b --- /dev/null +++ b/app/alarm/audio-annunciator/.classpath @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/alarm/audio-annunciator/build.xml b/app/alarm/audio-annunciator/build.xml new file mode 100644 index 0000000000..019188b2fa --- /dev/null +++ b/app/alarm/audio-annunciator/build.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/alarm/audio-annunciator/pom.xml b/app/alarm/audio-annunciator/pom.xml new file mode 100644 index 0000000000..085313f76a --- /dev/null +++ b/app/alarm/audio-annunciator/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + org.phoebus + app-alarm + 4.7.4-SNAPSHOT + + app-alarm-audio-annunciator + + + 17 + 17 + + + + + org.phoebus + app-alarm-ui + 4.7.4-SNAPSHOT + + + org.openjfx + javafx-media + ${openjfx.version} + + + + \ No newline at end of file diff --git a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java new file mode 100644 index 0000000000..9494704d3e --- /dev/null +++ b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2018 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.phoebus.applications.alarm.audio.annunciator; + +import javafx.scene.media.AudioClip; +import org.phoebus.applications.alarm.ui.annunciator.Annunciator; +import org.phoebus.applications.alarm.ui.annunciator.AnnunciatorMessage; + +/** + * Annunciator class. Uses Audio files to annunciate passed messages. + * + * @author Kunal Shroff + */ +@SuppressWarnings("nls") +public class AudioAnnunciator implements Annunciator { + private final AudioClip alarmSound; + private final AudioClip minorAlarmSound; + private final AudioClip majorAlarmSound; + private final AudioClip invalidAlarmSound; + private final AudioClip undefinedAlarmSound; + + /** + * Constructor + */ + public AudioAnnunciator() { + alarmSound = new AudioClip(Preferences.alarm_sound_url); + minorAlarmSound = new AudioClip(Preferences.minor_alarm_sound_url); + majorAlarmSound = new AudioClip(Preferences.major_alarm_sound_url); + invalidAlarmSound = new AudioClip(Preferences.invalid_alarm_sound_url); + undefinedAlarmSound = new AudioClip(Preferences.undefined_alarm_sound_url); + } + + /** + * Annunciate the message. + * + * @param message Message text + */ + @Override + public void speak(final AnnunciatorMessage message) { + switch (message.severity) { + case MINOR -> minorAlarmSound.play(Preferences.volume); + case MAJOR -> majorAlarmSound.play(Preferences.volume); + case INVALID -> invalidAlarmSound.play(Preferences.volume); + case UNDEFINED -> undefinedAlarmSound.play(Preferences.volume); + default -> alarmSound.play(Preferences.volume); + } + } + + /** + * Deallocates the voice. + */ + @Override + public void shutdown() { + } +} diff --git a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java new file mode 100644 index 0000000000..8c1bca21d1 --- /dev/null +++ b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2010-2022 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ +package org.phoebus.applications.alarm.audio.annunciator; + + +import org.phoebus.framework.preferences.AnnotatedPreferences; +import org.phoebus.framework.preferences.Preference; +import org.phoebus.framework.preferences.PreferencesReader; + +/** + * Helper for reading preference settings + * + * @author Kunal Shroff + */ +@SuppressWarnings("nls") +public class Preferences { + + /** + * Setting + */ + @Preference + public static String alarm_sound_url; + @Preference + public static String minor_alarm_sound_url; + @Preference + public static String major_alarm_sound_url; + @Preference + public static String invalid_alarm_sound_url; + @Preference + public static String undefined_alarm_sound_url; + @Preference + public static int volume; + + static { + final PreferencesReader prefs = AnnotatedPreferences.initialize(AudioAnnunciator.class, Preferences.class, "/audio_annunciator_preferences.properties"); + alarm_sound_url = useLocalResourceIfUnspecified(alarm_sound_url); + minor_alarm_sound_url = useLocalResourceIfUnspecified(minor_alarm_sound_url); + major_alarm_sound_url = useLocalResourceIfUnspecified(major_alarm_sound_url); + invalid_alarm_sound_url = useLocalResourceIfUnspecified(invalid_alarm_sound_url); + undefined_alarm_sound_url = useLocalResourceIfUnspecified(undefined_alarm_sound_url); + } + + private static String useLocalResourceIfUnspecified(String alarmResource) { + if (alarmResource == null || alarmResource.isEmpty()) { + return Preferences.class.getResource("/sound/mixkit-classic-alarm-995.wav").toString(); + } else { + return alarmResource; + } + } + +} diff --git a/app/alarm/audio-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator b/app/alarm/audio-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator new file mode 100644 index 0000000000..4377b88ff8 --- /dev/null +++ b/app/alarm/audio-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator @@ -0,0 +1 @@ +org.phoebus.applications.alarm.audio.annunciator.AudioAnnunciator diff --git a/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties b/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties new file mode 100644 index 0000000000..6b8348d669 --- /dev/null +++ b/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties @@ -0,0 +1,14 @@ +# ---------------------------------------- +# Package org.phoebus.applications.alarm.audio.annunciator +# ---------------------------------------- + +# file:/C:/tmp/audio/AudioFileWithWavFormat.wav +# https://wavlist.com/wav/brass1.wav + +alarm_sound_url= +minor_alarm_sound_url= +major_alarm_sound_url= +invalid_alarm_sound_url= +undefined_alarm_sound_url= + +volumn=100 \ No newline at end of file diff --git a/app/alarm/audio-annunciator/src/main/resources/sounds/mixkit-classic-alarm-995.wav b/app/alarm/audio-annunciator/src/main/resources/sounds/mixkit-classic-alarm-995.wav new file mode 100644 index 0000000000000000000000000000000000000000..e1d7f42d5c6c7b76d8202a24a00adbcb77b47442 GIT binary patch literal 886612 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�Jhi57 zE&?BU;QE61HV1);L}{@~*1^eHh$M%NxW6j9@lrFb_JG?T4hmKqB=sK8YAOl1{ahtC z$2G?XSO+9Cnu{j(qHqr(%crDOt~jdbvADpJz@6-NK9_=R=;_IYI0%vujbx`hY;>qc zmorD>hu6tma=p#WWGCNJQ=Lt=<;XUeg=W4vX|egyw~vRx*h;nhWv;?7#c{(xUk;gD zpO}_7+=rQ8q;kpc75XhdK zccfJmW`D*4oLcXw6X{U=^dQOMmqQ6DdZSc|MOx-BdyFoj0DVzCcU-l##k~2TVUGH} zPdAgN^?Ff^)VmL6G`!MDIJ1kUrC{}u*sO=kNWDi|GnXgUO%9NaWd<9`YvY+O<%2Z0s-M5S9&mVuioiz~A$ z6TwAt?lXyh;c+X&3v2Ywf@FgJHo^pg5iEeKd+Sn=JC}hu*$=iiob~75;;?EjV!^2`@HP6$V1k&gC+o3uWh z>@ueAY4EbmnTE5VphoRZe_oz{f34_*bD@WMCa>t$Oi5>`Cg0Q?0mV#u2gnYg}9Ze0R|=MSER? z6PJ-3>>()ghw#qKHfu#@6DDGs4x$v@!%?%IX|*cs@_XBWYBU&!^;1>E-QoU^+Fd*s z+~XW-nLl*DR&^8e)juRmHls5p*6)gI=ulE60=l)V)9 zTzuTuN98?{PYKyrC6a~c=bxh28uWi$ySRD_R=Pvw^)2&cKnh>E0LW=yd#|EzuqyAd z2}AVThzECwJ!}K)8ZtO)h??l4%i^NTz$Q-Y$6 z7b}<1sY*^~cMU{-DwRJL3N+MWX1JY=qbLw3^9;JY;kO&d8<-Fsd1)CP#pHH;eMQONEI?X3Th?%7PB-bvtjT0D z*bU?b2kYbZgTIyS=Ko->MUFM&BS)A`krzxw|0r9Xw1ZZr0UHR8kc@T$F0u#jLNS>B zT4?-M@wdm4v(Uqiz*|?$h4GFJmz`}^*%aMe)I1#@EODt@0&ZHK9JFnYv;~)wK6)Ft zP+^io-|~M23ej2gGqtFQQa(eyH;!Kqkw~$b)TLP{VCLW#`wd#KoUE;dW+VxD$JH~^ zG~dyCzhJ6ugC09Ss-8Py4;jOY(1mW7-N94_k*Uxb2hs>S-hE;=Du9NTO?aj&&hhu8 zw&bG1cBbozhkv1&>m#qg0G4Ea{|Ein2R!oquIXUlCDH2CxtJJoz+K-|Bm3gYo^1nsJjpK(20YvMsZ_ z?EVZchJT7{@AtdBI+#AO9)8A?Ft3&;R>c-Tm4ma=FH=gai?}9hx|9($_ck!yd$}&6$ti6zSc>HucMc2?&Xm+eZ8eG;HPX8Fja*F z+*BC@HmhYmsa|+5gjUN*32n$0kol&Em}p|4O^kGr)VDQJS|{e_QMmHiV}=VLR9>)m z(OtfPt5^@_CS(K7V*tHpbm~MGm*3Wh2P%z=F*Q?NZ54w|@x@?KOX&IXv5#!H2!Z=f zM}NLZ|8{fDbvUOwAiF{MAm^hFs&1NkeNAicu31h`Is%-l7}<GFeJTD3_%e_&6* zHlKCu9t82N#bhuF4^1wf*%`c+b3|lhcM&@hwk)Et7#A^FB#b;EbfhN>>2MKh+JSRS zgnt-D+Wbwb-uWhRnF z#?co~6jeyd*(s(7HW!GgHom-ouQLSIU^ydYK|J(n&{UN-+u0j@7uBxM2J&Qr!26nl z?A}m&aBTktC)$oZ&DFh`a+h~pzViN)kG<=14=$S^Z>o4oB19<_+b!qL2qZagBI=*4 zV1ui9nvdu>>f5?rJ$ucI>2`U8#cy>H2Yx?PBV$PGa&QgZaB&2}>@60nL09IGLH<-n zWz96TA58%12%sj^_Um;0xx@o*iu|CD^>I>V1m!!#>6wkX)*}z|imRcnp@Hd27FjTO z`v%gEdoo=XggYn&J~mP>b?^M!+%SI`*U(?ZmhsOv1^q2d1^;H#-(SOiW}EFfJqV@Q zYna>yB9EAdB4r6qx^dLywrKJCzzm!OnaK<{I~ZU3V3HUDs67Wjrrt2MwSr zI1P+$J-PNX>FR5UN+2vP!DL67qqqb5u#aa3?E7Oo#@EHA^*w}TDTiihhggcU>mPDe zSF^+ZxrybG>gV-0>lBO&m{2P?*qpMWAi08RJ*xZMDZ)@wufs7I9aZZroV73La`ux| zc?&G!G0NJkZUSc}16^oMkp-4rs8%E%L~V^YM7Hb``ugK;j=0Hm*Hjco#fqOmU9cVT zRd(>@cSQr5x{I({2?7te&jC+d-+(u4Quyuadb!}r$u|@uWnx!VO~6mJ%Wi@*`XJ|{ z^bh0j3&ANdQ5|M;O*8KClBmy{+F+cz#o=f3p#)6ezS|3ED(l%GaF_h{tStG`P~Q^Ww$JHp7pna+uB$6X!eYQ6-fei?;*a+?Z0a~|H^b=;F} zbS+y(3!6#@+L!)m*7ygyshW4dz|4@^6OBD6x0xIYvQsB9X{YCfpdO9XX~ax@*L7lh z{ABWuj)UWFMc2k2R%+c?+n(I2%RHG={FJ@acoaj!#QdH!Jc7FXI3dzLw^gqb7LvlXL~>5h`QnfD#PK^;4YuB8`V zVU4TrU-Cr{xY~9Yil4;P!mI2Bd~c7Tfk_RDFb@A(F2&pz9t=XRwN&r!sSRZFN@vl5_m!<=V&@B1_~iS0eJh zJ0ICp-0(j@#Tttaa28!qC)lhMkGvrpCTjnr-Tqbn9HQjjASi~_2aE|nmX>gFd zq#No?4c&rj<{MpEU-Aze=Q~8^BMU!NBqKMmF&u4CI;Au=woGAgTkC;xln#c|N+>g# zk@%J7!{@xQmDv)sfuw>baYCcLoXrEFgcV~qwQQc zxIy54v0n7;Rbup2Fn`N^}D@M@kCuNQ)>Y6c+ydnGH z658xZa*R8Lt7(>81UJ433^)fnC2PZg7eo!1gS@ZK`mGDlVJ?Fn;hMsx4Zy=RNawRv z(Trx%^GqV0*Ot>I(8%;)$}7UBS{6O#bCCGjc!XkzF|rblkPXaZe$d%BsKbKkG`}!) z1fU#aPa>&c1qJCg)bZ~m*)GR(8txvMc>`qxEd<8mlsRVCTaBEipdz%`9UsD}eW6JE$kKHP7*YSi&aNifAn_Q`>*z zT)K#lvoEZ{4-~<@$;doMdLzC8InSn%Z|ycZq$2c)ulR&6yY?y`8i&T>j2a;NsClHO z-vC1`LDJL{Q~^s>3ER)BVN>`vP~*WseC0(ipHCL|1<547Y;1<<4hsIqtw(tnhHq*= zZuqbKf2+wnP$Ij!L%$n{n)NKcOx`U#qOG{i+QaT$q*`IJ=2pti!os*d#JLeh4;eL_TssW?1CG>_j$&) zLDBM&y<R3diLK&pu$V7fNS~cMmP88>v z!%LuQT4t-#vp<7T-7gZLle~?N>L@ea9CCHv%gJaza-!J0$j>wzHQspf7ky$QGY_xm zWSFNYJY7(ayO9Cm;TU~Mg>C|3(iSz_ay+-E(P1n_Tc6ptLiO_YKYqj|F>J-V29=%vx?q4CdkS&R2LU)u*zXWsjjP365g=SByhAB2h<3%OPVux zcjjJsAO_nyJfjgLt<`6y-z1_L553nAcTq>$awIxtM0XV(eru&0q))nSIvC|d0EqNY zdl;1SvPpz1;-#2?&Ll6W4>vGbYt2;{nv`ecAw467ZZ=6Xk70ph;4dn`w#EKvP_Ezr zZl+7RLTtAxqmSAtIvhP$WmnfMC%xji>x4QWHnYWEkf;8z=VhrqhsiTpPAzp*vA4Ki z|DY2cYIlepb~{|16Bwh#U=2ZR>FLYvy^b(k1I%8Vg70?-DL8fQLweTn zXfETc7HqYsCzh)&cp}e}{yau1y8asMUpqjSRV!DSsWF>M@6K~a<^^YMA_|IgU<+YR z@-}S8)w>BN;TN!tmAn}xadNJbY4AS`b&o-jZpd*k3|DY}#imLIGi5hX$Jj+mhlM}1 zu_`N`qk^o07Aq=;XB}uvEZ0=6x3kq`wwi3P)xC^vAq>z_($rUZuS8q#ib%+&*>xzW zQmGGYxLCoxQWA`^ugbu5+k^fv7-Tmmin_t_Clh6uy(2c_?Rw6<5ZfgN??}r(C&20P z1U`O(Ys{OK4^_c&kdpmq#y{e)$-*{=owg&>)h88*>!3TB`Y~6Ox;+TD)_9bHLs9L8 z;AUxtmaT&srtj(B{(E|cP` zj;9XXLNEEpBq6!A8j6KsJe}LPcUR)jcx$iOe)wt9qwAdpKR=q=rZL*CJfbJlQz=!D zlb?z0HMiUwxTPgBh@ZKQe29B@zqm#vL&h{=p`3&!ud*nr>DqNDOsG%Jd+4=GTn_*Mq zP#OqwS>1uqbE+djoEr1l^+P4f31Ne1E9#XWlrbDn(=5`vrZFR?Bx$KVoYEbRxgT|FC6}R4LXY=A`VLaxp=g{GmXB0nV*PX`Ume+QMTpwgta)v?ztdP zl}fO*Q%yNkV#84t4n;pTII4!CHSC3N@CjQ?e%m@E!`wr)^%1SU&)#+g=^u|$yTmb|vva4PJ^VRKuVX9`p zY32+xh3Ymrsy;A+p0*pFgs!5Y?aimv7}PcwpW$~Bt6z{9kc5ifN>nAm>w~%_qI-u# zB{e}<@fXQEBnzV4`x_Tnqz++@T8hUgvCww;|5Jr){2$v1hSf=npJ+9T)#f@eJ12^} zL10|kPmR;F#RQW=7ANr-g)m6yHdLyp+&LBB@r37)RhaBQ<9D44Ls*A|zO+28*d$W; z#B2T4t<)dfIS`*@CXm{i313fHnEdLX;U&;G2|U2t;4iCjC!|JuQ4PNRD4*R>x|r;2 zv^YktoMWTjD^$NdK?c{5%ypXg;yq8i8>x-M>`&&)s)orGjm-#C0M0WF=uUCBLX3uc z`hrKJ6L|(t=@F~qVxP;6x0r0Zs;IgzvLmGl9d95==~u8{hgv(ih|T#-g73UE`Ti5h zHh6^Nqd5BCVI)~h!wp+iJ_IF=>+N7uz*W>C{cS4v^)z_Ri=nZaEyjT~jFafzWE(2r zDsT#PRd8FC<#lwdO~G!?pltTz52wj;A5q3UCO5!>l|}bz;q;B#M}13VR71c$n~Du+ zKX16YC{TZ(OGpBL(#S?b3$|2LveD@BD&o}b0G7}R=DvVD%%qeDMFzP>E)UGzOD4Yc zoanvG_IuIN%p_5uB^w8-k?zzR&ig;O@CqU(nyS)XMv>H0VjK+gKFO8<;Vfy><{H_B z3GFHW8dKaq+?4Yl!P8sF_R=@mH3!y1|MJ=HLsd8sg?Rv)q*(ZVHj=vXok^v**n)ea zp~&YR!Y>7rD)Z4E)EjIaQe`Kh)t=4%t-5G#Bbs7N;Fk-U=|Y7&-N8}_eE}Abs zp;gGw1mnR@<(2=zgH8lPv#>>ZLFO}(j?xnaXHQcEBygR)gO{TZ$pKq%I>hqU^1Pd% z-cQW@-bUTzu1>+(m_XA0eF4wIcd`O4Xi9O&*2ecX*dB7n%m#9bgiAj&HQc>Ln;#JvBflTQEtFa<|cP#@3hFLVVQq z)cxQrKJw1gz}=bFmP6S$5Z8eFzbD{7YQa|6#**TKx$jn+>2A3R=Ghc;O-x?*5+&O( zox;^b72H{8ac6W(IIy#*XwtC9w*Vfb=wR)0L4o_zZN%Z8xeXG#9B=ts83v!(6g9;* z^8#(i$|x=v-NHlmx=tpyp$@A0!ldlyq$ADByVDpy++#S=CDf2<@;fS#cBI!$!H2rt zT#|t(A#dp$C{kLAz2-ex1E+BRN^tPg)VOyr=?T3+_K*w^Nqql_!@Nnw;RkoB(rD)Q zfT&;L9DZf)DTit{8{XIlHaB-kFhBJRTxTRgpo?mbV=bM!ASaQoP(~FcSLQ7K-j>W5 z0&Mpf9NkZsZ5cFpF8wP<)RpN9VvAxmlOu@;}5r2I-R~a|g-=Z>|z#>@lU$EvC zz|9up3Y0R^RYBqOHx9--q@NT4OS?>uc7thzt=?43N!+S+MLW=i)9xF7k}$g9a`YJq z@Prpbf47KAjCVn%kuTH}@PTonuvZj4V_|dv%Rx6lJg8DfV2hu)&dgk`!CU<9s@z2m ze_=S{v3MG1vln%!oWXROlc)PdB^OJ*U8q#IfSKKA$5Lv1-YrEws&6Rz$zr4nykW+A zL3&U{dhz$7r#UHK>ZZK4{c-1IQfolFH;6;1)pJvcZn^Gi5tXP0Nqo=YR>pB(cd(;y zdGPOYPmU$WH4{GGpL`zoxCT*+8kr&A0=)q5RNR}qo=sa^teesFZ&u(;bTgZif z1rrb*9nVq|1phdclJ5TjlQf(z=7bH>67MW)ka05?*FoTSVuUO%5j64ei}QTpT~> z&LXUj9N?sG0y!UfnR9c4hi&A3tVM3wOIXu9=!3H2%QKvpk0>sZ!R#+*XKq98+LS7v zTCKj4Trz-8bp?HT3f`V<+%m`L1|BdUE>p$r0TOBpp-&};UF0Owx1a0E7LH1E1UKlj z>yR1t*=exx>-3{3ar*N;h(HuxHSx)3v5qG-o$s^>SoU1BqbazXHi}$KKlkzXMLA3E zfexG_W3UpF#sPGsmB_eHf|v7eG|MeP-AC{i_rcGWh2-!bREghqFZj(+v)P5h0v6}< zD38Xf5%XCp6fI=Wpq3s*577g+@KJEhg=}1!M8@GM(|{a|KcF|G&@IKs&%2GSXybJ$ z)W2oiHJ#a2!u|h|N%JffXcF(5fYnPc($RgdK*O+&^ual*qqV9i%8^DSc;2(I@OtFt zceKIDxz&Cnk2g8UMM4xOJ{g3HtSObF04d_9+J!?jr*?b#(Z{x;+8yQkiZqFyxkB+RPn7rd}$iis=N!C`!dkIaLrqrJOe zufRr)w`6(R7v$!ww-R5SaYfmFa0DH6X1RdgsvqpsE1rEc6gMe3fmLw>-Q#nK#x&Cc zqU z81bj@>r2=UFaUjA22k*k<}dSJFVM^NA2zk#VdumzJaLwu=`}t5Albz2kdsN&KFm&% zdZxJu#!LJL<@RlI!jHIVc-TU~$_J3te8P68rhd22nT77a`?7-;HBT2uK-CGtKqwP z%CGc?wa{!&me1KfdQs(o@wm@6o2#7itNfG)!NUsk>E`B^>Orzj7chlnOfHQ?H!z># zaC}Qd0XaLOh^mKe_g<2*@}7cqugrJsbDalKBfSC&`?nePxC`&+9vQT z``i?Kp_{<2N!)?Uxr_q8Fx%1Qkq_`)mU0zUGq+HUMdLBj)kZ%sjdR`~_F%gE?Jwwd z`>V2-=qxB$3h~2wBAM98+xs`zLQc1u8h;zU?O>D}mOEUNP7GQu*TT*|w2NUKN4d%7 zi0fw>inr#IxCB>tg^gP4N#V|ghOD#%36}5JW!Dw2KS&-u3%9M?K<9E0Prt|SnzKb+O6^>cD{g9Y>8TFWBL}sMnxq_uS3Ce17G_yFw5hxE`6CyQgHJg zBmI4fDdon}XB{FFH@_X>9@&4%{HQ`|LpWI66i#()l|i&s|BCH6u0N@9DwmfKhka5{ zt24MlvTsu_LODN4WHM^RcU-+C?S1d0y@}f;jkk`Zo4e$vCIq8y1CMYEHBVD^ zlm`KLNy}X$kTF&lgJ3|@x__k!D_eV|D zj_;r(zJt1;0B`jSnb6#l5#}EGr)%(#)FEl$3HjJn#1OdHiK-U6%ae20{0)Xu2t>`H z3f&3n+mETI0bO(pa{AZVajF`bFNxW{^M#pxsLZeW~bZPUhdwX@kwc2ARHV=jVi%%ckpW!q3g)O73c?v5r;Q;~Ml3Et^4X_Y}JYNA}0 zKk#`8>PdW(deVpxn2&|jv<&P6Y6;FV$ZBJZGf8bGxYyRaac%8U7oWbr92)nSBvIG! zkAV1o%(Bl@0MIGX- zlK**y9xM^g)v;`=dV*Kxy-3Bokdx142$Pv5ufL<1ZpMhFeBCl2pKwL(=!>GkBuypj zEGxK61UyCL#XzAk$7E%zdMplLWnqWye*W%(cAi(oF7ViW=9OTdWlc1XH9$Myl<<(05dYzcyk{!m z(q2gNTOg;4X^@kb(iXD+kVxDgH*im;%-$%8wY-L^t*g30{XBzT@P#}>hK1j2rJi~d zR9`Zi*QsIhCev0b{PO2;(S1N|)Sk^tQCg2#d{47fs7s}OGM#orgLoPRUTTzXjj4KF zncsSV@&vOvF98b05H@IBhBfU4`reWXHvydUE>G%jPGnRv(tYNRK)S5}usS1R$vEWr zy%%%TaB)P1qmUWE#84eBVIwt$DH^VzFo6l$@}#mEARJQ9L=tymMttKQ>8bXXjO6J;R3k$^2z%Z-T3|BZ<<5aQ{zanhXOi zJBBNbL^ZJ1YrKiYc=zryZH{54uEJb5iXA*(xs9Wt=E{gZbfb9-Mi4=I`9u6uhvCYe z>j*JJuVI7QCp!2~;892ANhZck^jaOci|;e(9+B)2Mz5X-oS+GJ92?*1_V(M|yyMkD z_qJ1~@w2;1Xgx2e?I<2dwe(WUE9xj&utmgr_?4o(&tFYpn5YogpVHLO>8?836taBm zUH1(}rUiLYj@fq(I2p4!$mju5f=ZJZR)mf0Xnm~#tLZGp*)-gA@tCR#;`15|*E|7k zB%gTULaD*;QI|FOA4lf^Ue~h4@tN5tjmp$*YTLGLx2bL0Hd5QTwQbwBjg-;ZGxL6V z-~0OdUT<%k>@$1KT7NAzfM`9ROo$eEY<)yEP}c?qoRIe|g>ED=f``^5?=ge7gnZzZ#@Y?Zkb29-gy>+D|f00+@l)C|+=0>U(S} zAFogA(Qa2biPvVe_k>yQZNPkZSAX|z(jjgC=hy(pEbqKW{x??43h}$<0`w-Z6OMwtgMX=D1=@u5G~>i)B;~b zc~HvoWcp6Qb9)>F?}iv57xAmf;JfDAOC~GKKqlO~yGha+g$`vfF5o2izbh~yM36zy z76;8SaSMJnz{w#>q86y&q#*(Cj$EMHsV^j>rpKSy9@pD{aE7Vqq`#uoDr_6s(RLm4 z$YKzn`)ut@?=&-KsUE|e40f2RDE^80{7o-ZFEh)ztNS{q^j#I({F6J#JZ!_8Wl83p z2}h@SH?y*Z>L>Xl2f%`M@aK~fz}GpN%~TV}I^9amziZCZoj7!IIbA zMw?v$Y%ZZV!vsbyGKs>SRRCvskiCi$zAzZY9dcW4le+Lt3|8^k-EkcR(2oo)ho}Be z?-31ADmTy>$Wv|2U0hmvL^yZaYHHYMw$bkc=Zj+UN)LOD ze+Yy5fSnNynHd8>bZ?M(+0>ME_UPSCUVYBVq`Nwk^jTGnr<`51rJg8L!}e%YcscBNNX@Cd@tTTKvdoyc>kUwe@u;`^`;Zv*X6jf)6t@ z_)-{sQy2C@-Ue@qPj${N_wf!?A(iV3?A0rBQ?r}H`WQRthue-+%3eB*{PYPp93PvG zWCDlcHO&i(Q=bH;DZCwXe zdtL8DA=^{EFi&}sXGzU%$|<``Le~s-I;Dk~nn-H!e3U1>_}hk&j_1d{|J^lgJc-G8 z58^G?xTLOdvL}*m(af+NjwdsVw>1$s`YMo@zxe7OqRGezx;4glWqwqh4b5{tk$gPC z4BRFmaI`Ur4>Q1r63|ghM#mM`3?*M?tO>-io1eW(MewKp(#>&uT@hh!6Ta67Iou6J z85O2V)BhaQU3fbe%Y3LhI#K67n4k6#48}jIe^!v#jAWqx=X6oqob{@ylaI`Y)bg{u ziJE>lJ6jwRsNd=f-ZZ9=caf=pUi7m!iQVR=MD3D~d9Q)pqZjiD?L}3yoE~|+#m$R1 z;|h6<$uN|(7j3C}FL1!HKiEFzdr8DSkxy85dvpNj&ZrOBb~tMD!`f{?;rbX1dm6r_ z{rDJ0aNkL0s?s2fq2OQeAd z7me;2-o-^Kjn&E@FQBi@t1`fx^(WUZE_d$@cIq6UmX^b%T2j#`p}O+oscsLtR*-z3 zyda?&xj+3y1RP!@{Yz~yTXwUUaGXuGag%)HT)#lge1Q!xY~ki!X--!|nim@Ev9K5^ z$;Z5cpQI03+fQ;o+5wCEpq&hrS7cT0h~jK6ZA8yKNwy*Jy_;N*KWHo*eQMHyYV+Bx zu({cFxX&GDGtv)NBCGyUOb#MiEAn6Eca*mwxEg}RNEo@=FjKXdX6DFCaDkI$GJa2C zw6Q5+yYrg`xY%;2)i`NSk?isSKL84EXTCnEy6S>zv);`1qN1RZ{;~&qDjuQb7)FZM zGTz8OWDrl&i%8+g<2KTz-AslA9dhH!Fq=tv677YSWxxw^$Ole**}(HwO!iC{^E@P4 zdgj=qo=tYHBS@~EhF;jctJ8+r(`VDr>;>MuHFA5`)2HGL!5dD9(U& z7jR0VTPp|m>mw$^-NhlN_dDB_>ViL2HC;($X@rs@h-Y=$^yAa3uP)%-!Y>Evqd|O@ z*|$znv&?C#LsdrIT(;0T#0}lsw&ve=U}fhp%X{s5I;Sm6W{!PNmvsePZ#$`y8_-^j z2N7yugXvf+avnPK7Bz>7()b~&!N!tL3@d@k%)G=EKAIh?`{+y>!z*v2ufT~xPSScE ztiPD6?p0A%S5yhiM-{~0;%(Z(NRiyMuRV#?c5h#@YQ@pannfT#j)Lo_j4P4{$D1~9;E7$V-b^)VsbiF zZ8C16sj5A7<&6yCM>tu%Ti^srfxE@@hQCb*{u8c)%tfutIUQ^)TZVd)vMS(D*7DCC zG(=&fYt5ueyI?)1%sRN0t@N6uQSi)k#*3t$pLVp9&2B+cumdckoSCUN>8aY+9M`+Z z)2T;}>lP50Rip^DXAf5-$j}Bh!Sv$URRCo)B&Mt-zcDqMzl-i^b<%yRBHg=en7*J+ z<3an*u0*SklO?nhEyXXY?`Cs?ujV#b!7|}3N@zvWBHAO?*fwA#^?xkm_PLB|qyD&A#_y8Ng zxfp|r`kM)*)2{(9xd;3x3_dfQTQid`DfYQdgzIL-uM(RxQ(3&D?_La#v;$>)OTL0@ z8bt9SYY{7N5ni+vuw(Q(fLH?PAz6@@PR zAzfHv(GCU6O8U=jV0TNYO6@osNklm?t6}U7453de3D?q=wCpGBj5$IT2vN7>7!@M> zs6C=1nFIq-cdkMMby%emTh)Gktr=K$Aoa5!993I1LhsN{v5Cd>p%<@^TYt$NGse1Dxo`H#&&y)gY0iv0^Ty2xuF`k5&LApg`R^A*M>E0 z%=RuEUGgxhyw++Qcf(M54JRe|H5h(e9N8;yH`b*8E00sClBmJo)|cM08tTCFXdml> zrcwEI9Wal_ION8nPRYY(bW`pW&D998h3|hoymm#>v1Z~9JAt0whdIBGy+f5qLAK`y z@U>6!unAK`%mF9AN#OxC@!TSvdZRw5_UM-~0Xu=t!ZGcJ^<5@ffw=w?!{LI8N?-U? z!#$lvAK@I5spbY7Khkr4u`{6_OwD$&6(2)ds@y_x-1&)DFuk1Pi6zH+Kz{hK3|(n* zCyI2!>!O;|R-8ieI6aee!0}nmMM7G$Dul& zA#N}WW@mcuPt9YmqVczt;O58BKOLi2y8{N4iq8I`ZN#+GPCX)}Y&$$lJf}6;Ty;Qi zmg^zZ&$;Ymp_=RG>{tFuEq=oJyu|FXk-PgaXsI7P=P*8pPdK3y;p0BT*=)*_`~d>| zAMej-xT`!=oueS8C1C9x*y_j3q&Ili9x?~}nd#t1{lKJt+Y2yBYe?+;pyM)^zk=s^ zKnAHYTj&iZvoSVMUc;it=-TH}2bniRy*d>)k$;9(gbv!1(>2C5mNqJ|y_Mf~+ zGGt9u4jm*4ZdkXpOoQKw(=SdXTOE6?H*b{D?8{N{(mki z_Ho$OOGb7!`%|Yv!;+b#vAVW5yjc!V_*ddLC_*J!QdR@8)}kZ4&k^c}#!J?U z8XC#`9{}62gw&k+__Y2}M@Qkg2$CD_@~TFd(r+} zw2%1l4R`cwZuGr)ReK6HJb+GZg5~cGM>*6cl^1vxW$4tpq9{!b*EUWNr-N`>8q1AQ+l)G2nm(*P}HL2@(-fRVQP zf6~P;T}WJTmxx;K5wm1z1f*V4(?Cg&}j)jf5c=UaVrrS?gdnBQC_{pqg0;GvKR`D=Xf=W3I~PiS;=lyJ4m6(PERsVTysu~ug+Yt$>}NX;+t@BnRJrJ z=rDKF4>c6Co!BC_=c|3fW|k-Pr)+;A=VrZJAyRWY4xocv#I}hZirsfOa>_}KvJ>4p zb9*uHg%CQ>jLHV69*a;H@*2FkY&99IcbLR%?Rjdd^9g%+ zpRTiQs}yRE#CSv>fi$h*39kml+DQ%FBYe#)F^}DaeRWN7QkQ4`FGboxF|mv~`H5h9sDhKzGij!lC!B*`a7=U5Ka&tQQzzNOETQ*$P0dKpK7*a0Fhf!9 zEHPcEvd#bR1B!(^?}&LrDosZfsi&(Ox;?jgxcrZebjRUS(}D4I<_4L}4By*aWbZ%^ zvb((I4sTpDG+VdW*ijxtd<%C1=?ds0V=>o#W0G4B-nEh3ZiO$PzYJu5`Y1hVD`FvO1H^J&A(`AW2t&r$Doqm$W1T}#K^GM8ZLb)y<$!n z?B6IHrl9Y*&0df1XncOa}o%{%tHb)_UHMt7y)m zO=4A!%$bECllSO*9;2i8pk!Q*Myo5gL2v4&zmpOSFEg3JRnWcU$>u?b-nxgX!yP!+0Au%r2w z%+2lb6n*AEp6PdP>KZt77o*#Y78THEwgO=&i1KO&%A?r4n+=)1PMD3P`gSvA%xpT8 zNOUfxnKQC5cYdV?uQcImpPB8{u}7U~JH_b@UpI$5!L~TOgK;Roh67s6-;*0p=qGxk zFC-4suouWtEalu2lj$EmlH%J(c0kj%3GKxx`t>oOHLuaz1j!=se?#%z_pp6&lHLUS z?=SY7%OrCx5gj>uYz^UaZ4#rMhC7X>Gxh_8SFl0nn11GvpHl*s_9MBt6ZuX^47VHc z3spsjlMD1bHKZ+K&ez>5d*{Zv&&xEnH|m5hsa?u0yK-^&r~$mzk~8Kcs5 z;9z^;^?s>Gh^6E(t;3<71~z&-oLg}wRagS^jFab}V@VHdQWVWcF_6caAnZNxye>i` zxQWVl0L|o8lq5I7N3V-_pzi5#%B<6aVp2)SipvHEk(BT7uU(GX^)8*;Z@Zk?KAkC! zYJWKO=rp}TB-o~V98|1uO< z!(3!)Be@r~OJh(#ALhmODmy#i-{EFD%+9`Z{Eify>nVKwiI^E`&Ayf%SMXe?+OqT+HK`g??L9mjmBk?N zpSN((Ddld^D;#W8uMqU>XYstG!C56&2@1bDWV-z0nRK@4)p2J1cxDDU$~{yOw)S-*@AMCS@*T6% zuHszRLIHFXMmh;kvL`pk8yrOi)Mz&3_NL;^1yK${sdtP{U>HiPyR2Vs`Xj}kQty;E1xORMSGsDK?2NnT)7A7(r=(?xke?rgL2~*81E0d5?^^j6p~7GCZk|K z9&ayOnoFtvx-tk>Tvghvlc(__vG*1{aS7W2F2hYWl41P5D)g=4wjjOgFvp4!PBWR^ zxyTOs5^5;SYB6pbKh?nbAZt1o;C{sMit1rLljRn8w?$ytMPc_|fugN5$zVLH(?M4wzw-;}lEp}vn4;rL z2L|@N+lww`JKGiZ$rT`ugY9uq3)L~Z73m3rn2`R$&Mk&rN&=?76fJj%IRdVL$BgN9 z0`tKu)0h;Z7;kxT@`nym|Hjfa&Y|=AhHkzC{m6UNSJilW6Hqy>VE$b}t^xHQRn~C$ zt_U0b85Nn;x@$z!*Q^{b@_*^4g}y{}l!{{*||)9Fy5aGBGBI0IL0J z-o6yn{Z*t9<>mxtq)u<*sWbtLdI?gIkFGQ&^UpJpn)4i!w7wqwPa~U@J}9}(LBBKy zH4tfc)ccxz4Wn^lrgaOE+qcvd(uq+{Z-HkS!fx1^I;9h?Bc0pE@qmCi>)1_G2#?`# zSiSAIdtgU-)92|+VC^@RFL%-%Qx7ifH>c?mPu3y9cL{o}PWmhPov$UhCtLJZ%69ZP z4xif~SjRJHd{S~Mn=n}{LI=+&h3~D!{L`E~m?hlc$54u~Gl3dZ*JfpF>2G`%B$%Kl zaP!ZLHXO43KC^XK=_VzT`WGhbcH}36GbfE?Ko6_@h6pEP@+^S{h4J@~QY_ta**tal^ zyQ&~K&TNz!=Xn+l$qgH}lhOM(0F$^67U7Wj-xVEOHD;dm;Bh^uqiw*un{acam(R@x z`NxD&v*M}i@b0@{!q$^zb<{ZWH@8VnZuXYwU}n=fzu?K|rdEV9wH{<5S&3_}3pKJ5 z=uR^x!0qG?1fZ!dA&;u&a+vCjda4#2#a}XIy5WdA2%?Y_25$-XUvvKaTIPgcdllzn zCpx&D^aaP!WL}~lib=>GMeiE`pSu{IFiM@YIh@p(&`yy>Iz!%+CBSG_tK9M~DwU^d zEC1V2t`@@|v1@%XWZYcA0eskWV}h_PeJ14!aV<`+6NU z-fR3IBgq*UO-Fy3U3E7!`aSOMZ`?VF=uqZM1yfVqwIYstP_%b7-F^>rOc!Jy^7-qF z8!{dq;!)%Vt%tYki(lpoT@{;f@qCt|lXI9aeN9VhX$e%=`9TV*Qjv!9mfz;7#u3NJ zE6ssQtFtVLGOZPy_;${59O)(ZwI*1_OSuNO?nyY)3uOFTZpV@O6_ZADc5>dZ|8#P) z2KJE5$27sdUV&cuH#fptHXQ%tE(ie4&m|7g!32TjM1w0&6FW(8j=32X=Q*5rGc_1_ zWjdttBB3oqmTW&bv{lT7C#m;)xFJXI>0|_j-;WYH6K_&?GtwT{_@T+>^uRH-(~or; z^yw*aqoxoK-DJ{-ojp(VUQ{O0u*J(%B{)ttG~z?(jV^b^|NR^9Nj;maLfAd^%iIF% z5o$7gM@1_9Dbf%FR5Fu*jPs9d`Rl`Gu6JS_c+7h4r46W%PjfeAhkf71$t(`{HHtIT zmhaR{_c;iZ%7;$n8i@Q|6B|_KzENd_c`OB{nk$&dasE?Rq6`=W>{BX*%Z>8g+L_H7bTZZR}ht>Bx2 z*`Tc59JYkJ$$DH;R#7p0Axq^PYS)7%7MN*w(~D=km&`NRYy~a4NyW@9FX3R!2&*15O;xtB&S6FqBUQ~wTm;|#hTXqnG zWP1`j=b-)D$t}KEyyWYw2V7wAZm zxQ44Wx+ZxEH)R5J+*yFV_w@n|YO zq7G=y)HWS`Qx?9qVKyflWE0}BzbWpZtESusD$;~?uWTQKE;-QHI zeeDWvWYG`d9$@R&dC?XRV=!D<8j|QH;yY`}#8;fIJWO^MRlr8es2nmG4oojPvp_Y9 zER$1Y;(e1X)DM|fEtdP_SD5*-+-&<%ySG47=HQe)1G+KA+yFsoMAvZ~Z@~xnndI^% zO4|Eq8n=Si^?~JUYU?}wZC~dzDyGLIVf}zojosR(|RAG{;Iz>Xg3 zrJ{!3%#T6L-Eq<7mjanjjxYIz+f!cmMvD604q}rxjRswV7PKpf%e;>b}(}#E*LncI;2w6WfEb|rY}xIm2O~L*Y;-Lp!g)KQ3>rpozZukv z>7)i#S4q(My+vO=3FPOIILsM-%^doMt|mffB6(o6dFlG2bb6uh>pP&kNAyM1uJzEt zZ(|m^$_}asQ5Bs_3Uqb7(1g^cyS@yfUypt$X47|1di}KcXikEWm6r+lifVBK4HEg$ zH=d&E=fbrx6un*;+V1^yW#`}r<1>RdwvX_~odvh{fc|!)>%Pk@lZpAWIU5t|qEe5t zEle3uh(%^1KC$L{u&Kc`U4?{+c6uDnkgBi}cc`b+(eRH2IlW~v(7|7{Z*T(Upi5}Z z-_C|~v5K$v5O?ok`mf=fRJK!_Y4|%I(sLEyRFs#CTwM8_hO2IE_NcUl8Jnz2BuGM46Jnm|J|>-6Z=RFt8O<&88vRjI@PO_xeg!!j-|#+d=6i1szUB+h ze-#d9FSEx{n-|^IB@@biB0e*mTctR6Q%yNC0w$Sw%shmw-rW7LYS^0M4R&yieplyy`zT-8JpsM3?%cZ2oB>){nrBzUPO|CglYjAJ8Q$mW_*ElejeFrz zZtt9@*UG5&qC@B}hM*p(tCr#z*u>tj@93*4f=i9!KilkT>L*Dv1diDs zPgxvxD=io6L85BIpC?6$?8l!Dp2iy$N>V`^?u7B=sNFT&RRMck4Z-8`AJy)j9i;rl zP|%EK>KcD`ZFyE6l+E#P`N}8qI{J{)xGR6r5&Lio{_-gtq`Mr+n~Qz~e{ym7k^mBF zqWLVt@H;$lwwjinVQivZX$FG2=20MtAp4(j9>)0fLiq&4VTNmS?n;5$Or@V|PjcxS zw~5{6O=RPE!%Z$P?}+=99RfGlYBWzexMr)Pmdd2G?2R`(G1{pV)Qp7OLp8Zg73Z&v z^+R3lg9;)iz1~8;``<8D>4gvV>j+3ZGXYxAj`D?#OD17W6;Boh@x9E`8_X?KUp_?r zb{OsKS`^38da}HTisvs%j3(@uKWSdLacz5YIi|YDO+xk>e%5o*j%U#4t%L5goem^< zJty~4B*@At{AqQ0qA{+3T-3A@eC`F9&OY!dmw?|`i&m*Bz0yQx*j98wxj{;&!9h;J zmsk<@H9%wl`M66*wwbAJH=S<;Y)flp836!L*Z6ebX|D2FQh=6n-GeYd{?QII)HG9^1*CVLx~~ zSmh=msdTY1C=LFQQnA4L@=wdc%3so#?Lg4cI4BAxfNyx{0NJL)Tl)ewY$OSvfi?)G z@->nRnu6@y6r{$8M(!K_>;rbSyVRznn^~vJkSg(o|NMtvv$L7QXEd7h(6;1bb>k_G z2Xk4;Gn>RbnAW~z58+lg#d2_r=k!z*HWJ@jF}B!9HOwdCyyDkS-Gt*W zv&rCn6BR4`%b#jLSAMVXt=s4EKOTLZ;#Q1qove2fw|I_XcN#paSG&Hq&aZ+M^V>Lu z0{i?s^gGX=dq0;&pN{O~=Jghi+#2yKB1_~_)81zGWc9hHUNFm4Wlnu3!komOOP)2J z2WpAhM$PRh7pnK9Jk8ceMP_}9B*HG}HM%;7#c9=7tmECgMuiY)Zl~HbW*^%Dg6L15 z@kwq6GYcZqaTij8X;GeZQtaGzto}FrVTTi0o>;_jZ__w;^9GNK6!n3W1@%qL*F%xCm zRJ4T0K|}A;{r^E(m<)YTa@o`lWP-~8^FK@1B{OlA{_XBG``BubpPSMp^>?D4u9ve# zdla0;5z+=n!4xfH(<<5iaFvreYx7_h2cct1gTMR*Ss;E?t(x)>iEkP%e=W+9AUgRp z;8%vO@DIpc0J%FFZPPciR=aG*jEAea1lY?4w*H=Db5vZEB4@}=J_Mc|1iP~cH0ZW{ zhgzg_$fnk;9|ic{b8T`=pD>j;0p#(@ox*SG<9V>~uPs zttyY11wx5R-ZUk5FT1LUR)cM>_#|SZnBIUlus29yIVQxnoia26V==Rd(+$7 z6mw6RFn2P^FTw0#%m<=26MVWfF48pYv0Co-v=80;w!L=HwIzoIerF4CpG-i1Ulk1T z1Mfp0HjNIGb0aQC_xUvJ>(E>2@2=ao^T4~+1J>4Bw`2Xglao$If2#HMgzvn7B=IAI zS_hnneLYqozmxtM14acn0i&HwDwB6=^q#1BQFZlpz1lRj$K7Rml-pA~Hi1|J=XD&H z=ymf0=lwYSny)1n>8Wo_UA>gfHHi1T6@2M7^u`%Tx6O*$gv?Nq^inXdG^ML(!~GrN z%vQZU>7BYhNu32ggH#cpF*1>-JV~PwydTB!O7%AN-B@;+o56;=(@n6xsZ;9fXuC(5 z(e(AxIQ`w3+)~o*u0Z4N2V$5?wM6yU#r(%f-G-*=JZQp9c>ruDJt~UPM(C!dp?k?3 zcAMF~I+D||3U)U>xJ@!SQTfPYYASBG4(g$LuTrTx)Qci8^pluL!Q0TT4h4fx!gsfs z8dw;A<38@>GN7O<&c_f+WUe=_ zXy&<}^iS_+9pF zy;VP37EItbH}nR(;QtK4bojDxaO+=oB>3MAy_Ag2;iPB81>Jd%GAlVu#SH6b98cL3ENB-l3K>HMWr)-yb}1m8o=})WEx5p z)34B{C16@R!k@R8-2~lbHg@j_aOx)DYRBj_#-Ug%t4fQVQkzIMORbZ#iLWBu zPd=#N(QbZQz^x;i^SeL0#bCiQ;EZ`Fwwk7-eXm9#RTxiyLUe__NrNfsO>3t9t`a`( z^`7?$F8N$5vwh;OhO64FS-0id*0pRLNy|P^{^yKe-dRN!3c|%p&0oKYn2opLy}JUtvL98)B9y>axtrT@{|BPU<~`Nt*Z_1-f*ZqsUgpy+0=Cv& zXU82qoBWlXu$JANpXL||tzDd=B2?vt0ZW2kw;W1~6(B0#V0NdIc{ZBKc9YXc74-a- z-<=NZXzt4nK!hKjs6Dw*e~V@`tO-XiZ%@#DB&ZKvogwkmHyPB!c|)@hjuhr*>CLVFs` z9sNN(;l!+$6=grUhM$vjZd$3`vJ$@9W$1|JiD!DA{m=D-ZzxGBa0~RNXP6VDb6M;o z!`jDlO8#~J%FNDh{_So|4m-p~Q(0^R8Ow>fzY<;VM>v87sK-vxBew;GNWj#^^viq^ zn^YeVV00$Cm{5~yC4RaCL}7g!_jY3ZMZQ?Fmqb(q<`y0MeV|!PwpeAN_O71HB{io_;-<`yqrjMQ!U4sXG3k~)IYr0G@X9FH zk;66uHS+?xf&AQUGjstG=s%h~?ihAS?}Dp7%4As@EWV+7BE#iAuoI6{z_wMHbpv_P zdrI8(`s0c{BYT=E%HS4`(Ya+eTgfcBY8G%ShS<9(=40u?_N}*~QQk8;vG=A_H?`+adhqpXfb$4yaefbWY zefIwO1vSvgm2gYKka$nx-STbj7vJ}mZxO$0{%=%2PtNG7Zb-zOagXvNmyZCVXpHxv%-DvH4ao!Fx-?HfJ@(=&RC6X{R%<)Ks-ZPwMC>@~RTzC-Aj}^agy& ze@s#|4I|-5XMm)S;KuAimfUiahBxOB%CO^b{(rZNqOmZnmL<_PjijnajBGRbwJeMB0z{-J4RHZXr$29d_Bmn6iO|O|6b>0$k z)n=dvs);tChdgPM$o*g@zUYd!IgJoyXkSq!~2}l3s7t4lKJ%}_Bg>q>3Zxp zZ40-RlT?*o%oEpTfBv+COq=nfBRBEiThw$u%YtlG`lcd1+nqq4oSrDpZ)cC^t$OUl z?6#{3a@iMm^*EkR9aw^D@F;0x{q(Gkzaeg;3=`9qE%mophQ<-~$G3hPQmgj3noF|G z%)Evqn)Bb=d@uiVYFLxVG!dDit4F?$TpRw+T@=02GfAcQJ?IncEK@7c%0}SsTdGES zI&zn`)|E&_8c7GdkGC_mI!Vv<7_RdvRrE5L?Fy8?E$P`7ny;2ln|W`LV9Tgjhsw+F z`8@#>{lr%r#Ld!{b{|1p&K9)El{}4n|%D-x?Ozd=*OGwk% z!^GK%DLJPNLDPeSSHBhKbbs+w4;KBH=X>Hp9xM*9@o$?tkALBVB?%gC<2kC7N}z); zV${>9jW&?U`5F%BDjTqh311XDR5=sh%)m=Jg)_Vded22BR)}fu?ls-rq#}v-QES;G zdqW2~8QE9UIYwEJAMQJRPd zrba!K5iPKXTm2ZR&%~u{UX`xP_0U5&G+^Jgp3P z491Y|r%io!&7Fa{&W-DQELgy9dh?B-rqj^J41;570b)CYPOB+A#3*=(a;g-)(?@et z%-7%TGxraAq48|VMg@;YJOfW4X1bM+AOY_Hp^OMmI|AdbWGYs$Z*<{#Rmi_~i>%y8Zd`XHOhY+)wc< zCP?6a$p5C#_gHKEG6wkhZS>zSimNTr54`E!EAAMOlY#VV^KeG*XCuioQN=b;mqCQ4 zqh#KJ2C^Y1cq*Q&-E6(+hQsy_KUSJvAUAhNlj>y8>5J@)ol4flUn%ii1*;RBwGjEA zsv-}nVseC9!S2Z`b~c^KL25=F5c!Ot0e;lu4?Ky%@bYJLQCnNjWpV-20+FmOS4uSM zGKG^yTt`E1$l!P^Dln_|*2ipI6fEn2)7F%?nTLD8C^i=bc`MpFg;j_rwlmMOQ~h&tcTvK&;+;%J{~s!Mc`C_^p1xwR z6G}$6VD@w2-3F7z6OY{X(Qt*&_%TfG(i2sPt>#=4Ih{hXm_iZ5*ASn-v555n8~>p5 zp&hKmGi(OFy+;=Ipo906lL4OoaxqV@jg!Ke>MU{gJ8PZQPDiJS^I2V0ulX9+azDJI zFZ$&3C6+fRGGW@0bo- zalam*XL`)sF+pUJ$;h{U$yS_ry!{Q~tJA9CXaUQ^$h6|duEja-Mn~S4?Gv%&bL!tP z-I@fQo8-zb=H}kV6deRdH5$SvaJze9ejI6WMSl47$BuoSF7yOtAR^ z5A6-p2`}p!I^z;{lUv-p@K(~Ny?#2}+g4X`E19kCBU_Ih$5~AdCkprObvw>eT4eV4 zi&V~j+%T8m8p?<92ST!pfUX5e;}flxrIW| z({_T{m~LC47i-AW-`XsX+5b#FOfo!tF&kV{QM<qr4~ezvwr5yf@f%CUWnudqo!lzZt7%GGQ0OD-xrr>_#6rE_!P8 z@}J+s!k!;{w)yO^GfTJp+gxbP)HQkcOy1k~(YYs?QCWs17@DYTyvzwQ`xf?_80$-{ zXa2wZg>PQpqImerMXis1PwsjxX62#e(JiN*wg&TDB|$$FJfmcHsa%OSx(T@7B=%hg znBs06Gv9q_Zn}5OFY?>d;2u6@gUuT86J7Xa-CI5}z0_%OP!+~6-h~eA1NoaRWC>V{ z^k9dEO$vTEM|y$Iq~#MFfWst)L;ORE(=U7yWI%&f55c1|1_sm@-RcOs#E)#!9PC`P zr(tFDVOi z;SB!68f8dQr{1t80keXmYTP69|NHo-L{!(vAK@Lt7e$necx#rK zDn4G%XHQ4ZJErJf@PIo!O*|8vZO%hGRP<-|j3RSvo7e(R*P5px#Y{97x1B(BLG1wN zu8ZckHA>=QViZp+6-w}AIBMRaqUg`BRwF|PjEQrb${ZB0^=G@D>HQhiE4EF8pCCxq zBf%oQGeRzPs^H!23&WO`{ZExpU390;<+L$~XiEDUE~c4D;XWl@ttyP}dwEE|R}V}P zXSyxyWD)}X)1E!4%C>wInQ9L5?GK)^6 z^XsARdi~b>N;mXc-QL@W{f4K(|3X0!f2po!xs%3*J6S}2j}iVJfB6UYFC9v<+8{x( z!6>Hb$9zwba4CP`rSI@5ZnlTSssD46<=`o2iOQy-EdrLP$t4cgdCYL8%OE_?fod>W z*&Eqkx`-~M6TMzPc(`DZ*ycxJ_Eq*l>)lV!!Ot@geBrpcPgV&?v-!hD7SyRAmPb%y zH)kejPu590SgPYFBq!rpe$C_=3#Hlu=Bc{i_nGOqFMzc4LHlwR?EfM;04%S-y_FXJ zI1~?eU)+*Cm<8gY^*ZnV(}hW6?8q%PR;|ESJXMC`(`d_kw;1*?zFnkSnku+fL%oZ2 zCGThO_L}YiZ(_HdTg(092I(Cj5u43I?h+3@V>#S!u6xY;Afi^d_uGW8W1q}_eE7_* zGy8T#?hM@0ZJYnmI45pAJ@jIbd(k-YD#p*7V0?niv0n!6@hj)o#5c9?LnpuIBzgS1 zqZW7vL@#v<=-Au?J?X}Wno9KZe_(U+%Clh5<-klT@~Pg|L1@+TqFukBtK!Rg2QR)* zmP8%#6Hnj^GeZZ%Bn_P?0`jN>7hl_=3&EJNgwAJBgjB z?-mN z+R;Q*oU^1lO=5Qb1&)6fRSw7{e$8*x(bAm2ins&wgP@cJNvenQr2+cdAU5B4aeB2y zNBsiKa-kR?kFX7-f-S6e+rMBcNt}A@ug)t|^ED@<_jv@b(2@?g3TpokxZ@&0Qx4Dt zFXP`k0;0Pc7k&Yl#I|(Ny)C&hC|w4FXr@8WV)R3_d38xRI7lu{IcJJ2;&~!ld%np8 zp15k6lR^bJTDDU+ekd*J7!ueGnt*=e}BmqHt+lG@S@@QB9lgD43883C2DNcFf~!`h?U)Em$McY%|n7_ zF?-+H0#fEhL*5@{*%Fb)xvv(fQ%p>mWK34hA9hSOPewly(Y%#icqUKi`Tl_4 zSEm}BK+zlXgnOXGsfNm>3Osv!Ts-~JzRU-Qj1Oy58*k7Ep0dk6Sct`y3AIu2+7JCCOkoKI&EG27U2wyW6ZKp{S~ebQ{`WcPo>7AEwh>{GO3K|7}dY zBi%~khntX`;BdK2cTjC~3)M*HR&(@i{C_dplQg0bxxbc^7NM@8g2~5zhn3)OKS9Q# z!9Q<^$LM#u>V9a8r=z+5i4wCcZiz(l6zNo-z&=ZoC>S9B!&f%JR#U}zTjKL4=0QK$ z7u8URX{3Lf+xjV(8OcP<>8){7YzOIzMXF#QZiXRbKb)gRoine^XfxkbrYC9vMt%V` zc?f9!GEl0=c!Khqc48-U<7_w~S0AJgT@RNLLH_4bw3Eeg2Xx1M-VG(?A|~L6qBI#8 zP0?cBAZ2PGxZ*43qYq32VK8!y$R0q+XHtkJI5%_R^H63b&e$rr$kuTyQ3358Qu*`f zV{9JitE0%dn25))xeTHMuLI88%G>X6QsU_^zFv1>`o-X_LE8gYU09Q9NB!L$?oE4` zmATg&v?fuvpg0K&1|$hc;kVJZUVwiEK8^^(|6OPNLRM1p-(P z?Z-2?qjWe5H^RenvgLd*^rdclChZF1nEQj=vElf|=po%3=)-=SHTn|TCf7va?0=6} z^&sewSDs|<4OAV(J@oD~Vw~|ZD@v1=a)+#{`pF&YjY#S=5{DdP8+aDkcAjVEk@HUn zJ5xyan?b7e1a!}p^={KfmoU{$XC{#YB(ivMHFhQQj|~~*9g)&ccG+us$QmjKeQPr_ zLVdy?)W{a32hC?opkgk99>IbHMRC>#p_7f-r0@h4XBE!wG@F%Vi`cyHf2jsV)JwGG zyHQR*5p~%g_wb!1#7Q+BJxC+eSmi;?U+_$?fVT|hPaPm%i!&tG%trNi6Py<`70usd zHW)}xJwN||!ifhZd6g|s_Mhd7mYdPwalMEJYwH~=leJXQG&h1@%P;ot?-{?A{W|i~ z^KaH)PsEMz=8>%XkQ>H1ouFNraKQo|B}Ia>?9R0B@UJq=(W;u zUTVY2jbp#z5)!VfigaqbJxl!>iFdOb>9#|e6IRLp*u+vBy;3{$O1ENqb5NsIIJZvl zsWv0oWiu}8|CpLipf6}n+HhKF@TSi}PnQ5>DIdAhzc^F3$z3{Py?Pd0R0ENalbPBs z;rV5Cj$9>k zlHChlvdD+p&ibPn&&2#ecY(#)g3dGqJ~utH_hS+oy5SO5Y81I@qv7mF=|vzY+wc?( zgmsz5{MZFHG7C6rdftdJ;2TF##%!|LP(S20*FeRpk)zEFLKhi`8eoTc!y8{gud=Q6 zF;wzvaoZn|aloE^;V!~ZbG|{d@Cn`M9uS0*W<0Ku7JX^d9lADhpXW)3t6z$r5S@(7S&HU;9Vqc~R93htzJ}Ns`pYO&teS?@V0(`FR)L z!8rWE9l6%#S2LLOK8yHHC>%*~x~Jlz1>Ev0oPxVanwSb^G{H1dciCKBPj>cPlpCEK zYy)e}^dE-ySyKd{SGL4Mb~6G^+u# zP?L%6Ac)B@w9k3T=-7n+$qQ<{2X(+%X7Q8GI2FfJ(OK;5aH^_B>`zsWpUtG|gYu2y z`R8Gtz0Q8W0{nf;#42zL9BueXhH@q%O$%}s{Yg9YXZl`5&fibj%4z5s6?iswy;KWQ zS1+2RSX6^=4HC9J(K>sB#dQbex|D5&XOcMdBlzq1u)p8#MQjVN7d0d*;$O;$R}o#J zK9X$u%ahFK4v9{Ot#%v8%R120TTE9od7s!}go3gOinKR$yq)A$^$pi-9QhmE<^f8t zw)B$4n57KouLAtZELh_bD26^W5s@>B1F0LhLo}L*(jbJJL8P02;?-q(oy``4hos1+ zr+@DQnsSH#jzv z{iN5dNWJYtx7Ub%GCrR2U-VM7II#)PuWZH-@{qr~8_tohu$`CfVpB}qM#(zd_D4Nd zoXrA1NJbe;?&4MLv0rozJ61m-UpX;~@?CnNo=tV?t`nfR4;H=Hl~4swM^D>PwIX5n z28xCSAg)irz}bYRE5kVlni22@Cs9n#L0Q%gFL^>X5wwpy7w-4v-lxQO_TR3#|JVNO zt9!1Ew?1^^!!tk5kNp1N=lQ_5aWe%SN)#5bJ@&6yCwxEqpY|^h(Aoc-dMtgtQ=%Pj zckecs-O_Ta<24a@2otfpq_#>yH`En$tPMJ&{^T|c1rrX(+p>@y%Tsv+NAdOyq5IiO z<$Gp^P~G>F1Tzd}#!nNIwzGu0Bo~_g3OI>!#cTnSzid`?jJ>ENtI_vYLIc^H?NNhW82zz^xD!R4U+X$glMhaF;D-3quGVlmQ+>l z?ZLQ_mzd}HEzY79?W=~9x@5^`D=jA|k37ZnAI_eGRcOqXP>&|?UghSUI|hco&Xxr= z`bvMhm^btZm1(*@19DSC#?zH#A@0Tr_`{lmzqEisYY*yM6NIT2F2CYVG-+_`Tz6L4 zo$9k$qL!I^BDdUAWABhmIU4?KadVKB5$-wHBQ3a{8So%(0KDop(rk`HBaw80T~j zuJU{6zDj`9{gfBP4|EH!oWH8AXOSa4>z$3tmpWfaowd*Cv@7$qA5p)=a`g-xawxcO zb0$4_J2rVNpdXFd-*Jf)+4OYPsi~z8qous-T!?5-7jil)P;!05#GOx{#J}~BH5;G>v(d)q2<6~n_gFT zzHpy(qV9h-tMxKB8>-1=bVq*NCRIo_IB%Dc=CwdoS1VK-wot_(Nvt(rR~eE^)>9Gd z!s84OsX=zepbsm+91_V+%M|Js6)|R$#VwtNl!D7};qbnE{pn;Oytq%fZ6D&^sw|)X zA7iR3mVk>4f)U-ooLCGVKC`T0a;w&+m^0b@a7LQap8CemlbWsGr?e}}qJzw&Jt8X# zf#3W)lbA=+sBz}EJYjaj*i505?~H%GG0KPfBnG5o((lDA+W>t@HPcgn(s^_uvr@Br z&h+CxEH7L-6ZXcVh8anIR|&RFtP`u4mYRX2m4`X}Odil!86`@nF?e(D;>(Mzt!E31$Ww!{oCG9%>C*`eXEVujm3D+(4O`5`9Qox`Ntx88vjNed$dg&Uo*N zG46a6pI_lf4oNNv_T#)k$xD#E~WQ}g7Pe+f3pD2Ti;e$y*)vAPIcm|Vg z2zSV8xsk8!9a^4A%!A`OOe--}}eVgueg-ickXus|5)~hRlvgz&vns2;K&Dot*>2yHrx)9f zM#*zYoujYJN>A{cEj@Fo9h1nOB&V5)?T>junn4!$h@9Ndr$HP4(a*g_J)Da9{wbGil@@nJfp%=k;J>3d3Z+<8!*iL^wsF|*xLD0Fzu9H@gj9}cY9MIs zLHaud7I0Js+DGcBJ?o4T={%o=@LU#8oO$Awvr!y?^DXNXfzM$_qg;vuw+=e`q$q5z z*($o9-H3B=q}c+ml?hKoK73buY&Enq)%iX%+w|~;=6cd+WfjQm-3angFu zIm11BoFblP&UWX$D$TYebf7TYc~LqYB60OUR2mz>nwK$mo#*^lZd-{MnGYB_SEvEk7q9)(+ zC$e(3z@o(SoQy#sJfB{#RPqQJ3J<^?hMB|cV_jgn?t!Ha;HjJiU%j4PVKv{%UUglb za3W*?x{cqQJY+b`1Yzicmu@;9w*zGRK4OY_io$pxZN;=>`~EULd~wE)S@Z?ye;fVG zhT#>@W;)?*v&?xtnB_B*i5|0iezQkuT}bry4~|(p|{qMT$lGK=1mbxC+mgllyhsK|TNRl7iBMx$m6m)ZYo959%{>?Oe1_Nm02BR!P|BI??w1$b>k9I1y@+$++8mByFG|`h&^170RM+ zDhzIW8Na|DJ_l}CcD%n~1V(^lTw`CU;K?NeymQo1PmJ2(j8JoNbx+}`>&U5HVvOCKHThHo^!;Z;Oiw2u6~=_FqTkW>z)tqbV9Ak*nl zg;(M3jg&8C9lwEoPm-id>d6_BGp5SgDlOZbZJoXI#ZE`+y{nfmTX6QNzGrU8rv{%} zfAxL68B;XoVC>uI3DH}B-j5p*-H06N-#tmV=Lezn9qz_}?JePS{DfX>j{c$#>+C4K zJD?sY%l1B%^oW#rT}q%zSk0+-8wdFfnNlrNZ<(2JevvIb!d}u{K*Ubl40bXbWfSnp z5%Pom5546+14uX|b!10^{)Y^e zwCt_fdG}Y_GCJNabjyjp?q$%z?eJ86(3lQHFL**OR?Ee2{8K-(&kW@h3I!AD2v_?X z>eh)Sk4|b@(wmtFwmU&RSQCHvmxLJsh39yAMup2@&rzAk+efzcUKB^sL@lK?q^Jr5 z3H6$M_}g~sgJ>y7nx&*|oN&Q=+>55Do7+6}Wi!#fGx)g{!sHhvo8>I*=3w+XDN$gg zQyH8PS2GL78*G+G;>rXU}NDz%Oo2ndy1-i%REup8s?EKikW#&bjL3*6o{)4p}5ddZObeB3+5M8RY;c1u17Kxr{qV%uTK;u~eR-zf4-+Jbm5Zsq9@Ec_j;V?&6^>$Jf^TPXT_PzRSQHw}_ zEXh;*2O8IIBB2#Cj=YlIura0S+X%BoaE%VucZ_ilnJ;c)TNkxiBEEy#=p4TDEZ0=C z)NplIRh9)+DbCcfT8IH|J{#>DVrsa4rY*nIyiEQ}ML1_Qx(v?2BeX^&Wxw5QR)9;_ zAl=r8q9hZ)H|l2oQSg|E1Yx^^?{8o6G?{4I4$0prlb$F!Nru$ zb|x9+CTB<_Nb5}0=Lf(!-q0+%7jNfLtDI({xidu;b?U2nDi>)iO_NzV!)n zL2r<@rjjIT<%eI^ky)LGeD3N@d@1oym*KtbgQjCV3{5X#QLh-XnmdYZPGaXD{KcL$ z(^7{OtW~f??aZ~Sx7*S#ciV4mQ&dh~T^ zqptl-;eLuc>133C-uwJ6hH_J$(bd#X((c|fYdvyfeFO1lu7Q`#0>?fbWGPH6R$t|C zXFpHNS5Deg@;l1bV=!JC#c>5NxodFO4bk!SXK%UdPDDrlADd7&?(^zkohdmp7n75= zhBLY!=TmpiR=6eb-A3I1*Fk0LsK#Wr7qx>_D5|?<;6TxOE8Ip^w7}<4I$lOI%HZS0jKD$}P z88@jI<>J;S>n#gjy=v|KmJZ#UshAd zcix=wt-`gxTwI~Yxj#?pSa{DjaIo=gNucQHyfQQ8?TRTA_w@7Qh=LExKD>6~>G9Q@ zPi=g)X8P(D`<(D;Pj)_1c%EM+Ka+e@l2S?g2aXHc71%WRNl5b0rhzF#%8C9?kdO2v z*B^|h3Yp0(Xv*qj{sdPDCQm1=s!j)5SMi9s6PLL3kU1HFzit<3zJr@6C7axF?rxMu z%*zQ4D=V2;Z?V6ZKvQ&!&XFDHS^b!1H{p#)%MNrC4Py|_v}W|nc+gMf#_Mw#{%IY1 z@O19t-f;1TTPQCo&ez=9jX~&ci}rFa?xx**0vzq|j=Pw4Zn)WWPTh_^m{U5R*`(`$ zzN|L?aeqE^S2G**gfHuht}QK?y+pP2yL{qU(G%}ZZ#9T_V>-(B)w&mYyCdi|R+-i& zD|kf|zN1~}ZbZ#riJpK= z?oad1hlUjONw9uE+H5M7lU&*e_PA-FFjqi-(=sE*ic7W!h}R-`g$SpPSm%@%4dIpY zIDs8K$a3`NHtMjfXovBcv%#a$BFY9Pr3d%VR2qv;$q68mv3%w=&LpyJU3a=z=r*^rbSRT( z8@R5A{Mou}zgH6&Ga2Sh<4v3>ccQ5o>St_LVoAx+UuQKe+3sBX;3jjL{w&`><(iqt`> z6&(WmbZvdW_;g#cc|5p+m!iepgbuTfGtOCr?mxsy;nY+umA}j{58JD@7`W09aQam? zL7fc9B5iDlE$?Izt(_&@^fU0^UV?*&#T&31 zLMB}h}hW=x}?TXHN6>sMOS(qG) zT(Y*OoOtA%Hm%iJy^Gc-!5LTcO0drei6(Ch%1&Bi!$sWrM4mwcg7kUP#TReO2D5k^R z`X3OkvnZ1MX#OgLAFHD~-R$r+WbW;TKdK|T!@TNKiIOSsyt2xW~UrH>q+!M z8|V9G^1GW%V*FQ`$u@WmaxsVrEHR1n{l#wB{z)3V47HbacgXQN2kHBnRV8@gYjz;N zi99$`PwB<<)sEKL^h5WN9_;4TtK-YYRE@0v{=%C(*IHctvS-bnztE&^|J+F>Yusob(}jzKho7kD zQ$3u?>ZZC(-v3?B4IkKSM^w03>}XUC%b0p9pbAdumN5H$Eli+W*EDc5!u3}(uiX&q z$2{2p{5}O-;0Q9$yx{m*QIh}RcxkThL>pm#^dZ0M)1r!Iv(}NY>e{&;y0A|;^Z7rjBPAAKUp6ploFz)Dntgr3sVt=;dip}R&q9__H0lkJ=vXE z&N%f}7Gd711X^|n@5ey;4Bwd+2^$NViR=7cpP<~E0ryuFyyFhMf5KcagtPVv+P-AG zsU>Aba*X$)PERc>k&2Ur)4!VanfLIm$8~!?vjs&OXFB0OJ7PJD$>XWb&os(J!1h*9 z6+~7Cd_i?pg-E@;h?b2;EqppBK-e`KTc}V*%?fchjSNCa-h-^R23AcE}@1Nd% zJ^1BLbff6-=oZnzk)?iKi;>Yo$RxhwS;Sdf2vzr6_o5SRGkgC~EzmjTghfirTwZ{k z<}k{~Qs7@_;QRKWx>}*C$hyoxDPe4`%7S>CGO6--LamAB+xQ6MFkPl(3%$vkP8JpO z>rK${UL-FrLZQ-wb77CE2%4B*FXFvlYcrBLl!Mt+k^1=;`i=o?#@ou)4 z<>N+5iq2+={tTBfmA&+vD(3WZT+c@}(tAX%@cb^aJM-)-6uhC}dn3`RrV)Xlpx~@_ zJSl8DN!7sxjUH;2c>|OBQ>L{aas2adWv^PnepdkRSZS1d-Efm!Id9G@*vz`O-=*FHpkvlYdLQ@tR z(k`eEmXeNe(F`U#Z4FJUcSWe%U0(IIQGMN<&Uzi_j3?tf8C`L${`Z{ULXqmz{Xs8E z;1ufTJLoo!`xZI-`;3<TYIR{sWKsH-56SpkpCu4{wrlk%ctX z)Oct7xy5J6yC^Nb&^<8&E^4g#7fo_w-o9QaL66~1c!@6T6#AwugcH#us$2DTgu-nzJ%TJg>J z>`{=awjfpaNhl5{0oY$0cJkR9G#50N!`#B6rTd>vN@_uQIE97y1_OCItI|J!!SInLm0c(b4l{?&a!;UPYpdQ_ zk2+2F0Xk(>1@$G3-XwMuK=rj`~vMN!cl*Tzu}7i zM15Zdl~6BIEXshcEi`>lL|WU&R>50cTMPl&sfc@|H~*VN8}6eVC;|e((5K?_nt;ZM ztRfYNx~!h+Cwk#6*#c(tlSG)BPH}PDdBW~IN{;j{m0P^*FdnZ6a$a+Kw?xacpBCih z;;XNb_#7W5iuit&f4UL!hAyi{9tjcp)2eoum!Gzk6gzdoG^b)0_pF-CVSbQ<-3?G+SBp00>-u%C?uIHw9b?w zwfq38igdaIohFG?WxZH!bOTk0n_KSpO%ktsnMlryviDtsyXqMUFJNTmEtqPke&VZ( zyCYr1w2$ZRAGjNNH2KkS8_RF{Z{6Mv-lNlxUwKphecgaULCsUFNVzBRkkC8YyMAh z7-W{3DrO9)Xb$_4xA398uHjj9Wz>?p(09|)1MYdr-US_RsHbv+PD^lWsI;WEEn?2> zC&S1^kCvO&OnDr{X{5NVm)hQLH5=)^hdfKDu`)%5jSO4?)>X!DGHf< zWKD*uGrG3=&%~==ZDD7NiIk1t^>^S$9-_onmpMZZt5Z5&EMa5) z#ob5_?hm-_FuF)Opj>x7swnpAO0@AEC0T!j zU14s(euT3juj9-MQD@Z!+14p4qntxxsb{D#p4MWWXCnH+7?M19;b1F3_Iqhi>LAqx z9=I@V61U-*t4ca&4s&ZMydm}RhJ@qhDhIkcA0)IY+V?`_W8f>pQ}GmZ{|(;o zQfQapU&T&!-wtxBi5s4)BHlYtT=N#RH9U`X1vOdU#ZNI3t=AIV1K&h*X5cYAXDt$~ z4{e^gW!Af8Ka?BTEPb=99cy;T)5tcsop)#MJ+iiV|GC-c!x3*I%0wNAnh zpN(TmMc=azweXfx4V^eLSW4*hYQ34{+^2zhh20Ax76dLjk&QfpMCo-XSz^g!Xs0aO zQ-o>^)7FY^>M-u&iDceaH+|$K9Leu_%OtL(7G$}lfrHM&Nkq@4?uAe2IqIMlO!{q6 zTYfa{n8~)|I!XqQxkOGeA4tk>BwK-kwq_#}>`p;ozcC<=e}gy{Lz~nOv}2t-hAQ<} zT8_)&%e>3H_foY)S!7_p2GgeG`p4p+4ClQSyqJO!HbV z(p5_0nBNNTv5)C1Em>z>c$+hT+Kqx&2{kKCEiwt>Ku!LnNpK%Jit=>DJV5o(nhkae zdaJ=OX^CiTDIl8b9C(@Dal5a^gS%dgr6cMK>WlHX58v_|y9Mf2i(O*}y_RyxVD5XRyGR%tVo%E*c1iL(B zTsy$jrAKUx+~O`2MSWhICccU}9e2s}jDK#&`Cif#y@=GeHzc|K$8-R4=)AS#okOO! zGh44w2i^PfFE_tT2YT`nl;;S{-2^fVzLMP&BF<|C%05Z7(f^Tj@&x74OZ7WlMHlG0 z`IT$4^28GIS&QSO=CePLI)#E^bmfOUrL~o(m zo-9k*`!p%GmNQIoCaeB-IQrkwsGlYN?D!=TSqPMTL<=&c`|1kNcsJ_g28{&>P81 zW`CONSjGO8gPJdH_PEWrR@G|Vsc|-IMCPkzkKXo0lijrP>#L}rk-MV%MWu{hADKFS zbZmE?zUy#h{n&tVGC@{!UWmV(=cqf*z}FT=H{A-2nZJxM0_1oB4B$Z6whpSI+{u4u z6ke|F{Mk^|2rTz8p0@%#t<_LwkJf|rC^LW*k}sT}J!v`qKxcV>G*THz){cO$-;VY& z5l^QjuXe9IK>Pb(9I+90lAH}{-ik@9yMAuQfr@M(=Q}e>w2YwlFj?XZ>`hANDbwgn z)l{@`{xsE{bf%A!&ZhP(=B{iy>3_n=4pEzIZ>NKGJk@M9PfuISv&{bMoUm)u5`On9 zV7G!ut(^}CF%>Rz6(0Q|xUimsM$rYO>Y0(Mg1IhJpp0$;C-ffFKN;S&QK&2*>0~s} zKQfnLBzK_XJ4udOIvYefQ&xRZ?{^pKuONvlb#iu>v9JKM+{MZ&Yi6B;AZ%JcHM+9MOewYKo= z6px*{;yn)VLGlO=AZ^1;8F32rJb(`H&5X1_!kZ9;KeB zuDUCz#7^+~&$zt@kY~C^4kpm#0nb>jG;4z+!-esD}BWlAOp68_7 zNOx0L5W-_LHq>D6E2z%nH!Gje|HQPDhg_S4wDOl?C^KKEJitu(XF^^Pr*2pJO(pMY z29ev7N0#(zn4>(Z4Vsxco;3KtuF_!0QzDk=8T{7Pkz#b*$)kGumkLT3x;L!^&Pj|}_qd(_3Bm)&&k ze609nX;kIrIAWWz*X9LPO(uJ( z!K%LVn~G35V3@-Bt(?bkLFXmfx~gzGnQ1S|iT)%%p3wZl#U<2;RITpt=G#p?&)atO z#!PfdGl#a9Yta`C_SD7GQbP2g2P%(@VpFNXMi&Ez{x6=Lia3&IgSp?9uXRo(ID=lm ziQr0<6S)<4lgZ(-AzZ_^o64ys|HENg(iv<0)M8TvXIu*k zlXork_ldBzTkxJVVi$jkHXw}@h95GVs?uJ?al_>-&9cMByjl_pkj|%Gh4>b_5$hJ`%#%kIZaMMP6yVg@g{o5{P0xu>wy{Mduo9ZDm?DU6PAdz)m(rS96X@u1rsFD{$9%8>UsF}ifQ+ypH;qKEltUM?x7>|%L|w$=HCmP- z2V$`9O1kxbb|c!V%?9@sbHy*X9Rirz)9@24guP8KPUt$M7xfb9$wMn<$D&X=FYAHc ztpW?oLyq52o}y1YIp0xnb%0m;|3t@fW*8ozp7?H$f@EA_U%oE}(1WrNZ^ubgaJ%#e z)Om%(N&TD5i~6>Jd5$A>h;r-{r-i8DsU}l;`oq$t!aFh<94-_@6CW5Yc|TFl%;jAD z&J$DCIjOojr5&x>ql(!N_A!7o-%30k|KLPe%wCp6kWm4W+*|fGi)nJUD3b^NZxRkf zk#tA3rM)5qCI3FLTWuy6wKrb!y|^4hm?Q7OeI4T2tIw0aLWh!#R#}IbN@(%FkfZV$ z-B%KE7xnr?CtS{U=xkH#Kvf^xkGLX!CDq|M@5)1z%!~Ovwbbw8w9{Lj^|Vm^y=T+` zZ*^6`J3=gT=E4sf{ecOxtn7-9E3XK39OtUvfRM-}QJF{OSXX{k<(Ot~TB>%3+b^$` zvi5__`7^Kfy@{(Ias6X~AHRIf5mzMobxgIWDUm~e)Q$DT9Ea&k=_%&f!U=T~6~=Vb zt-EA$c^uVpP7*$Ih@&KYjzmFO#HkL7+(Ulko^FX|F${;qK$3Irk}j7A-Xj9`Gl=fI z^l)_V^>nkzov2&;w(8oxOJ=dV3k}&l?%QklNq)t1`w6_+kxx}m5~0q}9IygCdpMit zR8X6rDErr-VA+Ub`ltzHPWu&|a6K^OpG*(wom;Y}vzD0w{*v=^luBo}gV=hVP= zh;w-~%Khi4N0zC7$qkzDE}mz4 zn24sM6{`0#q&HvI156^iiJ#Kmc1_>Ge?E~mnssU~t-iy|QJI#Mwfm@;p6In`7p}Tb zO?I~in#3I>|F2}fj0LBwv@nxZtj?#nKgqkN8#lf|%z__j-^9f&$aGk*snY1?ihP3 z+wF<*AADt#+)3Io$-hZw1k4Ov=QqxOci^_5Q2`eNH-a1$*BeX)aP!`322Qhe3Dcn- z&nE~ZcXP0O%sb3P4b~fG3Y)&@r?=s#zYBu;fZyK)_}%RA)3wO~GE%dj<^fM%M%Te+ zc^bWVWtqt#R}q$Yq*}z^KacBj3W}+CI`)#0s29VD7+`+aAN4esxzDYH@+KdXP*ZY* z9*P(2pY?cS2AavVW3>RyFDeB$_kHxtZ*>#99}RG7_r58KQuKve&o0m@Q0<&B`ALU9 z#}gS%22v{7iiGtYY7lPEzD#~2aUa!_1N2fc%zb08xd%Bd58I2pSrgH&R-i%r6-^6= z#2hfsQy?ALbuu=#ouJ-RVbCJ&Lw1ts%xKlPPcy@CFJ+&J)-BxKq}}?V2_dse#@g1T zR(4jSIm3?Q#|W`A;9aY!lJ+e8(lyo0Ud79j&sl?yGtTb9|8zt?LQB++$$u{D!daZM z3sp|=s#>Nk*U1ojwUDAxd8I=1ua!?=7+@eE=q2rsp$PKkSlUij&kmyJ~E<^=dJa6^4L(%LKExU zW(x0$Yv+khhM#7Tr?P+B;FuJ%(+;;+uKY@f~aK5Pv@D112 zje~I++fptx3uR$O@5_AfgzwD}RfWdRb!2?(*SSeTsxNbhi842;)fxCtXUeRi4=$Wr zXvh9$BTZmeOMxcbHx*?f@RTB;2EV8bWZ}n{f=rp=suJ_WZ5ou4k;3RNv)Od`PAc;6 ztR$cCe>dfybQvbGEYJI&>@SDFWJA;p{H))_0g@KuahW|(^F%Iq$^f|q#Q!x-y~DwW ze-lgHmN2u`>^zkHS@8W;&_W(@bBJHv`nIW?#C~wQqv1cpw11xMgw!6iBL9+g*-G@+ zSLl@CdojI1jkc33W=L)t2oiD#+-N?xOk(_2Gwoux1B^p%+TFHs`rgy&$kpg8x9Jb6 zyYV^i%txmz2%8l64`2>6U+Dvwmz~?rn!8pP#XG-fNylrXp zG997k>z4LEeTx>?Z*na9-THjX^*}H>gD{5BI8>C(xiw@lTi+_(z}Epm);%!Y!K4>8 z=IITkaS=aCd`a{)_P+aH_M@{m|Fizy+9F{M55C^t@WuJ(7rm#vc~iDX zSt4npq=|!G2JQP*GUz(hL`qGiqDCuL$XT3}XD{VcpCK4L1 zbHl!lHK)x`(!q<_8(>y-*jw7L>lG(UbeP?yGlKE&#Bp$%jeLm6O`20W`NM8Pbvqc& zwW_>tJe))>aX#}>2Iw^Msv9T9=?ZcS=k619E=S2(?!uXPg;O^t3c)#` zr_VXlVz}E%F%NxbpI*mL(v6Avca$-K{QWXcId-`dq()6(7fQoiKUB^$H`FWBkpu+# zd{DX+;{N%Ermz^dD|=W!G>t{wOFGQ$!B$jKjyKQX7yLa+wDNouA)W{Fpff=I7go-cl(RnPs8+7UdK6WCw8_r`jTaYT*XCMgA?Su#1TJTMbh|O2&Y7Qrlhy1Hb(02=9#W<&f~T|2`81G%l{Jh zZAHYm1PF(fb(HBNhAKEz3OYP!=Yr9!;F-pP%~{Y zlhkL^)~RM+Iis!e%!ET4Ve2|Q_%!KBA~;6lz!}uwhnQ9~;yw65o5N!{hJEBRr($8VTwJxc5gHV2^DQ1E?Co9ml+Flk_3rN%$AX3Z2%&=`hSu8V2 zYV}q0g7Yp+l1yU|x1r=%3?LWrvCJtYQ{D_GPQ-CDPayvxJxZW)pyuo8B=Kj@55sAl zgnXo?ZYA5yx5T#bl@@{S2r zVo9B3nUe1dlEDK5@&vRAz8kbOuu0%;d@dK_R>iN0U*n543EhuB*=VBmdVF=c)Ooye zo7Gn4pFub&ii6sWU@qNaVogU-=em67IYDWHNw>%);*H`gIECu&k|fm^z42zI@dY@} zax>9NRYhHqb-~dxptStmRzuNw)pt^NbyJyDs8nzy@*VWBKDO0E{JbMXa?Z*3Zm8Vh z#*5?}pYUCY&%EUX>Q`3qI4zQ z)aPVMJ=&IXlj~=`(Y}dpP9JGDzI7rinFG_z5LmBlo=cp0UfGzLGrzixuM^*{Oe0sb z1?~e?87{AxEbz;_KvSENjG7(q`5$Pq52J)6SCfvA5E1C0wWbxrPxdgCWlb988<9^E zte$z2`su)lp#xHs%{nUA+;YPz&u!AY#mUxn+GVY|vX&?Fia58_8X^rQC_?;+>uj?_OGk!ye*m z@-7#3{f3CA-rIJir@d|CRKfeWlrHz;oMeZ^Aejs9zJeTqk|VPyPe#KIFq)b?@$sZd ztbjK!Wjr>ysZ8R$$4nykYy}PIby4OH=Pk-6dx6SaWy6V;&2X|#kweu3MOwGhMLt6% zoLn{K46DVxA7(Sb2(A_dWdqrl6LcphT1}qt1)z@&K{i%{x((%%bdh7p4=;^|Xg4Y7 zTgbbtu6LrUtRRc~n#wQnN>1`k<{VYzM#ZDS4dKK-$y4^8+jJ^8+95Y3DoDKcWQ6AB zE=vNs9FA@~1)Ex0PVdXYfbZ|*^Xx!Fmr8cC1DFp}^Hha#6YkNeWG?rRi1%F;`E+A+ z(&uo_|Ky2V4_a{^N9!@VG}f8y>MFRUqgROr?i?HH=Ct81ZO&v&S3-6EhBGG^?*Li4 zq{yKGfz>);R*dlwSFS9-IJHP4Fj_q%^bY0tOP0sMR@VO69KRzKQIa8APrMwVYIO&vt zbphi8+W7YiUJ&?8Ko7slqNojU*W!!K4K6!frKNo+t?pn>!*z85O^?In&OL-jH8Yy3 zA~dn2=N;+6`}8jf1VwEfJsNJmBpUr-6cZ`M9aW4iXA{1eie$K@Wz)*bxjYn&Lnie1 z@$f;L*|ti7VTMmB1sDS5qMGukO@%{HSKm<9v*>N zFQi@m0TcE?bUCixhzs{mJ`-)scnC+xk~rkv(SKP~zGpgp;N-Pdl>#;0teG|RDyHq{ zzO4Fd{0W`UR~=VO1)11`7zh6mm=3@vIH8Ep!2H?9+CN zdyUC|g?tVt2j`2|n_erFnhluE#^WLwZ0g8fv}%;rAtd=mGZPQNCBDL0BpP~7%0+%X z0#+t^n0jc2p+(LY&saaa!TEL@+r4X+vgz*P*Nf<&tNus7C;e)^ERHB2T`4+E?9jNu zkrSiKMkS9)t5cXOPAjLQa~x%BQFfK3WSkv`&09qxUOjh^``tGl#O@Ue(llu7JCvs<>|4nIQHb%-+pY-zYyhg$4CO(^eR0B7;+w_%Ilp!cBlu5Cy2ZBDg! zWDT6`V?Zm)iHoonQ@G>)r}jkM(;Cl3H*^u%@y#sJ=j<^yqc6UG zvbOJ)nC9zB3fU47fkLkVPhbbsn4|4V__ty1KD75a(Trv&5ZXC+f54@;2cp*a0KA7*E7v zTqt>Q;HMTJ)h?4#O++Et-#u!k``YL#z9zbi`jb5;xRS(B^3*aNfat zo*Q4vTm7E&@DbvTE+Vh!G^h|_)pJwE;kic*@kIoqVIK@*mRZM`$?gnuHhydTi>Ojx ztA5P+p!}VxN5&r-zy9s|=Icsr*mA7Se_!5(eT)eBCAdoR&nY@2dYh<1KvDk-L7f8v z68Q%;^1tQDO_S!h_;hYd*V_C#yXx#CgO(HPuFc^bQ3XA>P>bXs@ubq?O+0``M>%y;ga>^`5;W?OOm;G{zY6nYtl;nz0 zVi*egQrwbRL3rB0)Fjns-BfynTLhf?51dfB@t{wIF^>}q;b_*mm*gE^RyD@gUY+#K zP>0?1>YbjX&X8@J3jX;Hdj3C+)x*tlGO_3Axu%cKLV{8`^nabuW2OVQDy{PH3@%68 zm>EazJ)4~*wdv}%PzOY7X7stDfLfzAgoX%6|dt>Zo z(%z%F@mHc@J4_?^Je!A|BE;0!bxol5+IBiG?|CJD_5(7AZ04G{cAnzXt8SlygzS*X zQKhu9AL!sn=42J$)fd`@yW$lY&JB7?f{Ur<_My{W6!c_bE}SZl%WEn^g{-lLtU%GUUc`feIHb8L0~%BQ4FMxt^R=7B!8 zH)uz1aXVs-u45WMS6w-d&fzF@-X-8ohLY3M z2=x0lo`X5?^KHNvv*J@w{tvw^&AW%EuG(4$(RJ$N(vYUOt~U-nR4(w);GX%_=7^ zdcI(?tINQzT{B^1BF)Ehe_s}KrYYl@?;P_+I{$jUs*cQD`%q3iL^G61XK<_Of^IfW z^c&`$dxqWWwK;?aCOxef$HXHQLGEEERov;LhJf_;ku%g(CbnSlotfk}Ch(U`CObqw zT;}CfD>8CUpnRvz9R1~5zSTR*kav|1z04E&N`#?r$t+95gRDYb)Q`-`!bZ|d7H;qR zKA8vcZOo1M&StA`zxnLGHx7w9drb;bh8EB{H5;%@4!IdO%pBBs`$)-ufkLk$dj4!^J1W9BrC|Q7sbj@*_lQkHO2u(A z7uQ`aCJ9o0J%7qt-Y#;yr?luwChI5ro7)@gx)9#BX(j`>T4UTTmpFU=;8YGbEk$MH zk}+6X#?cPm8%LJUt*QFyJ5Eot%JacA^_((GoH?lAUzqvi9+bqtJp&Fqqsi$i*FUaf zbl{Jf?{~Zkx$*vTzrCsUELppIZK*9|w#~j+=IWZCCu3yd>q)aDeUdy^V56Wwzq8(Y zft3Qf1WpK;oq${7lq?8ZF^(J;ht6<1Rzzl8Ypu!KEk_?mI`~XMs!M5nRB6y4wS=25 zsymakv`()DC(Mdob{&bAY56vO*wW@R3b40mwEBXEwsjgh<(!h>S%Dra{GD1TQ1XJu zEFrV=D#<%#nX?<&d~S98=GXLi_k|82xoZV#-(TQcH<7zEo2_X!Z|)gichNMyv*;GD z#RA`C>AJ%uiK4WXW#`^GgD%CoNp*C5BlmS&Hg`;XPCXLW(swhLC#weH_(sb6w4L(^>3h-I`v8{$SW; z^h|wBTQdC@7*a=3Yb2uiqY4RC^=(C%i2S+~n|w*;iFWo8Z|zt7Muz+2u?mo{;h17@ z8pNtdywLr`M5mV==FC^G;NV}$^^TI8@X(KSM&e#CgHtkAuA)P|G+u`TsFyyP)ZElv zNNUT-wq#HbeAcV*gmh)PFTsA-Qf9#^I~G6DDcN6?cLs{j&Pn{`Jz#}L>jN@1PQTJ5 z9IUjFI+jTyr74RuHXTz=DYB%4V7s>4Zk*kDoL6EvdfEl(sDkNaZVLCiMUJt*%Z%g^ z2I(|Hpn5o{V`OEcRU0$RsSO^G)JSK)uBejgVse(7Q(SfT+n+i&6I}s%-5RSQ=wAb! z-rl{z=KVIg1$?}P-h7|jL_1le4G9vHKMNw|_ zqPSg=LDBW2u0{IXop|RR*~B|U9#BWjBelgW@N5=!o!^*7JMbQyMa$Yn28+=uFLz1- zG_?K2ShOOA@qS&G&0wM)+Pi838IK)Ngye#M=?Ts<2KPlg4(=f~m}jm5+KK{fCF%Ix zhT}rOD`Pg03-VaygG=Ac446tqsZ*-AQ^*PQoTDdt7!6MGAlyl8IVYUN(BbB%3V{8X zsXNL7`l0BeEvgNnlF46q6W%*P-nmY8zaQ$B-v%|=uQ)BGr%)ZuL^rhD9YLO3d$Y`) zYpdvNA{UuZ;qD8%xm(h9vmU>BcXBvuksR?3F1`v+)m1sh*{+IvraOZ@hn%hs$=TA2 zs{mY5m5T@!buRZQYEjvHMJo)z2Ne+Car-ll+&#sg4G14c?5Lq>|3`{S|jBX6Coa zUp_tk_i^7d15VxFwsz~Wwa3;@4-eh<^TD;pYuS9Jh89fzGh|liB|jBVGay$$j*#xb zj{*h-WWi&x%U28}u^YGbC-p)f6<6aE<3ewX>K~OmI<#|U5PIusW{WIJa>Zl)4`*6K znFwEX7xNc)QXbRV-Kz7t@AO@FIH~)?+2H4jak3$8^C-YoW?0g$(x)2Iys?~_@R9u& zh4Mu=rKsWlBeuI;L}z`%hU=Vk(8Q4`+68aQeVY|t=Of!i4n5CS2Mf96!>fePV2=Ax zRMNfWaoVPRx{utApDdM*Gy~j*I;9)qtK%l4*(8f^HEew`l79NqmJ>xomZR2+oTO`9 zCo@NQ=E(Hkcsa$pLq>b1!HIk%xo-qB^f@Rp6d^8 zh-u<>BR58yBl-=x*j1#|e-dTjt6$RZPa_4S?9+f5Qt~>L~a<=TVpiMTS_wu9D-&7!T-k^gT)HOC3VV^e^1;1)2wThXw!B8xAS zMv-|o43$=KydW=`U(S*;JWOqcaac&V`Wp7k45TD3Lg{=3uWBmgh0ncax;h42G@Tu$ zo}p`4h{Jg#y2wp9nj*|ZJq=#zjVOo;F{y3l?19n$hDty)Ph@3w-UbFUgV`o2xcJ|= ze~W={ed_hsf6Z*<@2 z?=eGSjzk4S4vF0rbw-TQIsI}u13Vwu&%cn)orm+Gw2-7$OxDdwDt?7WsU!&I8MN!$ zosINeev{Wc%h7C;1E4O@9pGUr!goV12Sy|C$lsai@3Ki0lM6s0u9@fT35i9VDT5Ad zGoQQ^omIum2{6+Ll=Dr%0fu-!$upiFvax3YKBLlXkVQy}I%CVI{bmp87Sr*fXIHay zGp9dE%=v6O&q^{H%8L7{ggmD1sAkR_&pqdqcY?Fe>#wGHPTSJlKp9kOc!bubBia*M z3A^>h=iEd&N=~eD-caSgMok*Md6nB-mtv{R9X1O`WpL`E8e~M>P z;btkurzonrF*Bbcd$J}RaCXtmy;`cK{wI$(XMpRZ`l`D*(*8|ckyqctzIK4 z=t!|i&!RK5nyQ9hHC*O)cB(D>bmK{HstUi?!%k2k_^+Svrj8-M{X1HoP|kfn`#W0J zQ1ToEXY>No05s+i4A@(Ii(T9{YJ&U4Nue)#X6RF%6Iyuw)$i0vouE5>3ld#HtVUyc z!FC`ka2)-~J!1MqH~%pB{jnQ)Zamq0aBu3h-Pbtk`#o3h^>|>f<=S$Ct^@2Q)bSSqM zJ9|+<82yXW9sf`&?b?IxNmK=C4SW;H+4*%Qw}v~@?c~d%BjV$A9baP9@#u?8UR6Zi zbzBwU8R2Np4b{YR0c|oV8cs?vKt*sjp3@bqS6?GX<%`=vZ*?!|UG5$I*!^9XCnG8h zbwpc}Lbt(>@eD403VX*`6Xm962TKjwi?;^_(NeUgwQVO-!3Kc~Btu!1!oIV^Z5r<2 zo1930$)a>ET%-XkA<2^_B@pzz>V)&%$>{yX^VM_4+2u5KXk?<-=pD@#ksv6YL?N(} zDWC<%U`4lsOzam)aNcwl&*ZPP%cVfu`jm#YD=1ZyqdT~apEE`(ncfMO{ctx7BLkSm zQ9Xz6rza^@kz`*N74%)%(oD)r?PGYA{pJF^@mqewDV>X^u!`q2IZnnvUNFI?sw&uG zxcn}wGXIPt4X-x5ZVnjOn`)Nr?Ad85d;ih#oxR`uYiVk@$=gCyvsh+!ac&qhoE3|E0J!AD_={BV69Xm8O{!_ir z*}wn$J$uZpnDD5&(GPznk314rH?ExXP9FE0>-pDt#*d7X=T>! zqLogi@3L>?fwx;rQdT|{4=(*)c)iDD8owX1v0oker#CA|a~a!=`JyX$${kMKecUP4 zQIFLlH8k9@_5#z+9Zz1_#`8v;b+$44eh}%=Ki*JAF{^vJ%fCE>#VpTaKH+vVUhQQL zIONVTb=;MDxw}J`)cxrO8Oc3dPp7e!brojJ8@@O_)Yp+5=JI9^pY5cZU*C3Hac`w10s9$qYkoX) zpFtR}u@UYkIbtBWU7^lG+8UzCX&hqi*@CnK1@qf|q35{CY;t#v{l|B}b|vNFtx9Z^k=&+6u}wCI zxjb*u>sNRHI+M_`$wtTbvX|l?+Sk4(_9y4(8D>U_$GRF=`4?vEOyv92B>|!_eIX&J zGiI_`q0U8ZoW{9odOF$c`-Ha;MkXDu`X2gUpU*uVKUrUnA7@7S>cDu6v-xyWIQOw= zKbO!XlNEiKF6U8Gll?KArdad1~ zrwC0$c3#}-e{+`>6bDsSSog1Drpk@(W|%WuR`j%y$vv%P4QG}tMrGFsSs2dY1WeKl zkrCf<8_v69cwys7)d_(iNJ&px3vmxMcrr8t?a>A%CTn!C%A?LY{nS~{A{FDwp?-35 zq~+afOoL-ho~JydWzWZ9kc$0k?EliN7JzqlgwMMvwz2i(;yJ#mgV9fR#;bQuR^yY* zqetQ;=~0{DQr?k@lY{gTFKUbTG6M+3d6k{s;G;H?XOSN1B=NnLaj|!7h1jj;ZR|W# zKK`X(zy{F5Ii<*i?n%)d@iz}UO+K6d^`6!;$!Q`zkVdcrxY0(6?rwXR8(I#+5d5L7Eo3kX&Wx-JA=CgcXxO91b26L z0>KHc0fKvQC%C%>Pk`Va++An7t7@Owvxmdk^ZyIXz1>yc_i9ky+sX~tP#upI>Wmnig&Uu&H3a?)E=Tt?FynFWiG`g?>d z^|2`1iwI*zxFtb+mn)#hG(nc$!{d6`rwte(yizE)2O zKc~)w<&rnNnl6F*gcAdf0~Gu*?u_m926x8aT-Q79I(cE@G8usx^l9-xox`v2S&dX* z^(%SC%P7-_eiiGz8?LqfY*%q6)J5Sw1W#Qxc*wpaXwP*e@$ZcYM$mFLPbIU<>HGRm zu7KbESqz7FX)I=`LHxOK@DvPJ4b*pCSIzSZsdU~3SplWQUu2YQ<~;pH8Jkv}GilT> zwCB|4ye7>|j-%aXKP`}FnC`!zgieV|yAe*WU*$nqif|I5bFtO0Wdhx6e++Kf!og^p zJ6L402A$3RpgH^U5w{9Iaa40aW^vMnu_e_6=^FtenFse|Ro$oNTZPM}6S$gDpPd}=dOaRZTrDK@U|stVCXI99b(7v&t>|C7n; zh|1@>jQ+bQ9MDrV@zqEm>L-%Z$dnjv?h-RWZf32GG;6lAe+J|2FX%Hz1&z^@KEXK< zLmxF$wKLcBPqw=k%dPNExnuefyV@+ax(Z;ymF*u9nb{ue~~{P%#%FXmkAZ7-wO8A2}SOtJ@tF#nzMQ5Km0aGn(tW$0RJ zZvW=9J>VA6M)Ah>0+mk$8`l*!BQy8g2~k0o!S9<&{bAeaj5f9RjyLaH@DnPEuQo-* z3^VilDAPS6t$i4I%~odq-)-J8ooB)2{75}@SMj^0QzK1jIV~6@Dg=*ULw37?umUxh zVXvZF%fk*rnu1vkhrGr0_5XC;{fo}{H{AAM2A!%s!FS7%09TGgxa9CPJH6cQfu3Q0 zC2we=oCh-z7gy4+==0<8=f=cuE97-jsxG-O5ucoxlQ7#$T~hwWsLblJ!8#}WBCc-a zA*X#uDp+(ig7ot0B0Fx8@92vjkZ?k(m-yK%qA$9m+selC8Fgh$HsdGqU-FCJfPMal zdgitGoopA!zE_!ZX*R7yP8cx~bxK;X5eBQ8n(I>P+ipCGkjeBPv_h^yW4lwJqK6lF z#WSY$c6C#?&@**1T|~`S$57~paaMDvyDkS0yuSH!MX~%P4Non8hI3f8rFF zB--J^Zts4GY6TJ7QL075_cI24+ZxeKcuc~d@$rr1Q_jFdxiTuZO#X&-x3!*Qu1fW=>Csdzs!|dh^HI53jwit3Q4JD!<(M)brz_uQk4e$Q~>len{^K zOUzFhX_IqCFwI3Uk|VHI!8ML_Pf%XD8}=l#9jWU~z!_ z(@N?Q9*_;N;xr?m1lxv&Xf5aHWz)-rxqJ9nj~m+Of{ij?P*u$dTIxOKq8IM&g~G*C zlxZzPKgs)EGPN08J-*jVUQ~JD_Ff0K+;%^sIOo?vxjjq`F%MNOJ6d(4A0r7VBsW!J z{h#iyV|roe9^j#HsGkX%1>xoa(W!`BtG+CPPE^kTpnlz6S=i8a!aFEk%0zBvi36Nk^{?2D_1OajI>D)@7+EM^aKA zxR>AH9=m|zk0ckpWDu^_;vL>#4k>}Fq&|L!*yy}=mKLe#Zp$6XZ>pG{)K($-^VVV@6LEvs~EEG5VVZ zdd;^-#*b)3rdL(;pod8HDQx1AkF*5jc`GUCKe~qKTDSSpTmrQ0C)t0>;k+x%q>&EJ z{uCR)bW{+t*@ArAh+AO+r^lY4y+{FiIvF(eDN2hl9Zlr$#@lQ9RM1B?^goCek^9|W z5#jDs#0lFe^1WT)F9h3f1H)5V?&Xa*;f~0!G!bW~`Rb{uKxfTx@rzmMB7A`2gOlg3G#0Huo<0zdrwQ(`xa1w5;ha3*CldRN15g>lf4hmGM&u2aImaUD5cIA z%Avx%|3ptT^fj3@(=z4NmkU)TrsnjZ$vNaBm52Yn6c%6z8o+BH@>Sh@X4|P~s&kPEy}wSEkrmYQLPTnyXQY6kAyWC0BYh#{3N;N{QQGbXpr9DeH&zCu zZ3uVWPCLOpGToVf+BlzEXDE~Ia8h|zp*P$ES8xtBcTLq1_x>CA7~EhxN=bT@nXF>) zb5vk6^=y3IAy_TsX51s;fc;r&8&5`2l*Ds%qKCCW`GAG7iPrahV! zXtle-zA8ymluVubd(6m%uPVN7|DouI4BuydyYltjmu{cmeLNS@_S;lc6i>o4>sq0- z=){X!rc9A9R8^0_3EvAQBLYX+Qn0Xsv<|(n3CS5bBnPTBx(T@QU!>P`IHp#LuPhY@ScFL%pwGjC5xZtE(8hqC+?V`{i@h~j5 z$`f8dz5q7dlr7_ zIwsOzgb(`?y_?Z!9?9TllYLy?4h1V6Z1$rZ$mrgi+Bj3XvJ+oLMQ|7N>I^QEBluBU zkXzrH%;c0Nb8sdY8C(y}2gd?!&IHpx1dg4~&18T;WFqf3lRwh+2@?)p>DetYk zo%h(c!)jgQHJO$tS$2I_`(1DEJ-YX=PU_W4xIbb1xVz(y3!fLQe|V|rSEDbDbu_dv z`cKBm+7WqeO~0x8!8~+1f}W;Z&^Cxqj{6F=gSluqh=B}Vo4Ub&oL47o2hthhgM;_C zgE{ZMGP~Wk8H3Ao*3#Bv&!Rb?*#?*9J-5<+pey=G6bI@q$%XErD(Q0S{zwh^ns2d!J{6@P{VK~!fsx}P4adAyo6kT))w~AKJ)ar;iOiOk{ zyu{1crjNRb_@JF_U}-!1}Wyh~bd7j(I2nEDFhO3#f0U>X?s75|#M z9NE*IjNIbd`q4ze;2EgHP&SwlilB?2Bb!N19YWG;9^1)8w>!a8-v)I-!e7JE^ar2a z25;2GYoX_Q4RtnenW~_#$T%n(kCRrAik)T=8OZ14U;4P(u0z_>%T;S8=*>*2M`5BL za~l6czT-6#$g9z^*+Q4m=k+&lhF%!DrDW)fETNOrdeED@cn4W7)^=bDeP#j@NZZ4v z42Mn0Oo!ca*zC&quQJ#|2DJ>C_^nX#y@hdD#&$8%-U9Q=9Myg* z*P-`GtbbSS==x|!&Uh!<=_lK7Yzp_(0JB+1^!8ohz*Q{6fWeeG6Ro$2kuI28U_f_a)*pRoi-#vaa;O&bqx4%65cJ6DXFFQU) zev9|HvTPFU4xgdkhnC8i===7<5cUh@@M?Kuz_i~mO-{0P*e9FQp0UO~P?y9AU0r4I za)ZOJ#r@gMrIa(>Jy}RhV9Tw6_iK)9ChwsJ^W9o@m?%W)EiRgAoTlYy`|Bpsg}%7C zp%HFOD1q1=`W3HLHq|yXN8Z*uxRW2b*>sC2nG2>bA7{wpz;nOihx`SmH7m)_=iOd- zzRfxutfn9yiGK3DelF|lN@|2Uj+?y~I^w!`>)w;!oSPPa)$TlbMDNMk47Z<|yK~Va zGg-3Bq2)+M2QRr*=(Nw{I2zzxm0P@msQxvRXAd-|o6zhpbq?)OVzM0mGpAh#TZJ>I zimL4PsqXZ=%yaSeakrFn^|DNfax)Q6QB*5zUN5PtPwsaveUo&=ZmK38x0pd^u_tn` zTNpXkP4m&B2dzMdYk<=w!LxGHws&Q$q^ql=y+J4PA`sk7K|vFT9;WA}AiBg9B%(}0 z1y(>`6_xdRmk@>LLGj&8A~(5$T@maLDg=xD9zjPx0q0c~TrnkZRiy)!?M1ImA<+cB zZ-5&>QuaEYw8Z)e-(+o6iJM{k>HJ^|6bCb;=X>Po#aT-+hR+GIpLsgA+Z7bv4%e@NMC* zqQ8lrE5_;=*>xMQpdZ)VipUVG;}feJ{O)dlpBAL}9wV6V2e5TlNwb>8JW|`fz=aqB zaf+@c;;XrfD`7KdX;u5LX=P&C|AII?NsGt;n9NUc3jfCvZiNYQEXusw%tNJMKJJ>^ z;s^~A6a8~8TI37+^7|rNKBA;^k<0nMrZKa%;alqEdhkT2L!o!sy^zUiFdXfaT7>tl zi~plb8hOKJh+OPC`A0;2vk9*GhWy7>VA5|Vo|7qC2p{fyd^O{lV1J@fv#`7drtlV> zO(JwUugE^mjwbwHkccHgRamUWrfrZ8{YJPy$7ugBn%lf$lP!))atgQe&-~KzHGYT} zG@4{WVU$CrVV)es**uB#$x`+kE|8y{2G1^JHV5B=&VFq3I`UbN!v7r9<0-pHzRgbA z3g>TJ*VM~um6y++(S6)Ly+UN$dtK@yqGxQ0v zl)K|OISj%cU(lXr-{7QrjlQfTYJxnlO0a&qwzt6>8=C742;I^hya*=Iswx@E^EIv~ z9izQb(5?uso6&wkTh*UsulilwrJw|;#VvTZn4}OF#7P;OIk+(1!R&H1hsJtG`)%XlN|A;-IL7@%$Dw&{{-4H&t4q>O&#!z3*@(MUzJL6S( zO#WX1{2(dhSh|Och`-6`SfkdX3puRTYpddVEy-=jhu*sb`nB)4fJ?dgU}&33+9~W- zp{~E@EU7IjluMNjQ>c#)%i&>1WRtKD@?_|ptnd9Ir^D0~<~eG{TfWTowrRP|8h|FP z2lu$G&Wg_7N-^03yF=^rS{))Ec|lN1zVVxj)xm4tmP$C}E-??h6X9M((O73B5%L=E z%(UQ>6D37j zaxinD-dtr;kjps3W;5+vYm!noqEcNa5_z-LV(+D{=xyeX%@JCuN{713+Fm!&Th+x8 zI+$d?>zpe;>WJWjS{&3@3f_9V>5m@nBz*59Hw}a{3uo6;RE{0VS#C*J$_H1~CZ%!b z2mW4Eg?4Jt(l*D362o;Sk75}uU6s)lPqdRnI(rMBbXqp{Jor8@)p+Kesm~snhWG1tdg^{v<+z`B%P{Y=DB>j-!*nCq?FTj-P3j5j(TSBL~8eWhmHlkT(zsr_$_e_D{b)S@B@SxgMwMZz;GrtCc;TY zSPDp0c@lh5t33T5ZemW}G5bCl**u>9Po3RueaCS-FhR_ty^zTzdIhP8&)C4 z&KNu8NEMss;GkaS?F>&DP3m*1Ih_c{$%si!PfjOzfxPw!cu#W9q`1sxyIrf`D(blr z{&X`esL1VH%`l|~m;5ninHj-s@>?LK^=UH;deTSTOTP#duzjH^D*M^fZlm$HH1WJwzVy{a9 znkDr~RY(7;W9sx?2^xLIk)v6ZTRJA6%VW;n3NX#t$P#!6et0FgjQcaIC>e|q$AjB! zfN@X~z67^BKIaEdY)w9eD`E)?$HB0smHT+f5*Q{1vaOI!}=mKpcLgX|9(G zfxB-f@ovAUfC8~H?LtfO8+W3!Zxktid*moQNmBLG8TC=UP2U7DKS`#?3jIlS(Vt0` zYoo#?@wzm7NRp6O<*8)MRvz1s+P&O0-h z$>M+=OWsd5u?38GE#1yfNsXIIy5UZ?zuY7gT-5Ky0k5#=>(zF}^j`Gyo8a8a;EWn9 zGSbg0PzuHro76rUB3?7`fStm!rvMu{g3kOmGG%`CW7sc|qitS4l07;b9QE5o^_(u>Z7tcErjec~iu1X# z?A0llCI7PTR4+SSnCS`0&jsi&2G-xE&e3C zI&zq87&+9wY zIm`rEz+{s>Oci<6G)L{0RaPW%DVx|2WAIqa1~28Y&FfR994LMR|mOc|t zTsat&YX*9hFD7d19LyVcKEUe~OcMJ|7ZjZP*`(Xb0-#z?;kml1mUf=nhOhs)d4|8T z33#)X+e8eQw+PV}t%FtX(39tLhw&_J1cB@%vf~P`C=c6A>b;$;j=SGfK0dX&ut}%Y zec6W0(Y!J*&cO8aWP_)&y}skkT<*H_H`h^9m=Fhv!QN!?!ZWEyppO&A=b}iH5 zJ~CxS;97aYj$Y3>nvg1ydXt*G_u^y>N7xOvF<#;>Y<+Dx4Wg-KeDh7&CFYgpX=*}Q&gq5g+<*4N@M^$%U8oP1~&OVgv6lfn zuhdu@TO~o8@D%OjJyiUXyxXT_$~L2;w>YkqzG}KCDXY-!`_L7%E$G@!;hy+eTsc3V zs{xObC)mvVTfu(jl(IC*6cdX{E}m|r*iH+2DeK@(3(!P78*gkDIUbH~l^V@^H%r}S zHrP(H)m<{Gr_u^|%4Vd`euF74#?sDy%)FtkqZ4!gHg|)Dm= z|5zXr@+K_8Efbd~r!F@|U6TJ^kR$qy%&_QolV8Ey0pDmGjN(k{Y$M=&P?nP2(wkFj z6l#elDCkmibD946S>;7h(&cR z#iD)TAAZt{_zrTye9n}|>6+bzDlM9e0;GPR^$!oznnTlNE&gPz+WSWplb00TaMt#G4R=ezP_)xRajQ9_y=p?ez)V|1){F z+nNfTqhnAcSe!?*={yO*g+Ft~T@t1BGO^6tEt-XXpzVH-e4zXC+&xfT#T5n1!_!@f z1kY!j@$H#jm+)+k;Qnd?l3N*Fz~Nw)32?lo%kwJx-O(*Hb=R2neOV#Fu-H*08c^d9YeJi z1yB{N<7PXBs;q{rOae~}RNoEcQM?*UKxI3qudY4#`e{`hpVCybQ)f0SHTf6Yjm0~BS(9Wj{I2(yT>85=sCN45 zmf{~hMk;S3I)Y#I6}?C2(A{(mPKH|Y0qLdpWm}TBXWLU`J-=5Q=^$EdG<*FNd^DBi zFZh9mn58tvS5f1b1m>HEbU8euMf9!9N3Pm1y9e$j!i~iLtweU0h(^YlyvJw((SDH9 zYErl?q-BM3N_`LZJ93`fFL=QUlTOl!O!QIg8rA4$n?loX9T`}%g;7Ut=I;HBpCrDl zME6NBH$m3nDUnFRM-7&!n;ZFCuXc`4mK_p_@ zHMFT-!yWsQ?_@nJc2SyXNOd=*Rc^CQ4JNax3E6HDb}GJw;_Nb4IcMwB!lKn^v{N>qpZF9yg#zq%t!;c&&u#}hsH88TUOQz5=(IML zHn2pe-3XaS9HaN4A}4|t4i(%EPRJhWBVD;eP#Z>NYedLTXqy6QzA{)jGq6Z2C=-BlFUcU)_3?ey}VJwzkJG*rr~%^HD+QjS88(=m9Y&ILDBGt)@j zB1fkJR-Q?AIDWojMvxO$1jTJHdIwK&612cA{z4R&^-=QmmqWagFu$o~!cYbIR&V0T zE6KlazdC}>x*eSurJ00^gE$ouv1LMWhK`B}BDPDDT$m^{-d#TlY{j2PQLM$tvT}&~BB*A8Ao2t4I zBpQ}R?;Q=b`b1pjoB71ioeP8J=O?!6_39kRY9ajU4ul{v6L&bzPZ%nxRP2L=c(OyF$ObdYH@3RCo$wF z*xR4n9`MHDq6#H1gZ!yK+e8<+@&|jL%!rXz}3d&+5DB~eGDqx@2E6d z=mgBqlU+1S*zp!I_*AK6)bQeI;!YkccjFvbiG5 zvn7=^lhr&kL%lY&+37E_|J9RYQ4aq?)5}!!Dp$A_^4bX`h@D_+D@BgPS2|ek;l&u8KkaeQzviYf$qb)4soIMB+`naQ6MnM&IHNwmvZS*sVbccDx-*KtQK_D= zo2L>{DRTe1WwelXM(rQY8M+e}|61{wy>od`OkNC@OPqDMCneLuSDx})q>j&*?eq`y zPcK7xT^M)Yo&W19rA_T>GUq*vl&*MWayOo{bd28x9nT%pi`vqy~M}&HHAAHP8vP-R5EN^;8JL4eso)c>uz$81`TR-)EIN3 zG+~^e=tE%19;5i`O(R!Dc7ZsYoJZRBCO5{Vr;DpJu9*^eCEq&UtO`(62Kbf9EgqhWN;y8V8)A7RrZLxU|{9Nq^`^Q_>-G zGbj`!G9`WAl=ln%zl}9FiAPC9H93Jai8?B-!x>3x>qK*dyK=1hMh|yRGDi~Ibu`1S zl+{VbeQVE>ZIqF}XAk^HM|X_7aTz%kGs#d2*erL5zMGDgyCtsP+Ae-THlq(?9~myTM8+0>N6vRy zz^qsKcW~(c0jd>YG^kN#*z?Tt90}`9(Na|;F{Y@=secJ(s~kaWRU`N=PX@)f*%zr! z)~PHmtNzn{BaJzqDurTcO_Zu#Wu9B# z56j1nK!#mOJx{D;M%v8HIf(te5`TL(`xMM}@_XciLM`sCc+#--)c1eZ+=J~Dc`i;-^FDXS@HIG)J(aa2InN%{7lF$uJe=Cw?&bT`!E9~Q+ z_MzQ`%cLCI**M%-uh{!H$-DGq{K9SF;rx%sS$a%tXP(SVX8TUrS+-<0`~md%i1-z4 zL22IO_+*Ke1EYxGr;D0qI?6jZ{4ml-sSTy&- z(Xj7yOVQG%a|g&_0f9GbRZ&w@*D}NP5>rlpFq2efzJc^4S{5MdWezxETK;p8X|x)7 zI|s>|k0S@MUHl39Ihjn8#OQZs;k!vE4EmUPxS$h&am{w=!IA3XrYyoIy}E%I#8Z zXt>`*vOokALrv5(<8cYZM>iP9%;u+%WZXW-7kEXVwjr7u_cF)K5j9YNHo|Y*l}?GF zv?8suBjA;akb<2Hty~-0&~D*Z8iXRVILOo$+DLMeT+mo%bPf(y(8RH3tB zqNn00NNRe~?$JTUpkp+NyCw??OG@s2@Tp_=I4Olm!RyxJw|VRS^EZk~{y90yzoIe( zGxSOWy6cweMX1J7k;HOZuab*(l!mdKs83$wEGC8PY&%WR+gI=((d$#qosS%V)_|O{ zAPtPkYP-P21hG0}>XKCQj@xYvCulym8pX;R(b#3@)S82<OMsdrGa&#YYAre)~*4&J&MbDpu0{| zV@18m#MfJc?kZg{1U2_T4!x#eLaF&1&bz-wZQ8IFfr6LjrXLRLmD*(n-LA&>@y;+= zkfih0HYH`GCJ4uO(wrKy6?P*1yBhu2@ojdp3?tm>pp=O{M=+}XCf!> z4S)MjBqtP9`OSHnN2kdZBzx8ai74+Lt50bCGO=zF%0erUd_^7y{XoBQG_%``V` zAA6oNv?saO3(z~2A>%MEEmuv2ha#q#ZY+y@7*$|_z9u~wgVXK|oimRHdUwkDx6 zC7y%dxDDrk;TDzi#6q6Lhb{>{ENy6bImYKz-r_NZBmbS$mzpF3R|7RHPZoe=r~X;> z6{FQj_VPMR-b3XnT;oT`SlJ_wxyiJ`XOfLceXGybQUuj^UYa!8xmV=*9OIt3D)W)G zJr1wsF)>!37Xvv*^XR?!mDVXw?a^OEJTH=)rwl1v56JYmUIAuUCt%&bh8l=}L+wRzZw)BfOYxoTXt~Sy1}rOBiC;nNPQV@+15&l-MXfT75YoW32QEg zg^iT8!qUjWp-yNf_P7|jHrWxq!HZYB`}imia~^a->9LM?n?@kE)I;=&E@BfYrL)kY zSJhgQFi%H0XGKv{-_@o$?=Q2^c4UV7#2H!=WNR{0RhTG_lCv&Y_hwM=dP1@Xy(Ry0 ztca`j(&_U%tuVvUZ5>7L(ks}(gj9}k*KJICoZ56x{8SKI!C<6xj0YDT(rBp^ow6KzrR zqxoWL1y9LXeobQ40XV5T@LT1`PaG{qdTrfpuPCXDy-aS^(0nBc{<3LcV+7CaqsYwe zx5&b-pr6ETqRsj#uD4ino7=>s))i-_(B-kJ7W3DMpZ&Su2+qJj?Ny?r8p<9n z(RAi!Vm~c3nOTj&bw`KGzJ2(meYcCo(Vsp%wsAwy>_f5sc!d@a;PdQKO+g5Ga#w71gr_vX4R zUU9cVU$d*#Fq?>f?+`bIM2XFIr

IWe+>rwnoi1hr2cwDwNT*6rM*XCPYWHOuu{e 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ÝJ-gD}G;QcpnuAs=jy)h;#A|E{zOX==< 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

?EakRNBYn}Su9rHu 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±guA z4{WO+JAMXrkk+Du%&kfJBazIOLE-_btA9~u=SGFMN=FKsQAw{T?VBQx`c+en{Ku%w z{#(@&zlLg_uNZjqMSEA=ft}l9%FtLeAFWCq-CnfOqvTuNgKVFhD$&F;Reeya#A`KI z-X{~{qiUq4iq40!3KQ z8BvpTv_5*D$**(6g5c=^ckn0Q?-LsA@tm(ioKk*mf-i;LO}{0}gkt_>1~=H-bVBd-9z3mca%5N-S0K?mXhQ;7L>EE?TNGdE=cr2 zbx9hPlkSZ`HHzFq@)*GWeg^AH%BMVnG|PP4S_Okaz|LA2v?b85+wT&r4TGt^uU$<~N4i!Ff#aaeV7#C5q2F}4Wm$Xt<*90M;#_TFE3it?4m4; z+oHr0=40X?=AAp(=HYxu0_u|j&Dbr_g8ULGy-01e*WGddeUbxU{7<7@yGG04GqWM_pTt^`=RU7|)boDSap!RNjZ;=X zTU~D1%l)l(`#=Bf$sAt+-+}PqDT{^`NPaS4TTth~n4r|5KZM*2Y~epv?e;1P|AhNa zZ7;#^ys6>@>7C*c$v)F@%>5xRiD8oD5K%)umSucNoNK-KWDa<_>3Xtv z#S8Qr>FeGwvbo-Jo}MMQvn+Xf#c42$rP-;zyof`+5`NsKPFGdKH&X5QHB_yAA7!XB zNY-LnY>wAEMpqCMbUU7pi#T-J%V})=1?5?oq>k)}PdLGjkX#%t*U3NV=gq(qosZq2 z2>zx!;2CA@ERqDOduQo!ld6)L?92y&El9duq8;ITfiK7rb$s7!7GHbY!x@UN{fd5y z8mJ|^LVGZPX|yt*0;7K&RafS$x6PYfi zbBF#evfC#%(DXt@I2SZMrD%ZH=sC{yl`w5DY*kc@^Sx5Gw%5sK@TS^W_pVKXe#Lr^ z@YH=$x6zlBgbkRe@~c+-(N=9y8&nEsA82k~)X;BmO`WGtw~Z}8-lN7xN^>EaZk#Fh z3UB2bABnks-{e1jXJidusGOswh}LZJG@3IbmBOu+g$$KHgrCSJ6X=z!Xzxfvf^>0B zb^++{UbMHNVvRaS>h>zF%Ortgh2k{*U`YAd_LA){0YQ&YCp6-6~r#{n)Vid$G7 z_2#Lr#yDlgINvn6*C9hn{z$%GeOedWv1!qYNwV5M%p{BGI4VLS<-cmGU-b}^tbUf< zxpI|DTP31-Z0mC!-gXuZFE(tSXKAh-@K?jWWc@hg>!^s(_~kJT; z-&@O@%0t{rH%JYM#1-(B6oP6Z9BguhZmu)y^7??zqsf-@>X|>><2r}iS8s54=tyrV zzlRMtE(VG2cruRQZM)*EkguHKGT6BVnv({l#0^pz6Y*Z3X5JTITm4KUvTM8G?5+S# zbwFbfQ1Bx-Y>8j*#**%aua znvtSd!8{y6AnALJWiD`_lBn1cWEHrZS!$iQDnFqUoI=8H4ZE40s)i~;21Pn} zf@Wk*Pv7zmUkjRvi%UofXT!EV>4ambxX3<3Z4NhtmnJa&y{XK`@dmv5IL&aimsa&Cd zSA$F+7@H9C3|rfaI<=XspX-76Dq86QaEMiqC;E{-YGHA5J2_2K-+dkAd*MA(Pu%LV zjC;@yakqn6mo>A@SlF)cd>$!710x^XX>vwyq|3B6)UM=pBv;OWCF zFV6b!GFu+4y1XpO=AIkZUOIPqXLLebOz0osG0D263J%I1IxR_yz@DKggPVq>4O|tF zkw$`k@ipXv`1Zb~UO}Hj+Co3Qk~w1_H&uD3n3EE9XL0`h_XV7>$bcSWg+4=8-E{cB z4>l>E!X6scTA{Op@%B(8;1R4PqSRVkmYw8}>K(Y?DOs6WWTV(Z$J9XIT#?#&5363u z4o3e_!O*z{{_;}a#z&0ukW=FV+k6k6(1)afmEj!D%&c=3*XK*Ig2ttM@Y}~>Q1+sc zONtw2Gnp8nw37czs(S|YjJ&%5JJHEO62lGfvzy`?|Hi|QsWa}-1RLIJS$vg}T! zNlHktXVDTI5RFMlJjoMR5VZP;u0;Ysc}~~l=&FNkL(bAiUKZhzRx(pZk*ARXYcvptY2E0KjVGbgg_=l=^^U_IX>-#d= z*G^X3$r(&mPg~L7X)en-qv7yYkO4S|ezCvcQa+IzGe)P9nYddMaDfid&*6|aDe@cD zKlIW~6qRH#oKzoCu=FHl<+l)xj&Z=^1pr&Ym^ z)xv65ZF8~Rhql++^{@J}O6|0JQ$LSA6y4%e}Nkeg)A?@A#w3NO98dN2&dkF-PtTGx<3jL%~R9^9s0ynW~f(-l$z z+%G*Yhtg;%V&oYor;@(8GPBcId~dFbBe!6;>5WRV0h@nGredK(ObdO*v@#Ry0Q<;( z23dU!PP2r`V4)q(XL}sJn5blSE$fYke?ZCZ%|Jmn)Fk&%TDrN+q{I;>KB2Mgn3!Gc zaHorY%x-l_L0Sb<7LRLp0IK{Iq_-8r*-JAYKJ&NaCTFLYwj8}vQBE$BESuQjPBeH? z9-I)_`Bn$2f0#8p(NA^+-N_WTd@|a2_p*@@QknNGQl7`JGDg;MilDz8V6M@R^3g8W zukA9F%=>Y^`k^lP17G4Tv(uYtUZV$3z^7fyPUOAqr>|PY%pOC_^*B7DJxLbKWh>KG zHP2hmw%oxC(z$8#YQZTu-i!eY?W~93eoDtr(Trr^j-31BNp||)5c{4d@|=0#4AgC$ zTHal`$W1Snx&zEQx2)dezV!mgMS4O$dZNySbFm(p{RGw1ZpNXN=zP(GooC*UYO)t1 zpLsr%nt5$U^fVLT_HL7Ya0=Dtrw%EK^bBY93^c}L6Zbl!mYVo{kshdiJ8aM_s$ z$LnmVw=whT@~gsj58QM3!HUPBVyH7S`Mi{VNk1p^0tO|inIw0>>EMEaVSyF=ipeQn zH{Ca$zF7CI+NWF7UirdCC6-tJ>Oy`;!~|b|Iu%;esFRJh!7kh%gKaHzv?Ik`(!zRh z+vh=dJKNkw`xcF(@Cz(YB3a_==?dE;$G~o!Qgc)iCtOuf^Wc-<2JJOmdnNGbR%fGL zN*+m5`ajayp?WsA^#Su2xe*!EBzn~{(T!ZfWQS3@4CWKawtJoPp`UWaVAY4eZU8d7 z6jhVMGwodPY;XHI z+V{>PFw9zbZSu>Bwu{POe{kBOTD^zBzu8u%k7}Vr}#ZC6In@DvL-#xR5hK04z>6CFxsLY(9orqWl82a zZezf9Z;_u9h0gsE?G?-HW7R{PLruHeZ@+xt8!CsZrtq>m@VRDFZBfJSv@=m&R+1BA4jUcgd8~KTy;TirjHSIzW^!&IX zhogPzgiEy`H%c|X4x)!&KY7a+tiH0x$I>rY8ckUoyy9i{_EIt}nTNH}Ls_?keB>QK zD?HH&5z@I1uCs?ZJ~iz1MObV?_Lx|EnN)Zu_wp2^r2p@xyyOJ6gKfmM8Ep&@R#l{zN1Dgb%i1d%F_4#YWrI^;ShvQBsOpAIKJ0xmpoJnj= zS6Mp$TXMW#29hv#>rKo|`FuiD@=el%Nu7KO$I*=&>(Pzaq?~7@!pK;QU(GyVz_N74yzZD3&KPvFQ%Y*yB zzDS2`UDZ}M!w>Q|sX|{t!OPK&^((5r(K-R%*AaL1az5RT@&l=ht5DuGG>e&>Jh6z0 z;0=>(DfrJxcvm*y69Ma1pFrY2=r{JTSJ51GW4yHPG%vq<+8f{w(hJ?)rmO4OCT@US z?EWVEcz0>uoDOraMo$KNSPpKriTtn-QInS68t_%c@Qv;z&-ta}vqSs}i2;7DXzuGq zH|8VxMUGSa^Qt#^Q(-eww$bP4@U1Mu^aFfPE59c^l2q0T*Gfg3f@l6AX`+qb1_y(E zjfUSYgNFKF=G=E?z0PCrq9$9FxW(2?Y-o2S`kUSEHLrwj%BD5ndmtt;!+bLPKwC%Q z);PzFRtctlkPbIpy$CPFtL3Hi(tBmSW!@TZw{E9j8;x>5kSRBUjeI%k(RMKM^~edG z;*7E9=v1ET#Ce6Bv0k7`Li5@@*Ut`jCz@4mIs3r%!|VQ!nCM*<4qXk^bsL#SFP0Cz zweqAlR~FTO$-=PE>G8 zNO%-|`9t%!M{aDn)^yK@-F;UzTs?n%@WvYFx}2}}E%&#?uv(%0Ql?B^HFS59p935E zh6k_kZyYq$zm8gKle=XSmbw0kVWPe%hv(rh+tOQ1zf)1Nl+!uWU~BH`On8T^2T7*uq5}f2>)L_fVeLUjz#krpvAHos&5I<4_-*n$VzxsY1{C4;? z@TK-&t)9Z`zW24^rf$Qt)m40NejUJxREhodlxeB*qgVs)rp0s#h*l6f*!mz;`T35H z!pPir&zr$+oVVIrfcpL~Jb-_gs;JGAfwXo-HM0Q)w&MLc56AL|Y^IF#&36!W`C|b+ zONa12R}sa;NOBtw$x(8yvse233ds6?O~o!>N)hk$5LG}E%B!@@sp)x|Q^Go)CUdo- z*rqYR(122x&8svs<2tonN#_@}P<2;j)oJj6Y|dF#$LZu`afENP`pri=Dhj2eXoLEz zT6hI-({k9v>~L?`eTg^eRo&yX_pbO}=p=pv?IB-x5RZS!_4)4Px=gmZXByy8ts%d9 z^j>)P=)20v)7lhIbYbaddkE6b&{51XbxCJGNmjv7dhG*Aekg?IFPJRRr1Ed>yMn&Z zfPX^!C2yEzR*pVpGE_Lyq)PJwZ5y_GQukWjH#u|VJOl$2712B5_sEFI>T&H8?!?YY zEEJV5{(ej&x1vembaV;?G*TaYqsi}|BxgGxWM|(kb6B2t<4q0kPcX>^oL)8Y3diI5 zPv(>)FS>>31vimiruHtOh0iVC;V-+cPmw!NQ(Uqc$PTE3n)IyO!lrY(+njDI``oQ% zzj?(_Af*HCeusND7^U2Fds_V?u5H{!Sr&#ZE9D8~;DwZ?;?L9}4uUd)eGk z?y1D;URn1Tit=z9?9H&p-FY_79cvT306X85CnI(cDI4SATiS!a2inv!l`V^wvo{Pj zII4QD-#bC#gD*yg`VDm^_4%-9tN2RP=JYisIyumUNHUcOcHc;qa-(_x_c? zYXcs@PVx`@e3^N>>yoZg68C3vGN~0FjQHE#O&4TK*_HjE6wbTJ;NCwQLq9`tRYQNF zoAMv|*6T;R?IxMsWKs7?5LgW6^)nrU8a%oJp6T+i%mJJ;Ex(^{_)D6b-(mmKqF+mo zC$|p%i#0k>Z%gPIJ^X9gmrI^!I6wY;zpcOh*K1|6H3PTL-Z|!OntMIrHlC(vl&pPt zrqCuqrF?Y)%Q(M;bO=}yn9hFz-JF5$4~eh6r|wg<(T7ZC@1b`f;Uhfm5O$CM(0wl; z$%oz@FDaAZ36y9jWlnn@m-0;C7;(;Tv6$lb)Nc2Uu`Brw&w!*JMu9ZMwnmwZ z&On}`H{9SQ*sMn4d%Gka>JywyUBq55r8w%fg@5`A{@9IEuM({xQ}NRN$7Glim2q;= z)qm_5zL#8bAxw31(i2cZqn^z|pTl5z+bbZCd;Q6yE2ui@vuc)J&&eOGa-)$r2Lf_g z4krC>l=DShMU&Z7y_P4K)fS`pyW_xAIhF8#J(YLl0l1utYN`BBwr3~S;y#|2!m#Nf z%*Gkb4LprInUtsL2XNdG=9OF2w!*#CUoR%%S;O;f7OPD<@rW(!hOPoCvEFRwd^rm5 zIF7qH3+QKlrNl+~(MWZ|q339RIg&ab! z{=tsm))=pQk?MJn(_*3ALF(u9yIOUGSRf3y`6_!_jJH4Fn4OBBU>1CDNpiEt z!t30zFPYsEX+Pq4w{5AC{+zQw^gpA*)^QTr;VB%S{7cE3?j7iRXTs=O=4=eR%fs z;DDTCYv=>!vX{jC>n$f+Jt=JmwYcG4vZ>@Elq6{MtEPt; zPO@X9+32OCtuTk(!2X%Z6r+!|rDFe5XU!t@k+kt~?h3guv52^uFv-kJIEKy~Q#;zLr%qXxBr{+?f`dF0!fmjd$oz`zzk#9r~6nYQj*@CUAlj zKzBQo{J<>Y9$Uvd^B6aNCsRgecY7qfi7fo3@1wsSHaOPn*o$>R8wRdQx2Dj+|8aB{ zP*xpl7OtvuZZ|aU5ZpBoAh^3j;~G4H1Pk(lyF+l7;K3b2&;)lU5Hz^E)Ay9j*Ry72 z=FNHu>Aq5@{{8R$?d5xe2lF20iTl4e-=)l-{8Zw634V`z+1GKK_t;%u0KU8{k>j)QrO!TUk3>)=o$NEh#g=UHuX?r0Kkis%UP={Opt~m>Bt&UEp;V z`TSwxjn~1=@OIlkML4OR$GX4eK{$?s>^EKmvO%fX(BFbWP?4nDc=6hnf;OL@Y*{BU zkS&CR=k3@?&0r662#UdbL8eY#5u^D&UcqZNPWa^9ri$Toq&=j4X`pbrLEmCeK#jqcRtPlIV1a5^p;|hywbo9Qq7kK^-a)WdpS4$tk z;oA}&EXG_gC(SoZCVg<(tY31b5>i%Q`27>;@2p1G z%0c?n%X`v4>?QEG!mAnu1$#X_rn_b(y!Kiqi}fMQDpSX`#@lztJ5YM&@hM1zk z0KGkYJs2GRmQC~mCgw<3hKFdTOD4V{aX(J{Als4w|H?0=4wC?^<|h$t$df!_lB(s6 zafST@;xu02;(k7L2YOj+*lZ=3eFm}t;CAF{;~`ANU8at0O17pDS@{t(klk{b%_+BD z_Y~E2YPk!y(7JG4F*K&6i;6j5-^N6^!rYHH^3M-NO)N{hKtK2oS7jv49qC*euZkRMlNwwGbvkKb?tY@Nb|WonZU@6PfV|+&u#+VgGb8 zbMe|Q=RLZJH)cOX10N@5RaE47G+~t@3*HdtT^2NqbG9By@RB&dV=4FTX*C}7dC2Dp zPj5ecaAEJoGCSt}nRUhP-__ba>yDgwep0@F@_tRil!>w?JDRj^yq$4dM^^OT#hny7 z7Wbk*pSRvp@w>@WUP-x(4Rm5ZnrGfsRUkA$P4g>oUs~%bLVBzM|2-bQ%C@?fy%sd3P2z^_ z8JuNrdYY}_F?K9h@M-*JE1RumHoL+SsO3qaA^q!KlHiMiZYyM0^|wrl7O~O$8cntt zzRdz+G#YqMmBl5*HKe_=uD!4DJXUnSc%|qC{2v?f9?<&kt8AQ!w;+|QvSIjSA4o&J zbT@-rCN9*Px^(5HB>ldZeGW4n9~KD)x$fAlXi!9fn3G?(37;=O&RKZ6EO&t>8Pij_nmXI!=N}s7Z5ZVjSYXlLzDK zi1f*~CwZJBoNZqD&t*eRCpEs%qIL5pm4;RB5;}y1b0@^6uN-?<#&S7_;o7 z{j%_V=`VY}+{AS@Hnhp>92pi_{MWcrtE=vziz+&zj!We=)qO-#PW+L)N+(TN9@Rg? zPA(wVh(z9LoFLUzu*BhOGFW;N^eq%o4Nn{c9s#zqTr}N~fh$k{5?V$NwEN$15o6rIc)6xesTuegk zSV-4)Z?l7(;(zY@V2!I1q^E_d8IH0kF15{wgZef1%B?V4&g-wx9%o2LE9FbjQwtv~Tn-gxyd3oBEXZoBSkmPZ)3yIPt?H#!| z&Y+0Pq3cl}BBP>O!Ne(S`kL3D$JvcB55!SDj@0Eq(<}VI7S)GjYL^>#)~((Ar^F2X9FOf;SssSc0GrjzV}DclgEiHJ^x*#aWvNIb8xO1rfQ@p?2h$4zE*^R4V>caXNaxW8A8@ymL>y)@n#9Ngd1xSPyF~vHGHyH%~f=&>DIT=I2#99bV1jFUeMxl4r)^-(^Fmx*2sgw zD!D^lvFC7$MBr+w zr~9~3JaeM;eD#N25qhrP#Ge%Jk94)tEh;jvNZ05eqN}ww-$gf=S$|yqDqm-enC|C( zee31DSG`{4{#@ZprkJxq!;g((QhhoXUTzwyPTuOsCMq17<|gu4c+IVFk^f#iN4L4; zDnjR|K|*?>`w8k>Z&F!P=sFn&O}hw`qi@L6-j!EXEz+8|d3Kif-k??_C&`o2{v;Ba z3huDZ$#(V&Pxa4`A!^D?I42S_4>gtlh%RKu%6Y%ZpFA!5scBG0c-q;eHV%`Az|~XE zj^})MM9RE69UuL0WnSQZ&;tHbUp>JkHDSD&T}cNdhcGq9TW5EBgV@RKu+P*wc&~p! zt8cE0+P}jK_0$*_w2Nu1Jrj@ zE#cRsA`834z2VMK+;laigG@$qOWYMR!(PB2Yx$f=|{kT1#To1uxm7dRE9`lLxOeYyKlT~N)K>cYJLkPMk z7tj_r&Go{Smd!5bPMc2V`Z7o-d0FGl=&Oyd7uzTQ>az0e%KFO}FJHDN*puqvsE0M< zT#QpIdBPMu5)4h)J2Wk#K|~NSF>brK!y}4>Cg5SzIwqzDy74PFQ|B{D!b-P~iR1p& zR%V7HcaGkZW;`?R*wZS;wN&4ujsArr^S&-X6H`*onzp*Q*{q{&9sEh_xb19(CAoof zFuu1-MEgzT@7`N>Vrxh_d^UOPbs7)W>rc$U^Z!3N+@4mM<7kaT$gVD=t+gb{+q*O% zCuC~x&E)ng-Q1M#OG2By4iTrlZzFnpV?&C*$z5u&U!J?+DED|X9;_8?eurmAePZt*l zaEIoBh}7L}us^W9Xe<(wRmg>l@-6+4pJlPoud-#xV}~?C-1VQiV_p%qweh$iTt0Nn+fG{GZc)$wGt2K<}(Kj%;$7&{CR} zw$O68-TT_VD-|5X0`?W#tpze8M63lW#CBu_u2w^zj1wz3_42Z9ORq9{9hB#_BgIe( zk4QC@l>Ep(lS!o4zruf7PImJTdO@ao+w5euft_?Evc%hBm3_5RwD1P-q&+5w@>Gn6 zYq>3a*-oaOyBOrKRf8I4Km7TZI+;lbu{e(_!@mCtJlEQGB}ukv%xG0<09-Ak`B$a2 z=lpZFd+1NQ&*w>|XfqRMgZZX$P)?r?zSkf0Mib_n(9oW+cW_O%H92fc^P|1apRo>2 zttxr^=}cvR!_2(J_U3`x%!WCRorE(W2i)v+v~BifvZqc5J=(fCy7w|h8 zsRk?VW6-)9;aAKhrsI<>B*V;<>-2cHI#_9Y1e;;lmv?{isvOf*9aA%1cJ*8~=p5CV zf)l8AP#v0^@96@VDo+O~VYV)kU2G3EOKen$)O&edoffscn-E>AkrCTu`*9!WCQ8zI zal$qui@zPGarxkD@h-eWP6(EHFM@sk-$6(JmmrOoSSM6Txux$EX+5hl`#<}g{m=e* zucRL^QRHGro6GeudtDA3{O^NlFlBGzw|FWO!GeyGm)PE>m4*4I7c#fsW=0rg6}$0? z!QY}y&{qB&w2-BBdJ<6kQ0z9j1YUi|hmH>p0ikJ7%RfmA>SKx*;sC72*+k`c_n_4I+zBswg?HD>yyk zm=|W5NoFeI8j3@wavk244BmV)9>vr+uQLggw9w&Zqm>LN6P%L-kUxhisJi}O_zX+cA7q_!ssXAf1c2|nvovdOqmStx zq%J(EC@A$s1{kCtL@DTLW$;r)qSOyzW*$yA2RZYQeazi{$~dIlo7qO{d-GIv#g}p4WQWtcN*po^TvE~m?MX4NWqa|-%P0R0 z%@ngDdbx|C{wP0T`Zk8xi7pFwb%)<(mA4i4%xAYc!l-`n>cOfu|!rFSh~tavu&!_s%$Prmq{SAE*};nK%O;Uik9ZlQ9X zjv(>iwZ@eY zPIL1#kq7$tOo(=;-4>>|#HJgmpp4w_`ib&Pz{fcw@8TuwFQ=2ItA-=B5u2kr&=yOg zO3a6@yNm9?KCp6*@a)*7X5ptF>qerGjuz;#JS*Ol>a2|C^KkgN$r-L{7ljKt8zd4Z zOfR|*DvCMW@e;^9q#4T5sd!#L6eH0kUj#*E{h);G8!V73brrRRrkOV4M|cgfKhs9h zg-u*dHuV?m5B4wAh8`n+)FtF>C@A&Jaqprz;~zE2LqpAJe}if3ueC8= zMKRXPs_v?;-Um6_TMpSNHmTRmb#V#MJX6B?`6BD%1&505Eu$Iwi)at60~F}J>Uu7kb9u5bktO=>&by)ju)nX;iq{@}hLxz)_;4)-XFUMF`1iC}V_#{Y8% zE%hUxji;om_tHD@t9VJ1+9>yzD~ne*KG}#)oK#7~eRd`@@Yz@PuF6c@9(Q;Z$rzmA z=~|TL`UreYh4D1B!0Gy%EFBm*A>2>>8s4Wm1jke~dPynK)%&8hdB^?LUc=CD-U#22 zp3dw4LgT~$jpL`cQSo64T4#7j54;Ao;+s$_EqAcJ9iRB9T zlUm@?{97F1e_pX)sVz^^Hj|S(%RSt!&c5TbyB*bfogVIz>(O*aj#LBP6~Cg)#L_Js zdMXe4BcY1K_T+4alCz1-+k9?=7iot|qN>FqNjm$9lumw|h++(!2Y03K23F@(NnpzDFk+zL)yQZ`aKy~MDkuejKZNEv&d-s)X)E`8S@g8trD zdW$N~o_DN0$)~p~jK9}BCCA}Doh)YJxa&X0f?mS*~V7l^OPU{i4l245%-&jYoEiY zEF)(^lY0(ZB0gLAoiZhx)SYZMXOae3ty|DW^dIU%b-B~_Ro~#d&n@%F=khCu7lR$~ zEpB7^g2Ezy@I5=O2lkS_%DnXqUg&B&#dWghT}fL?#F$AiNyds&I-Y0{yl^doRI)1j zh=wMkpU+nDzj2#!JO-*Y&7s$M{jSomT!sAaJ3RXp*}hdG|Meo+W}oP?ut_V^%dx_% z?Edq=w(FdwCzv=|avoRVX6B(MY^P%fDyI1zowShW zxoi-xUdu;p_-h+$dYJ1vmF=eA**@rSMB^@oZux4}n7%FK#Qb#doTWZIcZCL_ge0ECE(w)%#$WkQWHrNQC#z=SwE65sEa`VL(9L6W1^u3d# zZ6SZ8?H8(PlZMW4$JzsHyBB?SJ=xwpaF6WIZZ^65d3qiB(e;Kj9RA8R5IkzCQrzl~ z(;U9dJ0#0`PsJ>pU$^P`8YvI3xlaR;_FG7iIoZ+uED!4Js&TMItqb2*MT0x=ou{g9 zFzQx{vMP;N$15I65&95H6mcFcbS9_GA@PSyLvAyjn91E~kj={P@fJ_qU7i)j?Y39k zNixTcnXYfC@p$(Wn-e0ZS%YpC$$y&>_vltvL`A{eT<9A4Gwn!!x9P$YHFoPhP_1FF zHlLaAi?|Y$lB7LQVcaJ9U}Kk#0@l`$%-%Ie*a z?Nv)=^N;KvXQGQ174w5!B1cdiT_}}U7))~af^2S&{u#Y~5agYc{QVbUD7{vLU^ooG zWs%pWwTH-%ch-;1S-sc}F}Ynwm?z)SP^rlQXT`ss2+vy{+;LOb+tn zH?*hrl3Uq_&*O-E z%JerHrC_%!$vt$PD+~wb7ji2dR8IF7nofBytvZX(t&7Y|)~+K2i)vzNuuVh-b!C=d ztNcAU$fmI(e?}|x(r8j&X`#v&<9T^eZR;r;a3FF8uykoT?_m;IvW@k@RolW6& zZe%N6LH3?~Z8{ti9lfv29&f0&YKU$m2kC!pobc$FsJFvkb^WjYjhBa79+LniyPp`XxsAB)Uv~xlzuk22G5Mau?t3;s zlbI7+(1KM1?#&k)h`}(%=Kep;{}`KrCGw>Dod)sVvaa`u98GH+h#6`99L4=0Ah*+9 zE};`}o|&(VKB*S#VQM+q`F3nZolOd%Jr(owI^XyH^jmsE{Y2hB-Wy!%%W>2-a69k{ zuYi*E%6POX_JEBwo-?VNtfQ(4kLOz=Z>Q;^hMMy7l+idg580gdsF`XaO=G66P3Zpb zAT^iwrgMg_wtncn4TbXJE=;U?tI=Lc5%R0sx%jSnk)J9rkK***=AJW~Cu45zf)jW= z?Cq~f<98?9d;uQauNKA5zQP%n#!GH}Z5ZM`@firrgKbg+a%1a z8Dw<{OcnAX#ZfvN!sUTx!^ZL$yComRBpo@}HSAXN@r3%_#*hxHNRt1UJPd1mg8HBM znkU**8UT9G&o+*2aRIu5SDPMgC^Je%T}YkL^SqLr7X`$dP}}!qDL#DlImg@cA0EBC|FNBZ zV#bHch2li64z>3Sd-eRg-hoinh<5(QkP+8aKlgz2dU9_i{+jsgZ!efa%-T8Cf3QZs zc9S`Qs^iK^t7`J>iuLI}qD`{4w*^Km8y~fk`@wBn)W*OF9s^?6jea^fNQWF&HnFJK$H`Qw83Jglt6T+k&zx zTgom>cfGhjZ`HNwUwLZp#dLLtWA2FN;p?hrFx`KzXM{@G`=RD;e<)IP2{jafKbc(i z0+HB{rPbUNkI}W;;cvYs_j7jV#mP6zNf=dOKBeR)>^Gt_uH{zd26^n9d=t}iSDps@ zhLMk5*|);r4ej81%C4f5T#Hw_55Ab{Y6_hb>vc!jTK^!whB9?2Jd>K?J)ed=`~K49efV*VA9Rb^Vu$GOc-4;c3QdI)_Qn;nBk@+?Me1UAI^Vlb1CIN%g^69Kj416`&mE!^5N_7`N$gey zRVta?o9C)|M67$(xjdtrHZ2W`Mz*2@E4X<5!cCd&D}#YwLx?RCCfr~0EW zk0AYR{$DrmDHFObXk@?0S#~66>26t>PY|usYBQSt8w6TrSFG+0kXd>*rv9zNA(u-AZTH_1s%A- zoW-F%z%DV>XmBYcu8OpBj{GF2!7SU31FoB!p|0RNU98KN#?XP>X`_XxZCXGkC+kewl2>38O@v- zrjM=$Ir3$^CMn4x-7}fkkQ73#+XDA)A->T5WI&Yqk4~)`WK(j`8qkQ>q>lUARf4!O z#H=t@*D_ai7WUP}=vSF<7Lb_VXga&^%_eb8Kag4VbDruOy(d8de}n$he`h*}YPg%B zCgOG|ESiN<%G!Qbnaisu6R2psWOSI*xQh$JWkAI)Ck6dt#UXv9f;EHQO-lC<{4{Sx z2R#Wlb&Na~IN3>eg+)?T6r%&ME1r|%WViF7>lUS{26CPpgcEa(E-FjmF|MH((iHa} zXU|PGxAE;4RIR&qr|D-Oo6fc%EcR7+!}CH3JnV!@Ondxo93cH%R;Hc<@|}>*wuvmsMuIK{Z7j zKAWp(hE2l+4{H^FP?&kL)>35*R;Jl0H zzD9woEiRgd_*!a-uVChs^TRtL0GXXuP=L|0BJSPd`r;(bK!)O0sWIJaI3% zqj0aY!gSqe`Y@-j6bbAr`H=hT_uLptGnY)ZnIQ-q67kr@Rx!^_VtY%cbT#y6mr?f+ zt@W=o-!&DA8+R(QnJL5>D7-c4SBdqW%#@F~H?)Q=I#p%k)o+1+m^~p4ACdApdft0x z`B)A{LRdBpU_*{4>+;0TwIg7JFM#7v373B%+;dq`->%Ewpga~pja-V#@l3XYX1RbZ z^B35H1H8>51|{lepUjkB$!_$ODdw*aN_tJhC*;}~{8TYN*`48m=2+0oEYb?MUJ{5A z#cAGqOjhk*X1bpEdTGESAJxU(cKPw?MBx5R>0U4=+*hkn@4vQLd?@ff$!^rf9Z;ti zkxD*?4lx&9<0i_-R2k)>)h+gjhix>|bd-0IO+#&01rBvW^&>iDDK!$W+9d9<6KT1> zL2h@lZ9vj|B#PB#nS!)basGtrke<84s6B@Vpdr0k?|5Fe;x+3fOTmdAE@NqMW93l3 z!{5QetR{Dhe2~I2vvo`%rAkazJS@KFBqe1go4HXkBN}2!QVJh+GG@3XY8k%Lf^1#( z+J|tFI@s(k1Pe{O=Jp}yb`sgl)P~<)4!yHEPJ>q_uFOnYavJQRRJhlYqUF?pdb`{- z(R)mM+@XEvAEVUtT0UZ^;LK$d` zR{5*v}$|`#-x-Ri}x4 zxc-_Gpt08BGDymf9DtqF%^KYLfy z<2yHljo2lUI=eXk`rFpF6#Jj_sCnt_N1hJ%&1BAyDDrK)NS=%pmr-oC;{eU7YN{k? zQNz$ehC<7oMXK(DU1ul5tg3@o0u*{)KIAO z1A8h!O%67QGkO?|sv~4yXtKk<-bR*m*o}Em-$C4jlZyh(aH;HX_65K1K6A>%c6IiGi`TeZkmsJF5~@_{lxRVA z_#b=#N5p>ek(=>p+$9r!ga*^p?DR6i40(qyCBM1}`6CrXwr4Vr|AX4)&0;Uwi7xnq z@?T4Hm7#gw?7)@Zmb}|{{8n>pZ#Hf(#CR*1IB$z@(QdMe4s=R4CiOE}{7r-PYR;SM z@Dg8(GqkJEP(xijJW}=K410(_@qg^iR>5LuF5}TC-qY;CH}yvS#3o=HsmHbWIcC_s zkT;vC*v6E3?4DoPleB;TfcO3P;3@uz>0sdFkZnF=6Vqm!j%SjG`|b>rM+v!uo!AGy z&mYO)Z@}r+Os?e2EGM(VeNRGTI~}R=N1El5up!vw#xO&bfZ`Hkf8=$ZWxtJe9l@=A z1^Hwco93Es6P*DtQrHLFWaC;(MX;0plh5>FZ=mew_Yfof$M&_C55C$j!EpI~IE|8Z5Gkd_J$mE0A^DuD3S*B&g@B45qRwsFSI9`h~2gm&-qONm+`N&O=i`JhbWYat;uO*t%X; zcjRE4El1eIp7PSG+1?Qdq7~&{S{QxW6AW5#3Q?BdW|S%FlAC7ioGQ6_X00s&2jH}A zXL`D}IBp|hs-)yw+6o4FO+L$e;i+xsKH5&Ua0TR2wl$6Ha?a&XJQH8aHqbpzkzq?j zE5{2q%(dMDQ`yc({n}y@(BrW&$l{{tV99OH^V=-;2fMkU7UE{;6?uvwGHvLUJmEKC z5B|IA>yJ`3{o_)5+e8I#FpUnqxw-8iLpp$b#6J89#dyMP=DUB>&QN=7K0m!J7s_fj zg)->op>{zPKU;8LEeZycvTTl?u)%%Otz9BB)Fm{BVWQ?1l}Vf@rcXT~$eb`r)Iy%B zuS9nnm*&EhY@W8_KK;w}wJY(%A2O@V2vY?=csUb5`}{!nY9A)w=Hfqdj~spjR}g~V zZe86@)bGs=eF{C<&>FFTtzZO1!e^Xb3vj44b7D&bL114AXbtB#r-y9G*sGjazzCG=$R33fy) z+eYoQdA;d8JGYBh-YU7+YoM;H*|2`;Nsj$CasO;>4!D=Pn(NBDJsPjgb2rU>fa>2+ z=B3?L*%Is>EzM7x;Ij=Bw@BX~^IwRKc+lA-<8bPyzLpCiEX`0GZCSOM(?7Sliwd!U zSK}YrYYOp=A50t1NcOU4*tY*lXHZ+7^o0&aGs)_%d&f-*by$=9(zEQ!V3H{i)HMCW9Z1|QFbiXDnl~{y?X~bj zTUA#R&TN4&c|=_jDZEBd_};Q{^XX#Q!ZW!qjEvFxv^*7@Pz8gnUS!bL?-u;xkJY<9 zh?2P0cc}B?W^BiV*F&uJuDg{Uxk9fyccQiK7TfcV+|a+Xx9B}+uDaT0DiPDH5;eIs z=8);sAf|;%csTEJ7bRWxV_H1-#WOGA%sr zeX=Fa{+DOe1zZiz5NBxh7;#aH#~i<;a8zwb6Jr@O_HMEHjqdd`sr#{NggYYc$X6o+#ey#Qy*9!!FEB@iX(=UuGf9lT_?2>oKqY zWcst0A}y-Vx}WuR+@vks9VWeZ+~#tNG$fnr+g>)N_B2oapzH7rbQTu_lQp@+wK=`=nCem?BJm zHAP=NL(Ia@_$^I)RqX_I(>C#bwMqObrnR41=lAoWW>pIEs>i{3u~{#6olRc%*c>Ba zwwbxNCuILwsF2UZ8G8pMe>V<|SLO-sw8=6szv5;%FCyW~eB~nTU3OLN?PUGZJl4NM zil0DI@Hg(FY)a%cb4$K5vt)exgxydgrq%x3t%~qOEP%(qmMdy|xv@05Hsy0rQ%q&X zD@bN=lU;Ap*&Jq|S*24MTn;FNUEs|WwTH=3d}n`VTf9~NqI_?>SJofrwGXvXD&mAl z81a>B9(rX``2RC&$eB*$sr8-6&SVtZz1oj^ct-< zo(xY$e-31iU%3e-gR{OxR6=?1(M5Wri%i!oWIZytQM8!!#WOs{j#Sm@0PbZcs%B(L zhv5a8Ov}v)$euS)Rc1Mwn(PSFo_Z!9o}jSqWnSsNraW2dcyf8{4cy4r;jbY8E((Bcnps>nErvW-Ey&B6%**wRwo)O|>9Wwwv zMLL?9pW_$20TJ;QEdAKNpa1aloRghpn0?(7?+^Kle@Fi4&yyRx31SgFRvqO5SX--f zWf70RHUtG}LO<>D1*gen?=*f8OVAmuH}R?dTW_}+AYdnM0H~3*D|BRfQ zn@N{k)-}~`{MSQF2{p-1LCd_YhKfV#xk$|Rqy$^JA-Kt-d9|;@YS|>?iMePeiM%_c zH20CC2w(yI;q}9b5npXmnPer}FE+a*@H0b3nu}WKu_ijWZ?A@zh$KN+rq*Layc>*>vx4e!Y(UovTl^OAwU?3fP3I!v<5b4|K8@Vsb(fbM zPzzWuL-CFLDBq)aUxE>`9hXo)x5(s!y1575Q*yL|uOUBX=QRE*;OQT}V4G;e1d9?gTl=Y)tURKm@#LE5@>raFz~)kw*hJzWA%&1XJ1n!L0H2nGtM6tZ^2t71Q)K^9dqNG>mh8;TR*9HM*B7l(R6w&0_n_e_Lr93}^|-<@(z;dR6@xy3j= z6!*;HxRu5|AEavq1FcXiMAIy0(*tEA3ak{pE#nZ_As%Dr*-V;;IOJQFs z-=0)+*lzbS381|YkTjBp13 z?tQw#2k`s<$%cEa*OuIGW?@Jj#5P3JsLYu+9(q$YQI`JKMC?8~;*eg*RxXO&OO$=h zjFktUM?=oqf^rtq@1Em@WB4mSlk6E=TqZzk`x=eyI`jAhc?kwdQs_)S+6;6-4Ac8b z;teA=+r)ftJCNg=WzM)KYzc>uA|JwypaYDEcOolF|2EE=)hdz>jjnL<9?9Ie*|+IR zDxDsO6KSQ`0A(hVtLp}f^llo-)=p|6DnflbT4lE<=nq&YO3>0Z)s}}DgnoeAEs0Eu zdt9qt>TA;ZlT|)g8RuMPoc-tdX6zT)2_+_inV!O|mJf7iHj(Ra?sRq=!%c00@DxE<)8^M1Xfve&Hlk#1hgtOR^e|A5sesYpJrasAD-YsUbw{8*Xi_J{o z`JuejMBkjxP5LOT`E}B%+H#fh=}=E8zY!JD|9_MhHNBd8F#7*7d0mfI>rHxZyv^oS za?8|TI979s^Uy$GebXWRLCjb2 zgTu`+-BKsizXoab%W!tHBRt5)3pV2}Z!AWeQsOi2w%e`^&Zi4x$g(hxwKr0yq zPj!FrpY!x0_ei%v$%h8$cHx6uXDf-*Ow;*Xa+06J#aezo2+TZ($EhUTr*Fo3`nj1% z&eBA*Zr}QUmT?8UMh~;U2Ip+!prETyN@ymT*+{y+zwwT_mi`de(GToz-eAiE@&7&<~lUqOP^(uM=^1#j7X3lWOxh8v|{N=O9N!d2^H@I^CCD+GmhO6!}uiq$p6*pBR`SA4a z7xd=GWMjTE?M-GK%jFuVBZ8|z<)B|sL|+a@m^{q>k8#GV!nYn$)x7PhuwUA1@1O9- zdKLYoYJ^`<ecicUdF+DmaTjj6pbS!Dhl)Gy(9Y`&8$933{!cT z`bzsd3kf9U$+PO0jnR2`tZ?ek-Ipi)61bIH|J(c&dmif8Uo~I z+S}f`Roo{cxV^%`hBlFohMyv~zO7-_*+n{s8>qL?gma$O!69_&tU*N^U|xu6Oc`zU z9r2$YDUzbtwty7ei)~^}Sq7iRLs1!LX$y4nXo&Ht=q+C>+v049Z!?IhuD_ecU3af+ z1p9mlTY&GG5Z}XZ&PW1mf~jjKF-@eivrPihQ@O~HE0Kf2b6&pFEz}Pq{g*bHaMm`yON@a>=LAi8qVebZcA_zo{GeTp#lH#*v9| zpbp0$kq+l$YQCeb@z$OWM#ve^iT?_&h|cI~b8)R6WZK-$t-cwZ2B+ZsWtAIo-LCdx zWM@v!1AZfKhX0XdXq3NHRrLDPPB2n*Ggb}_v2|_sxRx4dNVwi;k>%( z*acL;7kYhC)?Sm!7BLd;!z`Pe=jK5u$+#79H>BZ7 z-;B;CERkK^ zA392!?!={Bgiu7Y<9wptb%&*r%0BnDdYv-jo} zlmLH79HxYhP|z>HLe0)3*1+vmwb;bgLA~zCcXy%d;a>1f=v4EJ0aPl@s9Qu`5;dyo_6u*4Qj5RiFx`Fe(nD1yl$g%GQF?nnOTt} z;x3Xax9m?k9(=a0wDPTF*6q$M^`^}t3=FWrkPz0Ga%>cLn_M^zvWr3HYZ(`UEl))f zJKy^0WG_F3%B%nS#*B#mC_ zbw-oHj6_{m{2W{V|KIwj&^Rq>sO?@!ud~0&dr0d2q56Q*pPGb38Ce!z;WBa{DO3V{ z?EAXhdFDyh$(EmV-uvC(LZBgHI8AXDGf6^WUrCQ zeF_YZQpY>LDre(xae9(WQ@5XkK zIK3Or;UZg*bLl8P(B|mmPi#Fkle0L2euO-5auSM)Vh^d$%I+2$$R~EcDd|q4Q#aMt ztD)Df~PT?BC(l7>ggFfXK>Cygua7 z{mfAtOg@p9GxR+UMD1!r>Fx z#otlgHDSxrk6r0gag9593)PA}Mq@LDTy`7inF$p92%6PP;*3ix%RyT>8YGjQgEOKR z?(%Q+K9`0x;U^q_eQg{zXbrvA_KVk$U2-OyMQt~mackeky`x=fwAp`6cXiP&!r{=- zha=1UzZZUUMSr>b3%#P1H-{$TW%i4T@BUTC_{MHTUs%k$IYKQ^!Yil}c`LbbYToNV z*t=`)F7;%3_U;APkFvpleU%AEz$_c3azhO`X7-ALbW?QH{hbc_lXv;seb5QeuyK9b zxgrVqm0g_J*TpMT_!53AZ)&Kr|6Rl(|CfkO{=HCKf2W@Wwo8<%hOa6W&%|T!4EoyK zG(poXc;?DqFl-_0Wfp_4O4lI-p6GSZ9VlLWpAb725m!w$H*>dInFy}tL0?T@-~ zPL?qh0m1rKyg+C@4Io8AYLlcmsa*(xp^3C~&cB$&Ff~>>+BR71lt~8;)P#er% z?}b^yyF@3gYlik9=pv1vFQG9#70*ovxxr+W3(a%!$h;PF(SyghFX$$(gdit)9LGaa zTt;#ECyUcychk_Ui6$6<+sG%m@&qSQU-^oCX(d`EyK=7PgOKwR+FdL6)}~=+m4!R$ zE}abS!y)mV8G@F#$GdIn_-9Q^f18p18-BJ?wxXAr4zWS5FZy^Y&Wbzi`waf90qP$J z1jEc;)yH&Gf0)Fol>J9uW5=GEo!%J~<7_rHtf!V@i=G9sz|q%q6^?0XJZq=jaa|jA zY#H~bb0P!({yciH7{DYJpMBM4a+$5{*L3U+AqD<{#>2v{EKiCWOc-=r+ponld=u?V z9gzetUopNfSB(!-WU+jK`m}_!e^@m2o{4PUpQ!TFnKqlybF1l7%E>co4L+wR9oLQs zXExjL17C^hr+bI5>uSLi^xDxk@vMcIb> z2a~*9#CshuOq8f8X1$-=49+l-)d1gR?zd=AjsYQnQEqCn} zrndb7e(NPuUi7qMo|z$!+c`M55^!ItgY#zybIup` zub0{7R&--XzFsCp_8SzP-&`Mgg3Rh|smO|~LPPvpA6CDT>b3d=3ey~V7slBz-}xNO zqA%1-obyZUVHMB)$5}lAPr+c+=Oiu%8P~3)EKb_tXdFr4BPLW;xKWaT<<341K3jQ_ znA=Bwp6g|JmJ~w+D~8Xk0e;2JA= zRn*^9rl1^ba>@Iqt1O3Sr$3Ge*rIA2d6#;&1udzK`Hp5(Md<@AXm&B(zH)i!rg=wy z!yXsSo;{u1OSV2N#&E~X23zK|SBAFHuC9W=$fomil21*EDpSi&;GHeW4PptKo(%8` z;;0DTjRU+hcL&exUV0Fl^1Phy9SvT%&L-`FiKX~AE8}^M;<` zbS^7X`VrTX8(0y(qgiQZ$!1^bXggMqu}}3ecFmVzqHVH^dAb+IQ?r+;yQ_R5E5tr` zJkNXSrBX%wMk2X?zz*;ZnFDH~iH`=?U(U7}n82Qh;_eYjb(^$5;^(Kl9Dcs+g%nCT+O1@WS!IaYLTva>%=I%G#k|y%q&g zpCLBVHL0EC>H+;9?M)kBn7n>=6Yag?(|6paXQ#Lv zhv%>2Uv_rINcsHDcj&Znst+1KT35;c!8P$|9`!znhhK~8C=;3`u-}g~B(C^{a_~&g6znpFA#bsBW#*U%~ zCoY7qD!5Ox^0XVwM0ggj#7@4wYv>M%Ypc`ZlU{z$dq_W#a&Dm73^g~dEXiLF_(qn% zcexYyc1Bg3Cc{ahvurMRk@zS^$|=BS{R~paCbHOtIN7?{U)c@SH1s8!L2T68k#V0- zy1?kaX;$AUHCwj^wkvLeba)wuqimdq!I_=A1gSgxKLzDOGRlqE!Pi9HT?U8bGIx?} zwl<3Dc=V8O+4Pr!@B5te<*(+8+{PS%dMq>2R&;~YX+P6{EGJ+ACua^B##8YUO85x8 zl8=<&bKccV_Xd;1OG#77HxP>AG5vPsR$D=o;eK0%R<^gg44qWlxK~XTb6p+TO8h1J ziOcXqmP(Hl*9X%?e%8_QXMEZHby*x-jd+#k%5R{yq`~PmK*XrhWCT~aa-QMz%4UC1 z$>>6k)cIxhAg+8C?k9%_Pvv19Qp4ox0jMsI`D|*=p5EdhWWDz8H=M@s zxM*WOY+k9_?39}SA4g{aZN-tT;gUWVhXi-G;O_1a+}(n^y9ZBjg1cLAcL@-JTX1(S z5(p6P>8`5zm^F(x@6Eix<(%%Sy?-@VWgfejg!oxJg8?aDd#$t6(E!aQd*d*PevMrW zu*Y9qbX7o{W(p_}x+P;p%u!V$vgo)G#q`|JR#iB3n7o%>FpDGb(x-CoWNB{s18xWX zIyK4YpB`*<4TBIIa$8z}pW?Z`U{(d?Y@Yy>D@ZOH2Fc`@;612$H#H7)Aq}(o9CnVw zHYw*xM|`dYm{fYYK>W`6ywaUQ$+ru?PCeeTTil|9aXA$r1OGGmG`C%U(EKFsI4A02 zRPDvwUDLz9L~mNnehr?X5gqAfF$3?%w~$|ypsyyT9IV*D)grCbChxV#9a`c_hPK)F zURiu#DQOch$ym2Y5VFT4KW#v5^2RhG5s z^hizPX&Td;9QB`cGjorb^0RDU?+d|aHQ5be`uMip?Waw-k@%Y@xKU6*^$7B5X>RLt zCY{&VruFLBINme3l=LQnUKbQ5_nu9cX|XoUK~?k+cTug8#6@OMd^?f<*&bYUlwU%; z3Xc>)7oWyYcl*vd!Uh@Z#I5Ji%Alo|?E#at|2JJCjFMHH&3* z^Of9&`JyJ+aW9^y3G^>FU}kv0#B1eTu*CYzGHub>^@sC}r)x8}7Bgej0TM~7+8J^> zC-gj9f}8LczUk!LEj*T6y&$Ln>CM4Hj0=KpQ7v7yGJLuUnU%|}r`W6C zsiR_;{t>PsrkIB6po&*cyy5n^DmJ6g=qD-#TU--=iS@(X%(?L4V1GC=_&vPNMDwrM zOMY^35N2yECwfwDwGQ@gz0?lUC+&O{!P(QwWx;p*2dMW-6JLJ8d3l7>HlDcwirQCh zFt2EdX@Z;KF4@@w+;ldGpGkeHfv;jH9L;T7J|4j1?19Hfh$b@)JJvR|gHz!Rhloy` zL<_-^%c%|MzY2nYd>{{DrMKQy3VE)1Xrf)>rLi@c+Na95$E%w?R6)+ukV02Z@4o+U>Ac@tE~-zvJf0&BlB|?{l+tFEN)XdL5fwLQw0Y z=(q>}pC#+ZYRZpwBMp8j`_%%|PQ+o~k7fcflD!5u9B;}LG*CPALHCpQ z$z}20x~zJdTZhiT!QKqQ0~MEv^@874Jqv%-Bf|fBN5a)YJN^H>jHZNl&gRwm#U1v^ z?4ksTwBPXrj1lkn69d=YmgjR_qK^AfdQZ4rs6@C?#JKRfh>Cuuh`PZCubQbzLe5w+ zVE4m?f5UX=<3SkflTzZ3B4KI^-rBl+I)A{bmksWUoPJGtEPPJ*;kV=?L<6N7Nw)SB z+uj{RdHyfC6o%A~d+39Y*`LH7=C-ZyYSU~TQZz<036}%Un!&vJ3rT|Yag+|kbpq=v z`jK1|0q3{ObvAKDXL4uOnrQ4WSHG!mK@;}7TmAzSUgzD{a0yqLXY82&)Rqkjy9L2) z?(uC-o9^h~D>4I*L+Kb-MAL^v5t#7<-UIoEmzm7?Y~&w@rRGeVqxXxXx-xwodEltu zn!&0zX{E_ra$IpCy~A~8?;1jOaZfgg0i0PoaRkPJgC9vwO>$Eme6Jt7=5C&!spRk- z6-#*=b29gLXUm_>efI}xP%TIs8c0`O1O1L}rOPBqo|linna+|~ABoDOue*nz_XOU) zIAkMaLg9KNNG7^4eZ&jeprg2eKDV4)!27Ts{q=0Oot-8V@7@Z~s(N5h9!|M+KM zymH7pWZK8o`ROkyhbFRwo2u%QOS9TEm*1Pa;yQD(2^x!Ppm5Wza3S=@|0y)k&*07QNwp1Hp?vH}Yxp(SmiwKo0(yZaxz=cOv%AM^ zwux{R6vu;<*PDe4uYmuzmm|E&OAxN;#r2=)DZvz7l03fiFr-ysht{eQLVDxfaWAJE z@BQpn>)+_po5^`xP+j92Eky!I33mmxd{uP;Z0;<;AXHN zdgx3N$;aH>BU}`|lLamgF3TTD=H81Br3B8i!<;+C2cY8!gplci{Ogb6#N8=a~YjSO>n**w5Wckdh)d90*m>X*`_S_{aEy8 zuSrVj%xxY+_IBOSB0fVK8WpUgrTw%$Vxn-M26Tv);;cPIFI5-unhfmNs89E)vf^jd z%E`SD`3UpvTwNRusWp$~eG-ztnp&bEr{^b-s&8lLKAdQ`WE0y}eJ~l-85B1LoNx~g zpYK6J+T*?I4vMx*ZNx9y9~LN?+{NZL7;b$6x7;}9ghuGjW8+FV@A|SK*TT)3*6cye zdIF{TQSjhdF3M~rousl@#ypWnq|}qd67Od*KGclfSl%5s!9YgEC-U>bbx;fM>dL2|d*|PSscDKi;r~yqnEPe$~NvW1^Hz78SO;qN0lzVI^}j zB_0pTsOCW@wb@Ura{4`FYQHjxHw9fv)ENVU4D?SY22XP44t}TKK#I1?HKg!%w%I~4 z$rLyv-+GO7q>klf)_cK6m#C}YC2Mh?7EvSZFqt3Bus_=7O`KJOQ5aXSqtIq&l5yDh z`jC;lnRMN{cs5Go21F49%h`-PseM6daUxjhCYrN$87|fB?x$4ep4>vSXxy%!l( zOH!)V(yemamJ>1XEf+N%-K^jzS0=dZ1_lj5>0XI_=&zcK^!Rk9p^R+l?&%`7y;mD_ z>A&EPPGdf*d1$4R;yzeKigG#_u6}GvV@cEK<|eprc-doHK@_p|Wo2_s>^Oh5;B5wBDQ^$%$ym%x_`!YDoVE~A1PgM)V#I<*Jf%Go&y zd%!Pz!Zl|29Ili8nqIdg2R1~?c`V%x?`uD|7nPBeZRq~X%hX;9q_qpVhotP$6L8EZ zx72VTN`fAK2Aw|%<9wF0EkufCBwfQ)K&0q+8tgs`-aT0=Uz6`)H@v% z)AIuq3b>v}Tbi4gIfc4ya)Sj8(DUsb(rD|;7~&!`?w?Fd1DP`3+w~;;brXMs<~_h) zbrmg4U432V(;?NK)ZIu_4=ur$tQo+W`3>6$*w%`;BK~k!&|l=D2WRLPEdaB3T67l^ zWfru;-RO91EbplZ5(~#MPsU{9*yIw}O>R1QC(ZpsVtRP4Y#07feGP9=f&YuDk4k6~ z&YK?YNAgYLiDLX-xnZrI!6yxMQ&0{y^9I->Br5LrwgqFnHhv1PT6m*A9~EEQsHW;u zcz{e6^cVG*iPC}f{ANFzzilV-tFHvVxgIb{&4L5&5v*h;Tb|jxv^yn2|7XO&kl=H- z29NDDH1uiAdAvqbgBT(s+ov&i)kT|A58#x%!!%b<90ReNfn(++Z+~pL6n>g0;6 zVRJ$C@IS!4rxQu-3mk9laN2G|6;+wE)#2o> zRzUMG8QkGI815Y<=)XD2ywqFt^%{t|-XPH%ht&(6TfR`~VKx`zn?K{qpslNbGclHF zVkdp;=VhaRPWDF}&f$4*aP4Jhy+!Qy4!9Dbi}>c#+OF*8`(-)Xi8F1VlVYVC2eFz% zbfn*GF&QUI#dr{loBjnc&A%(>`gQa*ADk_S7I7h{717C<&{I<>l)*OlM$q_|3%zF= z(pTzo12`;wxvbs+KF_$S zo)=%w)yH*JbziR)CG>M}#0enN$AjkR(XP2$Xyyx=-i~>l^n_0Mna7YIa)wPf14_bC zVhp&~VX^_DgMOtzD=c8m9p0TbFj^03*J~@AxnD_YsfklCCf;Mb2d*$@=X@}Z^KuPo zDeZXT2k646pYyr2s)a2Go6#M7|0sUaoS=={IN6%uyefp=ptC*TZrBRof-_uak%r%$ zzqcAMI&y}VV%ok#=3?Q{J=2o@m#U#`LF`Z?zX_*HHvJoI8FTD?eEU<-U?wG_zZBDI zBtF2AZj;*0Ex3Sgo%-Y!XLYYcOIKff3HRYP{<|{n2#JC-MOv{?{3v43*EO5m-V7u`?o?;- zAa%8Obab1{>t~X1h91|(FRaJ--BdyUH~B8ST}Ti{ZhI0PtI{X>rOUxK`>=5x!X&XTp*8tu`~qLA9>rl@1~sd{0o+GtAPTW?9ORDBzX_w%9k z{TsGqFa!;M5;&6QAYWs>rmlXdi_09kYzuqNtRiL3{s(kt(QO=@xo!9y_lBcDw{nsS z_6FYjliWiB#dA48wBpIhj)Lq0*vw?v9klgN_dvXHWneT~fnV3fjaQUp>C9}sb^R{t zM!2Sa7k;n1`8B+%K|`;IIifSr8@Yuo;g-EY9&#NMw&jAdF4EtGrYs3ld`C`$4Wd2R zZDD(vpDRB&`)HIj|B|D-&-Qb-V0DwAG_8bUq6cW$Fed6{d>ujYChn;)NWaBZxAJndT9mF|VziI?xat8W{D&I(zB+2wbCiJImY(CLGt z+B22B#CEM0!yWUE(t}-)Y^XsbTBE{b`mCv2 zQ31Us)1d{L)t7Ed@Y0P5rqNlES~fJL@!?gH5n`|$gmb$cEq+tg3bb&OQFNtntK@uh zNsbLNsu)2>)h5^oa~)rww@vf`H$gXIf;-G9)RAn>Us3x1hPR}E7uUq~E(K|HcYJ$| z?Q+t7=JNeKC0(MoosN!d0IKTFv?^Z>3X_xD7c8k2j-+JhFy6s=M!8w?2mW^bMSyB9 zmU_Upy+fdK7jsd`c9Hqel22wcSwYIdYxH`Vajw76(WLNZxv_c#Ue1DOe}9Ic{~0t|N)`2h>xh%3Y|+H=%Tl7PM3M{AQ{O zn!37XI*Q9{?B%y;=;(sa^=DbkOCy?l$~{5@^P0rb_~6(Z_}K@_zHINmlUy-^X|n(t zv%i_P=JIw-aLMIX4g`=4vIl-dXjxeSx?BhWcX0(K+w|zPAS~X=a%jZlMjTyMgdcopJHC zFjd_+vjlg3VqAnb(X1aZ8=2~U4(6J8oT{JAIZ_hH8z;3mC(Y$0VHHF86L!IvO=Sz} z;r=wu+(FX~{n`OCjnazVB*$b%EjA7uYA=p$+Tr>4-L46JRU6UDWG5%N9*og_p5en_ zeX&(DHvW{Nide~%J5yA$A4mo3=bGA8xJKKG8SFDT%yO076w&BQ^;T0$?ckZ-XY!CS z+Xvo01hcix1|+Rdpcy@hXb^0{Wq3-&=hOcYr+#%#`Xjuvxk-^@7AF0fvH6 z&MnZ{-7&VM>++NR0Uz2PG!325MMe`J$ILnQB4VPnec`C?_)?wy1T^e)OC|3qmOGep`PhcBdz`yPvn3xQl z>ZQq>e(O5B`0$r2NN^YicmAKhMimZL=@h1y7u$~Z4%zu6=)$e@ljOExGZUXA{m*D( zl5qPq2n;II1+JkWU(0P!9x}?RF5jcq2~nvvlO4m>?-I z-~Sagzsm9YS5W3_B9p#^W9$?hmrsw`T$5Im$I23$2bg8e( z@RXi0vGf?y|NEIw=(FCcWcD!=z!^2fjYs>CmUoh7MG%8lO!WsyV*X06=(qf+N3w#7 zNvaFH7UV{{L9!gRTYiLE7#75h<0Ou~cnA9%u#eC`|FJyf^k z=#i*A6(saMxL{jY<&(^N(V68d$R!|LmuXqcXa`9TWM?J0H3?x`CUbVJR)2$?w??n@ z)DBa9QL+}af5GE-B|qs~DnkL%$@<9*w7yp*ZMXvM&o$`soy-5-nLY6~x`50~XV3ID zdqG#l&oC5c+dwjw_s9dN+4H-hBv7s(cjP}fhRt?}SdC6{9cSKPaFNRP8lLuLq9{zw zHd}*6({lLfs?rA5LMUBDTp&rHfVUKNLVved3p#$g@F&KB9TqXgCj$xAhN+Q4}GQ+v4Y$JIzh!49mi=Kj+ zv^UgHGsCyRBhJW`%&5yfA9c(MN-uf6nF)?@f9dSen$a-QG#8$fw&SlQa{p?(pXo3 zFq3Yl#9VX(&o~M1a0)&Qdb*@RGZ*$tvdI)+s?6qcxZ-4t=i~j$?e3%WH~{ZDSHeqCK^v$YC0<uaFEl%w^#$O~tmGg)FG@Xrsp1eI&#c<*6*H z2CxbPx&I z2u9=c&tzvai}x`9(N20<6!pj8?@FSU`(@OF;GXPm8^}!JH16fDq*)~8rn|t#Rokr= z2W)(hgBNa@k@%3`x{92lD@b;5B)vTeeP<&h=G&Cf3c6ttliUxX8gyv4;Lgcxrr5Xs zs=$QD`g6hq{QKc}!S_KaGs!fxDM()W9#vKe?zc>ET=C(`tBbRIUs-UB_Tbj4YOv1dHKu=Xi=w5rL|E8@e#QS=MEhC#v15$HE*6_Q@bKw`FnqL>^ z##Oig9QGx_Y+M-DM$S_*Qx7w7Xv0N^u z451ZyVOQht$?CS-wComL@N>L%_*|LJ$)p8Itc0etBIy~4(T%TTGu=sMLI>{DPAKr2 z(G9epZ?cHeq6)tBq~K^xxD!jFpq=R+ac4Ou0oX{Kpz&}XRFXsa)y~}n6;3Y>u{m#L z%Iu8l;;MRNM$<^wOh<8Fti)k+1!qrYT?kje9ywB$Q#G0UhO$-0CCe_JOUr&2#;ur- zX*DiASJ~V!+Y%Mbc(>av3j8 zY@JX-yzoAE)_*wUGT1F(8N2PDb{)UlFX$|%@}%U3|IC3xbTWI}7qF*)$Q3P1LiBBM z%ugvdhcAi|;XdLNd`UfjmB<$~lVePCG=IZ%HQCm?r#^W7^kc6*p4Y4D2i=LBuWBkQ zH{U|0*#;u7O3A$Zi4@BN@ZKxA&5ohkga5F@#aa7StjEVP8L#U&_?08K+VHo(YsxaEF9Ju*X{wm1?9WH}KC6qTB*G+Cy-34}2haGKGp?gcPi|%a(^5B> zW8wy!&A~YQ5?ST6;2MsU8eU!#3Y|2kz47*6JoLBJKjh9-6J=o2UWmM63rX}n!2aL3 z*JcXyI(|Nwta3C3IP@H|c{&a<9gT3wNQ}QklVT$go7psvndr1Ffuca5(PX9&i`Xz0 zfYJPkswIiLLlfpop1SU!U8U^-lI*slp%@RkQb?q=sW<^E;fvVLyIPm|@*Mgl4I{8h zj^+0BY+Ls)lUsf}&iroI&=Q-Nq`SWM7n1m{kW+I)RE5uf$Sk_WEoXvjL}Rn(wy_&; zmT6#~H@M4U43mBWc@(^)9lG9bl6Vi^HJKBB z4qZ(hXc;(8L+^Ndk@WjNIb$<}NX3F3+7N_Lntf%nDMC8T1#9yieRAm3oAec(2kU9e0s(2*6SE3298PSSUCPK5S!(ldL& z-8__|@vnE4H+b6jqY_HMIg-qlLQC@8cH!hW3HGuxG}Z2p$mGsO{N=htR2KI_Md`4d zOTtEXX7|S=TebILZo|X;(@_WgYT;=?6%<|rgEV--hALCSDYK{13VrlTZU3<*7+Zie~7{3Gyl5 z4`@N1BMXzmloNmKGrNHEii9Wh*Pey*Gqp+1?4l0gNbiYTE*m)ddCt3Rm<>UzhKb}%zD@C)U#63-EUex@af8(VVIVeL z(MQ(d6iCBenG~(gEU=7%bZ9*mb!|@0#mH}ShN!GQhyysxn|fc!@*bkLsh1!S3E2|< z!70-W2S6{>7K3uEDu_0a;)j08|yP7!ku5)(n z0S$TsPFqqG`_>iUpAlojC&lP+Z}B>uN<8!f5dALv{m0R$m`IOSH$3`qBi`$M5n*)NaW5m_qh>MH5wrJ4LHlanTfvavWa^~iujbtO=z)bf= z{KE5o58S&O=UyuF3@;1r%pk(W!*BKnT{*}6_o_s&O~)~-bY=V}b71m4*+%}L#>o4s z3jHQhKa#aHJcdpJBIlb-a)il;vwAT}gd!Ek3yB|4&b<#jKf*nJykqo$ojYbGeeFOfI(RP;$LsvN|a@gR<~b!WSK9Uax`< z`XC!&ewi9C+IEpo)Mq}*iRS7tr*B2l>|hmbJUf_%&OWHeKZC>!Gplg&F97xRQ0Mhv z+7Xu*4|W4=$~qJV{PfGQ34T3Y5O0Je=|uZm)h! zr!%>!d)P}hzgY^#+ST6lli66o9`jF-(>yj4gSF(k#HE=z4~ZSUId4;=)7{5}6$WWq zATp`m_-=~g9I8M{?uuZrs2d~@Rf8xuKQOKV$=s7kH#o?g3;KvuHHFNOEbT@19$NmLVwGm9Z zzx8mdtR|!F-8a_&r_gX_-3WI??X+Wb7u-DUXyM#X`*l&*0c1WlO|)%62`AgEGAF70 zvBV%{$i@E;UTl}@DGkoaxopa7|TyaZJ?{8T1hx(SLnQ z#-S6?1SQm#;DdZZXU=R>PIj}iIPspcL$?O|%gL!#i5p}u3cP+Wd8ui}9EMt9xctt2 zPonTrPU#Zl6%+?ISjrp>=YXf60^4+UzM+MDb_L;;3xnhgVM_V~{bPu(lOmi(?ckAr zAy0I**aWIONBu~uQg6=YdiFjkYjH^`NGyg1&FSMy$|hBeRL#Y_m(KmgonKca76(BQ zzNb?quAa^>{;er0ZoxA{~@S$r0iw&d1KUt7ou7z?eeIp0#c6fzCdZSsZ$-|{D?S)=J zeolHZ2JRs>NalIw>zDR-)Fz8%Iy&S+=;Ggy{Qbc6L_gS$$!4?|1)dI@EGmgF{3{#2 z%`$QuZsP!+@)L9O6w{LwmWDDZ*-^8?kKNsHb~nb4=33ys=>u+aj<>V`nuEdeU$2=; z5V2kjiMXobMa0o5LrL{>eN)ZEH=0-e$6fauw^~aSE{SkmEEkQ)K>f$Q;w&1%8^0a| zY#)f@`Jf`M^GPVT=aZ+~gls#rH^^gB1RYH` zdg7@NKaFLvg-(@zu^H0rBayxTKNBER8bWQDb zYr(bK@aq?pat}c!a-j!rgZgA3I+^wOOXJFMgC|Hug0G{jkKm{?jq*^xHDRc z{31Q9LZr;VeUYChs5b5A?Zs@~oztjcO4525!iAHPJfF5;ljlgzPr>wBoKLa>(@{0M zk?AcR*nTD9qZoZiF7R=qmxSw4j`je786s6{T|F)>g{kzEjL$nTYh&Fv6i8+190DXu8;#Y2wnqmTOIlN^m3s&L+2Lq2qo=2+ts&n9ZN*l{9EDn{yo1O zf3M*r>Hf|{T$aQ`AGh~rJ6+zj21GWMNCjhkQRF3Ew6yyO*Dyxysp;O$5~*EgY)C@WM;g*D$Pzk^sIF&OtG1B0HQ3Z6 zwL7VrL3>FNw;Y72qU_^7qAyy9mu5d0cSTtScj*|kVSnN3i9n@MOuT2qiSFW}sa=M8 zPk>-H<=o$kTco&ILDo|&c^%(bHd6EJ!rW?c8QkFvXIceWky)=U{+O|B z$(zNuJe&P6ij9J3@l#{07n(72Q#kYit>ik@J>_#y1UEHnt*@PSL zPybD4&sdjLS+b6j(+A_byXvM}susB)P_&I;0!#=?oRoa6%%nq0{9LPXie?j~xgiJY zi>{#OTo12?NahU{y zab~8KcoJ8uKSgK#pFE*2sr5R8&Y{2KMEy%;Qr*=7u-__ji2O&q=LYF3tjH?fan+s!!a`(KE9o5;;QKaB9~V}Uhzt**r6nPxz|;X*Rgbdl^e~Rlwn*J)ls=c@|hoC z|9Wemk+XDPcHkzd#+`EyRhFl&n~!7#oa79hC&vdJWCv3THlQRPvl1l8)RYZXd615T zxbxScKD@(S`YjVbjd^G8`ekehzk+M&4;4x99o!7csJy6YYqA&C;lybJcecWmhLopa+gZL^MDzN?NW%7u8wxVcjQi`JH0!gvcXwR*NjxazwxdS)OIJ;T>V$;emkyh44msXEtmNIaq31|U$%*Hzpy=)JD z|9;Hf3*8laidKyZXvMO~nYjAfvVYD353}UYj&x`23o=X-h^c|%?wIY0fi52)*`yKf z;V;N+dWpQ5|%mwY=JmJN_G2)ZXkjg$qSvlAVKY_RqbwqTE8Ye$Q^;C8J z7&;f;^m?v|$_$#_P5q8DJ0`n8o*Q7cl)zF zh8F4*YT6ed%I8V@I)kbvk|ez{ZmAsPPOzbF)j3cH-{!j-g7+{U3iNSc+V_K)vTcw; z)C$&+KwZaFFlz!$L+*Lo2VQLuw|F<^wM3-Vlmi7!D4v1NmqzK=g?6vM%n;IG;-e@! z$DKb>3iPQfKrEBNjkE`67zs927v*^xX1)(Nb@!v`>O|j3M*L3SyY!%m^GW8|DR+{{ z62~LE$a_S;UzF{m+b{u@!!6tsulqjRT_vJ_qc`4=Q*OLS01l3m+vP>eo(bn*VUa+k z7yDpf4}*da2LT>#8^IdZrxiLg+|Erqj%Q@ITw>a)s9+Q=fBDtLV7c5+_Cyl&iu-UE zhCg*)gwmn--MA4@jb5-;{|Xq|e1 z1*}39n^9Z@4bLGSqxQZBrnnfd)h@D_K7ecNRfp|!^$6!-YIcX75WZhCEMq80mx2K1I}dqcp>yNfuglIX$7n-#5yAiuGS?yia|eCcEs4wdH_Z2zV2yO^=FYw^ZWH=TwZ}B~r`{$0(g)82lrWW*AI!u*e_4FX zDvFOT<~Vm>H}?e0xg%MkN0`MglGt5=e4J>mJDpM~T>jvmE9WPck>L(1hX1{u0(gf2fR;1dHP#{;dCPp zp@OO^j`Qq&QnzhF+<39I##PeLZdB#nw;44hPZk+K_(h6=^!47M(Up-O*c(;qCYq$dH6`R38P>3ezhc@AkZ|e%6E6B!qb{BnQ3A7+R zVBH7WW}r2L;Me<-D^P{Rwd?TPxzXC*!`DD^5SaxT+)@Ql!*y$ zpPyT74gVn;hpWgFC~6n@y~rR+3^%`&8SkLG1O8h>l{9m8lVG=(GUyT77Muu?iV&J( zhkMPK^DdDL{22GzDz^h9t1ya#&m`e4x96FR3)|Nw7R^dqI8`5UlWoPBP{w`2H&#(C z02}FG$D&{D#S{9MY|2D-Cm?k&xNNf0={zGC01i|g)zA&RH6Am^_aKOo;2fLSBig|A z=Z4kI0p~Y?8!Iu1xi3k7xyCH@MCG$<^aE>XntG}qxXya3JD|Rx+Rselz#0?g(<#fR za}!0>R+|hYVY2#`O;_9Y^CXDF1+&)kY%Vy83HmB$#$M4t6_CYLQQr5r@G%q7ffryC zNX*kyOVotZ@LHAD7X5!V`jY@g#UblU)C-0U(jH4 z+8zty(<`xsl&n^`4ddciO+&v#KQV~Jwc{=+Q(`GY{(jIz7BpGF5>KiZD0g;C z)TQVSx{>i1P30&1y$XCuH*U@Wwjo$n4HRt)$qxSxH_u{_Q!=T<3`uH)Jci%pfI3A&{00SKJgr|RORjVns&GNLeV zcw89%vz%+?NOEi{Pt#1b6fe#UaJ9~&h+765ozf+c$I-X7w>?w@>EZvH)~c6H&2%OB zi7)ZFOhDc753Njb#W@g-F>EDcMOF6)J;Mp*cWgHa)N@n=Bhhk1=j=S@((5J8sE#gD zeWrh=EWcMT(-;kH7cnkaOVVR~bcTaDe~U4@dvdi{k2dx<@_v33No7XvyHR9J)CgXp z951Lg`k7SbU_58%AL5@NAv_NVnVIW;G{f9dQ=TTGGraTZz|;ntD`dQ6U{Z@I`UfwV z(muN;XobHeJkE#b3Ns%zz-7@zSEv2)m}%qnG_lC7uZ7+)zq$uUFa*Z&6`x2F@ql;l z0sNka2QxYOklbGU`dJhtxqLZ!JpI6UT5>x^*GW|~-Cx$wb5I4YAz_TX8MK}sd9IF- zGPoO6{dzFaK0FNzv7gQ1Lf@q>XnwMU-IB6^U$kprxQh$pa zlDYW4ABbt+65Y&Pd(}MQ-e`xqP?~ihBu!|jc*xBZMULNeS>!f}bvI<6#>B(~uhM zZvZ*%18_g31!Fow7fXLsr~~Z-bJ6x7u_%v8E>f6}%yrMC2XoO2_2YD#O+V(D&x9B2 zC7JM&{;5-(qh!djWu>QmJpl^NTbu}WgYh zU8w`0Oof6LvO$nV9wfb?C>pYrrl|~@__8%D)*IN9$;=EnXp`6iQ1%(4m;Y;|zOZuft*BiT8Y`qj|TP8KDD{p7(Hr<>^(nzSeI4)b8phv1$#XBOgT z&8_0#yKYC3 zTU13FgEqMtx}&|S&zo2Jw0W$v+l_jTt);Kp5$dRoEvwr3?vm+XN1!4cZcf_EX1Dvn zHV_@`Dl|&-L{Vno`Oq)SOt4lCUT}!po#ziOxC$&_PCUu;xO+A;e@ze*VdIW6>5+2ciZUnc#s9aKmf6WX z8<}y=js@>s&kcTsHn9ku%%;*`=qk8vCZKh@0k)HdEo>}|Z4vP?s3n$~Wu)*XA+ewl zH)#({WeX3_hutxd$W0xsMJzJuMU;QNGWG(QZyk zKzH+-*<~V3B0DtbX|o3hZ3&RQ$3aHY%o5}N+R1&H10Nc_w7P=H;*~L-*fDc?x$S5z z+(tADFJLdz%l`Ns=JRyNMeRdZB=>D8)aKJkdb!BFok|Sl6Pe0www~$!lZZn{%wYY8 zoU7-`)hJmufq*>V_I(d0a#1^5UC$v0VyMUh54{Vly(R2)S?15y!C`UG&nCwBuiRz- znA;J2OOF}O&)FXSXEbR|N8AU_^UG*w8iOHKGo#6qxo4W1MkD~oHvP$N9K}3dQBK6A z9?2OR+g4;#i%F|v7&f6QTx>iPwhwr#2a|}lTNmaGJ&v~bfqg8ixzVTsFWBg!7oAS4 zf-`XIAH@g%ElSe8q)kr~4gBF^h2K|X2uRTkLNISjNY$v~vXDFTlzg20C;(8df-jYE zdrcOwfjj8j8`(dC5hky_4#z)*o#8?-2an2ZuZ6whUE|(5ZR_YJ)5}(M%MnkoQoZuEskw-c)f(z~>e+nO?GA&9^38&)85a30&pK9d7?ZVEoHA$0GCIY;}6+hz~W-khb_Y!Rx>rEt@z(l&b2vkFPS?!fVFO}PM1KgMmWg73PtVg%iJ=sK$6WjG+ zH(f79S6PIv(I?DsePt`Mm=coEvK+pmLok4@i)pfgzeuk4+miBsPFxPYxVAW9Lv}v7 z-WS+So3kOuCOiI{7d4I;D#vr?C#UD^md%Ur>m-{?4ztKE@n_i?;X5{Sm|iN}v$@Dw z$QD#Un|7KFrZi`AHkzfXvA^d2zan{(d}z9XyOamz-OatVL091C?B)`yd9eDZHAErt z#bu-UqY$(2M9~iBza5F~#q~Xs{J+;vRXX`Fw5$(%_krB#SyS2B_3rQ&#LIPoNvPXlneZ%h-sAdrRDWvP$ZDkHjrz zmaclDOr;Cpb)7FpqO;7ePCJKAWg&RObN-YcL5EYIC!k?L{sWJcK`$d25RVf+w2I7< zL+EzvPUq=v`n1>jR<-jZlp@#l6p8GOxvMsrYU-lNq2u8$8U;?12YrQy?(#lrHxgyY z%i1Bo(6&=lYPzeQs|HL>&uREg9h7l7gYNcu(1Dx%HYubN;R?Tpr7TVJUrSsqQ)o(z z2|slex8icuQxw#FMR$I(ta<>=eRV`)PNMCgfV)Xs{7#)@69}OnsA$gXIl(ktAULhk z1b0x#C#BcECmxcgXekDPpcli3)Q$Ti7r#pkc~$LaUcQQ-q5yeQ@%Vh^*+1oUSpJS+ ze>Z6!-bQ{>M)d3#=z5D`XW0zAaYKXQwodTO^bTs9i^1KXh&dk2Gs#FLK5fddmmWej z)Xi0a2kW7D4)jX8n>?4&Tgr+=>0C6hbpPtpf6yKL2xjz{ z`=*3=BueltXSN$;PSa7&4Wfe)M~T0JIv~7Vm@y*7Ui%E(ZVJhWT}@qgAJ@^E;G#Ji zL^qH4JH?{wqKzqML$)*9ZU>l!&*Fy22d1uej{;xfb2*P z)^u+BsGzncGejq{T}aKSuA`wq?r27-NS=suK{}YVM&eJ?)#V~N@B?m!QRa)CyqGB7JCLpZ4KDo4tE5F!dY8PlrZ^4 zfVslJH3gHp5yE~ySN}_6+1FB}wT3_<->cpI(kAf@OnP@s7)o&Rd zm&^E=W}?Q(2J?N}?zI=~B{X3<&1ziz9v;p5qALt%3ucc)!Bw#e&ERvGzb@hq$?%nE z0gPfYeW8}4uCAk|YENa+_hna{J@nnFry{HB4!c8p1=^tE;MWyMul@?ZU)|7b#2z0N zPN~j@cgq%jN%r~kqNQohKCe&-U4-L&pe~h@20a+7ISpzx|Uev~|t~58U9Om1rJ}7duhdj5Vai+jgj;6pS&@IGj)bUGoF}YSRkfZVaC(u=BsU9W@$@MUxZ_p>tA+chI&1I9g zY4#VK6%SBXJw#LY5Y6~!*NKx#>wYqi8js(&yB^Jne2`lrilm|ncBtBG;;D?JBlZeX ztFL}#^~vv}#s^c>DKnc?tS<0zMLC@ej)rI|nHs1P%X#WCyX7wKiH9aGdchecxA{9r zPES;C+=3NRMONm{$VL9m4)Izn6DQDmJwa(*Tb5_fj|mcA2!-uyw_W{)>H*gS%7_m3)~6=xZS$p*Vu}mDl2EzeSA?5WL@!@OgH7lm$$rIvQsFp z8XP(&pLu)4dmSHM@uQh1?=oYZB;$89Gi(i9Nau8J+uIA#3s)$}p&$A2R8IO~W(E0a z%BxA=U^#T6EsQf)gU0qqFwAnyb@vHchcFR9?G>r+~IZ67!aj6EGM& zqB5UV0t0?%Vxyn0&&-&N=EX`RRXrdRdEWmODGB9i`e^o$+(!1I+yKU%iM#1Ox7#+< z1_MFqJF=C|M4|T7UBR)E0|j|6HpB~fDf*jNY~gVP+c)VMV@aVLq}~Na9Sd%&zOb{& zg8K5UznDqlC$Tu_DmuXwR3IrR0ZFWa*uOQL#Qp=$B}7q|;( z0ZPFg!@#?SZm(F)uF^bsEcf~!<$3>rOc7+Fxon7>T-%yi!FksEj=X+ILJ%bstq1k%fPzjD9Yv>3O2O zu1cPZ2J^g+Q+5N)$&YMiX#Y(rJdf4k$R}~D?MKt`$|b^O(jM>F5fqyXP$5LYeBQu$ zvs;qWsS=Q$hK7#qJvROe>BjMu3;dgU-}d>?TrTvcs3M-z}kxQML88$4kLQL#-opUmVS zm5CkP3+e_HjUU{_o!OQ>XDLtjXnPcttvCFY!kgAg7Pf`uLJ;7h=*FXy&KnclJgeFk zG|lw-U(xgHZxc`Y-yhSzG?d!26Riim(8Xk-?qB#9_ zC&8<4!q1bns#CI`O%&txTt3z8G)W#J4{U^cuRgg5U7ar3e@Jj0<}DIGhF04&p~XQ3 zFRve=H-?wVzr$t4SU(o|vvf+Cjkp(|(*D^I&QiE1CO;|f5AA1M=ymKW6BCEvN6wcc zs0M1Hs~Reo+5PAN@-fRCRcFwOm6MhA4f$A=<f^9DDE>kz{g|m>EFQ`&?mF6- zmgb_U9aN$9xfiajq#)3Z(Vdh4*DuSz4s#D}HHS?ey7Z3GEm8_BV=g%vkDVfQstNjr zDd0E1(+a#EZ($ZGWC#{~mQCYgm;yK-hM7-(dZyn{nhy^xY>p;rCb}3t19-m~Pr|2DK*j_8-(qXT=cqsBxTklfZzElCjj3WR@gy zhJTr?t%@pt&|b{~T)Th^JSp7V47keos3fA;cTSm;Y<0V37SmZZG=HLcUk@V^51ngz z`M{Sx9Np@z?3$82s>I@kC)=J`Q-h(==E9um?P=7?pCa`A{f^J}=T2>4$ij~1K z5S&5cnrSM=Gp*G{(@y7{_?4~tu4qaM$B#5Ww?$Jh93*)VnOdFcNvtLE2PH)*evRYT z9nSjHrU1DD|GKiKDV#wWJC9z}mUe@`(R~b;mU;XODt=(~G&9}%-mUhA(^9j8p6spa zJxSi}R0b{8B6X9@(@6GsMc4Ir3e6?&>aTLJ%qa873nZ`%f=$f=inWma{SL@>ep{K@ zbB}$3wy7)#^D0|_S-FopV8`My8t2BaefZAHU06SLO6}J|=Ypq=xWV2b#clhUap;)*LO8N`E zaDS=%`X?Pz$I~52+*ycH=P=sAa-=lp;2d8i2HSb4jDA%~T}k~-5i$(*O&gWoN_$c_)DEm`IO^8rs1j3=x4c-=b0@1BUn&!p-x-|7cr(7jaUCHcemSV` zAaP2+7n}7qu|mh=9Q|sK=;U^so(U#D%uH2(nYgMlC&F|5#Yf3*xBLm&@n$81gX^z{ zizNEK%cM5ZO*|aG;JSbsr^LL})O%G7Uu`e!2}PKt8zi%&! zP1iIy1(Ws*s-WuV45GVP;zFj{SjB! zV7FQ>CbKexwsJT>V;6q@Odw$e&Cl*}Aeq3|h$^N8xqky?Do~EaK^_^Rr*5BrPWZ2D(n z;E)W!G%uloud4c*e&ipPQw@WI(nEQ9(=Q|L_}lQO4s`QiJX_L@l@QLpw;dyUqAR?C zYp5nqr!)<58(o$)OaipU6>+=vbHA8PcDA|AZC;w5-EgpjNq3V_WZ))3^;|-<*J)*x z{zV#omDO=k?M7YxK@4D@$c8TM7RmQ>-12YkXO|iM z+dCWSU$bw+m+ZB0G`RNeE(eaYAtSpI`#_z+}F+hNht{LT14`2iKkHrnUK=`S2%vloyzAzG-%_niYkmFWgekXpytv`#z!K)x^iBwkcSsp{>P##8c57}C&ZDMqK zk!CMCsuGs&bvDX1e9oJ=^)rJLX2FB9jx+g;NGU#}&K-(^w2DhETakxT-PKSJZA*L^ z7T@g{w%>F(7~`WHY=mF$6O->jb;T{$Kez!P9yh#RBsnax-DtyDj2rI=%Ah>BO**1p zTy6$(YOH5FXot_ZiM%A6s>kwzn#e5uEeCrE3X)p*Ig-!~6bD?WkeC^)<>_iFE8#Y| zkK#@%!_1#dPoRgO32%9H{_`K`j)^jnBvTHft)qFTQ2b_E&YIZ0)x%}EWa9rVF@Uz{|fx@2pN<#gGHF8rRG=3e4Az2W}g zl-$gnbdeJ;5B;T+L@`<<({XqAvnOG0%JPi9vfry4Hos2pUW0{Z=6Q+Z{Q$xn7p>Vs zXYtV-r@7@0>d3xqZMEzqRE?YMdGMCm?z5eO<7E&2-@OVit6nT`=st2Gyi!$A;siP? zd{ce?-J?;cy!u5A(t)h&wUR%114S>L*G0-_M&r=xVp|12nN2}Bs9^HboP68F#$R)d zNoat1A!5MCUJts<7n~?9OfRrWk~UmdwDJ>hpw$<7WJ$ z#Z7u57K0jIHT6+S^gtE%*raFvZ6o|aKdOTX>u=?yV+@7c)`hHIey5nZ$PHJ|LjC^*8d&Oksbm@&_ zwU<6G=yJh7r{sU+2>b=BpO{R8N2nLxdJD)QUXL2&9Z5P*YuxXPNTDt)=77#`#ou>Q ztaSIwV7IPnuN$GP{7f5ZA$5Pun~03fj5BZ#t6zQ%SXwjdyGY3pVnk)~n%NN3dxt$!5Z*T^$Ku{NwOp*cb-FEv< zwzS^hJQK-JI@|58*S(hw4FIsHBkT@vz|JpuYmM-aejII zJRiu|E(Kn>-)*kCp-kya^Kb`}N9&=syeX21*F5=``8{ut{&j_UW-}`KhTH?^aF^Gl zjmpn{w_dz2$vMM1fzrLTBhZr{1W`W$a?u^X({$&V$O3=W8@29S(kztB$y55$F5(&6 zrxuDgPIGbFbKO?-sbXVj_9^K(VXVq$SFs}|<9FI*2B07bbT+$@YLpvJv)5CS-Mvhd zB$}hA??zkM0`|7%qOjdW!kGu9@LUq8x0=@M?nBWRbjNv8-EI^m*c9rc~_`Z1~*LOs3l+mQW%T&C@v*5%2k#|9= zGV%#7F%xh$?4YTAxxR(hd>7h>58#F+IZys2o#qbv$#43vi-WZ6wnac8ONp^ecgOgi zLs9q!%0N*-PO%R~0oa@x){s+N-n15>I+E9uAZn*QP?SPG|~h_ zWGOsgd(NA$a8Sch^_S$PX~f?_3lEt4I3~J^%vqbr9QlZzGmEK=TJ>)`4K?jsbV2*M z*=K^xkQYt{>QOs`-EbhO^evcc3kVA)mQ1#$m*qI$eQM_N&N!Q|y7y?S`iFMfQTCbJ z&W_cYZ3?#Pspg};Zd!4tq}7-06g`WnqYLjuJhJtgsI#C#Wt>bZg>#81swKX8OM1y` z+QACS13EzU)9L7c$OSLE(&LD#o*t+i<1_nIhU038nj=KFBO$Of_i<6Cw1g@TxoW+b zJ#xb#kH8DH$s{6i2Ty^Y%1BIgwkWO*L{6s)5SnM3cLpIj9sn<#S{3b6w;N+kvX)7TSn* zk`{241l7$ud)+g~4)iHy`};(g*PgHD1qp03$W{rE6Y%|Xl$Fq2&eX5fWYFCdW&+df zO0rX)bFa^V-I*rl!_Ou~=fBiuMXNj;hVd*!hEsgK3^e35%%-0lNCM74X6O*K(xud9yd1%z67xY16l;<+g`YTB|FXAn zrYTUb0J;1Bn`K&__7=q2PhgIHc_%WE zyYrq;fioFp-UbwnzwyVkB|+~UXYV2%MzTO65_WsZCwNJ(nujulxGKk!+dDzwvr)t7 zjMzp##Ygsx*1V11NOqqKZ`GS*;eSB>&w+>gpdu|{a>6XtMGd{#o;0=4P~1YLk%_4{ z5Y_Mwl7cMxZV5TRtD)IGr{9nu(2jSqgr01V(8PGl?JXzi2c$yomt|1{UqcoAm2{e? zbWEH?i@Sl&z@=z(+TzcRG*a}y5r*C#b}gKgxb^ai+YsN=ICQ+$Q_Fnvyf!sG1<;7* zB&*>bv+-;*0HhRN)cnEI?vVcSH+T9ArqIfwD^o>R+Z&7_0jj(sb{1}-e__GuaY_*} z!xT_}@34%lg0|8_J_~JwoW|8~Oa6x!6-E&>#jyF-a!c8B-obW-H`X5aF2HA80Cz+s zSrNvqBb(1?n5!Igg4Ls|GZ5$S5b;~b;C(+}U&AJJLh0XGq!y(~3MnMw(A`|+=L%+z zodS}Hr&7$|?>dNU{u8+et%X1DODRyG_4XECrn1gt;qT04UTVV=u9*-{zy%z!pYfoi z(`m#*T}|A8v6;yIwiWf=LAKJ5AX(ix3rfhpNT~5)Hj6WtY2G_c!_N`E*&n(;={}V? zsp`VLOw|7w!3OZJ`EJtl32w1J&@>FkQ8Gx3lYQiMnL%xmHBneTLOqa|*?FS)#Ef5o z6XHAE&wV-4yu=H%*9q6k^GhE<5wuNpqyyoC8OXbGoA;m-R(55jZu(2C$fDZ}r*0XDxJp8T`iVHH&e^r2O7zYgF$4YtF`cj+W+ zlN?mSNII{(s=eM7>T}#~bv16W%0TMvXKyCu&>%6GpXVxhVK2DbT9Fh|1oyx~7_if{ zcnX`F2|P|LL%X2(WQOw%ejy9eheZ3cOagt_IIEzl%MU(P8YH4H&ooax|7!~Q2t{Q| zky))osrp+a^W>7JJUL|)?F$b)PeoTxFEG=DWEOm;D>^k#{AM_b=J2|S)N|AaQT znsiMYwcPwr;DxBi9Oo`truO(3&hV3m!Hs_wjnyq7QO)jPf^RKLf_`u0SvW_2SQGh_ z@2Vc2ynMJSH*@c{=e#P(?fDk%as{+WMP)9UGIFC9-lr;>YjmYAQ)%75l|PybsjtZf zoT1yu)gBJVHx4IgJX}=8(6@9!LBAF(r3!yfTP@X3{ehFAKWuLZb4?i%&ggAlf^g$>IW@obJ5*Hk@X=Y_g~MOe!6lj$W!gFqWO&Q1_R2tbX9#Z*sd&@j2`g z#mrGAmQUoW-!|#xQ~pX_{fB$LXtwH!e|kntLE1jZB*D?W9W3t(yZmW!K?L!=H6qnF zmfRKcJ-`4Rk)7GRJ=q%>NNlYjYuOX{tzS4>Yzt3KSn1bdtQw8(f@TCWf!VJ%8ip1q zD(9k!zXopm5*^z-{9mbINcu5zFT|BP*yM&^{y`R7XEEBl+!pp;#oL?KRB}&%-(J;a zOhMk4uiV_dZ6Pr0YP_+FaJ~eJq_996IF+U|4k#X-(KLK zSjf!YLEj`d&8u>{N1c-H6=#j>I32Vlxi2YP=Pb_Q)wI`r2W@C41Lbm_n2jVO74plysIg#YD`QxZLrrMZ_Bs~3=xAhG&i!bt-4JKo54^Qc1_|#JN zD46#sPeC-mYw1@mBGQs=Q&z2KPyT}-8|KvebhTFGz42pyPo?^?b8o?coW-1$H_`5G z18+ab)9hoef<#_L$==Y6M+p$fE&ByG%Ma2XCYf2zZ})*C+~rP`S2#1=DB23IaNHPLO9;c2eIGqBgLGm|*Aj)8dladI^i zd(1N4hv%>t4@h($Dys3lWk3h%%YIwfCghw;Lo!_&lUK&$rhcc}(`nFHg)-HK@^5|1 z3Dijna^ez$OC&Mjiw@v zs=XXM`3xBHD07XC@x6No^fHGy`)5|SJ-G8bg4UL%ojEtUoJ8bO_%pdA6iHEmcT=v| zue!@PRY#_B_K4r=kfrC4`6-ADhDpZDwtNWddr>NaRYQTDRl|Hm?;sHvbNoX88;;mdx?#v*x z8H+#>Qka#Tqg&)GG_QlqB{CP!;{O_ItoG5Z&3SLE`4zXz-ifOwrg=A^!@R&Zx&;KQ zE9i_8nbBON#dq5u{38pg{~z&?O+p>-QCaLb6yKY0idR>?*kI1dskrV!U*PFveUNMj%p~M`(3<7_xK)yc`r(V1Rh`qNKI#O zKa-l@YmMy+s+9md`wJPolhNSMB-bGe-tXE>d!6yz&DF0(HQfYXgvGu6z^+0MR18F* zCVqw|PFftFvr&=0#M#*ZEkhE{mB9l^Ha)h@22fjOdrv0=?i-f z${5)OCgdI7pk(SXzjBczJD30Ms36i#ciGqVeeQePufNatcj+Z+CfhgYDL#_6`&3Tzpgwc*S!N}h+*1ObX)1CoJR&RG&lvt} zH`snU^NIIHSxEaDpU);ygZ_x-Wb0%Dfy>C*w;EMjGh2)|_6?4o2h4#>*n5k~Dc}mx zqKlJEB=Mx<-=5U`?PzzPYK32~lUoA)!#r5zM>fnfu`8^^{Sn3--iq{{O(a{^R-+aeJ~+x7JgX0F9WpB(k~g`4PR!D{kK0P?ydjOKJ+9QQ{Qr3Rxx>fl=5`xO z>|fq$cAIyn-Rza(5-I|ZTNlUfU(62+K%;Yli*|+|&W?6t4UF2Z#vVEXOOq}UeS>m>KKnMGdHQRdIJsKw@Tru2Z7J|^+=%0+f7s?Qd%A!GGz z+Q)CGu6hhTE){*Q=(;`uW}PQor+3F-@xF%nWxutx6?q0eczzGbd`@Gxm@@MT5#yy;7gm zmqEL`!F4geGrRTSExim1V&NC&i&&G)6px+}HT~nrkK?YlzR`W}wSC*xMyxx%z2A=P zH}Bm@7V|1bK9nkZw$zcWT@%eaE=+J1Jis z-2co_cN>n9E1Zrq#8}YnKDx3y6-`Gnvh=Qkr}h-@@BySF-R`_COtRw)IP-P5Ins(M z;Ezqw_dEg#sDRQ&s>|A6wbLPF7nUcdX*>$0%{B;*whV0REoLz(Yr~lpmEY*=S%lXz ztxe)7U`l&3>W9vGcb?N+Pj^Zgui8k`$u*KIaxuYtV6&ef>VOR56SZ~O_?pAP?Uu`b<|6#IIoMy4$gQc#O~w@Ec8*|@9@0z8JtWepOH6J-kP<3)u`WYf19W_LtD?N zIHAIh6iHIVk2(~&>__V#Nh7w0&xwhR7EzTWb41pRNa#A=8Z@_-_UY|hhV}Sm?zlt9 z3n}ijKo!w~M7@jly&5NLIOou=rdDg&qyA+0brNNqpSA@%VJ^}cvVk}BLZOfbe$z)L zr-yqAy7v~SuXDg528+WqU*1Pw-$J*Nvq5O5gE74$NA{YXK(bPO{<{ZNLSib|JxxeHpkYbZ(kt3Y`gY$+oxF5S@GG$ctV5qNw#G-JtduxHb) zY`B}69GJ$Yu6L4t7gtYr@&48a+ix!_Ztj)(30mRj;uo7jExUtv<)S_) zZt4AMw2ASYF(czOFtg&F)&Kb%*K<5MO&p%YJKT0T%@Mbp8{@`$^PmwMZV#JW=tw)U zKVJ|+4V2TIFY+q9-xQx6@;{&Aayn%=D{(EKXBN&2vJ__;z-g`l zW4@)g>9J&s2f#F!hP|A~?KPR{y^5-cs&bMzZmzrQB6j@F`FiQgGUuwE9lJHn*5qr) zuf2OBr_#1f@+*dKCV#HSHPmWd50 zdWxE!_$IEYyE>Iq!pw9Y(g5EUfAJZ6U&W9lchruA)ooz2^C@kxA>d9{%wsc~FAk}e zVwZXbx?c%p?_To}MRFWZOMH@0r{Lc$YL9Z?Z?hG_k7LkVR781K$lNoV^bGc?XZ+Ph zv}+W0XqR##+08e~znmYUi}Ko8Xv{)Mp?V;y*f;puu8YBPjwq?>FjW{bg0|TOq{Ge- zt4V$D!qlD{t^Yc)z&s@>vYZMAZ|%%ER6|XXiJcoFIXR@m$;SF9)7vk!3VgvGc!yc= z2U~!3ze)`b>gS$Ob=*lR(0#?b+6C7}GxB2l!D+OX{otqCipiwfwZLo9pU*N3?)0Jl zYF_GQ>^!;ID{cccl@<6tB*qI0dqi98z(L^8S3 zvs8xq{3EaX+!o_Oecr1--+pcoqwnl==j|tEffRN)ZlE!0vD~Bzyk#x1!+62N5+ zN8x$Sc2nW7e5YuTSq8(hm=3*l_7J+9qcU81{D1pLrmdXrU9oY+LK+=t7~Y{y`&!K+ zn>;S83J&qD>$B+Ri0}J(0C5Dqf8mxh(&oGa@p~Aytz!~#wFBO>C{ltV(+LSsJXh}vC8bcV>QKdyJVi+$n^SW+D+;@~M(bK{;v)3pr^&tA}-_n_F%VTEVF&$Yw5c}@(FpJ+d1c912UAa=OBxH-Er zfz5WOFwvycJ>B+rFx!##tx!&t2dk)!_N*^k&M@0vgqv)9UMcMsU7kHYm`tX_u!yD6 zFNA^6FSiFcMdpxR6b#~2LM#R+e}{^1wP~+WD8VG*RfY*~&v{dldF2cZF<v$k|%f>?b?of-Z$#I|B;#eC$3?a4Z~SK5NPCdKJVS4sg@1#a$4Hrmq%SJZs9J^O~_+k;H{w| zRd!CGg@3S8nA5fBchDI?Y7cx8OrC%l#eEAVxQIa+=z!iX^D$?zrJ( z2<|0kYbOcVn?xg?kNh;K>~@cucWwyh(HPW8TjK(Y@qG#04EH*0>%SGK0#qmfPNk8Z*R3oS2ItpKJ zkeKD(AhEG1IZONS==O1T(QSSMPUw>z0(LS0=IkE3%Qv=C{FyYdeMV(5huLA#B-F)y zRt1e;F5QX5!?d*N?1OzDK$lHb+^h%Pma2>H;6GRnNg(3|DQ@Dq zeu2`iF&eNzVAcM(0@|Z*DaDPONAxA3^>4OAU!JNRG>3NLY(b^2X4+{g0Zz#BBGh?d zU8gE3P6cc^cC@n2DNz|tXE)!&blD0`f)5InKDZ+C(J#^ql;bV#vTUdmGQvJTh8asr zCschL37}YHwRJEvwHNi%E1gT^B}>_BK7g8acf6v!(@}=toNcMH%V<7&t5V>a?9jl>C73QSnS+|59UFdMy1BG4^mQsYtI?!IJVsi%{; zSKWs0Hn+I0jyvHYN)O%=@r73N{GLPVxsyjVpfzP4j_)0y3X{-zECeln#hU+-b<-eAW zm=zuuT{~t;vOm`CQTr-~<&r!IF^J5w5kd`0okNVjUV0DGS*~jsx zSP32-iK?m+>}VpK)g}2~Rx0+w_n<#{T=`JlokD zLK`vNL*D16AHMCydWigD?xB6lW)H0Td}4 zWEB{|WoD~GTbXABxy`3+Km384R9kyY1~TWgLe;dI7MilEt*Ys?cP2VfhDhlHT(^B(c7Jf?p~O_y5D4&rIl%DU&){s8|^); zpL_S|mToS#<4}_iZ+0T4&cW!lF7uh3Y;oWC^z)-{s{sy@OxKL5 z6+P-(#&6dj{QI!Xf7MSs+E8~(lJ&heM<4HVI_t+DU-I}(4#pj`?%MP?JC=imM={~;LrZiG9Sn*KSjwNcL8{AJI!-1tsT>;bJTvTmu#-cc zJ2<|70Oi#T232=O#BnzPzIxuUdGPRdO;T$N= zfa!gbnoOvCFm+YU7Zj*t`RV>qZ|Ub7!Mkz;Z2lG-)*_ppX>l;Ey1Bpw%X8)rviHEL z(x4rB#63_~*5RoX#K0) zr{#$cVi%r+iuVm(rx)x3OHr>4KtHwvmFHJ+6is&loCEvehmITACmMryxVvaON-poA zh5Ao#Qo?+aN7%B~F#r3KBD8_bmm6k~US+Ng&N75psq`RQwcaOObIFpU#*ojJ~pQcx-6_&K42^E2?>((N0y*0Vltc z+DXUG++9`!x!TS@nG09+Qm~A2%q#Kf(-rolc9`=0VEIUGcPe`FC0d#2XO=cu?v{&R zHhy!zrujQGX|ucj!rBLOyw6G>g!}MYy03*pyZ?9<)g>xVY`d87=+05i-FvbAKG)=T z-}XL(eIiJKU21kIA@X?wI4=u<_zu!1bSlp9#JI+zaTDw%U)i5Tq_rfuKS2+1lqRpr zc*@!{cSfq$dhFoT2aC(=M56Jb+tb3bi?{*v_SL|I>w&_tKyo96p zVJD)m=>slt4iuykN~B#jimmwy$%qT=Vpxza&Tg~X0oozq{|(9RAtJ&XARKqEt?PC| zOPbLZ)%7i{(A-Z|L^YK`v?QgbjkA_B@;XdoC+^Zr%=h=fYF^2EoHS*)$3`;|N8o;+ zhiCZz$5MR+6IQ28kgdy&HTY>F1*v)O*;ut&)s&-b-Hwn#eosu?}Kl-XiB$OV2 z2`wQenkeSY4!jpdaPYm5qnK3kqK)axN$J4q2Jx=k;9jW6Bpx9bn-e(q?+WAMV9_OQ z4RET$yqkOMe6$w_c|-5rQ zFWc8s%*ON7C3ENr8*6*fh`ll$b1>;LsKO?&?d;=Jdxqk$KN^a9Un~Nf8P&EAVswWOmCBf7}Qq*mBOlFYICQc!M8(qHC4Fhw?ch$+whskO>_a>(il77Y}(-OD&Ky)-T9?MsBo!&>;dY#j42;b!` zdQ)cEEBXoDx79>BPOnp{zis3MnLbV$bZBWwscvg_dUl$3?EK3;Y3O#b=)RNUy_m~s znFD|3BC!r0FALea!#QD_n;If3Jp35@pMGwG*}#HL7`u3LS_C@F6-tv3(fm)xmA#9O z_N=NW607$pp8es@=aQ}P(k$oaTgax~4INDy9PKUOgZdlIxqg+-nIF8#*_GwH{|!&Q zOpHYfPn#auM@jkn3v<5gWSaYkuJ{@43gP%x>!H}_37h^&EJaZ;A1!i!-mGir=1;N< ztVDD02;6Eae`OET;(kv1&v+n$O?N!5q%otP{?ie1noQ-gAbjoh3cNEd$Q#NfpSb9m zc$|eJ?x+@Y8z>O`^iITOtWJ;d>oDCJ^r~s_}3peV->scbu<;p#RIUA z+uYYdu&8C|?@P@&yVqF*Lqq>MlTcpp>n?ISZQrduDbyNID;ns7xQ)K?CR9Nc($93p zm&(tlbD?l4ZjLiU9^x}d5pRLdw4~l719GkVYeTgymHM_h*L-oi6s^Wr+gvGix&f(Y z#5|1}`MnXbhIhi|#T<&t9Wy6#MRc8rWF~uD`}jLmS^q>nf3u~|GrazC6HiII#XFaIe>K`K`V>qMw?b3As0YxQe57l#0WBMuY#!1MBWT%qN-|9) zdrW*bYh*z)nvNx!c&2QYZ3Rs7U}oNASt5ivl(QH_93I%5XD|8E*nA z{J%+q=|kUpFZATQ(d~wtnsl#C$2T=ic*um@%1lv!sd^E~WiN1BWR;4x-qIlL=S>sl zx`}EhxdoeTSvi7J%)``wgO=iY_AOjjS?_7O>awBg+yhc*(M;uM!>G*ZGSOT#eatO- zi>JbS_eI;QIj>KG1ik!lOgo$HO}4(o=CJ*94i9^h{J9HP*)6F_ zdN%Qk#Fygx`0k8%DE@oj)&ZG)YWN-ZE|dR7K5#!rjW@BeapFW=6bjHT%$VC?47cN- zTY&##3}@#T+@Bw0M{uV|PRr%Ypbt!Ke7wiZ4FcFdFEy{wM zqKzG<{t@jx4dmZGcqu(i=tY=LhSe+_)Il)ZB*%cDEVXY{Ao)S%ak5RvlR95dC-bK- zezs2N(=yww&L(r+^H6v2`QTRe33L~EZh1qTIc|0*5edh~*m}d^$Op($GCz9um8jqH zkn9?5Thl%nqT@^&_Ur0qJNR-{zWr#@IZiWeeperra;lRcb%$(|!kqeVbW)lfDv6Q0 z2QIfvU~nV&lU4u`d(4}7RSfY|lf~m*7enHu6jOX2S?TfG2`ZMEc|OywLn>kb9+z#P zNbT^>A4g40GYNi&#CDks$18QiROLw>D$@V|eMv8$p}t8k2dQ?Xt?YpR_Mj;xeSmYX z9%$46KEVWNJE!4@9F5|392pNK)p#^nqfm>l#)X`n9-M<>;#nJ!R{h!#*1zo2koUf}_QBPY!w0a}YLW9J}2@_OG*MJ}mYs?jW@O z5~VeN-t$hRSn3}WzigVxDIXPDl0Q|wI<U4mhL#1sBp_@mAD zCy~`6`^0{T*%7rp@@4eb$XCqb`+diHn)r5gN_g>XH-@EC#lKPcm}RM zg?sKAuEuy|Hk`2;^);MY1<{cHBVV&wEn#Rht26}VbghH zOc&a2I;k^yw+!HJsAXN%gG4blU{4&F-5u_UIiv_LlE+o3>`qE#NqFUxOwEUxE{B^y zl+nk{M^Xub*(?@`)9?|;ah%Q5AH7+10rEMG_Y`ig{hakx;CD8PyUa5k7`?1Ug>7uujV{jv796o7_j7w;J@?Web>O> zTx8psK`U!_lAG$ zx&~^BJf;l0$QQVt%(%DjfaiyaBdR!C4L%;{Aya-Ut4OK6Owwyw(r~7u*FR#0^W0no z(|$|(OGDL%oxZWYtCF}C)lqL<*z2RJiT;eqlM1`A7 z_<7lHR_UguqG^xHsS7vJa(xx;W=`r{YuF8=`uT;`!=*(Wis9xW<+?uZC*9A8wg_t8!#;tuHMwk3D`7;j)G z(^(PpO?PA?*!(Xd3NBcq{mLXN!V(<9J01A9wXjNBERjY-Ez7Stoiv*A}-o|iAMd%nvuIAFL@JJsZ zNA@R9zsBOL8^)YJ*gnvYQA>0qb$&cIxP*<8+M zF*n{+Prt+=3Bxn5$h5djo6={S&TkUeu4UVD4MOVP&ei>|1#+~0{ypS#`yW5QoQuf# zdqh_;-}z*BhQ^EYly_#TwP0DZu`^Fu#)|#(b9y z{51>8y!cKQQ4YQFZZ(aaJOdu4OYVI*RwD+w!(=4i#WptgXmSrG(~Yn|O5U%`bXeu) z{6t&E&y=1$t^hdcN|8{1yc==ZR6`y6TxOA1 zNl!opho>T`NQF~jEt`H%?@*iAd%;F{yNTucr5J*CpbTta96wz~&q7ntb4-tSj_Voh zRk7$qn$epQk3^V^W;SZ6NxF-h%mnI}rU)l^HCQh{RF|toA8z^W+~6Zb;{0Ce3AQzT>e}u; zcmO=7Ne!#5XOi812{oc&7n{SGvxT&qfiTw%nU0#F!LEUt`34DdS@DfNq_r?PJ8ux% zz!qGd{rSd^gCQMZ3%i11VTvAPi0Y2Cw%vtRd3o~K7YCF?hboKuKRbxhTRADb+}jR(NVA0{u?M$PD~P-ynOuN zfO?+!zAfVY9E(CQolJcAx~P3(~S9ZbBXRUSGGL@bo2w z+3gG#l7Y;OsWO1f%}_h;|1C2$WdRf@G0b6Ym>K5DS*YnNk>MAgH0eORNS$dUL1WQZkMD3BdYs-W9v-HheCls_t8>a7oCyA~8#9@+64~isqBqD2 zISxwk1RZo6u;di55(DLFoZy8-1$;oaL)#!Q0wF+z0 zSzgAcHvFWHMP zNSG=qF3HP$qL)=7=Y*Pp26LO-gnq6+n$p3hA6VRKGac6JsD6f)WgZC!2e_*?k#toZ zCZ(gyPOrphII626oIk}SR7xp8X6NvKU%-p|qxKuclh{@L(tVxYCctyetaFaA|7T%TfCq&KCji+ulILr)AhY6f718j3xjk{_wsmK@X zTu#n`>K|E{+?dnoNArTo><8bdh-2;qyxU@HNq;DXtFSB`f`ynv|s&|k~ z^T57vtJxTDRa?r<#ZGa7B&Bwon}=afE~9}eC7!_-yX3@VAO-Oq(_t=H>wn3p*~_ny zs0%CM*WN-_(p}nR;>&fk&xGJ9Jc#0PviaAxVYBt;RQRBy+zsZkTaJD4JS~g8aVa)e zVVuVybPI071v$bzlCa7+lk?&V8O!OH4UJtU)0&?+A#8PPn2xqi6dJ4;FqD#L!&jjS z+=!dHs?KftzgmUtd&ByK9k6Zjt6T^DEu($AGO{Gn+D@j0MB=pti z94}7V$T&W&xh64=_i0+_CoT#3bfR`O^WdGVQ`pq;q%=)E7fe6TBRk!*jwZ@=q5wBh z4zvit_zchEPOQ(JeH6#XUbR5>MO~MW$)TRi$6YfHolrt%qh0O`(+G7@X}Glx`j}4F4 zKR$jkZ^)I+d$$~2pKjyKV`Gn}e0$*iZXX{%l`K)J1_|E=Ht@;l|J<|IuWP*f{;5>T zcxk?^%w*uW-`C{TqymkHp5gYY&ePNExh)h$3BU!QsM4yhN>w9DP( z&R#$A6MRG=#FObHZaS8b?iM0QIL?zSfA*2jPy9m0jWMR-Ae7s889 zg)`YAH_K=0i1d(FbQ-PWY0pr4Vqfa^&KS3#n&JhEjhknG#@#jDy(7(AZ#t8bGqj+4 zPLFo|$r~PEPT;L>jcVa1Zk1qrNYv!^zrb04lgYRQ=x$|Z=BdU9v?~OqmQt6vJ*%ia zPFkmjlg*i@rm}GH^&Xj!G zC_&?hc3;}oZk(oJh5YIA%<{P{Km7duZDUyP@55rRMud7_L{*H9AMr8nTVx)3!Vkx* z?EHj7uWb`Ek;m7yMN#{b38*?9Bx~W&{@}+A$ph+)Lg)}2XDRT^=b(qm=^%42N($m80oC2G_ zr?<%sG=1){=l?3&WkBy!?$@|<$ZZ=QE8J?jk=tlm-cXj}`V^^Bjb1>VbwpI8~b5_bK-@~ zOdEA&xWfr30RKfB-3A_@2HKrNOx1t7`<}tCJ>smsK^xmVIfu8RIjCQNc|~UATwEZ@ zan;l(Meqa8uEzQ*?OxrTGiXeQ=rk%$hl4upWlQVm?3cqGD;x2vp)*v*sPABT?sH$M!|}(3AOjs3~tc=;El4 zKDoQl7tAp|P~aEhzI+C%(@AXL1gK_x&6k+O(Ye1i_n@ipaYFz`tPE_45N+d6yB40^mUiCsnsu3N^f9o zQiH_|;~kD?r&I(8-U43eMV!~QcuKFK8@6C$CbdHsIUXe;Bip?_aWmU$NW>Ca|L zB2i-b4hAk926r$C0b9v#PT=#{cU9u^$vR{ko#SnVw-p<<9Nc_ehpKJ7b^NMd%33FV zag#S@%g-TS2SyYK8|-}%y&|q{^y26l5yG21#z&RIxz-3jz&W&+FT8!pGZ^Ef0W;c1 z7x)TYgT#iRFa?`9E$-q5ElFPdIJ^zTRC2pbF4P&+A>1|7VgG`7zCP+-=z=SntW1!5 zOl@=2mawU~qc!;aNp>6B-*i^e$R?WB`h=+f8c-0|@H<U$8#wv0 zx!+A8H=SMNrm!7!IEdj5@ZcAy9~#-QphGuhPwuy2|DSb!nrpC`L9&a@%`Q`fjp7M! zz%2PoHleeQQw&AqE|DJ$;TKq6CGN8P=mSf0LuAu8bRzvPI?!}vlAJLmNiCQmZkxGc z0{+xChKYgit2+CUqk?5}r!F&F0vs4WL=ti%YM^F@apg?zfr81G9c#APAX1`U{7RzK zZ~X=awlE!=G3E<8ju&{lt8zCr6+c0|E|KY!j||0|Y{;P|6R9#4IBg|awNLFJF${&t zdYUsQs;{VWFU#Y+IVI&blbL>*^vp%+QCYbnf!M*5;3t~@Q4OP5{moW*jUDefbNDV) z7Jo8Ow!#!Fp(z&*o0;>SSc&2|2<7e)bpBi+9D(p0gYIHt(|acC~2*DlhQv$(*CrU=GvrZVoVmO=JC!9^$@nbGY~1(YUEP z>jb<9D_kF4Fs@|O!QbsZRC*hH)qDBYuARI41*`R)wvFBM@%r5Rk0R+xPEX) z{UQ_k_#O=y8c;aVCBG2?edz>B>uv8X?#^-B;uU&`(`|#hie!p)C@mw^B>9Gni%6W` z(`b48D)W;7njI9mI{jpq=`LFfD_Kgnr}OH%Ev=u~9poLxhYh|B*82jjQCD`${V2-j zGDRkI($lH8O;+<%m4lt8B$4bvMFC&U&UaUy;Ty>*FEW>9L-~^w9Qpt|M?Z3gPe?C0 z@rzKRSETDZ#N3v5;iiAHy(F-aI*^&YEfYXKQIIsu%Q!0+fT;e~i%F1}LH=x7RIQ2W zqUd32q2;(H!JAZDnFam$I~>rHWKoqFoo5`X=Od)iB_koEI&Dp4x`~>caWir1jYfIe z%C<$teooi4NA*ZHxC>0x@AZ3lvl1vh4JfdSf2WO1;EWc(m`-n~<2d~{nk(wEIlx{w zoIJ@K%%;0hu10b1H75h<6TDg;w9yfunAYuOYk>G~HszUdqR7QaNvhsvv>yf0Y~J{j zjA(Phi~6#ojYoBA$mHzAsl5hQt1m96Fz&8*ax(m7XW5VZ%4A@nJ4he6E_3U@&_5MW z^bw#qnE+>Y1Ri=gzi!xQn32^y>z0l3FjMYrJ<1AQ$R-3wdaS{V=n5*t-q{M>X)7=! z`k>MGR0Q?-1jheR)pvr_1%=L1z9*OUvzo1*It`u2PG@Ho&c$H-W4lT7@1U2;eY8mD zlgoXE`j1PvC4J=#{Y#cBey!=T235PHY$&@8lYX~Do>z)zy8<_20lm-ma37kNoQCbmO3J2J zp%*G)$GR8H1$Utt1)eerx6XEQf$pQZxX89+WdpXYAt;=tFyFa)ImzJF^ij^;$!Ls< zlYhBdxTuQ$>=?T_MOw;Wu~$xDQbY;OT)$nm6dUAq{#pmJ&gb&{xnx>yBdMje31#cu zryHT!+KR@vgE;{wUJ53s7}~#2<{VhXO!MAmrwipVet>=aT>dCKirSXwkI&Pxc2UKU z(VJ1lgLkX~E<6LTz(+E82XYsu<4$}*mUC?;b=Tb{%6NO(J+b-B=-9J*LtK8d2p74J)k9rWy{HBRV&XC~Q?1rj#6V0?aR05Ri*V)fr zFx%`W|K~WIa0=8%L1d8BM8$JUhw6PM8l<+38=rpdj_~&l=}c>`V)4thqzP;m{BtD| zS_;AG$I!#P6Yu#Gc$6_{x*pm;JkWkLAdh4fS{HYV8cbv#(Gq5p`(+32%o9vf;ph|( z>FRa`PSE=9Z2i@}pbegyXmj4I1HG!DAIU`XeAE0J3&O_$pxyU(}?DyEHK#z$U!=Zx@^B* zLZiq{QffQWCUQ(PGe`Jb=h3z@5ajj-o4*4>-PrC?ImAvUR2+9I%erj)50pg{UXFbC z5cc=mOtAg%I1S?8T2dC^+_(=47idT0;!ICIURT*12k{Z!&7&%V+O9UsnW`FXn9n$4 z+M)q}%9-=G-Gh3O$9T`Zz0FR?6}b*H-4~@sMv_}f ztBE*wzbn`2>5O&WbM}PE(o)HUqAq^^!>FU-T-`lpmb=MR1&1CAl3AChbriSqFp%v3 zn6GiI!SLl_k}WR%Rivn@Ld0UcE@xE~n!y}4r|M$9%1q`hO}P)jh#Ja1Jm;xtSpS6b za4ngOVR9#!Zn$|uYHl%g+-6qIY;`gQ{6UcN+q~QnXF(>vkzjn5-R&rvztre97jpV; zf)&j|5=dJds?Eu4D9?Re2VUV3zV3DMpngxXVs9b9=>BsX;V$Wj*0Z^IuUn%zSjH5a zUKY|_jt-*Tc!eDVa_|{fKsnxwOYEtzA?Q}iu{))q_h^J}$3EqN5o)mI z;`o_w;#`FK;vPxU zX7kJ5v%-UNXRQ6J^6^WP>%-;5|66&~sQeje@< znIfi;DNj3loX4dk>!utc-f#*|Msxo`@AAH9F09WciZT~X+JB%QH%ZAvYfG|J zEj5d`?K~({BN@h=JdHeFfBVfOF#n>X^&?*-#KyTk;KvVGsw!z!70ZerdlkC4bt^UW2bjQKJtk6+BhfhIaH|rvzI2+JzJ6_h} zWGSwc8m|19s)~yYj-MFpEEAtqCy>d{Ozf56WAm!>wCqhWYiV?Ur_;HQ+>9>$@va|z zelK-j^kIiVa9_bPJ(Z_O9BgL$snl==m%%U=G9x@iw{eC9lk@D^rC?Rd(R5H>8aY&k zlJlI1=lugIC^bP%7c*BTLVx%G2lG~QNd9tN@x+^76sE~>m7XoSu+y*S-_aMHZKTZ3 z&Fl}a=*43(4^`tXG?(Y~6!W(p!45Ot+<J zIK=l8g~$Cr)6<*V-0}W{M=(DVZU=i&-lpB5qj@d2n~pG|MVy@^{&ceyoFu51bK618 zXj4fA9|-dM1<$CIW8@UJt+eQaW};j8fOcuWNreh)4ySNC)eYQfJoEEE{PkAo03R{) zRL z;`NVvR_t3HJaT>ZEz5S6yVB>@`QLkDG9?TP^bahZJbplx1pEDJ`<3PVOq`&M{}ZR5 z%A}{b>v;;UqX``&_S64eiwv*H;Me!i)a6srW-|H1QQYu}lvg%oCRvI(_Q@l%_zaC+!XFe z!i3*4`$+;@XCI@OY>&3)gd8E;Dm>e&wwwviR}s%i4>=5_>26yQ{oiQbqb;c0qCh;$ znWFR#PBdM(d1|>n=1JjokGlSIB`<&@%)N9np<>`B<$?vsE9|Acn%vR8Zy=nuvxrYt% zDhX+yz>?3%w#*cgTQaFkWPnz@`1s7G_HD z@${DYJgwC#7^>_j-)Gy*GLhabHhbxu_L5QLy+qI2zhpYa!1ON@cgdRjEyxx@nRm{TSwfzEDM=sjkwUSov0 zW`2W;TgNWO8OX_OxS!ltH7AZ9`&B2e=#3WioIkl9@44&Tkr$DaUJCE{AF}ANOe|^r z*0>gyIp7x#H4}9lO*1+vZ*&dr)>r)F@en1iKUE3M&$xC_Hs|tH{ss zl83(viy;;H#Y<=!@>NuKn#gLlv9mz8umO_g$vE{UbFaw49chxyBP0F&GCu6l5qgq; zShrzg%T3;Ckd4W>|3HGWEd7Cw+?zJhiD>Vvv*pEP_$WEp|1_KqY#QiSO=W&}gnH&q zR*l`oGM5^QYm`IO z+MBdl8QvHTU?G3ylrLbPMpxeBsd(C^!Wqp(hItXL&{BB<4{Zha!}_Q}ciDDz7oW!$6Lehjt0I*!{rb|CmVvFro0WQu+#dYO0P45{bCxCJMf$sgnJ zEinDf6+HwF(%+;(UN~*oK>U`pO#(c%wepa&L58z)ySv{&EQSO7jtoF~l8D>Mjg^$& zv-ji-dzWX4(Q=7eAey?*{1on3dq%A_2UR6gPsOk&BwFV~b4yM;@wk6gl~G$FH%F%U zreeC9g&P#@TX%H*0d4+id!xzoChLp*PkX2RBdmU_}9%frp)qGL!zq(L#6X}Xz_`k{`e%bRHL zj7g+3*_pZ>PwP3-Ze{4FokVBr$h7i?e93!GNT{fM;;Z`&=QmeA?N1dXPqIP#3X{VoRJRRSrP&lodGE6J57`wYBKx1ZEdGD-K=Zk2HA}( zIE{;WdcMVl8A}>5y#w7DeJu-LUn9OF1HFgN-B5e++AA*;uy<@aYDabwMI)SR7RtW8 zD(m|Pp?JL_*S1G(5@l5lY1pCjid#%iPOo*|?>2|`$^PM`adzl75Z%6$zxY+;UgjgZ zVq^fXqU9B5bIV=MuvU7CNoxKwtL+8zg#_*%xG&?yF#ME1*~r+)iBYMY2=buMcq{f3 zZMaq5c3N^W?ByQXNtWkKZb(NevA@6$qnWgml#g$(>2UkTyJ?E+UiK1`<5WD@eok2! zsq^U>525jV#4Og?Z5wZ`T@t$JZx3w~XS@n3lle&n$tg6znZ1j87GOTDj4mBVpP`j` z9upZ=<3*dN(=KPdm~7|N?c0{eEK9qt=(;3l|2v!R)1RLwB^Zz>b&_34BO_uW(gYg^ zQ^j-Rjf;0DUS5@+Z9aX>EpH7iz2#&E{((4kK<6U;auj90hsfYA^qpd6?&y##k}-io6ICyj^YYEAj8a^8&|M8?s$5h_PK1V>H~RVu$(QIsX;Ojm?8bZ zDfxi^UgN%m$^4nQ@H^))9Fq%XmOqX6?q#!rmC$2|MwDY>`XL;nDf^Kq>JWXbNkBaJndso$Pcc_#qk$NUdN~q4{ zC9;VID2^Xse%6*5L^w?&_U6K5iY045jw!1Iyr0JG^l8S1r1ea?x$S%!)baRPUEuxCizEv2# z@_oFIKFRwKuBN)9ob~Q+{t>r@f85RMH*pu+qADYsFc#V4xDvxrm|r^!-LvXb{M+#l zW>}GNQ^}I0pEr8kWKQd&Z4=dQQg=!A+c{rC`x@}B()+p}6MZ}syDTPs-1L}Y(bK;? zi|rhfj=B6tdJq=%<|kT*y<{a(A&|x$sYb8~`mIgIZpH1W;*Y+K`@=1fX~-9n4i)%%l1(kQj^IvE+EA*8)y9>oTe{MYq}gUywfv~g&X6a z<;HiN-5f3&z-W<07MAnXNO4ZB_oL-e6D4x$gkp?W$w{rJv$3)k+R^W1R%$?~J41qd zD>=_9w7rw^Fk0+JtK8*(l#BesY$>eHufcGLcQ|ZG_6NaF>w^mH4;xoPf}mHpi_;sWaH zA2Jow&=nfz!?@Q>A$O8ku4JSADf)b~{8#=WdyOX0QBxj%2hO(cgeUUSFKyG14!#cu z^%|*`zqtFZ33as%f6th5FKue1-BCXCuhdj74?bW?lXF zmu(-*J^J?kv{Ml$?r#j+;4fRXG<=)cQtig5>v5*F>5%kIl97-qwllHacdyLQAAu_jR`TX7@<^BlG;|jYhex{{s zX+qt^H4f+8?C;kQFX-JC=krnxf<44W@tM?Sd~t#u@JrnrZXUNr;E9~(o)tZyPu9Xu zYRQSZ3u^XH&M7D}OX%=0neZ0qNs8qTZ<4BFg};n_SI5!H+WSj%J*Y>YQC*ut6!@N` z>2rTR_qzN{OW8;_?R1i{i?unf;WOsM!t%kX>GGjS~ek^b_s-AlnMZ zIU{f%HhbB*4X{%qbi{8&g8QWx>7Rh})soanE;crG!v|j@Dk+lu>N^O31L4je;7+CR znE%4Xs|hK>c*8C6i z>~OxiCH{O_${vvEpt{}FC*@4DTn@55(X-OyqE*G!De1i7`EL!U;AgQ;$%reFF{zS% z(;$DxLYZsn+ACYuZ8YH%yu=q&3JR&gfoqgL?yV4SO36%hpstBSLQSQhCM`e zob0A0I&dNAfAgFT@V55ADXmKi?j_UgZd-zx@}_jLgS2n&~C&3qL>Uq(%@dTiT=OrbnE*G}^}F-c^O0*wK{K1GMnc>&o0rN3&xz zIi!jvsAE6i{uQI6`T=%bWm(zzNdCMW#Em7K%*~xH+|=@rwJy(z-43Rg%fF#Znn0t# zPe2<={&#mAU zig`kt8>akjb;e)rCU%;*FKJ~Rr0+Zu7yb?&eF6cxCk2yL z+~KXVhmBsxp(+IB6>jdQaCDm6s-}gRpqrZ~Iu~z@`J8%_Xhn`7BX`=(Cy%+>xvb_u zNAD(Ps3kJ!W>k;VJh@7a6?dKa&LH0O3($1l)AMX+j(Oo`z1PCD)p=}PlhJ={x|15t z%*OQI*K9W%~*7V_xh6yzq|1a&eeIlOA*k_*UAFeaO!`!0bBH<4*>KMbWp3Z9x6 zYr2S9<_!+UJioEmndHej)Vh%W#6D%t$;r>S4>NNixm)E^3*7T+o7+znb(t4c9l9K^ z=!Ya@|JyQJAZ>8h_i>-t%RaX|q)C?IT}OzI&~Fd(P8&pz<}X`{)b1Ku;CXmI*T%Ij zCI+B>=ad!6tM?O4NtBgPiv7+(NOH5ew=S|lZp{kS`U(F=wQ;XIv%6!23UgnoFDgm} z_`i`drQ1LR-J{MNRe%)xT6D7-?5(+M*P^jb)~#$p&dx@>(WatNvxAEsWQa!VVNSRV zOg?8gzh{y~zk>do0V?Hi@3dn>$;3b2Dd^R2xTSupIwv)WX3oU;{7J)MKgXa6^g&Zz;zYV>oh6cejZQl}gcSUw zs@Q$zEc53tq!Z&iPi#tOx_^YTbRK-3gw6{21P)YL@t7XdbQ%E%Ay#F!C%kXXyU-OK z^p5Lm-WI%zrtqB7!^P>({cDH0iK3+buMYQB>E$_hpbQ4)%V93;SCw5n6VW^sIq=e;8qb=*=irJ5)vLMPaVPnd`fcnQ-C?Qx)u(>3ipeaXzzD@=Qn1McGo z)XfRJueb4wJ{>%`1)PVO{RX7i8jx%lL>fJh{grn)o8FxADi{09&+v9R>?flaHG{5G zQZvU7d42rTUMl~09i&T{hNtT_?4?V#w@Hknw;9r|=l}OLp+y!#_^#c|Ep0xD#w2>V zej55$-wB1;C*CD81Nq5iPebV~Oarf<^M%Yz>+g={c=8i;Cw4F)9=xi+h!18(mCZ(T_wIeUDCTKIxbXqNHi#_w{~?B?a=b`SW#G zD_{9}cX0Rkm3LN-TlekyUrtv!6MFaY{l0kDBWoxAG0B;Th4H2Ymj=IyXcPV*yiHgN zY2d%?iW_A1hyI4qa2wK4a_G?e^kX;-$7rI(b9Xw&U?p8}5BPNh+5A)PDsxz_*XhN2 z{Tuw4oNW4>M zNgRz{A4i9WOfTorLwK-FQK?#yV2GjlG8@(D7w)xh{nE0U-#{L>Q6$g$aY8)C6EE&e zruSV}CG|(}KKWJ!oO1FAo=AFnVG;*vD|esK&L`oZ9^R!N*-*BiVH02Y%)l?ia(-&D zqP*ye-XB4J^JkS(TG&p{mJ$m#FaE;H~ae<-RUGm$BlSC zPq?$Wq^%*uknk*s4?f>%q3=8E^k!$I`I=c#syZ3X?R~nZZ`jba-!TL6PVRC8Ikj{TOzoKVecGVWc-qj|uhH zKX?&F>ydC|UYdERB*~eW8=!P#V^%oL^tK0Iurb_`BBC9+-jrkyMxoGjC1;r8>o!aK zhgxP2xbv+W7-L_$<-Aqt zS>x_Rp#s;;O~IQ^k-_Ig|5+I|SUTOGCd^XAZjxHhrNV*5rfi%F`Fn9QyY z3<*pQ+snYJv~BpFl{+)YpP#(>z9W> zyNj&EPX&aIfh} z2Pijfk$E)2tp5wy$+mt=QrX?vVLKQuTq07BHDHQ9BEh(nJbyK)nP<>QOS0MMCfZ|T zQPy-I-<3f0XM)MW6xD#V#RFV}tmH2yaej|BHOvk(g45f0YfY5b*Q`R3ooe3bwsxhy zV*BW)Y({!+H;~@XU=sN+Onx}eOa0^cu`}p`W#O4gg5%eMRPTIv#4Vg%axsk5{M)pGiW)3E6A5NiA+hCsuSw#V7##7WTl7lHJ26{ zY29C@EqYL=f_Vb%$xdevTMKBmopFjru&3mA9Lk4cx_=B>PhxH&$!Q+`C4c1hlu4e3 zfSQ&BWoMr0Klq%wO!NEfM(D}A?NyQ>4maN){TMxu?B+UKfO+#E6X_+@JP_x;4^|FT z4f`fADLBd9l8A{uMHf>HNOju#&-? z$N{0{lMc!bdbu2+Z?R=)o}8i6$nSI^nGt&QeEqM8Fe9D(ya`WeFVrRU;Zx5~v5$&9 zdVS{9={K7%ZR)Z7=CbQsqPJwYmh}2cuZFpos7TU`3Fjof7x*J=vs)ohExd48{IG%U zD(8!?VnVSYf0Z{+9m)|wRbP>ojK3i*MiiUw5Hm#f7EMg;v)G!a;wAWDH z(C5gnunFFL-K^D~hU7AH&hKHeJ7>AwN08`RM{+}>H_ntLL<`y1xqzd(hAdEdxkzo0 zj+;S#ma~~b*`!Nurv=@SO*BDXIAhS13X)*j4S_BrEYTn3SP12NaS97TXFY~a6E05C z!W&NF=CN+#)Mo1VgEUz#X93$NN<$LPsSJIma^jXa;?$HK#V)1g5x25?#vSE;<3_lJ zRRvW?{vcO9a%=x21q4^4&tRLdEF z@7bR0=6PAhxXS9hs=QgD*gotY_Pe=noJFc4*@Ye4c)ODA`(QTOow}+mtZUc>dZ1lw zHj~@>M89ST&b?w|eFMD9iZ~dr`HBy7o6G`<_9F@T4&-U?vms%sFX)Y4HWf%z=He4; z$l7l;@4Y|G5}glXdr$i}^X#9bPsg%(H@V+h=5)5ITh4U!#G&#Web_>1SI;?3J~9g| zBC}jyl=9oDyv`RlmlGC9?Qc+rO#=B*mlthl9w5_au}x zIzimwm?d5j)a7z+{qUKxQ(!cG@$F<>W~y4wDAkoMHe0=gGUWAk!)#`^2I+v?I<1_e ze^e>#Dpe3QG8^ycb=)LN(d6DOb2-oc|7QBi2@rlM?tY(iIWjoO{NDN-TS(_HW%VuX zm^w7^rTvJybJ`hVuQ{9TZ#2xM@1E=L{D(ap28@2tQzO3haKkKrdG_mx_ znwqQVEsfdSaFKlH4E*R>CtBv|H76`4ykerwiFbt0j~LBg63I0wq>lTJ1*#P?o% zr@WWNd8t=Hh#SZRUIEAXtN)S9o)Q9%M+)wgvzQd_%EC^21=Ck;bF!-3GN@Lda{THl z`P7{ycDm`sZv4LzBpvU=N-sjI)bLjaIl;^NH|0sx#YQ%tvZkU`b_6G7LO+P^)f%X-G z5Mt1q%G0Wfh8fqIIS=YTO6Dd~F3-(#C=oB2UOGco`qOM-W?Vy$=DBcD^@gf*@-kj$ zO_k9(FK^n(Or(FxYevgWW~MA@ma+R|my?s9eHlEE-%0wnMWZiCiz_#3^+j`%X)}P= z8KuXWmE3LDlSiCSH>NJ^+*zE?1Gs5*bMo`PVlz6Q%dv3uR{4XN89$o2X1GpfDtYP5 z>Cj5kz#ERkm4hjw2rfru&gy^s25|W@!qBqpmT%_Fmct=lo+3N53ua)_uY14A;Kn1_ z6-kpR8QuK^?nLoj;4{kOQ1%cMwU^`~Jd2B@+?Uf3Uqt4375i?h$hPjQK-P$1i83ea zpQTIoU1h&5pR?)iA6~b4-)ivp8)}rv)*y2yKhhLpN zKwP-$tkk!h%KBGlxE}1RGz*<*n~~Q06B@X+V7&gQ?~4Vxy6kEmiZ0v%GT?q+C$+K5 zSqRmssQ+57_tU{D`jM|J2YY|m*ACaEy)DfwTb;XOLcH?ar0KVy?mZ4|(X->u>Y8ys zndBjy7w-TQ{~^+Pzp@d!F`Wf^x^f@Mtt94@b59px+S!eY;mDvvVi4L+Kl;|1{$qZb zK->{0NLDN$%iwa_q~NXB7p?9cER+X+4^fKy#X$Llc1Sl|o5s!w@{A!f%lJCJDaf4D zf*y&2tvp8TV(vd+E30|7t*UR|&>PAxLr?$`u*abcDH-?ciGdE((G+n8+BW$6jbwXK zPnMAj#1^vo!(?8lj$`N$w?Q3#025}M6C@$_l6%^2NI(P7Lk9bm`TGaiKKV(klZ8}C zHNw5F<^__v4+EoAjN4AuQhV8wml_u~%*L>hu9O|d4(C?fxE}jaP68ShKSRQZ$CR2{ zREG7t)L%kMbTI6cE~dR#*rtOFGS2gzu{xg23fEu`o$bGPKS4*PA+ge|^n7n_?C7ZS zA9FqnJ$iRx&$;*8YHW2^6j*U-;$lyl1hiP{yF8yaq6NI zEvNaP>B;9-CCCDm^lQkk_tv&-g_!|!^A>yJSId9Rb$X-;`SW|JFMdK?6&yNJBn|xP zDAD=pyFGxfHo`9^kNKlfce=~FkfHK9E5%~e|69-x|0dP670uwYbDxCt4`QcUC{$n& z`&;shPi`aeRuy7vL>@JO%)RedRBP=t`Pf_(<4ry>+#GcJn2pW^T5%<8a$ME1PC>M= zd~7kh=U1Q+J>Og+rQStsHodrG))j+HPT}K0{X}c#8LIbu|GlYBZ~8iUkyd1$o{44L z={l0wm`X$HYnJF9I;|p+x{&wII6B@d>}qivQsiAc`Kx4CP{?5O#-e_eWzKrxk0ABZ z!gR2iPyutm#gLqxXGm0~hAVsn9fh6cbXqcTbH0avI|+?#tDVGNwSpv!J~_K!MaH0< zyu{D^q{r#4dYV_u^!J+Bpq|avGxkcO86-wunZudWi!cTD6%X0|ww`~Ns;Zs1uio39%G0~#O8v7;V_)+#U#LdQN$MuvViN8miRCHPC-_~w zFG)kGZhf~S-{ER2YyQ;gQL8yE?=^2wzF?ULDe@#$F>^k*dH?!NqUgaxQ_RrWGd~ebQGX|~%FRMqgpW9yy46CZnhqC;5lb%^U z6SGx1G1DFqJ?(3>#A_r9w?JK8Lh`Hv8RIw@e*&i{iEiyT@iyB$x-9ABuavmD*5C`z zp#dBZUmzuUrzieV81RuW&wP?$2RUQj(yZAGc^LK)q?!Nhdo~TU(cgN-%;!*FTgwah z`Mth&YiN==63TBf>HK*35A{#v;g<`cXUY5e5_gNlfnWR?fm8TBoBcNAlR~J0$(c?n znf35SVD&<^IO7+U8PH5G`{~s>{}1&qTRZBL^S?`1svIrBwm1gwoR-FQ-s{1Ty(V%~ zI7~m~EgEemy3=d;OeNv$x`Qto3GucAl*Gy8H+tZgWy1+5B-WacQ8Xrxr^d8pqv%MiC@5d^^m$F zI=e~O>EgJfN!6SXE!9kDiW2W_4UKm}3%?m2{C?Dj5#$7BI%CvtqM3RrMxobrfJ2dy zoTNtIe`hxFP8e(l>bL%Uy+Cv@F?0yCss7Lh(?ano#oqe4W)Qk%U(XL!iY*VhP%O8@oXSAK`{JY zcoCJ`?hDO`eG^kEREtDvjGsL8*p7_tV$Ou}`tzY|yfk~ADI{U;;lUMjvcn->L#lAS z`D7lNzVxTcGrQDgyY(ehv{~Zz*Yi?Uhk_By-sk~zU^)Lq3!Ll#+aT{dk03YAgr1e1 zmT(EE%vYUmsuXVdXY{I+PENinX&gm9qbiB|nYcWE%V%H7+vKE1lZJja-J$&68j3N>w(FYH1wV(wsq?*z8Y$`!bTbE)P3FE3xAw zhx3s4^frj2#bCy)*TR{npFt3EnClaAYg~#u^PDEqS&KU5S69QsL_7rD$eRvB`$z`21bM1dL;68E7j9~-H7Xu~Q z{7SZ`kDXygnL@mW>)^sRBqOs1Pd7aY$F8IaYSGf!=5Mk0+4{B*w1-mc0?r) z1^tb(NU&UZg1e3`R5F6f$Kg|qP9k=rko=OH-X|d4czV_;t zkbvmQn`jY=bprpsdBk4wX%NIa>#pRX5;>LO%kCj{(Mb>SAL%`O4+i)z$<5rP?Q#y! zd6_uN?XtI-#xDy=V7^n(uMXR5DJ_oe_6_9tgw8eN`TyYa9U6}_G+O8@76wDSW~ooKg>XrQ*xlaIkCh~kYp zpZnOqaH%`-Ib6Y4{R{WcVzQ?#C*PVWY=_tl$%eUz9%&Wc+R0^Q7-+f8C^kKBrlZ)| zDQ0g$0vm*PFq_s>DW?93&Jj+{inQzw;0cUH)1APrprU^ak7|fJSD#m#ycRO8&c+nb z%h^VYp^#e4*3S7(12-9dTOIz_>-k;`L1o@abNG8mv$>qX(0^`-bL@ajBg?oc<#qCo zf4GChc69@PW0BKYWZ^qEl^$L%ZeBiVm-*s=?Z_s$bUfvI*m+VxRA3`PEi&qv5gu3tr0JPLnx87xWxvn>w^w|>0c{;{i2rBmy_OzBV; zFEXb4mj)lEJRS4j#fzgZyx!4cXXlkeSAX76{Ev};HMo@f^O5K|3I0jcD9NKFh2kZP z7!g)CP%mCsc!7w?VLQmU2jWtMO2&=~wGIt2-Q%Kl@|d-uEwNYd9U7Ajug1)~#i?^oTJ`XaJ58c^ zggac84=j;BfBjsw6~jqc)uZ)2!?`T)_%+l6`&>>j5pua6CFbi)B9B=Iw`Mmw?;^D4 z{CwiaLKl5cg4dx*zK`TGXNHs79uwJYI4#!mG)g;*>Zq(k(CKZh@ftFK#n}DO+Rqz>CZGl{vw0(yK~EQCsqCzyvwOJXquQ-&?<`XHojmNpr)1J zCpU|1!+XCz1g8A(NN1ybS24BpDYMJV>oR}!+R5hRTQJd1PeB4-1v!jEKqnbIJDbahUWx60$j3v3FX z7;kCP7Ae|gAMov*@@*;&Z(6$f?bdnPd{^^B^~9OmWJsfn$L@a9>P5!I-mKZ-;w^zLzedxTNtZ3@tNt4uywYGsES^b z&9?K$k$A}MKh@j)x!!(1=Yq*egOXZOdMVvv~I(%zV7bFHU|4%WJ%LPP7-FOwnFugoH4P zpLl;P`Nj;J?DBT2XY5$Ncf`LbA9Qj`22Lg|p7KPZ z%}Ex8eG}d~xH8Z({7bOA8>y~)pF$gAevN4wIvpxxV$2IKi)kF1p<}${c#~s9CX>@i zL5CtO#J;&WjASisX12%Q5jpMmauw8->|}DcK$#u~cj~F%giT%@NZI@WXLFlh6XM1M zrmKJ1 zHAzK_CcV--45tMckvKR!}~YedI&j(ht0?=7P@c-=n3s$M#3N&I{jkA!*!>eDzacMNOp9myS1N7c{C! ze2q?!-^$?O+(z|2#+EPV|BZe{b8i`m1s|t*D6MQI=P_@cC7rg;-iF^d30Ceuyj`vA zKqvHyh#z>O{tfF8Z$iS4sfVS@UvNO-zIE=^8Qu0>>(kAeHaT8Aap4OI&V&#A^yY2W zcb8wKjB52cajYAABq}O;VNBk*@9FLb!pDUd3r`zfB=Ep}?M`>^28sv%RC8q^)TQ%K zTRO=CPHj~jc32j<5i0U*zXFMkfog~)U2UVF@vef=Qr4+S)~y{n&~zuCKMvPA8(#Z- z{H3c*F&jwQ?a*!E)i&hb*V@-6FPZpVG$fDNFc`|odEz^hbpDe>dNy3p(dH3Ng`0Lg zq>y+vr1#r`@CS<6S9p(2{MqolhvDXJ<0m?WXS}yKz^Rr)&c$Wh#e|i^-zgi~rQ(=| z2<4@>SwqTX3$4*tLI?E%uP}VVwWzaCO*NYH=OM~Aw&zSjzb+KKrraI+I2Gjw=TG?? zeqE$9SDt1+)1R<(x1)poMb9n=S@nbbNrl7~v5Hft6B+C$(0odhFsp3gXWqS zX+M)vzirRr)fJ#aHJ1F;XnR8Z3oAd0cIj$n=MiZ9{n2p}aK|kmkE6Jbk{L<8+-8oi zrpG(Sy|YeTZXHuiWm(Lhj+T>LW+n&GNOkpRxkK%=KnMFK@Xj_0jPfU_otz8{dD>5r z=UU-p$NT6M;5*ZUPvo9p3AabEr8_FnTE%nEi)wNdj$me7mu^hRuh_Qz)oCB* zRKVNbYNz5SjS+`zAz8w|%TAAdB%_4au~hML=z)5$&TD<$5tdYd6MZ)X zZi|Z3R!vr^Aq6Z_SNP*Xn%;vX|ujjw-`!-L^*r>v>FQY3(SBUBnY87`;)s+<@>V=&Q+eXgmxN03- z6{s6FAncsmIj~mz2v6(3+_ZGe zz84-zr#}8vh!34;sdV*6l6h|C+;YnJzu{p%@hd*^uPu zcJWL-5_R3b#11;*3uRaG2BT@AoaI&;30d-?xC;S#IlZnV=(xjaF0!Qpr)iPD2aW0h zz4~?N7gw1x60yaoDtn=dlBixwhx{GdjL#eDcBpZv`*`feqiE%YHufmzb!#iOwY%lB{Wpw2>R)2g8zwmx@>zk&5nocBhO}BkoY#vbdh1 zGkSxG@kZ#Iu~D&kP7_mi{M20jT)By_624;;t=+$BmWJWSa zQD&D(z*Be*3jHbGg}3O6^kF`{z~1p?s1B`Zs9D^vWw4r7;378>Rbcr%;EC@cPQ&=9 z&F%S$J>VyRKQY0w!IZP_0JrU{) zaM3AllGqT}MDz)rac+4n@s4wFYy8{KLI30zHa+~Vvf27>0sEM&&luUtJa#7P(|&FJ z+OEo$ap=C|nz#ICg z)0&+BB{P%c+(#&&37|r6COz|>7He9kG{mF<{=cR&d&^gv<)qS=rW}DMZ55+yk^i~thW-(o7@MA)Ub@jN(Ug6jto*$D= z4~X|Y`MkxxG{52Wzj4NL?uY0)EMwALh_lc~LV}T{O&qR_e~ z^Y>mcQ71%iWJfGheKu}7?csZLgE5j?^wK-oC)A4VpPS(NU*bfLG6$R)rWuNPN_N10 z^Oal4)ZP{q<1cu9b(xgU`(J5)J?(Y=-I{6g2YQ&39+qtndzBl(ESW?skojZ|IaZAj zi33-ha>3_*zaZ{dAcHC8F3>z~dW%YIR;frkn7zt* zRSp`2eeqVV%e!bn2jzM4-#=k{k~EE?)hx$r0D$ z?TG&-Y+=M0QdGailnb4TN$&jN3;7YiOJxXmL{{`f*HFF z2{IQCc%5C#P~$!&)^Q*_ABqd!27|JsI0DdVA&myoe)r`)fm(%H^Q+x-=Z`;;&` zcgZpS5Ou?Us?h6I3tGv2?NHhe4NW(0h%5cMp%2cNxPxM3s4)rK#;T*gz&$CZ1#Zdv zffVXvpp^PP*i^0LJna#jCjSWR6C2%!=(Pv&O^4WH=ohcqM4#JSV2?o)5wfLaH)l90 z*W!E)Eg%FMcA)jOyLB#;6Z%;xJx8z8xs5`9%x%`A&(#tqV1JY$iQ=K8WrW#0O_YVj zpO3cr@8qAWh%mKOq)-(|e+(cu%YHi?)G0WMjmaexL(MP8eWe!Ug)~kIxf(W)!#%kY z=X3*eU;n0UXq~or zg(|S`F^7m}-k>A=jWU1JX+mx^Irq;NI3Ou~i__AEl;l6&e`bqUP&d&Tz1OCqx6jX} zzc>Xjxf2U)y zo!Px0pLx1C;yb8q!X(q)q0*eS9Z6?Df*YB^{YwpUe^(FHB{D;M#GibUYsy3rTFR3|tjMR} zF8a%Eh}qwx?cjB?ZLcW|qGS3imwy;D%57%{sVdlGBE28RiF=m&LL&Q;@714V{nwK< zXvKuTnH%m!C~ILdqew1$%GbhipNPJJmm*$pkmwMo#(r&M@1b3sBa1i+e=5ipy@Txf zJEOlUq;4v5ncwKIwN|g}Fy-J; zSLdIz3e74png8qdjV@tcM^A}f@Z!_6YgdwAs{fmE^0#PMeI0zGqhJH^6Gg%njdv1 z(I4%m1YP$WY~HA3no816>VrRC)`aa<-^Q`8riD`jQda=KWu&*y)+ci}#gyZ|d>RKc zK*G1Fyoj^2k*BSvxFUMP*M0y!yO-H%u9}m2x9zTv`2BSoJVCZiniOnL_!UOhFxu=D zd81up3)?d?W{H_i6YKu!wwFHeT7L;Nw37l&{c>(9z89Avlob~1bYAf!F1J`7yGtaE z+b1W6W~qkyf+}Lqs)No5calnh3cQA7>^I@Z+~Q&P<+8vCQP6$t)K?wIss9EWe>K^p zeDsO-kxIXV2kAlm>_n=H?Ta|JcT6hYWe?H4VsW*78*Pr+8oV`rC82&0H)1OdwrI}L z-g38&@KKEuh&-5nf%gt13 zI{~hLEF`IVem0ql)X@aj|tX7v^~0HRmwxiZo7M_BzHBlkoPs$WAtw z9BI+t{43;a=FpuULN+Mesc+MgG??l4hWis2Y#H7;X}#pdaEd+BFzuf0L;ri<+>(n2pgOj`Mh zze#m>vZ$MEq1g!oaS_>u{pLSCz>oG0iwmKWYJS{zDlG1kcpMkwxOxLxO;MEBmndAW zxh8)1&d^lZ7wDlE1oN6bfz0Nl8jUt~$V=td_L`f*UKO3j%gLK^osRGIFnPSY_)Xv0 z0PKTEZoI#cP_uON6Nrk=PxR8$kY<@jCps^aYZ|x=3_!#;?ES)WqUvMXT`yHSzWtRI?HuX&a%0XbVyy=paP-Feh zd=}S=jV2we!ZHwep!@p=OapO8$I1!1qPt$Nb+ef+ZW;StHS?RPA8}--;>lm43p0<| zbBl_0a=?XdB7aBg+e?=GYaeniY1f8orE||MD|QE}i-Uons2b?+G=_mQSpI3E{BKA& zrL@aXcQ)!QkUn3?)8;%IE+b@G-2B;4#@G8fQAS&soi?$z(d+ej*Qa^^&3>5rT$$6c zTjb`$D@QEPx-IqAcmK4z=7m=2A&LJ=S}^gMq&dS+#=8;d7ydD9VY~`~zTq&&M09j( zeJ!Sn=^c7%e+?xvg+Up|=xh2WK2|;a<$7{H+mp8Yk5ncny}V<)`432DEP#=*iI)5o z+E0*G`4&oa&6eFQ?e5>b;ZA&7}Ty%emTNQVuFmLV^dZziwlr*(*ysNRxr!%DgDP&*`8Mz!}k_O@`WOT;!(;p$A)_9}*6-Lw9odwzBv@I$!v!$W7iF00g@BKNUTmJCSD`%e< z5;;t6c^MrijPKAByN~YY5sL;TCmB<0Xv#KdaN8&IfsT(N6u880U-V-0OY{Y`_ z$W#YYUCcW>|IQlf`}8eqw|?+LlV*O2L?yc>zL~J|mv28FeZTN^$=EtyGMf~k*`X7$ zd*epO?uL<6FgRbe39cqDwUG_&tL#?yf=KAj<&)VG-K-zaLQd{N6`_s1cRrdPY_LGz zbt0UDGfG~CX#N~?PHVTE3d?3&Wal&3dUxaM`FK~ z^Fyu-m{~Na&%tSV4ecnsUsh$ZKS2k~rwZs|YKHEu9+_{{V!Iu7!U4Y?K4mASfU#aV zn}7_+J?{_mBvi$e4=pjrLqFT^bV}x;+-Q??AfBJ$2J(w(WFEjWEP(fS9%nL@jL#HY zz&Wn2abtMOgu5NxVx9D00k`9GToW44C8i^`W2w%}(dpSw{Fmt|x3j%2HOb$ieAfPU z*l;J0n6#>)X&`&)DCeQ)IE!?CXOMl%XE=vgNPlAk&*nrlgMUQ;+S6w!mtXva^b2#d zY3wW~*nLqp&{Sp&#EDgIZBg9a;!IT`oT3Q!T1+ClFbAz5NMf*`zd%3mYk0*)bMJ-h zs*|~E%+EOab=)7E;_gwBqA)+*)b1j;lbhMCqn63Sq}FmW+mx_pc ziP{A+hbIm84zpoR!=K1hs(dJ~mpx{9-1N{bs4A14^7@0F=Up_F$koQsO@7ZUv?Xu4 z{(?j?9?}blGyUvxX7@o*xn`OhaFUF*#K>S{5N^7>T~8&~uAA zEtKO|Q~S+Mx{NvGK0Q@z;cl^x+4r+9gLYWNbh5>DCGHTdok;Jb*y2T~4f>RtV;-oU zHiOE6J~{yY?`D6A`N>Iag0iaFqDGru?l0z{>SunE2T>`@qDwvEJnle4e}o@P;&T@c z)wgyNZs|5Y1sl<7huWn6SqMu%n9*#4$!2$X*GxyRCQsK~|1$brKN1`-=z|rLh1IvJ zrtC~^@;e1JO%Cwt$mgM3B9E6u^!GyS%XIurUN2ix-!Z31C|+ih!3}z!v+yvU;&oj2 z-@<_Yn)z|ryI###ULM~GE%P75Rdcq5P^NTs)aw%HWP`~ywD)VvWH!4jU`C28<}goT z6}+{7=&t4Q>x;#<6F>R!fo<;e1oaZR=^JHkTBK;HnA$HJc4%I{O_Msg>o3ja=ep!h zmFB~mH!nX8f4jr`IXa8!9oIWH_;FcCjwOT2TT|48d^ZvD%20gYCw8-VWB-MUeMLo^D3!$~cPskRP2ohU z+d65_fB4SYN|`@$JmSl`Id~=E;|*{ zz%1R*PNK7y&`XD}H$|2AesE*FM(#~rM9t#!{IlI;C;IfYo$6j;(JRzPR17T>%8L@0 z^c*IJNYx+q!$y+R4g5p4y@}HsyaxL3&~m*Z)R8l^xb5y8wx!u!v4DLvui2Cb+YoX? zYC92j{{J{S3uvpVwGGc$o9^xgMUYNukZzD}6a@j5mXhvLB&E9%=@gW1=~lYC&t5a` z!yR`x{{LRG7B~^=@+_R_wdcrmR z8I1L&6bt82kSRWLZA z-lBFY9*k85f*dq-rzZ2HyMXM7ej*rVy#}~^WhOc zM)!PbCiaKEESdaHJ@&g)Rg@$(*pSX}AGLF*$ofp|Z^J?DpjWMkKWo1Y6TeIUYS_K9 zx0auXf3&~`yYACPGZ*$-wP0`ieV_= z?%MlqChc#H;Z!qx*um^hx>)COYknr01u5mYSS4iK*i}TfSYzC#AdYRKtC$5Ukv)#S zFPC1Twy8(#bQ8!#*%4f^d&s89sC$yy5(`}9GxW^)=w$f;$4@+QMI9AWbT_$GkCC-W zL%T}n^JFz#(i+FfnF<^yL|1%(nQ$rTD3xT3prvRKnd;g^Mw)^_Go0B!gze>I(}~vZ z&v=dp*)nc+$jlr~C>lhY$kbtPmEWY$<=Gb!xwdkh8{sQ*n`9K3Y&KcXo|f}$B6Y>4 z0xiwPHqlSr^m~;SebhI;EDT+J-`d?^E6POb*&FoV8Bj(nvQNnL=>l3o7Claa?@*Zz zb)CXYE{v|hq5I4~HoM95`9h}>xAh!RNk1W_>K?hjzv*Q%JFcuHk^lUqAO+gjyk?Zx z&Px)ZtsZ4ragRWc6QeqbQDZQ&DRo!IL1%bN5oIDM?_{N zos#rTj<>lAmpfF^HEPm4ee=4lXI8pfB}In6GQNyi9#iP)fk!)EZ+QNdnegtv@cr9w zV?KIg;i>D>kNh&ugUFbGZHSHaG<-@c*-(Fq7N8J%hT5(lijD%ZuBb|X3F&?O@A$)1 z5!lD$ViX#mFVNe}Lm{|?+_rC24^n+*pyM6n<8UI}!1FeWO!!5vnVL(VaZ@*5R>U8< zpYJcJcrEkFM2g7(U1b6}&}{*qsYupgC%oS!$Pp{gv%S~QR7?`kWV1r8Ho3L74Ri;4 zordaN_~sju!!r@p>_T(GKaJk?Z(~SUh)E=7M*s11!dw0|4y2*(8m#?zlpAy5Wv`M- z@Uv^_J^?GLWnY+eCaF0}OGr-}$7V*0+Q6me&u_DXePt$*EoQ1(AMVu0qjQ6U(HapG zmW|xuc5dby%ff65??e|{Ta~qMby7DslElx6b>3fyl~;Tc3sxv7M%VFOG{edL0s3Gz zp>e4IyL*X7jz_+&QXr?>xSgMp1zy}HZzE){v2ROSi>YRH{o=ImN*EQD7aAJ%i8@s-%LF%114P9GP)K@gw@uxUY!c|PD zBf&BU>ddlTa9`dDJ|zz?nW`HY>GVZ;m1d#OaAGG_uWdvwwiii)7>0N3EzZ8(`1&Wv z)MSZG=HK+lZPRhQ(Cs*Hn~<*l6Upj|)T$KZq!m-0>}Yk=btGRXCm90Yf%min9a-z@ z`ldb^I^u^hFJ4x87XQEDSLW`0wCC5A@mK6w*mGf>O?5XXy;A<#E0p>+QHf;h}>}Zqiw7UfrIouWG7PtVZ#lCR~&LVuscQ z^A*hS`IU-YtNvd3%fb%|R!TG^VVy^P9u2wL;M&!jXKo#MHvReDx8>jc^L~6x0h=7f z$b{Gs*$pVzbUi&K5C}xrykhz@+OGfcG6pp zqu)FNe$!6A3A@YlVM3YBkQs{7rU$+A@!e80iwyCyz9C& zCTm4;9GahsGT_y#QMS#*f!~DvN#iU3g%-n}x-Z?(kMJh{>i3}li|wlW`yg7N%%ZPZ z%F|Fu?l*m93R_Ihu>0`2ww z1z@O=rs1L>CM-v4RyBE-beQufK{u!$T;AZh`#3Vn&yJkIAG+1+NNWE`A9cOdM<{%< zl94c;?|!v7E z-JM)7A`8upJdv3>qz70|EM3u!)A8IHU5L~Bh)a)Gauq7I>s2TJOP$(ybypWX%4+ufzTHv_t|_h8=t z-~rE!#ypL`OB(hDoC6O?ugm#R--d5@0WUmYqF2(Lq1O$^k!}RJ3Ya5XA_%G z%lt?Zp{tUeSCd}DGyD#tcpEB$c-*$@^&q>SZ9jJ8wQU?Y*IEw}{YVyBhc53r>WB2= zqHfJMpObu|pS@Pq;lN*!el#AwE22`#aiB^$^j+|xjdr^pYR>6;Va8xtv}z<2`aCjLu33p_TC!yp6qR$Olw}sm!VK`OcC`txm!C-G1-|OqZW9N=J-LOA{%uOYo*>2iBVmH8EEQG z!Q~ycC&?yC;A)0xTrqaU+u#^B9eiuE{YB7S0&td2e2f|;hugiWuDHP7+ktHSQ&H<|6;I?*JhyAbpQ=Cpt}>iv# zt7nK`XkOi>I0rLGI`^+kSy7M2=k!zX@$rMf;ry!Ss{c`Qj*>S#kcJ5#Z<* zX}4Gmr}M!bSx^0A->5H5Ydt(HtiKK0sI%d}VuCTQHOiLKZbWzo?Qc_@zQ6i^%uYWE z@7cfhgpWtZNPqa7d~Aq?MJbs6pG-!T&%9IdVNGM3`oX^N^B`yVv%VPpNL2|ph+oW1 zko)(jvLCzMcm?0;eQrlEz!i@aaD5{6T+v8+kiib(KNUxvpb6=vyGAP0Ee(^*hJTs2 zwg{Ox^HF6dfJ>iFs!n+k8y(GD5>-2)11&7Gkc#z=1dpWbl|P}|`v!;R3RzFp)T7ka zV2nB#iLH)CK9>uEYT|&ZMKXUyTrvG|&^dEAylS$BF{DoSqVcE=^WU%N%9Z@!LA=M- zyI6MBvDNoFiK?LI$vf%|z4lpPTYo{nHGqV*;p9Az0x4=s!dhPLyQiS62S{?5?Bc7s zu7XbDJ`F;#V*TC|*bPk2{91Bo#N=G&_!r*>kQ9^YaSxGyaOxemMEPzvFlxE=us6AM#I7 zGCm{aG=WTKtIJrVwau{~%m3Ui@!S_6y>AA6yf4u*j>9W8-QBi3oVFylvj_UH8NTDG zw1XVtn;r_+wVTGMzxb#8Yc9(PCX0$~U*o(P#7*!o&X894aX%8r{dr%8U3Ur10CCB1 z32kAwnooH*Ie=HmVqQpV+;({zUTJ`wgTM4cF3BD~u~G6HaL|k3G8Qc3Fu%tvKLAbA zm*j%SA@OZ7>Va`+b|2XkI<~)}ej8`ko5G z=!}5FeL7J{X&Uk-BxieWBu1hU zX)XTYKXb$byjK6<_8mu>S00*Y=KHNCzRza{xclK2GA5UiPO&q5Z|a68VIR)BAtoF8 z$JVkuX?n}ilE0N&hw2ZM{ejxc1U6J>=YOpX9(0sc-NA0LsEhkL5uFpW(HQ)I#(o|N z)3M!H-<~Y0@va>zkDTN@=kgP68#D-i`C9yxzcVe?xQZNW2t4X55C zu~SM}PDe#jRDSRDANY+ni#oK7=aa+XEG~e>ShB1ib8Fs|ySUZIktq^=^cK1W<* zIgdG`7w2qR5V{^PfIrKAJTa$LQ<)s?;AfE-aXvCz9Evm&sUsD{V!fSY^=CAn)zJ6c z$l!u~7BsiDgUtQQ+?|jGjG)39uGK^cesY~io3|>e*#t@kNgzOU^EzTQ4;Td z1&d-Mc2m(&r4*IouqZ^#Qu!MC4PostdoQ<~Lb#ezTN$H5jk zSr3ud)MdO-DM<;gOcMHa7^8v{O>DP6jO|8; z#o!;>(X%y-?SCY?o!+o?1HlH)Gr?)UpQ*6{Pk3^9$xcSCy?}RZJ=v0}*w`$aTPu9K zZ{5czZSLb;nMy8SUzB*KqR+@Jnd;t0YtsR>*_JZ1%vSQBrjpvXLN*Rd>&o~MN7+-% zc5mp)yeuoztWX{;`zus~Kl*b#uSen8CXsRC-9Wn=aCvdy2E<&1e4?ktH8F*Wx&mIbTPSt@^jB#+uchnI0x?J&`k*h;x5QOlLr&1oIpfCACG(Ch zLk9;vUj5~!%NTOXo7ru&nfJDJ>}tGy1I%{6%aoB#Y!x==Z`B#puYG)9^jU#ujuUQ` zC!Ll!y&u24@QD-Bh*knNSz~st<`3I;~8v6QMjmLyyZEF$VOyFdB*JbdSw9 zRb3Xl#_l3lq7WGred%ERkxuQ}t}i(C0hC#l(1F(hU09}O;NL9dSAtoc6)#9xT*(=k z#S7-i2QH(l=gXnuU&wBsmFc}S{^~!-U-_G+naPHp*^n8AuAVi~X|6}Koa-9>)vk>m zGJl7U!YB4;nCh$HQnks%3SQe1L45zaKJMqCg&5B~zJ#2WXmcb+Rk@nrYEQobhki;8Nk!d&OB3!o5`HCrbzK)D`8=Ue`eUg#&LaS?q;f z3Gq1!==)4v2}BGDvIGH_2qn7ItDw&?6N~$9GILOr}O=F8|K#P0e zXWD`IOL_=r=73zyz}ZtmHv$jX;p1o}Td5;B%udl%F6ANrj(02tNrh`&O8O2u;Nt(w zY@=oBx!)9C_SeGdeyPdsK6a^?BRZf|uW2@;VLi!|zf3j))0t;)qK=`f2h=eKs=Jx~ z8SfNruHuP#Ac~R$aw+^+TnO9ylxB?UV&2&+<~ZF)scCE2?w;}~T=iq=0sPxP{{IHM zM>OL1{g>N2f$F5zlOcW#kI-0rWwUv&zjhbh61NUSdk;SDH?AOx#qP{1V{LAH9RJvR zW;0LARZ=waxpW|NyUEw&JmIA7#rAT;Su=^`i-Bw+mE~D~lzA_{ibJYMEBw<%;oxeD zBJ`220L6RdN3olxGOb_@8oHKtk2^v_@EFky4ff~qJwII`Jzve@P54)4))hH3Q!v5w zlz)*{z5;Hqs2Gn5WtAFWXR3FmqDo+Ygqa`8KZyw%0Kd<#vK1ZVt}y!TBo z`X}(j6z7I1K+4s7&d)XQY4<>oHj2kgOYhV;dh+tI@kU)M^)C+1t;{dKfk1sgIXA-~ zK6kA_yKjfNz_*{cKH+*A(q_1__89nKI~aw+qOqzdKhfJ|JKO>dG#sV=)-9oDZ70d{ zD@j(H?K`8hD&~8-OMbD-jZd%$opp)DL3y1WN28U+ zmFDC+M0QXw6^C5FmSl$v0lAq$;>Z)xnp-Ld2th}7#x*Lv9;Vaj0pR_gqNO>5&S|~s zCC;fNr1=#RdvT?FMXJgjIfDe6gSg}~aFYCE50kn(z&*BWTt41{Zuk*ig9fgpA-1z9 z6ue_5%7>+p3w}I20X13XtC_8i5w&okVCZ^M^}r|3%CA0`BcU{A$ZV zDBHNPIt9;9UiTCHnU>o@7H6^-2K09B7E^6q^xwD0gr4JLgCabX*Ig}@!evs8ZD({D zvq%g&%luNK zZVH1r7uWJtdO&lDSS0Us$4NtK9r@Oc0ojY6 z(wJcJV*TsCz=2$sEr*$1WJaqxL1gB>Ibi0Zt4qi%`T^sZ>*m`6WY1>`uUq-;Jbxm+;?FK_b|-m9^zWb&AHr}jBipcWFz&L zoT(?tssUR{(7|WZ1)um zS7yf=O7`FlcanU8+O{5P$_q%Y{LGIfiJ`6fO*A37xu=XWNv4x|{BS&Vy;NC0fD^e_ zuu}dV>``TcB07!kqJNRYbV+8(M*a-Q!*-q6mC}RJDJNl9xn^dAX#7St(_PY9`qJt$ z5(j=>aGAPfBwgXID5o;ec(EFmaFY;vtdET|`X@NfH?E(VYqy3^OocF^ts4Gp?}a6u zvVXhYVBb%0lvj|=Km*>n2KWX_sS#GG&zOql+4P*;ZN;Uau14}I-0m7f%X8kl3K<}Fj%a2eumII zOYr&hRBVwiNt%|tC69a?X0A@WZ|gY=evhi*F-pUX?$0nv8e9)LS0^yz_5dl~ zFP73kG60Tvmm1D}++1vypNO68R=dgOdWCkRgR%|EGLqiII=Xl zy>4Ms`8k|WNBh#(W!Ky9CfWx0vg(L+Ag9&CBeF%fK;8*^iwmZp{{$ypd%BOBp~*`m zC)hP|sx7VhFj-}@sr4>%OqDlYJ`XpFH(_tzo#$|*y+dmL17_z=^j4EzqN;&$<-(aW zj>hDQavYn@AV1Ep;1vIye9TFxOjFbObI@OR3CSMX<}`ZwsbR?vo40bJ>w*ZQ!}7X0E%2ig53nC;5s8a(BvTHzDZ7ZTxK`~z0^ zv@Gem;jn+p)4r2N`u;FsaX}`&^tIIh^s&bum)EqDw z!*krmhQ%M;$3_NDKU(ROwj16@n%>F@AN!p=` ztDtVs!?=vkev-JLYs=kw0&Qhqp%4q?Njwj;;rh3`{k{UY`{$sxS6zL#5B?-J=lVlA zTvR0M%b}5~B6~9>hj^U_l4$W%H}NvKflh8OjZueWUwNFIfLS0ADT5NeKycYT)nB;g zdV~E=rN^0k%f!HzZgFkx9Gj9B<;4D|Nrz`y(zS7sEqH`VCs(RD>W#dl_lW#KY2Q^} zvuEU0bHt}L5AB3-si}wZZ+2MSrol~g$ZUq+Ye>pRQP9MFaK736b(FOOR5o{36?GTY zV|M0K;4Kq`;9e~uUa}*m^Y>^>7{mlz8>FYbE5u#5-KNAnwZV@xMMMGSp-)U3W}?;p zIlJ2wdl27ajN9pc@W+^qj?fBh!Oe#ICb)E7xYF{P8^sP$2h?B^x6B>iLyTt<8bxBn zag1*+1=DsUzslKfrXh%lG6xOU+pKm^p-BFe~dKLxnsRk*CXxKc$^Tw za=s?QqrQ%t>O%OJixq zbd09~SNIJL-d_2_#KoIZm6Y3FoKlOJZQ7Y>GQDa0|NZa?o`cG+7P#41dL+F$=q8x) zzLKddQkgj*sij0|6PKy)00_)obHe>+mih~%f zKM<$vN3m!$wf!xr>R>kadc*1Esu|`Y+14_od zHi)xQqvT!29@>DPU>e`>C!}Y7OUBp;yfgp#lWLMbp!fSSK{s(p$CnxPC$hfEBOk*b z2C60d+g!O2to;ewrliBSBdYOQKIC4`$t+wSz1HvO(Z0gvIT8haW8RyDGA?YzFn3s& zaaL=5pQmgAn9CMqmn;;+NPRp_-p)0YrRCLOS5WVkBZ;2zP`BqRXR`qAjDP!i&+BCIwu58aL6G7RyByQbBv^ zyUd~^xaiO>rT(>dHsv+Ln>(HhH9= z`!={p_U{F}XDxASz0|eD7QMwU#gVs!E#*DQW*2#n3*(ty%`W&9uG7dVbcn_we`y7n zOG()nb*~hCLC_A7?DYWdF|W@=CQCmw?$<;+(xv)x_y4G-%53^Gk}4;Qgux*ngSx&N z*nT|xe`DDbJ|`J;3i&BL(ev$gq5jyD7xJzIGVHC3HC*p zh8DXiVzBvL)+UoWD-E&u&^f7};-aX{H*wV*mJh;%Vqe(DCp7a(0hx}9Ah#citEwxr zL2~ZZc4+q7aAx%+&+;56QwQ;*WkjDN%Ul!ek-W25BtlezbMzY1(Ocm&^^j z4mZwLeBra%Mstfrwl9gW9mHy*#3dX%$H9>SU%QEJnOkp~xVdhJk9#+66 z&!(xlDoI^wNv_Ui2gv`-UCz^IJ~K&2TSzS~0aCY}maw)WBVF7l>`j>(HSIr;@~btuJuqpM@N zyo(NNF-$;1dBgXW(@6lBh}M0zKac+Ikxk~8($Db@?D;=e%>GV?ZB_C_Q=;oiftKhh zsKP_j8D+u{ao;q>TiwWYASs{}dcxFVe|Uu?kRCFAxJl*-N24k|DMpc&^s9}_O|;GI z_byy7-Z9G#G1=AEc9<&YX7LO)L*`=zX2Rw)}>~CF29Mua*?(c#k z>yatrnWS;cpTU7&*5NnxiGt_;hu}X^F6b!N;n}#Twxd?r?hlCb%z;%wni`;R0~sd& z2qsMSf$_|T7q+S^!%Vq=#EvBRhL52t$^ye!P<;s>@QNM%8}&@JJt>{9PP| zkKBP*{<5qcsUIyB< zy#0vje~#*AYwBk9w)7|zzyS-#8 z4D%&@1^+eg+Xxwhr{Ip*%mmw2T?c=j&SW@LCj=Mhi)S;5df@ia=#>!c_!zlQ>wj&FjzuHb zeUC{y7Cu1_B+zfu*j&!Xk8lpMioo#6)tHKso(mw+c%T3q# zdbGKnd8HJ-S84B8OY!=R|gqAqK~`Hx6@lKvJF*Z6wtNE3qJ`G zI0AIQa+;2Ef07+g$E@}B!&{oZ^qU_6u}Mx6qn5kiFmy zdWof^lKsuulhECAS8NX7#GOa;HI8q}@pHZvCvfo;_aZV z1X&7x`4*p9E%^5MdNO=%U7RkXx$*Mh(D_16!yol2ou#Q=nn*R5Bl5HB8eDN7>kE8; z_1Nj|*z6z?8BmYMu^q|bFG}uwU)<^wNL-lB{&EuRHnS`yTA)|@QVvn`WPp$Lib|nB zR#)^7psR`HV10!&ij|z2V@Wh<3K!H075-0X_V<(Ao{j{@n({ZM=IbycYtZws+Q1`(@=drKF3Cg+i!A^T4 zXlKg=pW5oW5blOvHV*e$S05jR_ADC9r@!~w8Q`=EBh<33VCRED8ZBZwLA&-*E-msjs&a0Pry#p zQc&a~C3SmO1G|x{9M> z8Z6678m0=k>-H@P2ah>ft_NXhYUP+jEI&^Isd%~ol;{`R8-G`TDrP&Af+#B?A zHyJz|IU&fF^rihKpA=P2Nb=kx_lCaS!A|;`?mQ~|a-?4$wzu4VJV{^TyZ?jtI6oZQ za`i2(xo3HYmVD@Ufy){WGFpLMIJKMuO{4m`X~5`!SoCHiOG} zaKG^1p#y%Y&zXVxCVh@ygMg+*AvoR*6id--&BgIO9e(7#TNW<%+07W*x$1&K9hZU} z;Ed{~+@{{qKu{gM#2uAcbmpm8pkLXt`T=O&WIT%p(OTt@<+!~gKCX`PM7+bDnxAyv zvpyD06A5I59Vs)M!3Hw=(tq&C&@bZsb;Siq1s!nhrNwE;2{&`%i3p zp9H6S8GFLEw+U$+{MEFwT}*2`*ZgLMjkuw95o~xxzYo13UB=A4vvDk39E@L_y2+x5Bkp{r>B$ULXJwSlc_kfp}XzzfyegO`)i#MJ&M^LY;{)(6dZaWvv z<9xrFblqRzF;=;x@cgmxCaX;3; zZuro$x_6$w67m~9BOz|9{FWW&61!*zFqu@~L}SEVznDF{1+&CFO4tiiqZuZTn9$s1UnXQaL4 zmA}$0Q%de51NaEjP*2%Z@Y$f@-^(f8P)Of`CWSxA&+BNG$i3ljs*u@@BK{m%CKFKr z)$&z?W^Q(5zbz1JpnV+aVUtEW+nu_p%|P!~5zwGvu7xW=uiHu#6{)}>ZrFipn;Xaz z@A+qBlZSC0n?AF zYvhjbL*i!wF$--_Qa>J*&|I^HcA4vVD{hjQvQ_-%yYji8#C3EQzeEn+hYXyzyWJM{ z=AEh}bAKN7S}#`h^g6mW`jY^VfV<%`4w|>}xi60cYl^BsAIDoV*6O2KX{`&Wl6pN& z1o=s5{FlD7W8ySJ3MjAnppk9)VGhzn1W7sY)}+|OC?4}8n{^0DhJ^P_|M-sJ}~PYCaP7bnht zhDJ-XlUXY#xmE|r1Rg0C@FsQT9G$=xb5*QV7f{&kb5ltd+9c|@4S1qr=xW@@3~&LD*zXJRL1m5Ku&Wvb z;?^FGc1iKtC+8h5gev?1KYdd?6szSZ{@I@@i)*H^xTC)CHPkZh&GL9W;*djs%4RU* z>_ijW7UbDE@1NOtxbuEM+YyRtx`yZ+O!8?WI9?*{TcQOIb$=;~qmgB#grh3CqmGJj*sMS%%jbJug zE=Ia+Y!?V1Phe@1^fEE-J z2T2y|%q(3_>}3X@z*9Pz_vC|;zM&``q>$Buoa%y(t6#z5U6$XX>zskQ;d`EC?qX5X z=am)dNhL=KKD~dITAsu>C&paz9DzO_>96&R+a6%A{j4-(dDJ!&K|7(-v|KQ6nAjeNk1^ zg>|lh*EoWsVXPQOKK~k$0gt(I+}9aF_=bx|C>N`7_8xS9n$G@ySU@xig*+QQAb*WE zQh!8$Rb#{LssULCMSUz-(@ZoSURMWX5j4To!x8+st04aKnVAiC!^2hZrFf$& zqb=%;E}pz%aL~)-ozCzj@gNs=JLDcRXZMqz`Ymay<%0=2VNg+@<$Eay!o3kU-BEa| zoop}}{8+QX-w7*RRML(L1v{d@QR)W8Ava4 zxQg-{`w@TjQ9KaW$p&l84E)60b}7v_{!%DKI&)a;A%*65`&_MeC-i)uEXXPD>4dx$ zvBWXem))T!>Zjy72T2FUCy&JS>w@X7m_AMB#D{M39;A)!MzNX~u46qXb$;26{b{b< z4?699C$DvK%A~U<=((!)aM)BSk=hnSu^U}JR zk-2oL`OED#Bk}pg;}bk*lc4io0W+*bI>5aSYuG!LLt)~^o%{y_$ zjFi?4myc0M+%eP{nA{}$+<@h*0N$Mxyeh5iL}KP0on6h+_u$UTpycWe6Mf2mPE$)= z{<&X@a=Zms&_#`RZ}|=f(+c{*yZ)H{c?qX(b!LGj__5aV_tZt-7r~c%3s*30RVcY) zF|&T~cf93o>O{ZKcINXvoN#?faft&Lz~4uL&hE%vKPc8v|1g$uU&rd^{tObj>7=cd z6)kBT&2Ep`S1?e$d``~s_4pBg^z&eWm0HC~nFo*R zdofBr!5MHHCDABcCv)^iu9I$Kt@_RMS7CTnt_!ovCt)3$NE-SCsP8-b%=Tx|z%-U| z&3^oCZ)Fd%mL9t6BBwZldTE-wD=wp-xX77X-7OwFTk(2u^St31`5uKt1orzYwG$0>f4u?E z2Ko~em6cUVJVf>QcQ0UPyThK4RGz@c3qB|-t9zvQq*o2eOkPg%?_PZkr)@d0o8NUH zyGmkyw&iZF8)8eMntNrxX4>dxn(_bNfzG5Yl@kwHWRIXZ)2AGI5FReRqvmOckjE(g3)VofFYlThTq|QL#@#8H;`{| zAoN2yTN9nyMf$|*p;E442m8>J^d&ekuhTX@m7Iez{xaPC{V*=)@&*4@_|(NS6Kp2C zGc4mKMf3Y+(G$K!m_qcjx9RHn44>Nul)@`<7e53yZUTR?$xr9EnTYQ(pZ!fvwu{t4 zPQc2dzs3@)7VEFn_rV+aQ81onkXR%Ux28LCs_)DDy4wHb1bJy+kcT%?#IosRS{egd z*t&YYEfR2s<4<3%>)AGRd#n@EeTgpg9}=opx(sMytBM;abRIC9DL)!E42?hT$ef~t z*(EZY5%j&bQn$<}dVyV`e{*~E3ZC43B&d%-pQ__fJ@Pm8au zEpuKr+U2Xr%-}3Pz!8La#hQQ$wswt363HcB8Y4D^yTq#KHzHp2g)b8Q%C`@<(AxJ3 zrT(XOuUKr7kuE<)#pelahXQIn-Dh)9yU)Tsu$X+6q2vN=vtQXEHV-}16PQDHI03%W z3jTLI9aW>)%ewoC?3XoNXtKtE@SZkmZcN90w~NPJV#~FQ#e$6 z8%f-#u14sobgd3lPjm)VRu7QvnGGk&tRk^o2A2Olij-C#P&GfBl)A^h8JgUTegh2M z61U9dNA+~s*Fm3q!tbL~@1jTplk*y`<`UULhj4$K!jbpZM@3E19}nYgIZ2jazn-Yt zqn zRHW)Bkk8O~Cd9*?jrP|!a)25@YvrFRg^Z=Eif($b-w0zc6A#vVlD3Yb&R;B^>TCYC zUh1psL?WF!En>uBT%`q23H*T;a3`F_!Vl*@+ww8?s3TzVKe}XK@2~wFQn7~H1*AVr zv=vdaZF6hQTa*csOr2cpi;K8nilPJN^ucR%5FW1)*`?&7v%mkYUz&~JNvm~yyHz)| zbM$$el9@Y+>VOI*zQ_PtR>^(KA5CGA+i@p0ri&yC8i3ujeU&CZ&&R`ZrZYLj@x2#`ESDAepK{p(K7l%q#NEVGGh9La%5Bu2`$EAgKLuWWSFlSg3vP=l!9QX;S@2WUMNvf#mA84)nWXeC zJA=f$@;aSsiBBjuzR-4{{~d8vy~3S)2;Q%@nyU+|B}`;DRfu-+WD z-I&v~J?@SvA~&w~U3dZBt9nx4=6k6YGm91$ckyLi=T@GJqLIvD{{%#Htexc>@|%8V zFEIcl6~|pxrpA}(o<{rCY{sj^3_iUT{I5|L4-aNezK79dft>`O*@@1fK6mZUK0rTq z18wAfn40lo2AI|FWE|w?dH&GdQN#Z1CXy{t3!QuyGlq1>o;GUkagwI;o5R*(X|#?! z8FN<7h`A?S%pIAGw2O|WfVzP4BoErSCSr$fC^G~fsT+C-2z+U{wy8Me)`=eWk?#ga zoZm!oN9=ItO&u7dAMFaeoqv7>Bk`TZb>;1Qo5X3?4{cLV*9EQb0taow|DEfXl0Hz0 zey!7fi^zm}?rTw$?xz*50(hNN1^rIE4sB7pG|?yIP<52^e2M;?ZDPC68@%(ib#sx0 zb3HcmMkl_FU%}CmN>py7`ZZC@X-3Fk)9Qtsdksw{yy~yi5Hmy-Hg zs!B4!TZp@XD6}sFLjr2PZS2p+yXCj z9qf6GyMo8#vWs%|v~tb3zeoG-d?R(~H+_df`6o4!?!zBwzUU5?Rg!(!qPm@A?~$q1 z7!8c{Ie2c*;rHGrnxWlj$Rs)sU+@>4ED6K~`!~;U8T9l!cxV2DmDx#dQD=C`WK5bV z*ltIIO;%OIWg1#drs?VEZ5pDveTs8>w8$0|66b=r&u_r62pcgdYl znaGx_YRikS%nLU++-Vbpfh`-xvVF|2_8ah`fB1f5>$PHD&{lSf43-Td6Xn`qt6Z&f zzzOc6e>IlQBA4Q)&&rhWp|x_CJR;ku4KleN4(H5F4CjBEbT$o}TAV5LSMi?bG##6* zV6*E@2kU$$h$-%(nQ4tV$o`(nd_@OCIo~wA<1dEo#40?WOZW_a0Ofk;25}Ow7g%qJ}4!R!O)ZT$R3)G$uqWSr2}zTCl-I{ zl!9b7Fwmdr)$wp`ePCmz|nzwz+e9qZN#Bjhdk@?Y3Q7rGNdz*LW<&t$*f=EnQlE`dL1dyrtX-F{_mnA>4t{7W2i@YBLhf?RFZNzk*gwoYoF#2-0sk7Vq^2tj zw)wA13)|kzbab^z?7a*Z@-O?>aaROvVZBX(i~b-y=yaMKN0YJC7w=x1Adi|6+*4O| zCp}dC$!YptkD&eNJg%s*C{f4b$(sf4yyX9yr+ZAWGr`nG$fL|44_sD#%nj0CvBOWd zo%A7-RA)1jReI9GUYcFkJ9F`nP%n{a;zF8gWw_Q6ZyqYkt#kC*@U}drh6j)L+RfdeD4#I&`Qgj zrkomJ9&!qn(pgM4{gHX2TA2On1as|Z)Kz)-`9G-DSG!ksxXZ!Yzs2-GiMEem?_;6=?IDvb$Gv&+Hky*50#CY=7>q=Wc?H z<*RU}b@cDRugm-A>@~Z%@3+9SG{R~2lG7)LDeS7De36#CGS?p*Q+e{KNZ~(@QaP6> zg#Ns+x{QXb0J~ik_)N5CVjoJ6QgoKYMg=ngOkuunW$Tex_?No?5|f^7stqvZ8BvR3p3^u#^y2=OUAD7xGyj-j79G?s)jtu{>kE0#zmTI~#sBUvNWIirf}QB8BC^$UWISSfZ>-qxZ^sTFYi^}Qvx!~xi|tHY+wIVuOf|)95>v=M2;2E+NPmjS zAkUhLayZCxb$1+3=?F07>S7G4e-9oLxSBGrt)R}EyXv9|Rd)2VUFopifR^?aXJ{@^ zf-dfqyx?l6;{FRg!Pg7s`gg(C{&`TqXATN8L44!yp;$f%xBCdx?IgHU%n;@hr=k{Z@?e3Dt(Vt3BGdv7FvQ{X*K#*SO6GF!diNnWqT3gnemvtxaA* zA@M|f4pX;8e67l&s^2BM>npMhC(t-mfb@fZ+_$9RXtx5RQ@_iUaKIsI)o~xK7B9DFnbK^y0rq_VZ?sG6G3 zIAa>=`t~5-;(GbSE+-{AF4-|vI7@$lpZZu25WfafrHFJ?af9#GFm8qCqAj0oQ#7Dk z$(Wet)`pX45&_X9mE=bFjlE=Un#uNev(HWgvlwFPyUu2h>u=J~>hutvJw4xYeOr<@ z`I^sfuaMEU1MP8P^~N`3y3ENZKxP$a$^mfY4C<7aNZxWheb7zP4e?{mc5QVfznoA1 z2;Q%fWV2M4YhZfXli{&|JnWhvSeQ<5$U<`TPHb+A9_F*aoo&m{{I@$^n&!&|B%a&(}@ z>qre>JQj_!k^Zi|u0y{XJ!51Sjl@Yb70=vAb4!d0tEolN@p@#mOVA-aA1pBEgOm2B zU=aNS3t*+Y(AAg}B|wagZ7R76;c5Q(&flPq{-()_#&k5_Xd#z@RKN~6GBz_W8cwOC zBn!qvyF-Q{_i<}5dEw^aN_r`h@;ha+f6z@@1tt7(-;&cLHf&`jrv4Woq7^x@Ycr`1 zwqV(IJ6fO&c>8}uBX`Evhu=zvBDAc?$;8+QeP2?%K)q>Hm`YpMY&7xnY)g3x&uAOG zO@qy+_6ytEG;rBXHjwUNu)uX_rGLXNF%5*QKfmEB_qR^#H|ayXFCFm%Jr>JhjNYK9 zJ&Gr}iW~`2R$PCjPwE!5HS7&u=qEVQ9|f&w2+-go?fn=Mv8U@rX0u9Rs;l~@lu896 zQ^HkJiWI!OA6PR|+0&w9m}U>!akeqY=vQ{0d(G);+&#RrrBqfqf*h4Eg88ajP+C9M z=k*SiDp&&HsHkniJku`%F&*(BiiXt(Ptf13&`F6moxM|DQ-W) z430;^cgekDnlJ5I`CrJ2iDMJ;lzp#Og(Y>4@RE8Roi4kE^Zf+8s<%mLOM<@en*Yfr z0f$Km=k?h4zD=`PrQfZxDt2 zk+OI&39mstS<;o2YeX%1oj&|9Dy@z>Aw0-IcBr={IO)~%k9vp0mAz}>rerlWR{eth zauXZ-Ow3Uu3)pfie|Xk_23b8?}|qN=F(@&>PC}wnUez$(r5I^RvNBmAB0e*%T6SG4_IS(0Xr0bryqqKeo&uqtT;8l}Tk< zS(N=R&SO%;>m-X*9m7Rg1-8S=}#+*Ai3ULEuI$-aI!xf=CXD;$j99h3*nc-UJH&Tzb zv(-iX92>Z^B+8K^Jcmu8CM8pJC+_7FBDt*zUvRQ) z<7(nY;N%gL$sbA}mY_dtqAGuP+XicNyx_HN5?s=&O+~l_`PCj-P#$H!7De|Fe?lk! zgnI5gljA}3Kr!ukC(Q%5G>Aui`8%1PC77k(~(f?rifI;tq>gDczR zkcpzHZqWKR22%R|8*v&MTfN}A9EkR_h|A4%rnHW#r>HSrFF8GwO{@<=oWkK%SIMLgzko=+kv-C{F0WY(6Mhvcg+-jB|KWzNAflL|VoZ=5PUKiu3w_E-GG>S2{>-N$ zc;k)-EA@_`x!xblRq@OzxfX49E#~CAZGY<4;+(g~+%1?EEzDnRVOFpMNzPPz2D7{q zWB|-zLmr*PfECP+{-%D&1O?&*&f`S*S}vpNs!VRhe47^4Wh+kH{Wg-iD+msm*(j{` zp~ZTIOJFY>|9^2)e?d3(kR0X@W`ft$R19S@)R3lzx0ExXCf!zc^i~y7IS%ChTdM|> z<;lj@=y>@0;&8y(dJ`+u-TywFRe~vO{4w{7g@<%z2$wn7AL!Dspo~3_yNbcn!Cij22 zQf?TXKmpm-#8+)e^gH99Q-}Sy%&@CL`I%+Ax#^}N-s5WIE01^g&3U%DzjO1UKbD!@ zbk4$w`mSr`op*Uct=xvtOS>)fhpilH3$Zd26~hr!qDf(bEDa*$qTp{hGBF@eT|#Rc zp{Lo9w;0b)S#%#WOgFVH=qwTiIqf&UO3<1GLKCx|O>Q&42VA3UWERfG^)BUPxkAoj zD)$qFxeGeGibGXtA-v7j=i!K!_VcJpXvkGKwd@prEN1!ldBR@e?fjmNoQh2NEL890 zw!x@7uD8hLdYLS#tI3@DnAo5j;OeX*%6WanTsA?eaZZ#HoQZ4wLAsG#cs~wJ92{Lj^qsf55B^6wBuh4U)3u=o_-q6t}ljfs)5uy2ZLVn1Frb1@Q8B45ctgdSf7qJiV$X!9?AyV{~4={TEkY0rV^ zcnv*^COPUZI+xt)pu0%roR|4!FBlm!nIBAmSkT`%GbCtbqXbvz-G89Qo`+L^D_OxS zphk?M3#>zbACEotX$W|~q55n~PU&=e0$$H}xzBACP1sPJg;kP4wq{;2#;$^~_!bRs zPuzipp_**B|Et}la&f`IGsl&CdtvwRjRpK?p?TyD;K`v zUPVq~c9BvJ3|Euw{Y0`&@Bt=8Y3lCFvX?!CQok$uqz$?PXWMkMSG^3h8Wl`~jIvB> z`>VR-%Id`GnZBbx=+j;W{m6@{>Usl6^UUSm;ez@_iqX2k9s7?z+BNWRxZeI|&XS5Q6>5acoF7gs?$zsJ#ufv2~Ep_6mNn_k+W16H~hx=zcpf!+XO#|0DY6W+=&~ zLp|DMc6mwBBV7;fdLM$5dXO2W%GvI+0@K_@%*h%m&6}K3FY!X&MQ@ll&Ku&r&_<`w zIW(en?!|A+A#%XOd&PayS{y@-m)siCK6j$CORJ~20y?TVr+V;?{fqjODJxy!Wp`b+ zz*Qcb8Qm*Ro10W`5NOm3*ImVsyJR1x+D-KhG&4o0>E4pH>#!2 zrNKLq+#NGvlw33U{|v*?zS*V2Njr$W`yTuuv-B~W-;3j>u+|W0gi+y%HjYcbB}4{bm5U*R90} zrddfK%0>x#!m9aK%wZ<+h&Pc`7x_&rExmDKn|E4F z_nOE{`YsA;p{}t1$_pnQ?Z1qTpY>AkSW2@LuTXql-AeXWD^(VeL2VId+05^Qt-2VN zSt0aM+eB7JguZ{h~|9m`!VoLM(<2gUM6*uJ`@&Bnd<2tQg$+^QX!RxLtB zaUCt)ddR*nPy)=s>G_%rm$#gx891fai$P)(-s?5&MAnPeW-?T>5n^)qsYn=xOcHLQ zKKn0Gj0b8M`>|Iz!%yIT4a^j1WIqQ7f|LHf;G&AcFS1fywhuD6=w_ac$!_SgZNq7}gIb_skX@wXS9C^M7}I2gxtxW6 z+bg#e1!FsE*SBJp&C66eyUf4_w>&h<^R^Nki3{$L$?RUR>-vrDMl~j;#l?O+{F6+7 zZj?6i3RLoc*yldMv-<)z`R`~tR>QJL4(hZG`Eo2Gyl zw%c`N{&0udtRb|@E5Tb>aa&wgI?p41QIXc4E@p=>h#}!yBFtYog42RLduFI5sdeAr z?lV*c?{pbA(mQ7VptEAriE1RbRb&Blrv8q>hGDZ5u$xLi=6xpf$sTxX63cetceHei znI+D%1vyb#m}vM59+I{PDHeK48&T43K>QYxlB`|)(b#COwhZeGWQlMbfX z1sf@KPID>HfX0)`;}EpzdXE?U3~8c*wIgv z-ymrY)7Nx1uc24R+pE)iMH`&5w1YA^)RRjMMXl!IJ7o!9+`f;5`ruJ>f#;>Q5Tb-jPQpeG zG?dPjqAoncUEZ3vFb(TG?FZ+20qzNr0ac#mis$f`ZT!V54d8~ z!lg+&dW{}vDq8<2kn>7Fd~8ldG0O;U!v-P)l#WtvDZhRMnucG*L{$WBKz32dD=wa) zkgE~8v7d8kv0yJ#>yHrg%hsGp&AYojir=$k1gd##kw zx%6RmO0ymD3JcG>$;8IEH0*jaq9)HKpWp>}Bc8i|#R{B8x!on%2@-xkGhB}kmS}?t zJ#A22g@XffxgAdrHx@lFq)7<>KZyk7D4&PS(S^_SwyVNS-?!=bdD6P8WEZ8@dzgN_ zb(3}A-m6daqo3e8r$ZOl6bH=$(ORA2Q@+bYbvvZA$^s7+?w0O2oPI_jJpyXmcsO#| z@P;@7Oj8{)gB{C~Y=(%P+lj)7{E`~zEE#&e+PoEIH4blZ|g67n(b4o%x)_3o<*O*|Vsc+EQ}69=)ye&$PP%Q70ZySdIE(V- zguO`C(q8DU|Jf5JU_;*)uVW@u1@~}0wwEuMIQ50KnhiEtS9^=o&BG0Gl$~iKe0En* z`tQL@k`Jc&6h7}pu)VkQ+~aa0l|3XbphkZdloKcY&SGcyH}N=fycileR4k0_LYi+o zaudH3PL_%q%sZOe>Y|qW1c&4ms`vUriTW~d_tk6H(>vvMgvPi^5xHC_;;yX`I&K@I zm@ls1p~cDsH=rwOp=G3ptV3NekUbvmNA85}_BE`HsU{`+w9ZU>JGme6O_7sD2IorM z&m{Fca<8A`ZjOQC?T8?u7wXz&Xi{xq>O?W!ZBiVu&-_DXE_(aDX0%BPLm&mW!7R?t zk)j|9&V9j8R5erCxhp$ROf#MF@9I^r{SD9D96K6nZ6gSUdBt4Y z8TS>WWIF&iLk#hRJ~ufeSF$kVZ%j@a%arUaqq!#X6z}P+pbH$5!}f6SFE>I(7_isb zv&P0XGK(2ZY&KP=tY;(K2hPvZObR-ZK-AdoV^h@~Mtu$T&mBbslb!|iVoO9${n@4V zR@k;)W3ya$z`X&xUQP(6!oizOZb(Ln&IYyNZgoN*P}g-WGTis0#Aqg4&|5^KGuvz5 z%WWo73VezOg6{HDP*WblYabnM&LQ!uE}`z|&+53Yt%a_uW1uWd10C~sQA_t1W$0N# zsN+VfXUwCzkXew!wL`rYoo}ZjwfxE;jT^~Xaf7LJQE~t}aVs`bY+d-1CxbD(3jd;! zXUW}31Lu6EnIdXJ`|561h>Zr0F*ROx=qht;3lz>}IFo1FJ@`GwiHa_QEpLB!2Ub}= zw!@sIGCzKq9jFBgx#-?UYrQx2OsJsC88Og(2yJ%#LZjSs@3t-NHKZe|YsaALTgVxm z23>XT1o;dFR?v{ivxO+U^k0V>kKf?()lg6rRW{w(9Zti>Py8oE! z##Ad&MC?UPUXcFyjx2_cy{asPYc(Djqq)$||Hi*3jPE&~P7DvG7%36Msd*2O%kvg0 zLIIK;7TRQZ?oObN9qx@4bwXL?Arywyptt6esr7Q#LQD8}8JK=>yX9Ixqe>UlA}z6&s$dJK z+H~9RsV$$Qrbx<^KPfcEg3uVR2N5tp%aUMpA5zalcmMxsXs6|$bO%3~lS-f(8DvMP zQSeeyqMGbR9knZ1VT$3{tQd3;Rs{8fb`U)?p@BP#3caJtN~hn|?8M>Q5?9nlIf6du zsEm-ap_AQa+crq87bE2v(HyNA#=NX z{=aT%c%>K?Ia^-+X63bS+vJatiDiy(Ptn0QE{lN>gSK)fe})L$qbG3PbV2Xk1yao@ zw0zUqK)2+syNL?Ajy0i3Qy^k98FFJy6R)A!qNIX=n5Zr) z{E+T!435JDU~4V5yRJBbhr*%RY8J{e%q<(5^XjV^#mwLh&)hkk7==m~^Z~8RL6*c2)ZMX#7lGsc;Hc|IyfIhODjl{s9vD+H_WqUDIo@%R+)VV%TvU#u^MrH&z zdPlVYGE!C>Ur(|RI6tl2hm*L!-Q()osQ8=i*t2->8=_4wlB9>bDc7KP z>rVD^E_A;c*kTo7*V7;7b#FF-(Oq9X6)IyvyA(~*I}$va$Y1GpI>1mHL-ywz?#hvF zoBBbN(jP=iT}L+3^<^9VNNm=bL_sf+>*J-j+G}qL>J%oeD$09)+C)%dHA~$K+sLQC4Ika28HMHg3JEFgXnR_ccL9`)|;L&$0&o zqWsolGT#Z3Ru%6qrA zS_;izBr~ceZan8(o*=*2%2Svo808fAOvnxr7a<$AaC_A(K8v|7DrBJ9-WQubwAXG7 zowv~=Qo0fmJ>2`y5tl9$>CThTmPW>P`c4g}4@}5o?xZ?yYd{|#OxN83%4klV(@As{r&MP*0!=|l z)d1bpCtMZvnbVfX1Newqv?x5LPOcr_MlKQu{-Zvd`8{)tJ={hSU+zN4pC2-3|%CtI;x}bKcl%zxEl>KyE5syuJX3{3= z$)8AU?m(sAgzeEz&iN*!95iI^RRgZt2>Y5`z~Q@(PEJq3l*i1ODX!{W2k|*?FoeJ*Ww2}k+JB?_qwMzp92|bv$HLY@Sst7gUw*? z9$82egJgP0a7SJUhKS;(E8KA(qW^tdP08V-Hgr{}#4q53tV5;Rjw<)9o9jxuXxyel znOmMm?Ga)USc{+g7T-}0wG8IXcfw3+*OnbvD_nj@ZBDx68zxMjzR{#-lUtAHxF+vr z9O}>IHi=bsp3Q9CxrAuwUc{ zy8{PlCVAgZW}}*jejghWibfWD->eZyzgLv*F)%InpUokBO|xzJoSJJdU}3NXugT1y7(T&ztca$dCPp^Z-+AMe_CEAX>!F~s$2fpJlgND?6pU7WilTcjt zVSbyFDb6(5Yhzv3P<}jDJM7vh1#H8pmrSFmb4|A>V~h*!G%36S&`Iu~_FnF8a0{=a z3qQ}zmJNMrMaU^>c{1;#+lTu|)$pAD;I%0aAyvq_!E16`AFA%bcJeq2dpYc{USd}W zHT7m44{uf?6fByj{2nffbJTM;sUZG?`n{a*sTuEOI_k_2r%*0>?uzKs(Pu*rD!_cI z1poaHzq%hD2`%fxt60UBJ~v+2Bhc;3sl{$Q)%9rgo(XsjhyX{~?Doe0l1%+flE4Oc z99sG=HN9%)VL(b zouU`%FERQ;D=f>O;Www53vO`3>}>9|Cn)S@@y>Rn%Fm9M`+L8!fm&-as*vjJ1P;kh zI1m@{{tafT+K^4l259Nk@bAuJmUS6cZYA3TcL81*$ngK+9h~RpqOKai>}46Mb;r|I zPG+H>E5W3(51ziyeq;HEe@v9~9u(+wm*{e}~H8ho%MuoWB8D|K+?(SGG~SD;YsMP=1Q z7d6izVGV_Av)g-Xenn9+Reux}$Tj*R)}SnVq_^7sdbsI~0)3Y3>1UFEg)7L4;eFBz zS5_u+rOF(Bp+rk<)PTtD!vUgnBOzvy)DAF4J)~Hj^LRNED~O7)rPI8#U`eR6!4U z&;NC|I8R^L&GsWHdy#e@o~4N6l3VgOYuYJ86oFUG$YR%wM7kypL#1 z#A1*WIN0>TM^l;FIssFT-FUF-+F7`fUcol{k@?A9wBZ%m(MvqS3E3Hs#s%D-okvIb z9L=FEN0-M$PP8VLx1~2(f;fy&Q>{1knSCE3bLXWPWfpV(#SojA#eA}NZCak^I!rYt zlCZRw(`^MLi8$;A+S=A^yc);@CLQ1M84<9-D#AA(&1FC(y+%aX%_yTUpuG5?huXPJ zOm=wr-7XK^pf_8@4V95syqPkyHw~pwp^5n_U9=7 znspILObhmIZ?r+hJkd0j71{d5p))w=o&fNE)8lb&8#A0gehmg}Zlb8dM|5=(Ip9yD>p8E9`+NHYCGZQOwq zrw^=o^2Aj|S4h=!DR7pC*?qmR|ItNXf^s|`CPH1d$x$8bD)I;Fpw0=S^!gyGDhPD) zcePVS)y+w>DnROO4cVHB>0OmU{fnY>9D25_t`+3JM(R)Vt9oO4t0T4=nJ91;23_dwT+7HfUZgvwciR+DtrDngRx+W8 z%?z^<70x#j@4wj&D5eGTi}Z4*c*+Fg43mU6XpX0Ys9k}((t}sq3%azV_TSS*o62SdR+sK)dbk zJ+>3Q|DafmB8Mso-`CIb3)AK>Td?+arl`V{vb}h0DH7Uh7Budq*%{^MQ;o+C{;hcj$>tZR)jOEPRibb4{=dyjBZe_gDsKO_ znQeOX_zTcB6=NQel>J^clLu9H5;9c2pu4$dZ}6W*HkBL04*!B}!_6Hjs)+(J7MkGM zOrsVvnNP+(`UkdPquo|RmLPhpOC-+3G8x|@-nx8l7XFMV@af`16Fenv2atrgODA#z=Y3_vbnbe_!!Dn4O*reY1u=s)&aCJAK+9fB4^Q^i! zY}0clbYnN!m{aVdNDLQ05)Q#`D(o!y&+DV$YNW1{c~Jnh$5+%DcT66X$Ul4Y$nLBR zmD8gpj%v!_wf;tmXmS!bR>M-r35RzPZ{pGKn-O+*3FIZygZ$wOP}J&hmd0|$p|YWb zVHZ;zCuCIk59eh!*8%3wzp5x+%w{+k2kV8ZzL#BH^p46sUUT`D?sFyYS9JLk&Z7V1 zKvIPxO&p!pme#AFjSg}jWet>8v1Lv)ZUd<%@=}9U=YF_?H{vL?!P{)uW9t3l2L6XE zo{}-VTyiz%Nkd&uR?>-O9PZBkswn%vb5!iBnP}hQ31{<4DoZ?^^<>-N$SP=dhIryHQN^ zA2IXV%l%{7e`k<6=-zJ7hwmbt;5~nDgh%r#{(`P-Y;&qd{v)-}AFg}&r*(Q#vQzn< zUhii|qqJF$Ft=q<_)Tj$jRhU!G}n`9+XA&$jFTTlQ?Zq8STWJe?nakVhNSP?yk}^T zY*sc#QNC}dxy2jVrOaMF$Zu>o@|pW`TJRp4SZ!#m$5b3!gk5lB{l&%6O~nA!Ld<8| z7{=kdmFo79*kA{v;403p?G^j~Sol`!GaE0B@6i)Jo8B>^APn60?l|@3bNu=Z`76Tw z!M)YooexgiQ{XKZheJK4tjGP-Vx=Fj?e9g(>UGZ6eD;vJ#|d-C zoJ5U30mgngX4c2WH!AmwZZFK4AE4#!r&?}6)r}E#k&C0B|>HL(xZ_tpz?T8)F6EI6FpB>@Un}kdZHVvhLSb+ z20rg6^!)$H!m1Ju{G94J%ENaM>NdG7+>j65M>g_JOc(fX+2u3;E_GxV)yiM3QbO%0 z1D#;A{ejuxA|`MNsFD`5gU`ig_%E9Xg_tvSYzI^mKbR738Ol{UE1Zpgvah;Ir=0@k zNi)$0rvAT7tjdvybW1*P15_NBSZg~I-F#v%Zm`t5;=lIt1xLM)K|^o7*{Q$M>AW}n z)C|d!x4#7TCLoz!fN(%y#8*7*IuOZ2FlL-+&fe>9{j$uBr*ovp&h+7XV)${<4_#;xF-tympX7X055L03Z-m>S0+ewD zGjA`1pdQefO4wCwc#4Wk=CkW!BH@7K5gCFWVmN-UME*Sy!@n$A_&vlt|A8y#7j}P# z@nFLXdLG%@R0wx855uR-7{8_U{d=}(kchOMO73p(J6na%uA1#5e7hVMY#ln^c}(e7 z*qv;nE7_x_G_LEzB%5rYBfTQ>xag{c%cg6>*)HsEdV!4}I&JrRd+iZ6(-l=+YLG=J zlxO1V8AQ6pAo7XNF$0>$B)7W!Dv!v)>ZP2f4#3aqEH9CPPzndqOs19*^w_K2CD)Oy zP&%e!2VnRmvhBn;-rQ{FoV*zfRh@%0S_cF4I(U9l{7QP8e_ureD^y>)s#s>3x`O_5 zuKh>l!|l=8B~=-y4>yt;nFVM0Ca40X&D!8rP@KD@71`@6++}=nlgtE8f)Ky9JhX@U zoP~{1?{z?f*%3``NwppAAT(Tg4^7tx?n3DY9e%zIdVIH(Wp|ylgqzL``TJ+%CNe7ui_CaGCo4y-z zCV6P1=)>39wahYm%R5Z&Kj3o>UE^t;;7paN+1e`>WbK*ZX!mg7k&K%5efU^ zfG1=_@`2z3zG&N7=#bdNy?h+~RcaI^rs4gAR8rY}RaJm^8aF*V{E7%q8 z`dy~gT|`Qo0@9oi6Z}G=U3i~(6@*(VNY#hrCwEo^VImL6s%}WC>2`Xv`p|;YNXBt%}3xe?pGX z84`y&+P`&VuvCBXf7FZMp>Ovusw7OA<_Af29+MMI?Q5p8KdL&=JF?n_sH0-A)o8_h zJssT31UPSe1$hbu?LKd)${HFapLo-#)gC*chq)){h~mLESpeI7j_7SpFyToolX9P} zHQ(q*CZL5#%EbC_df`Q`vbe=FF%8XXJtnQ<6 z7JG~M&_@1XXWRz&XDNP%)+m&d*(4}SN>a5C;*L7Wwm-Qnp_|BU|Id57D5L4RstvoX zzu^z3l27zlFo}vizf*N$;l~gjw_&rhh zR3Qs$tdzRF8blu8GMz@X&^hE-&hX;0CeygTm`-eD$JAYg?0$93{7!zr7g@vf!6TCo zjduag)`qw;+RC+}8vUm)Uc$agNlxSqd6<*CG`HoS@|BH&PKk~gcI$qo{@sJLbOpD? zApe3`7fv8EhV#n8VNZ4l4@W2Y)cq8uD~OEgu16YMHeA}p^V_=2K|`0)JVh;^)<&AX zxM#|nrlz^+YL4N?&TseI1t?`++r=&|yjkYZI2-Td-5g<S?wmu?7 z_^V8;_QS;Lj6UNBdB#-a@3u#i=$deXX0%&3yWzW8Pv;VYQMR`gczKr|zHryx&d z=HLQsk9l6YzzY?_V>vRY>cxigu*dXLz3l?F9XIHEGmAsq@>FzWpYB9cl+yGwpWycO zHHAq^U*;EdfBBzCW9=d;qsx8g4st5JgI(|oPxN0n4t(n82YA;zGReJ7jnj#ItrAq& z?V%Mt5y#-UCSc!Jz*f||%`|<%%+q~rD&5niQ(eewEG{9$FuT}@;&-*1jZ3i?iE_(u zFFwFi_=)OgF1z!UY7+UPuVB_*U`kU*6f?iL2IRtiB{@(AU;I+R7=M1S%P(Q3;UHSX zTxAJb*)k*sw1oW|kLhlAdzO^Dovt{pr!PTFYSx;}8OEVJ-6SpqZK%fvyT0@jJ%W*D zPLPUEpa&!(pEJ7)ZkxYsb7rEMQ2ZVx&%C7SU>B&;_JAtGuDv5l=k@H`@7NB^lKVlU z+=Yg!HVTY_ww4&c+aKa~d`*S@jmc{gZnBg*Zg5M_3dVSIOw7$}K*GI^uMnE{cYBieJ2&u!kWysDwNdDVf+7r3$Hqk_K0z952tx zdF*!=;31ufPG*uUjFy<0pNyq`hD`Vip20%=^QWAKU70G}cfS}Ecm535BOG>C65I{j;A& zm+`Ntq5gLD$sedz1s;U4j&i8oMTOOviQlmAH?7RX?9tP!RbPFBgkKS=sjh@F$`7Fe zup1V;!@4V5o?-49^ywv>e5dH4I`Br$w>jZv=jR?Obi7%33;M}@ zM!?Ohr_TH3)Oe_or~QBM!<}^3NU5F0_xU3%rWL4tbLg~ct-4KS_Ix#6Y=Sc1oB2j} zm&?9nmUD$keM-C*+ucNN?V0eZhmiD8T>pytccU)j?bLa^X}Z78tDC6qDh^d-N*GGF z*b{zb=U$$DRV!u_S-5jHxgu=UtC1Ytk*u~gY8ra{6|S`wq{XyQZ**0CPfyUTbz0pA z`a&Lb#7kWd=2%JCsir{*I0y!I3VA2k4@oc~OuqE$i`@wir3~-TKd_|wi83l9U48^S zsFZxNU74l65GBd9RirI_3bv>`!Dv0gf2WU!p=(7xQK85qa_+Yi;`6tnXnr2M=aJjp z*~nQgZ{&D);oC}=Kk|ml6;3YZ_$ArxmJ|u`%I!t{_!4^82D^e8T5Je+eN7d66n$um z;F=p1G!rgpE&gG8(9j-cgVEZJw9(uV81xmX5i>wUx{BMYqI{)G%f+E{VrE2CF)-q` z-4}{uJ9&3Z3Y_ns+0o}?p5M=`>?Ybp(e@qRRoLmSM` zp?hXK3%*mjqP?z`b3Wzg_Zjb+LvQ=dWpcexc-AzfY@6UO^VN@GigPwqF{S*WHoHH; zee*M+Xe^4#16rPl3+1<%Xdt&RxhcUpx{fK)Yi4Md&G>lbVlL zW|Ezzd)UW%zI~xHyG{BNTguNoHJxQiG{`lKA;Ibod)oi5!L4V0CVOfxOxB{*lA}pH zYo@-)QEHO9ESsQ-tS85?D{an}{#Ub_?ea$dyyzOatn@YY5n0x7?b4u$tIf&f zn`Ckh-F0((tW9wr4K`KP6xdwtO&*mQ2lIhIizeh}y~Q8694A{pe}AyTZ*HmtV{Lc# z`Z<~Ih3Vhst7Kx2e(rL3+gvQKDwB(uwmz;lZe?6y9ZYOzVP@V%|NDSj7S#b9$!hLD zrf6lU=(?c=9YYT?%?#2jf(c&Epi-zsa6eSUbd12$6mh^Eh-e|^M2r@#`TKx~jN(~D zT#+lvJ9mq^G#pxiie{yIsE(6C^MUPYa&{`kp^tVJM%-aS-V?5GMtl&L)o9U~naUo0 zlT4h%qCB*=Tk4)@h41|m4!aC8BYhNZDc7If+O)u#DgF&}I9!Ens2|;$a3lB$(Pbhg z5UX(s{fnls5NX8$6s7X+keg_yLy2gIIyM!vtJzQ`10!sG^k(0v;v<+})-#nM8)t@` zyqPEB3CrCF=qrz+R{Wj%bTB%w)yxw!sAD!#Eir?2dmOs`{6i`TpOx|a_;QASif;qX zrCATve7Cqsm-H80xUnSRRZ}h03$+EBu_haFl3u6Y>(ert_qSNB@49oUHr;6{w+edO z1s4-_Su!}>C)^78f}7zZIl)gzjA$a>Q1!o6!|?}hQbrVI23tptwOL8iO^MF7CnW6RFxn=< z5-9E-;Cy~1D&lglr{2T9r{nNqd3|9b6!mK8QM#|10*mPZ=@4J!Y;x}YpZDot6WX2{ z29Czl3ZLOH`>2Gvs+*xlh(z87S;OnC(siI%-;7Ym3B84;f!EE< z@dlVTaD(cHhT83+-0oRutBV@(%%zSv=dwlAa^)gA+YAwN%^`gE5up@DdV|bMRIpHx zAw1_pZ!=XsBV{s%+^AD>ns^YS=ZY!%hRdw8@T?rLdGuu4k2`FO9)RzxppB+;T8L3F zSl`+zBnK6bb%Oo{RYr4gF10OIo{rkWhbP@>|mGhg=W|7vL%iiC*md z9zi*2!2b0VwR(Tk5)E2QG1T;vOH2xqXG$whSN8ggnY4X(AiscxzlF|ZB+tqU6cD+% zPY%hgq#kB7<#}_jklUJDdS)!T%*5=^Pil4-sOpF4q|2iuIWN1Dt33+J#~jr| zx72&}bbXl9w3dF1C#;rGd>^Apv>3z0wglRZ>F9szpbvS+45A=z#D?f0mNI36`#?g| zF<3^4#W56-cTjWfA{jl2YDT|5E0{sDVlI-hYSEWf$LVq1Oo!Tcj;Zbvbxxd8wIF2I zLCGjdIv5Z4GcKFQ!{0knRCGgKCcBCaWfF0e8oCc#ao!qKDmQ8R5#Mgn%geww&maGzA-`QrgzH4y0~hu+p4>& zBdW;}bQVp?IQt^=pnCkuJoF$b3+SBD=ncYo#~oo?i?M71&Y;-t#~!meTXeBi(NT3p$zwzbQCsXM(?@4RX%+Djr zge!?&kqbn@$jovY?9kNV!SL&o>mq(%{jWbtL*UjG*)=Ns)H~qzdN?ybvbDbgh4iLX z=<2Wfqe(TN#uPrWx)n~UI)+!vlHn?H5l-|Yeh2xpSt85f=zmY*VI~-3o781A(0NF$ zc|x*mCA}9eb(hq-(H!6j1KU9$CDS?=HJ%!sqFU3hALKuI!4;|GAW zm`hb~%~8gjU>ektTdBI}%j7>R+3*AL=WnvVqoq9KVz6oMfPO1}Z~~IjVN*XyVq=;E zxV`4$Kb<9da=O>2=2<1%!SyLmZCqbA=ckA-uF7xBjG#d?Z`dnR;e1U`*40#IBfZg$ zg@X<koEjo?~A{8o!QmV5^35#vDZY*BwHBNZX zZ8op6o#AD`eLmgx4t=q|peP#V9fXF`9%X+)&c{Ob5jRb0YRGEbIZ^qgIGBM@3c8WAk{ved8FUT}aWA|ub=fT4W&3bTG&2v#L^#4; zD2ldbJX7+6u-6@voyjC+%tbdFl|A??=xeLcEX2}*xkjy(Rc$kkWE?yPb5ZNkbu#0+ zES_5SbM_&V`WNV&lF2>XTU&T0JFPeh(h?zsBP0oV-$5p`J6GgQ6o`$5z$5x9(T~(ZU))~Be{g|uOfThwED#l|- z*poy%bYXAdv0a4?6H6|{AzebI)UQQv{jVsY4>1wlCt9i-oUnQ1I`XB9$jvCG$LLfd zo_E`|_wKs>UPWB^n?!OC+NLfH9qLEs0}bV*|1%Lf$f0D1%*AULVv72QXbrJsJaxu; z&WzR2M*71Ci^~1lhI@9l>maYA0BvShsj_G%=h>=kHY&+oWGOTdyKp-Hfe&U^P)rpM z7O2F*QPm@uq-rtYNW~QYJIT2goQa~Qk$dj9VkiH?WendHQNsCE#c+RpFx=bw!GGml z_s@8Zf-K(QAigdR$?%DRr-|=39aEK&F1}sqT0%jZgXUtL|52<6ca&2i=imaWB~N^d zE89dSlKaCQWCzmGD={NWAe24{A^eF7R0jPAwd4of<&~&omT(`o=bbu4SKNfoDh89j zH7YDB>P%2zNx5s{}x?fxkI+_*yx{#bgU-t&e z#2e1Wj<~|NLIg;SkAJy2%LMqYX>W3qt+dSI;GiRdp2V)Tu*?d{XFWGWd^LpB^~`AH zKe{MPzuu49;&x+1R692!iH#N!VIx8Z%ra6l zva2L!pGa$Fx#i}dW!pvf`N1@$YaPYOTHUp0?%$Z7HvvECGSkd-F@@kxWHSb`)@Vop zANUlq;Ofebn>>Jj_skZe_sWMdu_`3@bb5hZq}$qcx`C}n6?}r9J&GCtAGrYMlry_f z&pa1<=rjkzw$H{SYZrFqQZTeJ&{}%qq{};~RqRu>}s4opb<2*vFrjEl_NA zQC(F$)j<6YoA^6Puq|`)tL)h8bDP~oF|(3C{Y7R$DbSbp;})I?nY}yi`4E*_2Ya0S z&j(_J@lh40mYJCH%srsnVEJ+w0EstRauXU zhdCI^&)2xI3&|~T>!Y!uYK8)3g?Pr#_#bEW9+VB&Y!CD|9Z1tFPd1#Q_E^irb2L88 z$#hm%L}r^p6K(|qd^Gbj2hZ&m^~wtM zg`I3+kwyO{wo(&Uq2tx6gi4@(QF&BNGz&?Tk0X2#go{Jp(>|$g@}mdXWS4Vy+)&dw zOAlK|kMNvxXs)}V>f`7f}rd4R5DHSqiyw@A@<40RZRdwX&;8$!9-+2sMiYM;4 z*~B*U1S#f+)dV<$QzN_RRFThB$H?!ApgGiIznbcg0{Jf9v77Fu>Q2V z;LB7W(akKmHdrF(2gAr7O)k@#z0lCcqg$ONel<75ViOOYWEQw@k!+OLFfT2P?)HpJ z%%-~)e~l$iV<7cFUy^*Lp|2mth9Nn>Vi-@`BYL_fwhWH<0_ZRQH1X^STiKpuCOiN& z&p}a@daxZnoPOwV=etSh(zehwB%;n*Dw3e(ddrl3C7aWRq65B--KvGt`Xa2U&NeS^ z(_Xa)w@yyCn`E}MY~W*vhtS$yxqUeLR*9GVz7|z=C9%^UWfNb6_w9(>Wp8pSkCf%n z1d-z)>Y^;ihwGlPc~z>my(+&lvv_E3@>Z4=2hcOsa#iv2dsK;M?Q*xzmStBw9xBrk zs--3D-p~76U2T7gE94JyBm8NuK!8%t97Rt($(6&o^e-x|hn&I7@RV19m~fAJW)j5L zF3fahP?a2}T02Rlvl=aVO=i&FwL z#9e=2M4O;$#LJ*<=ob^mTVZ;lpiD(7M=?l$2icB}aU)$*wB>~y2^DyGUXwk#%Z_j@ z>?dp3lx{(Q5PHTas+1_JYKVraB`%(!Fl=wbg&Zh5kvpB%Y4rh35;+wj9saK$*s-3#V|-5z zhCTG!=2oki78PXvR2f}he0ZQM*mTt<+!arQ#^fZdlQYa&p1YlNIThs> zTMxQXBkqT(cqsDP^{9V`P_yNi3Ee{Z1N?!)r2G|hvpF9km~3r!eegYLd4NCl9VniM zn9WvnJL#S0$n|8pJ;Gx#he>ocYT04pK0l|%&(Q5v%WeRb(WX+{4~ z1u-U6NaPNEg+aXpHQ)p`>R*^jA0XM|2n_NeDutUvE@UUQ0B39ynGgr+IF(rctPgva zy{A#eMhRlRj*&QF)c8qK9#8r{edW}7GZ#-AnITT9f+-&)v~mB5b~V)Y`}6y)NVwbA zsgZ>upGBVY>x4g;^tI$mHIslfjBS|cN;B>)1#HQh3Vsli%4$y8QwU!!>xns zUXSnxog?zJn)z*|T>tHos1kXBgpqo3R`6CXwK35(?}T%ZU;Yi*>K0DexonXl1=*C) z%-V>=bSMMak}ZOQ^qZO~o9aojKl;B{-WZk2>#BZMXPGwFgY{REx-Nuztrr%Lw)xO?pHr0cZ9z3-uq z83!L_EpwH1=yXc4Lwt&-aRBw!8F7G3V4Sqghewj^a?K`}lb9vnhkF)PtfTu$#SU~U{Zj=rQcEGx4I!Dk zwvESRaUL_r>YN}2P?nrRQ~JfuGi~X=CfK?7PP?F6xruM;FE+2AsmG6+Tw)Hp_icXI z+VDc#H{8dz3?H$A=cRpcPn1QweU|Cad+r&w*!Cih(Sy+Zuh=dARa-xp=2n=zf?l5t zz!-FQMS|p}N4QL?G(I=vO71;l|L(rF<>tg2;%psur2L5!kMlm8 zsOIW5ajL{w8OO#-5wkcDHVNY=t4vbHq8~8D5MIHmF-aTzM;F=%2`t=yS_3YZ&iN(}(zW6b$aa z;luXTOQ;bd{NHqazqT$B{Hj};^~^>e=uz&tPR5?OyU2$tF(Er!=h~t0Z0wGa=r;qW zLvp(rmF`VDBsgo*2k5i}2;9FIoG&TG3tTyK8-b`kzaDR7ZYBrvEQ)T!o!$@}<<(8wfSz&K+FMbZ9yClIjch|p0 z#T83Q=oxjyx-xkHcS#?X8@Ae2lx zZ;F`XMS=Z2h}~sWcVB0Pdbf!ES0<|M8Pp3#Ofy%wE0gL$Bt&->8`-KA@KTT_Q$cOe zPN(xyhc4?hp*Curw+PN}FR?IsC$CQ8esP{;znS4!rR-&LHrdo5XY+)OuUAe{(WPvk zV&^yYZT*W!FCyNKewXO;^{*#CzWci6{lzcWzf24ky5XS}sz=o1?x;6ANX-=HBaX?7 zq3bRiw|yV^C)xA`=t3r#X5?XfpZTO=gV#ZRwP#TOId+(9RDU-Qw^$(@M14q6oyMfg zH`7TcDx)U`FZG{6LNBjr%4uDeeAl&da&Qd_e3(tjKzYdSqc?5I-lab3koaOCHP{ZJ z*v5ahn^3ZSrOH|(-=kxkN*_~&p6021Og2Cd*-1%NL-kZQ_#1oD36OLH6GV zf(~frs#CrG=L+B$C~IE3(P)o1v(f8evdL;_>BiIR6tNNX(M5374z^PXY3h%a{wpfXpYOVQ*!)Ofu>>d1o53au5#QVHk=0LaDU0so%nEhPEN!MBx zM2!I3oEb|V^$=fgELiUArf3-ASRpgS+%5~FDPS2W< zaafr{skKY!`X;Pf*nu7lN&3V0gey^VM9|RY8cF*q5ZloiYu{FS&tsBbW`~JnTe6PcJ&NjL7VX zBgJeqPjg9hO$GT$e-j-%FT^k2)OdaWCEesdCa1=(|Ls3I&Eyoj3XjS^u5PkgEjz@u zKG1q|^GD@slrEOEa^jUghD00+oAl+c$bTXmMCXqE6frz{XTZz|TW z1zCG7Z61|Q)$kNk@lJJg+^0zZyUU$Y5@+5}Jwa4+$JkG(8YY9Or_+mUJ>7xk`?u`k zvp^b-s&Dd*?D`YeB{gu3Cym_VsVBxd&pByZ*xN|HvK!#4=edQn{5y+v-&_-nBIu{bUiQ~wRl(-<>O_GW(j zOj=|uvC2#a^FL3P+ynB!SHXV{LIu%Iplr~r4#7fC2_BE*Gf1(Q9b$4-6`AdJ# zXS_CPX%Q;UCgKNQzXI&BB-sQlaWKb|Fa5Klb}Mf|J==zzuqvH9b!BR?UdDinC83=) zoV1=tV7QsVp+Ls%d6dC_sP3|}GaN2@wg`1f!cw$B+h5hxfoX9_;2EZxnei&nBt>1< zhn~I-;*_2Zr!>>7^;I@gd{0eY_XSV=3X#qzv>)WWg1~JBe?P-a^)CwjgQ5Yq>RNXr z9YJ@^a9=e}-($MDFSY*5cbJ*1zHSLhx)0ZT4S6a)kuLW&GJNub;ZNd@T|HHJ z>moP2InwpG`?|op!G2W&_9j1{;$~teNqj)303Gya;H8i$!IuKQd6&YvrF2uo2iO8U zKUGl2mL(D7Q^G1RgetNZ8u$D#r*F(Y9DyaB%cOxVK3GxDxV1&79wE{g zcpTV|=Oku4WWozksnHY^VFqI76nSB46Y=~%#V3^n9(=f123oY-&gGtmm8V}rs##ZMF>SlsjjJ`tUvN;~ zqTbcg`#odWgKyDclVYw%r;Te6cR6Z!bibHfu{Y6WWc3#E7VxC;BrxTDAr<8p)!Bpc z+WDwS7J@P8La%*ha~tnUW_#29Wbf)hG6%}GEG7ktwif7@8>vKU6dvT~ayV#qAx|?W zpSOuK!LtgMt&{Dbg6VoatFNN&pRJbZ7>ELYeO zFh4VFTDySu$Kj}@=DQ&x8@ii%E}aDKQ~VlSo%nA=rW|9q#}>^8+iK>o}Z2AGh%Vb4#a_e5}(H=QQfpab#{w4r3_pB1)ht{ zHa%{wGkE`7fYB7S^_+92l@m*c^J!WqMu^?$3{uJ6XaSd@r%r+A`xF^&3BrS#YL9IK z_urMArOfiED^wNzhll^bMkzNKv3_~*jC;J+>uD#2ai4cFXZ3%%>7($n055R$MkF*h92;U$DtZ zHTg^55f#V`O>1wFU>JatG`Y@hi^u*GU-?tYuzt5|-@S2Q$>EG^=dSnPSa0*nvkB)% zeU1EHHu$d)oAi6~oxz6_{Sq)OpklBIniX6#xSpq{XEu8N!M+x5N%Q(A_hnMMrxG5! zlM}XpcRo@3#7o&2eBl6|mKvgfDngQ?kJg+8CIh(@(Xx%GAXf72EJK%6luV{m^nd%~ z59mwg_yD)IEAcSD);l`&Ln9{0?>B1R04!7Qc|EDTdf)C%zj;iqOUd8>L z75__JvK`ieje5wWngL=z95n2qHg-ANa5g^WB5Y$-=tKd9WzQXt`ZOP$r^J=CQ13?< z5Di0~ja|8y9D>)ot9+;Y)C}jL?1F>5DL8u>eDgO%RsQKb;G?TWMyHI3QMs*wyNnmz z^$;2iopBTBpHO)Fn&lg_tV_wbd_&D}6JH#*m< zV5*56rk-yjtWInCrXS;|NF93z;uWBbH0~JxFkpF@IHy9<%MzD8-Kf0P} ztXb}QJMJ2E3n}nq-E(u3XBg^)n##^_dzIAr z4xSZcBm4mUsfG?UADqY>@+J0>e^~?W{)nuAi=2)u(qlJ>ZMecxTLZVSo#csk;K%Fb z7Mn<-5D*2$IgqTjG#5Ox!~DuSK}lzaoX*)GTf<*ME2nLnv{h`!LhaA}nz`nj%&9W2 z^p%g_`t{E*(Gi2glgGI+d16b)youfrmDR13(8c>u|*|I%qiO8a^(phvKm&9eYLM>By z!796|YVxc)DJH4Yb{vfsJ483#pSNlv*@mY@AJQ#jeVe4$Ev?p}I-R0#(Ylcnma3=t zQ{*6ZtEYa>Fj+US?o6dIB5?#QwpXfjBNODgP z&>Qs-jb9y++Br)z<2vQ%Jfbo3r!N$ASImV+ERE)~EL`>{6jD!Buv(%XprNWtK6V-t zDtgiio)vbryxwB2z?-Z!ZQwN5F?CNt+fjwSq9r!k9U*4w;6B?>x{-7;$0X4Oz_|WqH$Ffn=m#c_w&ELHoR_xv zH8u?9`W+18KHF6mfiX==3wumLKYcqq^^Z}{#=UrWI{fU`%^9}UTb+L0%Y9c5hCQD6 zEW3QIswc~syiAgoN$&ff@Q?E=@83P3SKwjifVYD$iRl?v&DRw4^}GE`Mw%0JQti@P z_)Z$~&R&qWNl@5mYLZqKs4kG-lZE$XEWXtOroB#Q8t5FRmY$^h;yCzMfACc@Z|U|* z1!s~6Ut?x?<07g8sSBye2qvqMn?FKzL4{Zx@6BrClm4a>Z53O{U8p9C!m3R`=WxJw zwKrsC;dt`PoZhA~rT3cXzk}oam>7VAZ$5X$ zd(%l(2C?soV>v4c>bvpKv^S|~PPmR2?K-U2QrQ`trL|ZG);UC`VfX4Gy3>016wP%! z?Od7dasArNbX(X|Zh6rO{p)vk30Z<;@$OHP{yMihM_PC?d)V>Ee-=$gUlsa=Zkp%# z1Ui6HFUO1Sv_-|Tf5 zkTx*M_0gMeGiks}4x&XU?S#l>L9+wiq`R2rX^Ec2es4UWLB-Cu+ka}&py}D-^NSn| zS{m>zB1J^A?+3pZi%A_jEG~I`%b1wxFL60yPvLbg8qm=5%z5_OuZ-yA}8$>d2VbxPFQ_XSdtl{n%j+$|_NeMUikQ2^>Yv5m# z0(Kz67ID|vrM_PFo^P7HL?8Ahn2Q6b?SeqYySuf_COFVT8h8AHd(_66VUK6p0`T8`(V88A z{jO>0t0Mc$N50H=Iw#hEUcAx$=zL9Ymby1}6a0j~Lz=C+^g6JUH15dk= zwyr+tohR8{%(7uL)wGg(-6=miUR7?hM{T6Tgv=0U7(C%wTwx{jDiuMSO_Y0=?#~gn zi*97nqUTY})w6Ig?t>+H2&VW0o#_(01dbsKIeDc~c2&mh7i-s%%`ynr_d9tX=W}P} zCwqccZ0El3kD8^S%^CkB{`}|6VI%*$a5vq-fBsp$ddP;eYesGAd!q8$1|KVYJsX%K zByoy%DSw1)3Ay2a$M30sY5${sqx@@uP4tWX*QaB8xHo;ZP?7$Nzf6eb?r}b?y<&j8 z4_j9Vzs+ejkb(9Yn95}I9d}XDX3~>sbSi32fh_dZ(e67E{g&E_;BbGsg>bF5LTCHN zjORX$fIm4Q50Fp3!faDNb@!5eKH3y=KAXh~CBOQW6rk*MC5$3@?L28iS=2mq%>VIT zr?WZm8d`h=mB6L8sj5tTPe{j4Cd1H$Z&%wydKHhi`6Vp>K^@NX)gHIsOj2Mz+pX?5 zv)`>?_M$({LH}QOri^^@l^%y0xSzbl95vC5;MVSLzq8$RgzxT-UT7xlR38*SM{)6V zR`X;qny*&!6lnJ$&geNL?HrZm#U0g4EOgl3ocnA$dqgiM6VAXccB6QXqF}1YZT*-4 zaXZ;CcD-}%SX>+j$qydHUp^4a=REIxdnjzn!A+eeHw{)o7J>hb0!a#k>%7HtTMvW* zH31&Z*=XG|n6n}uz}qR5qI<}Meuol!E9$;dc(Naobn%HEq?-1)s7?Y?U$)6KXk+u4 zDKIUKMI!ejd{Zo#_XBlFXLD+p1U$n=O-DsB#>_>LeibgNKWf0IKapEClx=;gOyTTi zJD-dKb_%(qAs~sDm7*7KnrOi}Fd7das#bl^EZ0j&ox6&v_crX#Sz^03dLNV+1w%c0D*vBIm`h-f($ zG-o&{I?n0;3 zkvX%6r~u>g0~B-(-4?H8Lt9QQ#aGvnzrRna%TzM~R&lAFLK4OaI>UZP#a;^EUv_Z` zUi+Cou4~{_c`2&uZ1SvIN!&yYHq-ZpQ%!KAPhg9944ZTY?_*(RUCtV?Zhvt}?GkG} z_2ee+I9cBtCC)lGVagtWtZg^-VJ)}t9(%}XK1tJYJ+hE~>P0e(w_vUNc}Dw^un-16 z|1UXUSID7ifjY4|oBRN<_@7#*N&IXPC?aA&yb}LZ(s6bjWy`z>zdu#3bLYu%ZoKr< zO;DcK;q)$}hMSS>brGtA&F8!^uT(B>vPo!&D`_hxfqfN$&3MWUeZ|y55qU?~)1%#O zI>GlAr&5qO>edoF-7pdYti272SQI7fe$_!01HCEWX^Wd=rM)7*kWf5^Kkx780%y|T znm>BakBB!d-ps$e?nE5jvLuP;7knc`ZZ`%Udph(Bnc>D(=?EHzrI^e2 z*VVQpANYycL6&eXvU#e@;r5u#h_-v6{f7>V{p9ei(5b-TN|@$o>B7(oe@II<7^#t|3{rCVxA|LT;=&75}dkl z8%ObQU$X^hY51vZp8>kkLycjtC1s8Ipdc9^2_!oIj+UVmIPXS2=`pk-oMUEvhglN zzyHfTO#i$5B&ABBE}@fNh&r%5Uec|o6-l~c63z;uTu=;TD*nkzH$t;=)!dhJbz_v5 zIni6d*SeL!gEQd%$;T5s2bX22x@bl)gYM#8ovrTJbSUChkyPGG4kXDniZyZ~t-AOts*pQ%c+xvDpCj^oi}>_8YJbGC_};Pe zV+usSj=K5dwy%3abFaVCEZ~r5r1y?^>3lTly~BAfK7iTvpba;vu8TH!CrA#dM`WRf zlHw3fR#HVK&k&Lup4%gQii=5*dMS>Q{BsVxaStl_|7r0G4I{wrmwq#?D?#wv~Q7a;Ol~3 z=09>BU!vUhfPnFPaz_@#e-SBe%N69h&XyzbH`IksT>x(qfx2ies+Ll6I~ujpIK7I1 zL#@%8Ry#^r5GVU@v@RV@PK& zhf8%3@4iFZa0j}KIQdORage>FFIu`-KL6!pchymO#VYc{SD>-zr2f`v(5@w+hjcXE z&b^)XHoNDV{o;g@QxdDI;f-H`{{4)2q;HFjx|e88yU0{s5GTn?(9zNO9HU`qm+K=W zY2*^$bum)E?lO0@VYB~AXW>GawE?C)I)e6Or<`V18qd2Frze3a?`L0~DLar{^ejGO zWTNnM&n7)fcV^$&Y+Gt=zqKmQ`j|cD;DCq!J*#9_d;U&3HP!qilanV4j0^4+_KU#KRnho5lPE?~neChn18^TaNrbFd~q6+br(7bDS`wH0qU zgSw*Fo+|xtd>mn~-pXI=I3K#AxarMKyA8L)e`*!|_Fm_ScdN6*`?qt=6N<+>8@u#v zlApHOqcGAC&f+wLGP7s`OodlA{hd=yUv~@Kd3z0-+#P~i*fPG5x&=!@@u zj@V0%OUj?!cJh;;xkVHs1Ggs1_w!6gduW{*Z*pmp!rg~vnVVb`LbLLRE=3k%4)mvO zaj^#|KYK-ev!$I*OhiRc+uyUTK@={aCpcu!+WU3|iI}@VHXDlBGE$_K9?*iZ{E7~u z0*OB(*J0?z% z;cKRfrHYq+)c9GWe4WR2$k8&gN#+v1LbZcld-{d9`?BxrtWV3L+ef~Os~xjDrcG3u zxQ)@5V2XnLTC2L=Lo`L5b}Q1a5$IQ0tn_{nZ54P9eA`NK2;XB9o`(DEiw$vPRZ*qL zoa@EYRoaf_woi;ttpo`~LWbj$xrjftDQRG|2h!yB4y8d^c-%^6J}hs4KC|?uEpxy} zcIYvDzUyRG{1oB%1&;D86d(ibF)2`)bUd!?^X>&-6L+@H&&^FAb_Y`NhnW0uHgRYW zFW5V1`&ZH)lbsVJg14g!*l`Xf%=JGvERYh^X{s%I{A`|^dU8IgXRGk3EN5=hOde_M zRe1sKyr)Qs#{VEH)OmDWH{s^(2VV6970YQh<<0sv+u$=&B)YJd)FdVRwpl2vn3Bjau&rdq8~TJ(IDpjoN?v8-9fR9lSNV{O)BTTEr`+&u^Y|UxF9+UO@uObs+Qd7P)<_nVvR6>0kQl!jL4O1e4=ECm zDde)9=xq|W*({3-wjaP1ko=aF~7Wp3GmYs$+Vpsqzj($B=pM3O>t(qRvfv^7T@VU91!W?U1kP``DMok8&URKiTEMWgX0E8MaQ&?q_ZHlk9Uo2 z>fcXZQPsK2Yxz8?E?KaoisOK&E!x?CaSL^J+K`=^Q=Jt*cq&KWLbyv()Hqw`CyvjV zlmV1@FPhqROi)vpx~JP_&TG_jZ*YsH<%GHC9@Z7L(u+)ga4x!$tLg1DHtG zR1Eu8CUcnNry?eld<(~&VVFC?vQpxHn$M;mOh%g{w$MRR+MH%W`hmhE1nv1WnTy`X zmuk7A>31Jb%R?>sJNn4;JfW{l8W@tp?47Gb3OxZ8Pcrod{Az|=O$x(qFvLT+xiY~& zB(ZJDoqfo6X-OLW3(Vw^c?(nh2PfNfentbz)=I|PD%^?3`1><<9jeAzBt+iu#k;qB z>vXVgz`rLH2U)E2IzehRy3g|RHXg+r=wQFgR3@F;qVuTbq%%Ln|6G-H`HVINZ$t^1 zNS^0BJ;Ho_o$2nX8G@eiFMjWEGUH0oBh{Whht_1t#IXmB_Ovhpb*HQ|$*HK?t2n%J*T7jXnJ%E2{&pKq&mJ%_0lE?Wrr$*g-Ba$?onb=v zu{S=(=iLbuV>;TwP;$BEf*idS-*``IG2Okefox7&IGJwQ!T750;cT*|9d6gdB$x~~ zJ{MQK9&fEq!uCeyzE~()Fh6%Ajia7E=8KN&7dzy8%Wqwu1wCte_R!feTX$`Hu6JEn?B%VxQcxATrUO(IHLIP9P2(M;Slr@)e; z?Eq)Axapin3$+;zH~=g&1HOeZsNb|%LITPLgM$RU*L1k=3wSY-k(;*K<^bKDf#ai` zoTrX~uOE`D)kwKm4Uy;7fAXWMPQGv-6)AIoyf5XPc><4flJ3^>Jj>7ABfiG&Ja?~~ z*o@N?V7rEk87N!Ek(B;fEWnG%Zl*elhiW(cRs*!w{bA^yfive4hw*IhWVU}vZcZ|8 z)d)6H5>9n)k`u-=d;XlYbIJge!v)#2Hrd|HH$_x(8Lm3YI?k{3a72nm^cZbtLhgby zy)J#8abg)QE<2d$Mlp+)2H}`Z-u8L-qF94+W~520UYajxHP&;7oJalXfzwa?N4D85v^d5Oo z*^MD_a6L1{IMN}?bMsfjr@5YYtgtx1-L(X-^8aLxdir5Pg5wUwCo7j>G-@Qgu@=g|I<$L#uD4>gw zT(p8uBRfgnCv;Kww5?C_(-1zf<)WmV$jp`p%wxXX!DnyQYCiFDub3V(JoUyq0GGRojz`;kC>3+dW3r#Dr2drG)l(8WlBibd3|-?X&!!i%_98>gmd;YkOw(oZ&1H%K1e%O-sWbzKY8dX>rV^3Xg~4X5-kZh$`TTdF6!iO6q& zO`!R&wft8_$eHQ|w_!c}A_KuShoV&ei&WFsdOi6)Q_xqw`9Cvu)1}Lp)`?{(nzNA! zcoENC6HwI?c96Vcd!Ur>%)ML`Me7qA&K&p$PW{w66pZq!TLn}nlAWj*N#X)bB#rDT z&VvGc<=r1ny9Sv6dal@Hl5qx};?6xlGDRdDPYD!R@o0XkGg(E8@peA%-ZEK|8(%Ye_H@Bemh{;}3aTWs92VZi!@>suaQd&2Xs z=ZD>X9|Nl;i%c1wXlv*ri>bI zgy))B>A7p3I-|^BH37!D4$t5eQ2(j;MJ~&=x`;gE=Aj2^txe3W@r2uB9xCLiG?Qm` zZixm?VUpg)(@{BIX6D(eg%9K?Q$?IItGV?1*$oI|R!lh+}Q%DJzOJB`(98c_;5fwBX*Zgy14cj#+- zN8;82645{5RsE@fZ$PH!E;gJhqAYiLUUCJp;i3K&hvizamR!Ik>MI-DYO*}C;j}A> z^ZqkW>{YiBNnoTJlclhbHyAZ6dWiI(!C|mGo1MhU>j_Y=oPXscXQ)i;^p$_J!G4uh zRbKf}HOJdK0&MM^`2Y@Z1M5WUkzIFd~|kX^x#tdri{4(Z|KHZiH6MzK&5 zKSQh(C}gK{4!$H)DuQ$LI#2d>{5r$PbDb~Iv8wav#mm4We6;UStnOm75VD9fg5Hjy57o@sQ_kk`_rSWILH{Ooas%{aR#i8QpPHr#21qH}uc@JohV3 zmL%V*f64YeMVMbqc;v>IKYlcbdJ~=@u4q&`d_y_>UWnwL4j@`{e3ul-W}dG0PbSDY zw!80|&Epa{=FHA@mCIYLTkt_&eWmt)%7bpCJp$N1UknWm9<&oboh!!gyav zn-$b>9mx@g-MVTOTm~)TQjqN0hv%)csw|(fSsa9~ORKuV_P;0NXB-(770`wHU{If$ z5Be?`%L|mEYfN(M>J_AkJ;tA$2$j|EV5#Z3@q^S0w$?dhMAj5t@usH|S-BslfzFaI z$9`T;=Hg~f1AcuLH)RHXj!URGZh=7F7YpG&=GvO7tjMk^;V3Pxj)<(;2TJFC%{v?-SPX7@#m^$0T1k5&T$W1zLI=h4Lj>eI#*ql=>jvFMgN+W}vqI|xM zNMe}<_u8Ais0{l`64MrkLt!)kb8SC2tw^I2b2sF%%k+IcLC3mx^jUX-IqyaqMd#Ie zKB*V%;ibfG@f}s_ZJ|H|ER%FL91VkDkpD#QR1AObMk(Mf_L;-rZ421b)`DWQ?zDVx0x$uS_ zZ#a&)2=@f2;$zv0S!|#Az%*K(uD%I+4J`&yU^7kdPi0{lo~-Y{$P&0K)1Gi*0Q_wU-d9lhYXO<`+$(H7Q zDuIR}sr*yjM;TQ^^l@6TDUAS6=?bqIVRDk(SR6jA8ou;&`0)SLuiQhrjLw7K;IoNl z8kkNx$9`rA$EmD3qAE-T-*XS2Pq>a+e^c2FD8@&=>CJ{DJDFhx3fy`XJt+Ad6~nL3jI-275je$=kK^lMsdsb zAnoW9?{-#kpMCF|&4M0(8gEJ|)Zd-C{r1>qdL+~QP3w?%5N^Jq2=9xp<~13yZBz;| z9d4nJdJ0~!8r*R`IMR0drDoe{Xb(zo`~37l*HTy1NN1~4&2!N?=PBgeg#9eyAsN`Y zj|O#(9_;yQ8wFPOdy}GX@_+KS$y=gEjT(j8xowMdtlVK}&8gMfXBeG!NnB9$!!MaX zE{;g^t$Ey-=!&sFqS8eN{df|;CH6I&N^{R6PPR*UB1R;DQMwJCZKf}5#9Z0T&Ek;b z>v?U0JbCRgRl^PgZ5*eD;a)$;?$Jhl0=Zafx2r9<-?yT8YfHMz6g{6lNqkzI3}s0e zT8ZM}6@Hg6=DxpCr*|;L=x-)>fP~YWC<;q+9~Z>)vq(gvoZltBftME(jbI2apf&u1 z32mYsZjSKTR1~E^uX@T3sA-Q0dU|{7Iurd; zLpWClqh85~WAPx<$aLnv`F1I4((Wjie&d{31GhB>9`Pj%-$yp^%4#smlqIsA(^G~z z577zH>m+ZXPTb6H_R2lwzH;yQdgz_L+{WWta|urP1bo@g#+HrXbGg}ibD`}Wit2Bm z$p^|?-IOx>*k0a~;`0_py(!wkNg^W~)@AV=oF}iG48l9XWCA;0ha0N_XxtTcE>~WJ z#kr6+1e4hDe zD4*PBFwq=5pZ(!V{UnuHux=nBhijjH@n@;W}`32{OVg|*7!EsneGwh z;5M?Yc_)_ZYi4i!k@&jb*M6(@316x$%$9CszHvZo+|;N6fQUZ=O060$wFg zpSo{onPmP!=Yz%tg$313R6b;)|CGRAXy>i$CP9_<(sM)H@w=lRIgb+lqrbZy&A%;G zZr@>%GGVoO?4#!bT;d|{lD1EoTMW0u5IayFBcZIewxsco63g8BGDJtg`}R@0@Odn@ zEmc!gmo94bnQ|&z#Ag*ChvTeFD#pOxD?AigxRY|>@p-35lQ;8NmoxGFFC)kUxhT)G z(>GOccPFn>%)_qX0 zUu}ZDW*UH1Jrwb{|C?B!^rG$ThCZSvQ~z)F7kHF1oGUHOeVbArAjRNkTM2#+^wt~E z2TekYp95cK9yFwZY;^>qKd?*gL?@jK?(2m;XTO5? zWa7=*z_xS&#^{rpj=FS{OinlF2-wOTsOL(6OKc^zbgLc(t9XuQ;x)-UCusL5h)!WS zY)&0!?EYj-))(=#P$bZ3@|$SjEhBq)kBhV(mqTgoGAdAu@{H%_GTZWV<+e&RD2 z*#TJ;t(8Z7QU1=Upk~1;LzN7dOWi1Wwn_I!*E$t#-?!PdhS`do%s0gUu#Epc@AJfO zF`wE-=Z`!S^C~J=RD~Z$V`{|&*~uo0CxvsG^Sy-wggu47416U9)Y*5#ki!gsM9lsSkys1DEWbiB@f>UZ?w8Tf4KiyC~p z2iT(;+O_yK_Jdhfq(`A09%(^V>_@x?x8cM}GT%m_#Oh4OZx1+x0bmC?oVTK$=awwv zNvh&On$yc=vJt&AZ}o8D)!Xp@hM{$sYDO>@7J!=yhDjUiT(@&PT|{#4Iq}LfUA%+m zub~E;%i^SdY*(|lq%?<&Lyl}S6rEjgQ4XWucZ-O1gT(`UpfUQ0m<^jz5u9oQTJ?@* zv%6bgW&awge79)5^2!S5;)9lJXeNpZ|B5g7@rg1>pC4nlY$RFT(|2 zwhK5B+TgUxrGJ$x-C=6A>+c+L7pbA3|9BL{F<&9O#dqDLbL*l^nE@Yl5O%@GiPKT~ zv!D8CH0i;&aF?C!6pa;lz<6tax;3QCM)PAOxTMkGA2GbyYejl8fJVwb&IvU7@wCZ2 z=Ol^ayX$BA=&t6AyUQGR1MMTXj5Y4RV32>q_xXI4^x%ZH`a}FTy(Zz6$>18YHXp$2 zGyy}XNUmQa>%?u0TmCWT)3KYq{`+Nr#=}L{T-~r}Lx-*X&OW{9{dPAZA(0B5nBsb> z{UN_3$re~VXlQV{L>UvUOLWEC%I_a$i(*W0<>feMs>tL?g97G7{5Vm^7pE$@l{{&D zr`4{6>T;HQUH(JjWj7GD1E|9)iPm8Cw{>g#*w@p(O85tTvQjf>)vBv6;x;JcqffbyX!r1AVtZ=+!A*vN#L`P3 zmvHwML`T(CczSM(5I8OJ1ox_9I7?Z)KwO9ebYs1HyMiF zE%KR8$rRH-4FVUuz#A|Fk9G+hl^3nWwN+JQadOFmo{_SdXMud;3_`>50BrOvdwowb zR8BD$AE)Uer5cO#{;;&pSQw~sY9{a1PB3EHSV)pytsm)>xQ&y_TD)<+Y(G4vnMvLx zodE|yJG46`J&;zB)vPdk;IwJ!=}dA>>+b zpiN^Aw@@-v8u{#UZh`mmsT|^5Q@uTtRb$U75_oH%n_dU%c!9g&rqf*J^H_YM1H@1i zY!T$JZos9Zz*ky$K6subX%pHk=i3~nYL_Zk+k~_#+G$q1k+qZ6$eyKm`eDAxF{!^M z{&+oN_19r>J)+;lIMKiUC>@zGwsuS?Z^%;54SHhtk}{s%&9BnA=bZp|hT0t8PqvKf z>)c4V?`>}?d&}U=ol3G)Zt_;%atB4q&7|(RAoC+gY`vg=y0kXn2F~O7*#vbEbfCE@ zX-~54?T4ud;LdHp`#6EmY9t-*f#hOD@Xz-JRZqmHwg*1-4*iN=nOdIV?8s`XSe z5qz=}dCMEAKhQjcfrC^=Z<>`G>kb(Xt88)@$xYnp&#lY(v)?WTQw_6ao#bMS2ki?Q zxzB8DZB##$LX$X=529^phrTZ}+wEwQIcI_YH-@c?(VIjm{auWKpQPp1`D~*+3&m&e z6mi>o%C7a?#N|AIZ}A4)%SN82@%YA5Gs{$zjbJgqxUuN%Pnj@RnoGC}!;DXVq(5~b zPv=8)1AmeWwgn7txoPYAo8oBGGLmMp-fRcI{F|hVRB90}@BH>pp8hGi6YAJs(7PNr zJWdHq6MBnP39H0ipFg=pmE^BxF)08a@Q$SAex+f_ z1j?KyktnGf+SP6`yVgAmQZfRb>@R-ecsr4N=AESaRS+Slj=kgv9HdQdj=9TslLww4 zk7_Iy$`j(YNC##g!W%Ohp6$407Q=P0$1c*x@gwA>>ogeG>1Wj9Dc$Vmx-U6C(ebnq z?!ZNx4Ch%HS<}sDVj{CeFL~MdO}PtOE??e$Xm^{{6W5>KabwT4+g0xMOMo3qT02Gm zWGPb23#uKQJx~V}3E7+|C;4O(okpHi?iwT9ZglYN_eARw-pRf}bPAqw=Id(SQTh*0 zZZ|Kz)O*1(*W&W5j>o7w8lFrb%vWe_N{M4RJvs7m?r!`# zi{`bcp)@+qd+c11ctfVKfsP?>Vm*$8d-MbpHzD}(AL3D$q--40EBS;BQ*#Y_##hRI z1g)6jK7fIb#4qqO({Yh}#zuP$rnV!Vs1l(2-I+HN=@leMlu-fth#ZB+`ZlRr_k3&Y zT=!qwi5<(qk^8}HhN&Mdf1oJ%g3}gM7&O{Ivp&>3CX>4)C6&XUpgWkJFK9 zrUPE-uVCt~Ohal;5_OC`_ydl`cX(gKk>(Tyk9rV4QesZz^|C#h_FddKiRtG_ie6_D z9Q<-{-XbXLg`7=$c~yFfs*~=N)%`{ePzBKE)A+6*kn)rjEU7(y%YtBJMddsYjoKuV zwIQ`7ik)`2SnEs_ysx4oo`6C41m>ajIZtj&ptyyT@2H3(b2`wL#%+)srB?ux)gPui zTGx5HH9qAkWc2st+?b5l@vZEs+Bl^>4LwP`^E{J1SDifciIUw$azSws&aOL5PKW=B z#Dz~{yLuha-~ad2NmHgQ98h3voouzbw13k2TkAG04p*pBc6IV`p$($9MlSn0;7dSM z!^j$O-(vPguZj8*<&B!?tC8?kof5x0k)+{AB%~E*;}es#yu zsrSBoAe(u}+o+6gx->~u_3_QzF}3AHRIm9^=-(sDtsc(CoZJ|ps76!J#&Df9LnHr&Lifsi}{gC}RE$He|5rMiSf)v(q+&yp5_#YCf(2`Do zPc1^{C~dU8Z>T!=!Xk&{%tT8)}lT-p-kxKN}~E6 ztaI8GoSjq5UGtmGg}N=q1d|AmlN7pzpmgo^RWdIg(>x(tq^L~4(VqgIFEhdZsp=$F`{`^sVDzkS4i8jLC@9{%$)%AYd$ zop$1HTY=kqr^&!XbX!EB18S#>*;rJ#8T4Y?W^I(a`+ji&*`>Tew0b0V6D8+lK zn`EL#k|=W;^*{pq#AHytWF*{`BEdNcJX;RZJQ}$8pP9F-aeH(J9}PgmlbFxvBl^-j zupxC5KKr_V*Ae|6AA1&aV#8^(A!19>)fLzGIavFMeqQVKa!&^T@5$$+?3*NK=Dm;w)RqZA{lKEZcGukn?EKb)$^(hKwi?_EB2r!bKD8~occh>z$a z7MqS}QYX_q*-&jo@qUbS>>Z{jpHvn7L&v#^Od2$z_uYT=E8o95z&FIaO&DuO`LfZ# zl9R@OhGH)&mS!mazoXFW2eQ5gv_sIYgy47UhHrkGyp8(5Ac1 zuzkhIytI?er3dq|nxg8%b*xc8vwJ7tGZG|;w@~L{-Mf?A(A@^A#Ax#pL<)5c4N7Br zr3cYlv`pRQSK)}3m-SKc)XgrQ;e*J<`#ub%-Z~Ni4)f`U={%&m4JNH*qD(?gctz0~ zRwS0homwP_RCLnP9X`?g0AcygL>J_o5GmlpW|2BlM0NoyNFX_Dt35=f#Vfj;OOiX< z8uY0@8LbPs;Ud9Twxb*9%_s026wAlfn7x>((!0zE1g~ zV)93&i>n^6ngDjcKBo=1r;Y;+?r_bNT|sj)dRUBv_ynq}nt}*hpJ)DYZba zm%Z@wj%40Atz*%Q?7~A|gv6wM%;0RNsF!D=S~!MJXS>Xx)_@EhRx3z$t0O;?J&;^I zrXS!KZlJofoB3^z$3PqgSr53*kO$yUsob&?=M$~qz$#Hs`+y#%46in#}jM-ey{&A+3xx`IA)sAMn>t-*YxGSV8 zgAu1xZE4BbE2=R?RKvZHUU=D|TAPZt4(TK1#Vt;zuVj~WK?60^h6>W#Ne4ePA)wO)PZ|sd()oESnE&pzBc(v&bPfVpaH?&2v5-BRD z?3(CN$c(_1!3z@wB^ec*H1Y4w8}DQ{A8CUAWHuag=F?1e&i7S}cZYM^W+I)ts|U7Q z6$jaGqCB*+6(-@VF@4!{(TFuB&*BB2Rb>>#`$%8^VmqO>er>|gJFRBM|2g~THvgF| zxC;*HC_Dlq#S@g~ZFQLS)64LO{)UfnBG}4e6p20AUUBtemQLI1g_+66+ zEc0)${#7u@4M0-wf;|l90;u3jkCaN%VMNK2w;EV2sk33iuaA%p3zJ9uZ?}ppZch0@+D@0@K zL%79`rZ%X{71NtrafUg^9#@@dF-)D6ZJj#sB+~=+UiFj`M_^Qx4yWR}s)BXoeGn<{N zzMFK;dGn|9-t1tH4Rr3CJMyG?j}9P=X=gQ!%NbBY{-D`mr1XF@6eAt7r<{i?x|ObK zUc2>lah+6O(-ZYV__x~NU!QQ{)Dad$rz6bBQ;CXH4Z%&hkNm8hIM#3R+4NTJMKkr- zW>G`s0cV2${J;w-YNvXiZ*=})HDhahZ`-0xhIXr4zpHY&VqB^T$$Q0oiQM&7e)dHF z2w$3zAtrrXj~_{6)X z#_fKR9sUHr^E;GsDeQT4bLH6EPOxc|0{!bJHmVWws_J&!%$BJ$5-qIMR1Y6V`BJQ*Ws3YDTAyg_XjT~ z=@KRMZBv*8%5vOTBq%Uh{$_t=Vr%P0n=ifsl*clyj59X6o zi8EU9dk!`?aR;8|jeSj0cpc}Wo$MJc?t1Q%G&_@&fNZEz{MpOOqGtMFJMbhHC&8tz zZ0mNCIo#y(p?gL=*DYyt*#$D4)E3s;@E~5KU9XD0OU86b^PO&xshmES$)otkM)A}o zR%1bLACi=C3;o4tp5zp`)}EoB4rBX#EDEZ&qKq14`=hS?j0!8aS)yCJ%XAi>GJ6x0 zDU>kOS3kCTWY2HKUJQBq;Oyfw!P_crFTS?>hSK|=9De<%_KRpc#H*5(NZl%lUy2$2 z3xYTLeF~fwm^0A;{~3X+@WmL=2*s>CPSkJ`nOmSvvrsB6AuYAD=YccU+uJ$hx#KMN z^!Lp5q;{sG$vJMzfV@oM$#_k|g@D~_%Op9=w@LT*z0n15%-=zWHQPpk$cLcZ;XlD) z6ji6lRu2XPD8oFxAN|EcHxsE(#o_2P@oo2l_fKtUr$Q~8T~?LvL820HU{+%DxgtfYS!baT;F7q(X=K!Pvml6gL-7)jwENb4m5)eQd_{5S-N**siQ5S_M2w?%s~pJPCp0CQ<#^u5jm{V~PkkgWYA*Ovw5duf z%W5~lJ>&jI9@k4Z7oWvU)B=r}4+fIDQUJ^r-V9|>5^^)Ha_UTSrlJEY-~--YBffvG|Lx5O5tBXq za9qx)$AJCc>Om{)QIIrAyJYvo7yuiBm&qJq;4t$tT=&lVSV z^fS=;9HjTg;wo7uKbXDzL`P&>SyzRr`HshP)RW#5?=i~bDZm6y@*b@Yb?tWd13hpl zY<5sWml^m6`?$aQZj*BE$DmFc3mVYUNu}yJw{SfzKo6Wrr3CZdW-2(x&3ospIpB;p zlStlvhZ<-$>Ui`vJb7j5E&`KAZCeHf=5+Rjbk0CI!;y-#I`Hw6oEay%kymhPlw=b7 ziZakEOF3QSe@<`mmSg$*!Khq{!4!}f4=Xklge(dF_b9sVbn>K55Pl^6ong{g=2nyg z+!C^yyG@M7y)&P4@+LXZBbibp6Y*&jQ!l{T-nq5WD)rWWb#t&LrhxUUvyb) zleBlIHk9VL54W;vRgfqo7_w2Ay&O+2I*-7!qLc`85c)Ff8k<)~B zf=gtIJ<{7r^Oy8!7Ud2Y%g+1Brsdr~MH^)+QsCbD7L$|O3cNl84C6}vY^TV+d@n;) zS=C>ikbJf6gM;Q+FamIpBV>Z9evSluID6- zir*iV^;@-%WgnfoQ~u<}qkXpgw)yy`W?N>R8h5tthoWC!`8kPYXxG$-16ziU@@5OF z9r!G`L!ts9CHxEf`{TT|Zi3E5pGr%a5ug8B=0F-vI9szi>AZ{8Y0o(+J=fI*&tbBA zPV#pA5HG}4CQgICexv@APSQg91(@v){nAG0kK|B$X|{L|8hDGFx}W$LrR+j(*Sa9$ z7tG&q?9`p86(=HNK-#xapj(I^4N!x;h=% z9b))1Y`|k)j%4kI_#~I2Fg}F0KcDHx6SCHAg<5})+f~=Z=kZu?BE4%cDysRSCH|`i z?iunMXfAQJS*@3uy6BjhH}qoe>tvvww{1IIZJpp%8>;#`zcWMMb;_HRp8rg^lh2-V zX4&mdAu+;{vKWctYgA&j2kdhPKTj^W{5kY>Pn2_2J9Wp2RsB7UoSLLv4s=KskS^Fm zVOh@ZKvz;mG@zx)+D~v$d2ulm(rw`WQm8QBB)P{OCJU1Ol!h#eI8#=pH&@9cJ}>6F z*Fd-0NmWs|Ecy0+ zuqtFAy>r^_VdAnyKua_lqslzD0=b8G`K$GK~_ zIGgNZP>*5`eShi?ZjOrRHyhHim(}SfvO8tv2T}~lQBgGcFs>>7FA+B%-SVk(n&Uo{cM^RH%`_??E7B6@?^ zth3S6r8@r>b#eWMKlHyG0oz!&#nCnR6#gXurv&0$h3GseYnLOFilsnD| z&!0||XQDdGx3Q1iKCilB>d`(B4)b!tv~xe`L~dps=lkM*b@Q7)dD{a}8h@wvGeq7Y zjp>pM5qnS?41*W)d%0Y0(X-Jyn= zBRo~1p4@VlGmi;uAW1*xQ5LP_pXbGVMt@a;vKah=)HXXN*RFB-fPC9q0C(<|6v()u^ ze%A|};wFdl*xco$+KR%zt4e^Gx`_&VCt15!n6iB|9{rD_vyQLoSiA7d?0rHKg1Z%W zDXzu6SaEkK4lNGF-3qk0ySuwP6fIEPT>^v<$k}^l?!*0q-*<2C*O27wJ@d+1&&rQ$ zWT496oO4>ZecX?3eK$O?$?4@TP&ZX^b{|6Y!5LebNlzk;49{yR26<1#3Fzya$T@4N zThpJd6I*o?k;ITUN1ujYNli2NI9Febsnn)tP!_!9ye^N*^`5BAerP>Y!e~B?RcaeL zt+KKYYKnU5Cey28yd%q*8*O6#(g_x2M?s8{eMUOm4M=_$?Ou32IEbA7qMFkajl&-F zm3vLT@4rFYQq)5MaSB&u1YU`LFhKS>A?i%vct~i(#iX@TH_F?tK=Yry8u44rYq6+l zy@q#6)-T*M(TA`~(JkI@d_Uw>>d&J;4*rtsOXH|%pC3idh&~q#^%yow zI-wmdvC{ zA%&qqGILjEI@|3;{et(?*PZ=Vx{v={SA#vd1s2IBJ5SB0MtV)M_aRak7T8s8J6X+j zofGPiGn}72v3V#TlUBc1bDG&qC`fKVgE)x?Y!TV8d~Y(B=!A#=x;pI)Ld)<~Hg$K( zuI@BB2sc6kw*soxpV+l~9t=jWJBpkrrn(79mf26DLl2yNY?hfR|IWEmM^@vjpZ@>3 zdE&crOiWR8aJBBHyE5qFw!-E3q!-IrcE9UH8>fX@!Hjo56M@gnuj9f78YS}&1%M&jT%M84gL0{pUd?n%$wSUO61xQ{RefY`HJh5Zq1g)mTiY%u{tkOR#7w`s$cz_j zBt(2)KkzPz2&JC9FvLRt7T~AHWR$StmB(Sr5~vv7=?b{9;+P;x>$>Fk zERh}>n^qzroA4>Tu_;hN{lWi#TlC-f>TPp<-%MdPHQ%g5(N~CF+6x=%!c%u2+rONq z-=A!r^hhQE^;BiK1K;;KCci`R2UZl9a3$ABJd^q$r;=|5&Rll@~Ui&5L%j~F|pG$v9{B#T};)A_tlA2WhY_F$Z%1?lwZ@+GAy7_DUBD$x4(st1WN6XLf;eJ`SCUecYT?>jU7TI5zG zc8ck%xQ;rjj3$lpb!L?mweBF3Q|;xhna)@KjhXFkoT_Db+b%N&O(N#Q^~+|f!;7ko zR-q+3y(XfX%ul~Y?a3*4(wsm&@QL*62j8i>b}w^>h7j|o;tI|yCZOhT1y95E%h;vC zVih4Os{?kI+&z1F zm6g|4|Gqx?=_}`7ei#tlIihgFXNguO%^&we#E8%aVLc+6L{x}ZG@@q6JU1^2hLm=& z?x#v~%Jq~R%?xvwDe-S?(_cgW$SeQEeH<#2I49K`C$-y4dFT)iJ84MY$p!5tg+3=c zdL?Z+ZwN{MmUZZ>hmOgm3}WQQ`E4c}w)+4g2%@sYPx>7Cm3e>9i8 zjAA0w#_2k~TiPTDY%tqU;i_(cv! zrI?;GR=QooN2sLFb9g#`JB!vj$%7T{h@x(dmtM#*mR(*K6s- z3x=rBU{#sX>&QGSC_Cz1>I*l1B2&&OY~C<;TZqzdn)+3aP&=6jl#sXRv`^X|A|a%L zk7hW#z!z**Hpt9OSSQLGsB#;kJ2Cujj!{Pyg(7g0?msCibcGvr9^984q*dhRl<0!$ zHNO4qTr!ygeaw5ezKM0#!Otzs?R3&E5moT@B!J!FsG?{3mV2M}Rf@U6qhAiZ|M0q1RHBb>zE1gi{7dDpm!teI6~7kn zn%OH#sdjDzdW;!#>6&Wj9&gxVE!q4%kFDyz^>UiEd}7-n&}`6gY)XTEhAOeMdBwis zpjx39J9E)2d)%qFsoaX8L6S^!>Wd<{;p#GpX-v8fDjl7O%%U}T71zMtn69Rq5a+F_ z;54?=sAX^B!5i)LlqK1)e->%!s1DO{CngPZGC%JW)H-MQPqDanR`2!l0aQli9Nwru%NzbQ+0ySI3!tr{bfm3J((}x{6&T`8`GE($JPu5hfWmXHEI*KCy zh(Fb8-=Vtr({wXyxNkP1vee#eblq8XsIJOCIm7;PkZDQ>yOJGm1$F@&`~#|scS1!4 zm#Na;d{xMQs={;!rz;Mg#kz_c*Cch9m>Be=1yQ@QrNC)@#}}~Ie&?%YCfOL!O0poiIj3*sGo#SS_rwdz3(KML)$nY4B*=`KM>r zZH?OebcMI#+15|n-(A0SE0do~HcT`x`O}0wk}V4x5Fx_;3)>#CF7Bc5S)rp;XX?Kx z-dkoJkC{^UgbBP=lT~H1pk{lgj;pRt8Jv!bNhO%>40iXc+3s&lS5EPpiloxp>NgYp zyg2r)7hxlHUR;6G`0n0P!*$20vjP3s6g0B6=qo*v2Np63$$&WSEiZLoS}|AU=P_6_%r{-v-1ne(nj>bpG8lzMU;g#8A(Mr zSr>Cs^R;*D!fJ=#RXToUS;#*ilS8)1XL6`;C=0)#to~?PF~^z9)T^hh<_AP_y;HQM z>mE;5b3;1p094SOaw*rmyUG?_U zIC*qRGTE0fQ%k7^dQq~sR~6scN;QvL{|Y&Jdr&K8BXu!9w|G^wy0=KNnanIhvVZ>v zXKF^s2!+Hf2$21`-B-~)y|R;Gh*rXp*p^(pU({o&)A=|j>#_&94!t8GuB4yTAzMV{ zU>>{Mhk)ac%I}UW&J$a^EuUm z)xI?UtNGR@dmC*onYmc*L_;GIMSGE@B3Hl8_a$po?{5XaruZ_NYV_Wh^T92CQyGsr zLqpZd%z{gPn~HlgH+vE}ALaBNZ${NSMRAtcN4vG`<5;-k7RW8`Bt7R@K!vI|3cleU?I(SGFEFT&nC%>MSWnkQB}8&JWm(6?nTuZoxu9L3GQ1d>xg{6Ph?fYjQ_ z5Dg}y<>`a+wJl2Q@oY?!!=uS5e?!akjPB$z9HM7bVm;}HuH)dmg(~2xOlrocp8CCt zs~^#~v{EhoHL`=>Smq@SeHba?U765dwWFEPzhKTbjKq&W(0H{_b{p<{o@_r3B4%NdN1u_&yZ;n3tK-H)nZjNeVb8;?B(-0#kuha z|6W|Piv0IR_HQb@L~#BZn-la+Yt3XjqVY`G%Ar@vYCAC@o2#av`z?;^Co7+2A$yUj zL?)eHTwz;w$M2-F>nAFYo}%tUPn*GZ;I@B}{-Xfu)M;>_a_EnyQEW&s;fE=ajqVn> zlk#Z3BWc&xSX*Vqv6cVqYk%l7iKUGp;lzoM{)Sp zR-g4Eby?qqb6P`2!sK2jmhmZ$#w}ObRD@XF3zuPaxEwKJlCwY*bW8CE7Ll{jtm6*s z2Q?y!T&FF%BPxR&{t`GF!+A@FGW&aq)1|rist54X=C`}iuWiCB@fpI*BFItcQLOpK z_p;loUTZs>jr2qaB&kdtb5Ym8^X1qEeoN8ZzeRd#BB#6F>Vz1_O=N#^JBeGIOIPTe z&v3GIlw~+G){+{R0H)Y)`lYDL=Nd+j;hXn7k*Arc)HGSC0nX6(T}Ka?i%CZbx~0SB zf-?!Pb~aOxxleqx#&0F_`MqR2zXVV2AM}r8#p{Z;mR`lX-BOo@@xR+ofk!3Z6a8}-g8dp+fbKa}<5P!zQvuedmY_zQ)hC&Z1q=Tqx$JuG# z20XtBsb&i}ZEPUW!EO)iGYbPFOp8F6jd7dX7mkmo<41FkE$U5km2K)eI)j@~T2so4 zaE_`7rl{z?j=-O|&75JB-9*Q54INgrGe>q|=c3$7Fg!Zpj?KpOxeGo0cJ8mmDALB- z_Wm7y!)F9F3cTDBRBNA{k??bGk_1=*_rMT#3%j5&XF>Z^*>RmO;UnUHO7kjhIeF2W4rB{H&9x zP3AVIWi~WXhna_TVGchGXI_0~*r%8rWi`F^HMn2Z>`lmBnK=7Hw{F%MDG)EE-ye-)=3JUC7U zdWUm%G4rgUB-|O*fr8#sfrvpcyD`5-^K4KysI7RD7xa7gro#TN(q$5i{mzc0$@qH43Q z>SQ%+!_A*AKJ`Eudm<2HPUDUhf$pZXGsJY3rFgFz@tdhB6QP^R?o^Yxof@J%UiH^> z+ILYX&ZaM1N(VN@o<)Hh>5~`Y=g>vj2rPy3ScoZjUl~re#Ch?z(?m{lQZkh~3bio? z-_cY5vY6qwlNIp&P4!mGa3=W0JR>5#WaKPNw?W;2xBZlSO7>qp>ggp=Cl7JD?P8C) zmptk}aMF3)9qrL9979Du0WSPG)7(_UU6@-2br^kZZR*KM%=ezbz$iw(^I|lVmC@qV z*KyQN-IzbFDyuik9sYcIfiwNS-oys=gnGf|{0yg4fGtTenGwHvfiE?`^m#h=S+;Y< zFU;CBd~3=j9hUc4e{<9LOEa!!{Wd*#HK9$kHu1MaIpQ7RXyXd($z})k0L7fQ^X*G-(ok$)gW4ehwM?0|> z)!IN3Oji1fOcT_=KN<4A;B%j~*C0G}cOKwO&JCyii1{7wln2{(r!!uDbB@TNPKaEj z&e()Z6Zu`kJYzS8f+i>L#bgLw{qf8DObZh-1zH4)sRUKjs!WVDmiMZHx?{f#%^ zm}<+Wvn(5)jKR)Q$D%Zz&lX`8trC0%GJac z>Zl9oLw4bq(`+ve$r(@|>f-C?-*VcBSI&L06c=kw6zMr2m~En;tjgct(}cpdtt>mj zgXn-_>xzCNFZopIUR$C3S9X?8ET9qKdj3`X&5p8;s;hd2b`Pzb{8q9&dGq8MUadgY zN-cIb`_Qy{6IHrYsVqsVCA#*xXjJfR{I}IVPx`$5+pOT|m?g35%aU&gg8%3QWCNa6 z8(;@6Bj4o(gs~FvY*LUn)x-Z=w+kNDD>+X`LXOD`tp{Bh+Jp4UkvFI%PpP!_2%mi= z$e}Osk5%R-c%U`Asm6Mo-yBc%P~JTzL8#?3i{5O+y5jp>$?a1YMfoqL5|#BMbn~0l z2r(F4MZk>}UES$oteZk4a);S(>Y`bM%JK?Yl5?UsO6rBEMJsXY{-h#J7`JT(RfCS_ zBu~#p>dHR+UM#Mj>1fj;^*otdKa>;vjp)yc$aVfIQD4`9xTlydys~T1d~7rGQIm9* zE!Yq4sokl(W2ZZfMY^L7Vam=jeD;mg(rC*4UdUiYz*)FkRTT@YkZBT*k(C5ze5d zDpWkdowtSEVskv2UHowW|0Rw6O`b23cMP~Nf$}#?VvT);)^;R7nEB5CSXk+N>QgFsRqqEW|{kM`)yPbsagLKrZ^=7Aw>Fnf(WRV81 z#d+^9aX0wP77j+653#p&o8V%-$7_#brZ=>s%HlMY`ZE|xZSYIxlMB^87;96wZ!U|H zDm%UPp6~l6{7^sHg7h>);pK70p^%-6`!X@?)7Q92lgbciprPi2$jwaop^2y=FW| zLt*3(FTDvUbIV**>(Lo!Pa$J z6>K=Pe#L@g@}~$J6zBej5s}H>4}16F^M>eEU!Hz#i#+1m=bK-%`k5gKBy>lUh0@s! zLwD4X?`0T1B65Lz{MfphXX!)wL0dVG@BbUFtxoDsG!S8Y#=FdDabM3yIe&$xb{gC4 z}O_gz5J(orGHg7)|WLo68e@YZu;WIxC|riGWxehP`aKoL;Yfkp!dA3%8O3U zMzPIlD~>wj>@KIJnd)57KRDrLG3nWr)fed85opF%LHyq%|3lyVyA8oRb_thGQl7A_ z~~Zbz3f%88Xn0QKfZeCUxTTT*sX1f z2JV!EO!o5mf@|WjVG4`%r6+k_R~be`nI0O)SS-{FPnxZ}im@pzFSc z-g5=!M^-W``72dJOY}ryXt$D&!Hw~J> zDK<5`ql{=f>FISHQxgS6L->%*sMpUiYdDWCqP=KBH!#6jg@Ww{pL8^R`UOtgt@eZ; z6j%NFDn|d|bhMq^J))mGN9JCt6RrDuB}zuLxhxA$So zB*gu*7q4M=CB-PG92=2PJQST&J~qveK%7(PjUS1-a2=0AVz@)4-<8=-9ZJnGLiBfTh*l~we)R7fkC`w>F4$^#|6hqy zdZKKsbHm;prV_w_gN}<&y*jxaR}BPbRP<-;Q#vayMD(IZpwzWh_4Pk$h`y%U>9Ojp zzX#UI81Xi?sU7s~sEPUNo3GzS*wl30j{csU(#}@2`|Wog?SE+e7s?0H!$*)(-NhZW z97beHxrfv2S4dKA*_$shi>MX*;gh{1){5=)#THJ%b2PR2smZg$W*TDf>NCae2cgx6 zf%20mK(!d^{3LzV5kB-KYSAxtxpG7v=O<_o`Aue~x2a`6Jr7RQ7Vf0RI1M^Orkv#5 zkugpo)f66l89u8Oa*R_0weV)KMjj>;;4=HA1f~bGwynCjTKNg4DB-4m}%f&#;kobw3CvKaiceeFqZj@Wv=zZgM zE!(zSUp;-bKQbLopVTZ0){l&RljhUK$PO`=zEp_*{AI^S{dq}rp>IFw>|zfSk3ztB7wG8IcS32q(;;SW%f_FWdE%p{V;HfqTPiZN%?Ur5h zQL#*4gbtR)W?{;+USGB4O?CQ=?qVmkR3iJGe47u~XhPWE9r<3?z*g!9k>UjZPH7U3 zzvr;GM;ZAVy21|G8n@t0p8u+(J;j>%)DA`MdIx_kUJUfHg+m_Ocp)=Ig+Mw!`;xMaY9Ld~pP0RDrdBv(FH_5<)Qe$vTw;=i z`UW56eD<$7ZC0DYUcoE$+&m_sPVt$f7u|8U^kagO#a58hm=uMm7IK)%A#ReTcb{qQ zW0c$(P~9{}y>yNw^%xz&RCBogg34wSG|Et!k|+2uYT0vScGovc95}^JN7GE!gcV!Q zUuGnKRm;dxK`m#Z2@OtrP=A^^e4DF!;bEPqkN9VE>Zv7Fnm*2=r z)5pqo^3Os%E-2ZoJTBVSh)QiCZ8dP55j#m5SE!{mLdk zmESRRS09-K_F$?zP`u{pc*>tgczHwZe4dMDd}@;UTza+<$7EdejfdemoU_;YT*pus z1k?!fhI-q>&OUqEy6FZUj_%-c=(Xw@V^xmC)h)t7nj%tSAy6IlasgP7)f};_$Q>3 zW3~s|nf$?#=5%a`ogCZOu8OT=U&P)teS+7F@_*+$F3R?17_6CY?8+O6dgvOqI*p(Q zpyy!oF$#UcIJ7PQn5O9WOWOYQk!9H8Cd4n6lfQeLjVBYcV_S=>Yk^q``??gr$x*fp zTCtJPeox{Bwd}ld%G8{LBUK)ZTqT|JXq&pjZ{BROs9t6g*|XDBTYZ>pl8+*nt^m1k zJf6`dPDMG`sUz#CuJHIXsxTSLY%i-bQm!RQHu}5sL{>yM-PCk|{9ccVcu{JG=XfVR zS<+3RH^1->is`|Jc1W-VjMHlPcy;2|X2_KgIP_Uw_P4 zuzAhWwF^nTr&)6oMzzl$S8=;)})(I zeWh|c7oeDoF&AN8EH-!H2}a`hoP{^KyPayba0}&T9vO#COh5L50o&T#hokWx-*hYL zw0v?AZ}>1+jbFV}_P^NkHhFBM?H7C6-V8>vogE7k&ZP^r;t;!_O3n!~U>2j%Tt;&D zI=0mL0$t6tkbu>J^mc};;7qrt*6NLls3|+~7BU?~;=%S0l&90hah@B^`+Y;VF`@dS z{@1UUK0mlAVG00y;AAdkC*X5kjroVF= zFVqhEt9wfv4lI%nLZ-^naUh3dH_d?$5g6b!|`RBAe@$|y=|<9j}028bQZ+qD@=PhFi1A$%nwB|qUC zbk|4tO&s8vjx?>{)^z35iNmCz3+|YT<}f#|^{$8){&;mwk8=8&qfR(#x%MPUH)9&| zn(yU}Mw#m8*TR42?e$iBUC@HxrwX3UjwGDDS{Wv(Rh>5?jvI*r=Qg^OEvBcMgb(@_ z9a9?BjJK?>x$M+tGQ1X@MF#R<8^ff|PHlajw1;@6pig3?7ah#^_2ZZI@4CO4e#_q2 zc4*;&`fJ9m-mvn{s(OdI9Ig4{@|&iih2y14YEle~A1Bec&A1>& z7VIWb%SY=LTqf>*Di?XbfUuJfuFqOE? zEISvjr!{aE>zFk%F?`vDdIFwzxZn62lCjkq%v5+5%DabRv6F<%uKEUQlb@OGcQ$p- zFAfiQzFeb2#ZEel6kaynC^pis^6isu^o{*TY)@1RYefPLKU^Er#v~)R?YUmhTeI@%R3uY4Lp{Asd#uSlc)|5FzP`P3!{I?Xx`urEvq8n5j95ja|thF zesqAp;TXAZd-JRXQ6>*n?cm^aSMOw5l|vdB>|NwZrgt+(^|My;ZK3musc1u`RpJd{gk|zZ>?t^!qDs_IwURb&nqKxmr}&sNG+B z$3&vL{!86~ERc%X;uUn?K{3*)q29;{=)w>Eo_G)Z|4e{rF=Z5)@MRz~5GguFE zQ(5D@XWNn3`B}ba$96c7!ww0An>X$l{jb}>M7t%$E;mB0bc#C;b$lyvOeM8PAv?Av zQA5}T{s;ZhORi^o7yJ)iDee0WU?MfAN}X$`lRLDL9FgOGT>ZxT=Fj&Ul7RG0kMjR8 z>)4P^=2=0Jh1;X2tpMd=zZs6oyacdxfs|Q-j4emhM&K)cJxFs$Jwf8D?8e_(s~7i6m5+?+k^T{R39;eA+? zW0}eZ#C&?p$094|*hLst%jF#%haukRWatXm%xNUTR0lL z6Z0-_+wZG(vO_U%eae>4KlC2J3883P0 zpzybG$HcoI_e4Z_ceSgu&@uiRe;D1!F!;OQm=IMk$x+H@cJiusZnVwjLQF@ZSJ}yO>K7=l~I81=V!QDEv*+j%P#(gy8l~wp6a$@bnFt*(~Cw``GQYtplD8) zQ=9$38TO;=*oB7TL0L#g_e`E9Kde0|i38+l-jm{JO8`*G&d#q+Pq(4F)&142?H+fQ zIY&s3BUeXmlfz zT5~5(LKS_=|3$ifxSZ}y5mUVTB%}UdJ#Uri@7*=&yo92j=gKHg$t`}YO{EK(;@n4B zVZm0`v;3CkXZCgB!c$L019z?X(|uucx#jIuXM_30X=8SwJ&9og_XdZ^A9#M6m<%NA zjF1^{fVXCTPd8(a!cmG(T_2xkHIywt5q4-tpqUjTle8Z~;S zD-OGxoh4!C!doOAkz!ExnYoXbyIARb!&OZdH@@9uRoRba>ZH7rEXlWQ(F@<-eU;~P z-N;Pe5=WQ+Vxw9|-TAaIrqtW^|PU^&M`C zv0{R5ZHMb$L{Gkpg7PEIthDR`dx|Bb@+21R=$f9Xji$QOTmJ-y^lx=4_)^vhwwHCi zNurv*V?5K_kG7A3RYYE|jQBy9VT!`EN_DY=-Q2cbNI%;@4s7K(yKHn|0Q#LY)Z?T5 z^tPTJ50!lzJ=Y5qNI+H)df9SpP zmV3W@d%SvHAODMYS?AI9=#WS#ok!XvTwoXe)UJJOsYmFJ23+Aem}%Aei6@}^UnEU=CCxGKnmcO_Jb zraTj*cJl6A!h`%!N;I%>)bF;8{KiH%x9*Haud7VWy}8f-BHH@HQ8VsQH_*9^vMr$? zA7hHq18?z2ast-)EAZbJ6I*mfI7y2*MaIcqq#1^DKevJZ@}3>YS3E&a@o6kDk5QHm zNnrKlWL|uj z*~>F(!I_W}6SD0dNS0b=*;tIB?~h_DIEzhGl-Px9d5S;B4)EWJW`0%`I|^b2xv>HMWWT*=r=;eyw4b#z@=x>mFM! zb`KorB1|YXywcvhJ&SEUmD|#tpzqveyE#e8IVvv_K?nMUv!gwE8$-~f*0u4OV$P>; ze?oR)2j@Rd(xPgJQ`x!UByw-#eJkj6Bn@eeIw{97Q;SkG-k6* z#V)11=^-bW1nL+R-@ldawku`M{rwhTToVO~WN<&&W= zY$k^Af5&>4ZA&i|PS)kTK}%7X?&9}5jhcJ9ZjJkXooManlg<23^ws-R0o~j=p!Yku z%|U0GDemO4nd#oA(dTU;y|6qkzLkC)73n`GyR44fN*!2~{mw`Giau_O{6xQ4q_LNF@& z%O9NKs<2aon{KzWTUK(4iZ6V}bwwr8FYlAQGEy(H*WebEgT`804kT0h2XkK@@%zeW z-c|9~Yc68_GU8t|lFw@{`|y!sm^)A04lI@Z0fsA9laG^0#yLYRVR53 z;^WU$FO|uM=}1TP0OxiiQyLcDLej1u<7vBrE+ZrQx_ta*3d6C^X;bj_zEg^f`kE+< zo}#-?Lw->^Kg=2HFLnm|@tizA4Ro34&tr|Vk>0hk-cKdd z$V`WbeUCGB7N7hDIgljI^5(VtSN|!iYAIv=6QTsumF+q;DxR(ALZ!1v&vts6*6vVy z&rQV!r5k(RSn-)DBp*pZb7n2dbJ#W&_Y{C7(3DgaYW}Qm+c`l`sZkHekTX&f*jBP z+M9Xg8J);9(g~Sd|6s;J%gb$&K#y#R1}Uvwi*h3e&**ljY5~NZ)nXJI z(u<-tZ_F!wnO(=8?~_Z^D5FqVJ!U4lkX`mHoKL4ra_61t=p?Y8R0Yxl{V+!7ujU*N@)*sJ7ATUnY4u z|Hk5f`yP!sv}oJlE%|puZ+m`5oNf5>!mF0*qx&TO-b5J_d`Va@VrfLv2tRyAM5pkm z(B^SIs~jrE>@)*VvcBZ!P6AOQkw#~yA=IHqz5#u@9cMso<~_OTk6wu4aw2qrk!+c( znvJ-I2JjTG|GsJ9Il3fQ`{h)?f1o1#X3l=UyOYWYr<-W3j;b#*ySq?kbhFCiY6sMc zk>q?95ih*Oc4BOHyYyQHyDqkfSl|`FsW(z4WZv_c#0s~X= zt-NHBB*3}`zA2uNxlmuOP)D5CP7L+vefOl(fvm}zF3eze zB7Wkgcu~v49ot7{$2wFY>7diTFw5EKjdMEKfE#09IR!-#)P1GZUy#V|`OW3;UK@EC zUH?=3_&qo^-h0c@Q8cnQ*{Y7mt)9@7hCaCxwND2)h%vDKpzOl{9LlU~f{o$sY~`OM zz3X4`yFX5i^uIZ&{6F1#-g9SHup=IlqH?y5ga(z4n^ef{(zChcMS7VMW+;2|mvSiS z7FXB?6+}VnFafQ_)0fy;s%pENRY&(dY@gfsYQIk~H#04EZ9Q^*pX+8?LgRSixAVXF zpS%R5?Nmc8o!->eGuc#?XNSB;U7??Q1WoEY=ROAti{&!D9jyx5x2gr&kP7xI^R2PA zjP1eAdPVmYQ&7`(BVTZWo{SDP9KBl@v$`K}hwkCTIL1Wk5Dfb6wnE57;fC#US0sE9 zzhuUcOs`AS_~~}t-3^8|o7*Bs^&CH^$owtSU4NB#{!P7CAKq_$GdU*v=i*~HHu zdV9w|4pX5YnVOr?GmTNp0;FgLlE@Qoa?Y9NdK_D{!QwiO<>F=tJE)>2h1iceRr<}z z`I?4mKPCP57~Ejpn0=;%seQ|yMPac`Rk0V;JzTq+P<+x++7rg-emfd0=68vG8ypjR zEZ8#mvzNAv**o({Xe=t{L@AQ=$z7L$fgLdx(W8#n-BT6ujcq>zJqo$JO$gYkno1*}^Y>qK0YOUVbe6Y@k$anIj zn5l}u1ewDG{j{l}JYC=EqkrbUsNjZpyPeU&cFs~yIKD2Wp0OqCjY^}rm`CPK8`7sP z)7_`RuM~#b_z}C#bf{`?a0~Yrt=W4tf}r2iU+(AjYWg>VpZyJ9V>4POM0LGT{ti)N zl|oI6ujFrMm{ZDWtcs{s@)Zt(TX@Q1p{%Dj73>^nOKDAe5_}5aOliPepoJcWo42EE z1pT?Ui4RpG1U+RIGSFhgJ-Yh45IG{`9`^kY`MjQ*wQwAQc+38=o?XVI_LJ_1N^+lg z=9d$#{P8ARa8K}a)W=U3o_u}u_qousjkk2$ELZGW9=)~FwwN1n|C`M2$|OCVJY}-B z$s5M&7;jG8nc>aiUyn!_aUpz$Q-oV4tvO)I(tUJfgRsr+^ULdncs^f~tg{>6=0{y! z=Qgj%`rAQ*=|#JM-A+mRiue9`c^l964u1rj>bqz*ZaSOIUbluh=KiPaxuIr)Q`T-m z@z?_j@o`nry{~?OXHyq-OmSNk4O%L*6jEoK;AuZWuoMK)e&&57`u&*PqbV&*=>MsgCulp=Ij=ujq?9XXZHx#D6%>MPNke z{?JQ_ZY0c@es;Qo`IhH7UVLJ)Ma4c9t&<~nj(G`RB%J9T_BMQs{*d=W`S;sD^^M#Y zz4&A1*fY_&?9*U6SBXPRa}x#T*h}s`-8ittKkoMRHmZif-J(-)sD0o~W?wj5-$QfU z1h-czdh9v2IN$wdYDM-G5S?n-GbXc*(N*m>eIEtz5%GtJ#Uh$yu8a^cM=(?0t;Wek zPHq*)9j>Bb&ee4OV!QHv|KAEtXel%n1@$RC&o76||6e~$ukbhH53b7zy5AJzJL_r( zvpGq_tlQxHOs#6b8vacfXSRLr#2Bk4uuCdy=Ci+k2b1@e-v>U%QqfGGl0Tcl=qL87 z8MdK{A{C<{dzmZD7TV!kh(m>NPz^($JB0M9KV)a_>0F#@Ysp%DsY{8Kr+47hi!&T^6lE9(p$t13peGI3kA*%E(W}-@OGNMCj?cAs0zphg_v-Ct&Meml` z^j2KQBkdSH&pgp7NL{RGPLcf%MMN~=jfB1q=l+WNO^sj{bkOOihSNK3V8b&}_BTmH z3ccO*_y5#u{G0wjeazp+dH4^c^f^%Oe?r6Z5?50h^$ap#xT=kQGS)6) z`WZ^jXQWil^bh%Cf;Idkv5&pO-xhhNV<&inz1!X}{kNakp2OeT7w33#S%4Gnrj_g> zx2Q{WMkT1or$aqj?O&C~i&QJTQm*mx1(co|Xl!Z*cG{NiMOgWNu@ATb=XN9H+QQ5@ zwu#mJbOq>H;^M$dD7wq)DB{{OO|0-;D{t?suO`2f0pjdHo!yPE`@1jvobFfrDy85? zOoKo39FI#j^U5?qad?8+=VX$6CNQa=BuiVv&pj74*)|Am3v38;>KL<9-!c#U-KLbk z-(>Q?n%ihYqje$j5;D`-7Qi=WnVna2is5hTk7nfpJJNG<3bopBJh>}zW_H7qy$UtJ zQrv2@^)6hwt4MIlqAZ%PA51}axS{7ANL%h@*k z$j`hM{eF7;2u;K&krTp@;)KpZQtLu99_8FMGoJb67^nw%R2EeO^6Y&X-#H<#L7tq5 z(rB={8^R6mspfuB=U2a*bI_lSduOBbhZ*g>MJ@I- zZh&&?G`!485WJoQH})f77c5J>oo@tXyU~??NU~nzP}!Gfh=; zeYF5y&T}>jRYXy$;ieE+ci`fvNew_Q5+2(hm=RtxXV6>h=jOQwwWkV9lnAz_q_n9= z>Wtdu7ID4?W;>-rPCLs2C!8Yg1m~c7ic|cgJR%zKJY7b=bD6ZNO}f2QCQ42*1Jx2d z)1P!ZN9YkwJioBh%xkXh27i%PgQ-OouNq|9Po}@lNE*pf@@FgJc0Gl1OQ8fj0}~}D zERsb0^Gv9?7xLsa*1z$_UKAN|BF1N{7DAp!TD?@Y(lP3z&g^W%JF>zSCU@?&x`I9_ zh~qYvNrh&T=bdyU6!(y8ou=$fPobSZ zig)E#v&59u?ag2CwKn_7nSqvp!(SIK?Qfi&huOiDWpg@3gyS0j7j2{?<5;DBWfz@; z{aFb0QjE^S45K{V2AXGmlnqBFPN+pp#GjDuyb>mUCQ;lOKz2qVSmA~3ZhH@1bSj>a zqUtKT%7dKpv*kKi$Yn%Zb;KOyo>(iSu3+Qq%H{|>H57k1UQXfTy9NWSjA=xYz_nl! z`s%~}7;hY>YC(2(x4q2by!Rh!^av-p9*%+~y>pZK%5vBoOPkm7V2NdQ(yB0gzN+=UM(+{@dl=>js@}~$&_N2BJDU(0oBTo*+=A7)P>>Dm1Sqd0g8!eCRFo4U6l%zyXzVJX0;nsZ;O1{|a>< zbxBR*yLEEvl1`LYUw!Zf;g(u0GLU=z8fAP4J#IaIuZ2}RwjMi76Il~|Qblz3iO>q~ zAW7vONpM@_D{q>t>|c^U=vr!(`AA+`LWg8Sb(CIW8#|zIITP*f9NQD+Y#DqIZTuGI zvbPMU$?y7!m)y*Twt9@8xDvkl;={#gZ7ZjG5$jmH_H(Kzjv4NO;cNk3F=bX=a!>#BlZO%3y!$(g}fqHZvs z_!vwfDv;&#MyC*!+3`Ssr#kjXYFY`8ye{=nnAt4O>+y0Dj`|XMruyWsS4aIBZ1rp4 zjKcmd=50tt5#hhEKWp95o70)qt3{g_+ ze`Nz(O{V9WUZ6hPPR=mf7#cxW<=B!kH7fPxbeXz9cHO>u z0=uAz>X{!#4%1L8bvJmix2STz>LFs3X^l3%y&NTium!sF#%9LZUR!ivw!755iqB;; zePWoXASKo6Qk}&d_7gzS9f$vMIz-d7co8)&4{3K$8()xJ+3oeipI3*3+x%iMYV>Mw zAsu_oe$c&8e%@w_mB&E8L%XxlU+stZ)AV$2k@?MQW7m7v(M)F+jr2rO9(~GlQfcxq zFDXjuUn(*c@3W^^Z;Glv%udzBXjKHtVkpwz}NK|F&> z8r`#e27#if?TN==Cwjf(DucO25=9~BjvnlMgjSl>{NQvqHMs>+aC^??4M@TM;5;sc z3^?g(_)X|NI-wrPz#g`{n1`R>5qWzZ;ffxHJK);+Vm67FL)nlnpv#K=o(jup@(cQx z;x<3i#V+inQmQBBf=Xu_IVWvjH0Q~j_Dm^{IT^$n7s?;=lojqBsD$sZk6*|>DX+NTO%zK!AwPNRaivaGb@}X~Uro|zcJe0q&_-*D_4XipoZ9HJ z5~yCz1y$B1dBIHz6TPhaS`~CJlELwhsKsuoAgOMpxaVd;-WkZ9aEtr-XY`{pNXA_y z!`T_6)J>Ru)s{K^veNS+sfGG5vAt-=dj0KVH1v`FXIM`WFnj+amAMt?$60Q)Qq=E< zQ2cji&(KmQb`H@MWx{LM+3*&dNzTuvD^8bM&I1zxhh;4)047ju$=>34+b`0pJ>oqi zrE;<-IlQ~Mi|61xNP!opB9q>BXrHEWhUTHGuWvrf6Q+ez+%907StYQ@77U!S4cs)m zyIoM%P7u#bdGTJ4VYgsAw&^b-9{1yG z)0s`|XDaS$(1UW}2dgQXu~k8Hz*oqs-heDW=I=voO3i}i92+a6SiM7A!!$Q;$i6S1pD_;-CJ0wj5pc+6hg1|gq~qCJA~f+S&1(ozAeG_CBXLiJa79%Jo;(f zC3d`s z$zB!3#3^#=7ouk=AqL}Q8$+627|f4`a+WRv(UcocVtE%CM&ZV}}UC%)iZ8<9*>+>*fACofmwvuj>q9x+Lqh{GWlbI~>ODmuomI2jV*+&+LC>XzJs`d~LM z`>7~cQ{(TQE$8ZaYOFTw&o-#Iq{B2bBd8mTqbKh#zw*X?GxNC{-m{B7fo|Y4)z3gN z2{r2|2fc%nRxd&u*TWfzlkFa#M@3oN9j%r*xk+!#=u8rmnd~p+U7iIAU@mNieWopS z#439Nj_ZE3@j39OWw2jl7EzYTZdNy+{K4HN0#0gtPrI0_*0aOR1vNT zCNjNP%$pplX6Qp|gh5-*y<7w4==W5CSxh#w;E}9pC$ML`W;XeyZAZ?{zk``XgWw)< zI2a{5dox6Nl#|cesfxzXU;#Sfs>c6sj7u?&P;1A zpZQ&T4nEp&r-ZnUn_sB~csuW*t_;V+ob~(kLe7)De!|Fg(`1~xbbb%z&?8^kVdo*^l1^j_1$Jpt_TF5`>|V)Z^#7JB7%c44#7@N^d6@bO2I^E;G#c)bkhDDtK|irU_fVvJXs2~SVaOYasN zsQS*M^1jLrXM{{{Z_7%&i5K*B>dPCrPa>&mtFa4+CFc%@6z6g%JxCq&CqwZ%-iDJm z$kbQApp+kE2FajlAzRa<4#5qR4>kNuoGWkG4R%5+H<4O(hwfyPw zKOcr4@A3+NfXV1*g0_%?xnwmHt>>DpHiEA)!iKA8n^H9_AX?*unbx$)#M{&ykMLpJwYAwEOY)bKSWQY0r4d3^55rS)K55()8^wMAO z*mi`3FcuYl3aF2T*fd7zEbzxqc`;tlyXF0lqqBgzs!GG~j`M>8(nxnohlDiJ2m;aw zNP~ogba!`mr*yY;qo8zmOZPoH=3&+>W@e2J^51*T-e0`$^PV$9Ns^ml9h{hUOhjrZ zG}LT(`l=-KEpyC0vB(@n(-h$uI0>6D5B$Um)XP(3EFA}0P43_abu;KJ!(fn@5L|~( zxdvzI3uZ*~L}PrN1DJ@OM3cw{R3zi(-^b+QOL$i0onz9IM|{mwHNq4YHcT!0gyg@6 zRmh8L4PmAQe8_2aj$L0anN3jV`yINEFA!Wu zZStSJpl+CB@>uvtqz|i!bYVjAcbHS;w0-g2Tw_aE5{1=ddaz6ITB0kjlBn(~CiL62 zsFY&B_lqrt%m3Um$H{$^ZB$;o z=;!E7H!!)n?Y|RU@tbGoR=dr!pV{|2Y-4C9 zFPSn$HtNpwkTH=1wHj zqx{){dwML|=9Bu4NR4Cvk{k#RpfDPeJN_yj#vg5FAD?~Mw{$P_(RyvdSw9#Tbu;yg za{2CL*cuB$7k=5)z1WLaoPAL&t2!Rozg9@1$i2y(jx!2(|- z2uS}jIQ*0FzNeD8>|tNh)OE?3)*d4H><_y;9OD{?S$qk&QMKv1R)ht8ZkVqZ%@RM? zmL#t)CcL`_RB5l#cgKNi?BJ1=b3frr;?Ln)2mSn?K~8Z#SR_^jciG^46gyy=HJ2S_ zC3tGv{Bw5W52Z)rUt5%B|K{L=j1i6T&h&?jIhRx9ANec{NjwSER@l0`Z8P2Q4YCn4HAwCfm>}kKw?Lp!H&elS~yUewvYaMU<`kQv8ufSYj57e7%J~4gMGf0hh zd$BT!wJzzN&e zm$egJdQz?z*{OD(TgoRF$ITXpIdk9oVf+*s(NCNpVSJhIi+ix6T)>oQx8KaRFs_a( z4nT?-gVXjGG1N~cO(&#I`w2DEWShbtGUTi=E&a#LBd@uUy@^ur4Eoy?B&K&T6UiZr zC)SxZ_^(IeLHI>Hq65S??klSuXnD%{f@&?j{&mtPYEea%wRd0uJuuD4CWtO$*b#KX zzw^#7*CRzkokAXCf|-wQ;BVPfEQ6_aN}hvUhk}kwnSHoEPBV#ELCK1Uc698xx>rwnap%YWky#VCEt2n z9FNIx+Qyka0xiiP(^l>ZYs!2fwRGgFZxRuHXr!%c6+XssRmBc*|G@=%Y?9%`oGKH! z->B^Vri(d_`{BLoA;!B-5K?wSXg|r!x_oezq~Ozbt3HjBg#2n%*lwi9`Azn=McGP{ zt>EV~&u)&!q&A;XM=IC(Owcz&FuPBe$Wt#f(UI+??~cYP7lX605Z<_J>@;87+WZ`U zp&BWvPx8$@@E1{-F49pxI~u{hsuk1LNp7il?)H+FnT@$H0&783()aekta>I_P)yS}kbi(i%$I=-`O?m2V6_$q-pERMmXcpXl|OT89$K`NX; zW1z)9MNM8Dh35)(8l`#OPT;@?2CibtX#d^Fcm+ORZB< zuBS$;16}1dw7p;LF*({K){yvhPn4R>5LF3!ofYgB`!G%MzHeCD{}j%1=fa0}2PvW> zY+zHm@5rfZZF~4%Y*EMs55;QJM-CtlZwq~94zzUJ?OSxF+2m&Kz*PAES2IoiNHW5y zV6OWv>Xt7N6<3>^~aGY_86wczxrpFTyG)w{s2jgv+0ty`@y7mRG||w zoSXlKF;!IfL>`YUmf6E&wC7vOBYz*K8J%8QB2byLW9HlzI39pfG-0U7&m|BPKy9f*gQ%@#c7U6_d$ zgk~|9P1p!k#91bE(IvEhp3vjmX1lmqcba3aDyQ8FlNg?1W!DOQYHr`u!_V|-(UuJ1 z+}()Ibb;srFX=yWcv{J5sJWm?%O&zS`ux+d>X`UKiE4?*>#SdDEzXozcAfnaZ{7%= zpWjVVPSq%M9j#pvSB|-OBh!R)KCfJD#=_d|fwL|vOrzzZAIi8hs;D2W?&IsfWyaCP zw}vg%!2Jwcp}c%$-m8Q*W*}ViV6!hD%m&RQ;=l$6*lUy3Elex@f+0_1r7iSFmisSIAD^ua`LVa}vB0?c@ z%LcR4zwKkn!Q5_reVNajZ8O&$49_qJO=wNH+ptA8mu-97&*f*+)`GV<3rx^?D!*-@ z=YIB3!5qxTZt{}z%nR50vihw5CrB@T5%@4XIh#~AeT%OQYgCPO8?%M^@tZ31eEAf-gMm{kG%hP;0RWLVKhXq z7kzXRyhx|vi8v@hKZ^0pPM4xfG4h0%s5)^+oFq@`Jx=@NWJGnOvy2T%AuoK0+stb( zKs3DIXYdC73o|&6*sCUr1p2JFs?ZX`!D|e6@`|`Fno<*AgDX3Uy~cLGi+Soef0Nzp zM7p`mzAAd(ac~JrP_ccYavLLOvLnsrH#6~{AXDJrNeT7*nGz_||00!zp}Cqaf1svr zi66DOxXbSK6CQ{bx~D6sCb`z)kX!GpYXBF3*^>Cm9+ZcmiXMT|b=Eh4J2c#R7|S=* zyYQ2G8%C+-W`aV8)ty_t8 zY+FW0-IF7uHmi9-6+KiB&{I?aeV2J@51C$+l_}8g%rcu~Qd36#!>`xOKx44`x<*Cc zOnhg?FbQuUBt)(Erl)%bgKLWGWFENK=B+Dbp1U!G9Nl6+@r+%}A@c?jc_saaxvRgo z4fRA^mir-Hb>g1b&rSFg<#%Pd*r!m%#Cs~875ZEGOdpb)bUmu>^XjsUgYG|MPP-Zc zc@=emw6FrqYSTd(OG`@WN0h21R0eaMceXP5VxjmZe2F`%qP!jskw@|Sy|vwBQJgv1 znZdOX?cp>fk;8Dv#$|%}3_5+3Pv+y3gPM_@a0)ULOZtc5F#j^V<*S=?LfJx431dUm zI>=|*5N>>F2(Amr9d9fy+Ad5E`r-Slr+YD>sOtNu>>@Lr&{YU2H{Dlk}N^ErHK?yX|XyOUF6-9|Y%lkRUE^XRkm`|tr|3>eWTn07RS zF_;7PPhD=->Et`Efdjk+XZu!P#|-mdm@iO8k z)8l@msx7SY$eXGf^s`@ZO)Yl6swHT_XY)OLLPya<6?YT$uQp@Q$HooFRb~Uw6;|3d z*ZUqXGg(3%YlcV8#o{t=Uq>1O6+f=r;>u1WLj4mR!m<+NDTKySrCW^CA z{7M(SO2s2sNFoNzuLN)$zLeciPF{rgvqlZT+Z>8rP_x^~ z*VL)M`ekgRuE|yY1}F7AraGRPtRIO_sICpht{A z8TgebuPe~auW+4%k&av-KU<$J#7z( zjBY=Zqy3O4j-r`OYfH<}%#u58N6w9F(BW&cnT#ubj;btnV|AQ$81xye>d1G53@PNSZI}_Bgx56xG%~X#~ztUHg(xV6a@`^ zcC>Eg%_`U5%;a{yNY4F0f65#Z%S{YA>0XlQ=~GR z#g=fqs1-I6F~Tk)Ii1(murT|)nY`_r>64E#)qjpEBO>pL1NiPravHSbTj|AZ+SjE- zv%KHkAyu`zDL_@1!tFDeNSsveprjvhhuMlA z(HXhz&O@^p%x>-$74LR&o{h}+?wu=OE8$?zf|higYh&~Jr0y^~unO$Fr-@ALK}K*w z*R;)`M{Zyr*pjn(Ig|a8{wu1b7{P3DGngzgM5QPFvIdi-@z9hW<50R}yP$&}$0lz? z*hKslt`=GF`n5ElTyHbR-X@pzk{us@OS;!pa$qW}DYm-)jt*~+$flpkB5WuB6Y+I? z@kYI1Puf7#mFaM%-ektyUKPZ9Hka!DmZ>3^*r`<1`NaZXR6Ih%b%=Vl1r)JMbcfmb zds|Vv&NTCULUYqbGtEpVQz9&9J4R-@N3b5AeG28q$W`?!{6 zf**aCps?={%yJ2W%l3O+kyB3ED8uvYm8=O}w=`=;J+Z#11lp zzJ_Z~E2A#S?5eX|&3yDQx`~GBFM4)vH#C8-RVTDe^*H%6Ip{19gfyH+iIhdYGE&&nz_vb@nuwLzfY!RDa)sKVcPg zP_sCzFGH3*uUhE*C`@0A40MYT6gPt+u-!i9?u=fxWM4 zx?H-Of1u7W+bzYmZy|i`1wkm1M8%ddqteQWK^7da>7*bn%q(MxyLSNuSIiJog9 zbMe)1aa)jm%4`bVM^{l9eSHe{g(v+r>c(8`){l|e*UWDU*Z3d8p-g0_`x9XSoZH{x z8)}HEmGmngrH8?TIL+o2N;z37jeIU~icF|&(D_%RXz1+*Koa}Pt+OZXBzusb=mT`I zcJ2bY`*GAGL+xW&slDZ3b^(d(J1C^-Rd(N>P1t*N$`3{PJ6{*X=e(8k^P}*1?7Hy( zs=;RMZ!^}tL_^;PetsFZ$!~`c0!#uNThIov1QGw zY=!8v#C>Czka{1T zxx^>a6sN^B5z9?x?c2bo7@ zV0N3I^ogvHBBE0-|H$klB~|lS_n1G!ZKlvo;bF!`e;$K=wG}t)Tl8>%V{5$(HV^lD%$5%UG4RKKO4R89N9zG7e`Qb{t5poHy-qfqO18r{7XgG z6jva;Rr9lJ7EW}FB8%Oc$TasM@`uY2-gEWCqUfAv_;zMF6V$4HJ8ZsF(DfdnkXNDdJJ>hDdSN$2}P*G8RO~Lrq+7chTXD;-39o3}73O8%NMB9Pyj&8gU-_acrgeM(1S$IV7!Us`QpY82>VnT~E&rJx z-PKxZCn0A;Rw#}NEd%P?p76HMvX6+xG||a&5CN8v#xYZN!!dZsIUap%Y-*&vct z%@6Cs@u`gdrL%g=++ic<{37xv+Nk`fp%0PV7!&qwKPJ!%;H$@1Px$M_RRWz;E~C?l z%ab`q#-u_@#b;Lpb$NL|(-(rBn3#I`GM(RP*FZRzK>h~T?-hxm9hf?t6qDpLZY=g- zqz?>W5_Vf0qk@*yOU_kLA8ikH(N0qL-7yqQ#r0n@BX#JoV5+{Ma|A8ap`fxz9yP(m zjGAkE2S3@?%q}yLM)91R^DV@_=1}$0d6ckz6C9Zz^gLf#-|@H9FQTBz3um?obB_*8 zhoS{d#LvM1F*0}{<^-AKv>>C57bL()ZUkEMb*aURxO#Kr(t@Z3L4EZ(RB!`+2!FKQ`*GEjn*CJ{%-bQ|N9f8vcf!A7Yl-+y$-|J}$IY{X`FAiMc9b|`OKG2Zs?y~SIS z*vCT=HIuGwv#9T?h@a^j>cA}-!M-RR+WqX}p*vYt-{ajIYrRPu) zr6$Mo8+Y2agnTr><-q+CA79c>Y&UnqI{%S*Ln>R(|7q&@G_Wl4x|mF_G!;@2-p@n6 z5RT!2Jad1EFQ8NvkiA6{x!sSGiYu>{caQWY-_#Fri9F#4!6bzk=^$8|FZwcPh!r0qAAlL z3q`k)=pA(9R~eBgxa`O1{Qd(@)4Av(=BhT3e_k`2`^7KjDS0ZgGr3RZ7x{GDA|-f} ztKy0*i4Qg-1gU54rCp95A&=`|$y4Lj?Q1(T*(%BA8(*NGLv>g~ zGObII%auV@3+}4dx~NV}|9G5@O%<|CPcRi2Y@eu|+y&*#ZC%-%)1%C<+MSwo(4nX|&WP{X zN`6F_It#k=X}r^%qxc$H*rj4G6FY5((l-}lx*v~yBhF2=k94Xfasr)gVHI2L!QGKk z4wRk61-$t;)h>TZ--Dx7PaM?$3HHBuSx2GbzK+hcDm`!wl)m{ySEk?f>Eyb&LoS-1 zgsW;Ap0BecwiWOmUX0amNP`pTX?o*c+RJAT4jWF zQbvszP4pD;1`g&n$iU;EtY?%j^)A0SSmrhamu!t-to>IXwH5Vnw@vM3`+1M5YpsMM z3#I28+{Jb1>h?nGx*;?B=&CY%T0tE@j7{a7;2vE2Eb?+NQzi-McjI7L)Ic+Ib(!PQlcU}%$-f}Z6@j}j zRsAPNsH5^9*-YjTTg6Ow7f)A8A2KnBvb|j%+ubF{hx>-v>_(=si_KYo&ZLv5_f#$0 zNnf!0^<|PQ-?GPeM8;Kf>WH!^$6iotEfX=A-QQp;fP&7whKidON^t@Dnxkb(4?mLZY z;=bD}=93QA)(JPsbti>q7xm_0_)katul@&8MZXkHcv6zM7|g}bQCZe<4cT~|lcSgr z=fV@&3+gKyW%3=1kP3dn{7v@OJSGk+>o_W;yWUG;F(9@xnXn^AFFHuGux!G`Ajt^Z5yE7^BjfKRbV>muDAl@FrQvvcfrB^ z$E{{pJJH88yTs!#y=rF~>4vsi(8jH1Bik(~FJ8h%ZvdI=gG!_Bsh21RyWs110$ZsK ziC#~bOjlud@`OFdL#JH~Hg9EoW82*SV7v2kR`Z4I19(fl-8EYRlI?Iel5NIEZqMbA zEe4|?D=X(n_@(lcY^JWscl7o{am{@|6B#4;!Ee*T-Na?`K#nv4r2R&8pYb46TV`%) z$>Ay~I;fJOG`|*5vERdy+E4r-_wY8ik=xZzDhVpZSo#F2pI&HK3aXR7F*Kkg`nkQO z&)`UXXB!9k+^@QkR!gDca}LbjbCoKUVO54wvoZJ^noR(0;FleqrnimzR9JIQ;CbnT`!* zZkitVdU~iSljIAvQIyu3d}e)zc}XAKULDzp>~u+04IiQlK1*FxM&?n>59n*Is##3A zKca<*PQ_fnZiXTn+fU|h>gZx}Gd-}~RBYEQXyFP+eQ@W3jlQU^#h!RSF2!wlSiTd@ zNtDb&PET4o#gnoiOq#v!x;((vZVwdbR`ORIU=Q$P=F<=yRXP|&MVJ7rlDW_?3O7%) z7Z0-9_EGO_xu|&lIgF5`D#ibmDD#*HoPot~8tvp9zPU4gF}tzCq=Wwh1+0^-!n8G4 zkWux}+f-$BMO|h?R~|2NboYZU58bP6||EOm7fBtlWoGvOj zN0)y%tfpl6wcLaT(Mo>B%M*=-Du|b zv#E?ldTNCH_{r`$+w-lwgA<*1!}ztA_oFIjQb51CLhKJeu~#^V#%Yu=oL8ItF>2`2 za*1j$r}ADV3NG+|c0o&4Ow6X2d&fyR%q4?XmjdTeUo`&5sWi5-Mf#dr*d0$qdwox&k@xWJajN+k z`iL#Avzi<_Dr}=(M$R*3{^%~)pW%*dL`TxjUSeIAuYG5vg8ijDk_xLuaXxOTBD5Z04H#n*uP2PTIfWHP7?w*nKOsD=T=CI-%Zg?>~iWMW=ALj86x7 zC{k5Tjr5U=A`Qjs$k%>a7~jn_9c*m(&TPZ;(p%OvAJp&0>zsCHu-H|I$|YJv4VPa= zl~wtJRw@U)ojUY6U&AnXiH2`Iy-q3IlxyHjzQ<{hoBX88xRC1FXH><{JZHCX%$X;% ztN)c5W*dAS*-$b~r3V*u{QJcW*N+}NlS;(=Xre5Ny7r}v8*G&$f{Kv$hEWCmME2B9 zQcR{8)Foy&*#hU$*pI*?_a#(Zho`Fu?52b&KGn=-G(kV{90<9S-9b&Cg-uT%dEReF zvuL4R=2y?~AamSD&Tn@LssJ(VfcMElUA63cldXAOTV{;&~jE*H*8xbArHk$cSp2@^U{y~ z?p)bl&QQf+)=d`M3`iySvYv_|kr%UhWs` zzT!&~G|Qo#ULt$(GgMb;v3%ANG_}Bgp6Z%Z}x_6muuCndpwxerL z%wD@0$pA-P0$&jI#(DH+^F?xQw`U}&{!Dsib-6z9;&8AK7hwtUEpyStirfkuZf{Xn zymG&Z*LJ)pjawzY{pgd~TE3v|;>MZ_zL6a-o4RDWrO(0ktv&kc zjrdb)tB&|J>Qmj+C5Q2l*<=o(n`vxk@_V$Ro0c%ti$Sp|iN-nuRoB<-zcWI+{Dg<1 z9a-#8?Q(Iz_TrD!b7S~trPfqysh_fcO?^f@n76lX`q}8ahr5icaU7f z#%RI*A+04n9;!c#mjgqoV}&hshcKI*1*~)Hn!f#n`j2N8b?x@@6r-D{swF`ssp97xK|TTnX=p zUFgMrF-CMmaXrtyr4uOa;>%Y|_Mh1K>WZDLR@?L_Xu7kNogv1t7iq!`G}0&MQ=7rI zC_8-4(Ppk(88(qGBkASj$OaJy$Nxg}6SrY!autW+;ks$kLl5i#J)ku%l^L!+@6SZ; z%K7d%{@j*+IUCm&aNI{Ak_wy3SlkN2inB-I0Mt>uxuY7F9+1}dUeQ#TusWUT9LzZsn{LQd38@yKBiM7PH!z-L$w3%G=?f)cBu znghk6m)=8aK?Su%(Lbv3P`G+Rcj_p!!V_5|qRW)xIU67{7yJs@++Sjf`PuuWxOYF3 zVso;gWX^-%=Rcl<;bu4*xb)?`why*^0GJ?oR!!s7M88pVE2OupN1~;M=Gf@XsBwUkKY4lVHX?e z!alPtEH2n;a;~e*_AyG|b=CE8-s{&)EFO*r?3Rd@xgO6^0@I4)V9QOVO)!cg2 zD|u8R*HM+R8_6U5Qr|`kw+>I_6kkvehUMCZ8Fgt`^_SUZ#nG+NPM&gK>2J`Rb>a!x z4(l+psH%^`TIt6Qx(NHw6KcFmr{B=ie$LTuCs(SLB(8NQm!gdt!%VaguHQOzs>@6sU(C$4*>S)$GU?12GtgvZ@>$dt z=4|zDt8M4IxY6+QyW(*#=_^T1uJr|%5EtoGaS_#Lbk5lf)Rswc@_l9mw!{Pd)|b*p z#3pL`PR#oE;*)tKpTo*d!Bd$RE@1`SIt_W|mYYnXrdfrzZ-mc<7elc1-R>rf>fHBd zaqSG{9v>p-(`yg)%heM2lNPFUZgB)XKJ;(?zQ*JYzUSw^jsI{OiQM~CKQ~RK!c)|b zDPCK2ZhO&(Cv{0sqSbMKLjtSq`jQwj9Y3&^Gr4Pf`(@}7uj$FIrT%DtQk~Fur(utj z%+B*Q*wSpYA!N%IZYep^@q71KZ3}xUH2%EPOEqEZkPx6hCTPD$V+~9pB&+ z*mp8R{3aG6ON4zu0EpWU6XFq*GguocJ1L*~A!Zyz&ZZopQ&7Yfow!9`SpS!FR? z1ohP-c9w<2E|`H|@v8|H|9X7b8C)rU%SMMVRsu5HQoo1q?;GgYB~o&rEhb-WSuG*}LqNtJNs=tIDsdDx8Jt9C@yL^byrkS5a|LdS6$0Nx+To z3o*yNDLb-*y32%RgCEUKAroYTg}5t1{}5MyZP$nV)}#0^8rk{w7aSSKn1GFd8!?@~ zehJ4kra%-DHGDO_7p8xDkzQ96OVAR3gvL>Z{mdY-z-`5W@`}W!@9_5J8S526At@4pZk$kCn8 zJ2HbT>K}36Bqu2&6O`0Rq?ILwL@^6yGTEpuAJoZQoaawqjE*2RH8~Z)3Dnf9#Y6sm z5U$~;>`4PouKkm z=_J8o-AFIh=h3U)=bhYYi|KDnQ8fx?{j_iixiI5hFZ4AR&0d?umUq9>Ge@V6$cGYg zK1$UjxZggYe7uiWWEjqp9O`fUA}4%8dXP){u&*6l@u>oj�(4Cr5}s*df11b$N&T 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

fG8u>lnUK~WUx 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 z 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 Date: Fri, 15 Mar 2024 11:37:15 -0400 Subject: [PATCH 45/92] Adding the new audio annunciator to the default product --- app/alarm/pom.xml | 1 + phoebus-product/pom.xml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/app/alarm/pom.xml b/app/alarm/pom.xml index 78f8ff6551..71e072b45e 100644 --- a/app/alarm/pom.xml +++ b/app/alarm/pom.xml @@ -13,5 +13,6 @@ logging-ui datasource freetts-annunciator + audio-annunciator diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index 4815bb3803..ac3c6b6d1d 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -192,6 +192,12 @@ 4.7.4-SNAPSHOT true + + org.phoebus + app-alarm-audio-annunciator + 4.7.4-SNAPSHOT + true + org.phoebus app-alarm-logging-ui From c37209354fe8712022d8a6297f145891d1e90c32 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Fri, 15 Mar 2024 11:37:51 -0400 Subject: [PATCH 46/92] fixing typos in the audio annunciator preferences --- .../applications/alarm/audio/annunciator/Preferences.java | 2 +- .../src/main/resources/audio_annunciator_preferences.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java index 8c1bca21d1..1619a10dab 100644 --- a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java +++ b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java @@ -47,7 +47,7 @@ public class Preferences { private static String useLocalResourceIfUnspecified(String alarmResource) { if (alarmResource == null || alarmResource.isEmpty()) { - return Preferences.class.getResource("/sound/mixkit-classic-alarm-995.wav").toString(); + return Preferences.class.getResource("/sounds/mixkit-classic-alarm-995.wav").toString(); } else { return alarmResource; } diff --git a/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties b/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties index 6b8348d669..d154400dbc 100644 --- a/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties +++ b/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties @@ -11,4 +11,4 @@ major_alarm_sound_url= invalid_alarm_sound_url= undefined_alarm_sound_url= -volumn=100 \ No newline at end of file +volume=100 \ No newline at end of file From 576472e52a1bf4524a0fc23fa3ba2e19ce6d432c Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Fri, 15 Mar 2024 12:47:46 -0400 Subject: [PATCH 47/92] Add some documentation for the audio annunciator preferences --- .../resources/audio_annunciator_preferences.properties | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties b/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties index d154400dbc..019e9e02ae 100644 --- a/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties +++ b/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties @@ -2,13 +2,19 @@ # Package org.phoebus.applications.alarm.audio.annunciator # ---------------------------------------- +# The audio annunciator will play the audio files specified for the associated alarm severity levels. +# Currently supported formats are AIFF and WAV files. +# examples of audio file URL's, they can be local or remote files. # file:/C:/tmp/audio/AudioFileWithWavFormat.wav # https://wavlist.com/wav/brass1.wav +# default alarm sound, if we don't want severity specific sounds only setting this one preference is enough alarm_sound_url= + minor_alarm_sound_url= major_alarm_sound_url= invalid_alarm_sound_url= undefined_alarm_sound_url= -volume=100 \ No newline at end of file +# audio clip volume (0-100) +volume=100 From 7088d0d96b11c9b75f8c685c4a7d2df742ab69a5 Mon Sep 17 00:00:00 2001 From: kasemir Date: Mon, 18 Mar 2024 11:59:42 -0400 Subject: [PATCH 48/92] Handle newlines in inline editor by escaping them as '\n' --- .../editor/tracker/SelectedWidgetUITracker.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/tracker/SelectedWidgetUITracker.java b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/tracker/SelectedWidgetUITracker.java index 7010fa1682..bbe452cf83 100644 --- a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/tracker/SelectedWidgetUITracker.java +++ b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/tracker/SelectedWidgetUITracker.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015-2020 Oak Ridge National Laboratory. + * Copyright (c) 2015-2024 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -301,7 +301,8 @@ else if (widget instanceof GroupWidget) // Create text field, aligned with widget, but assert minimum size final MacroizedWidgetProperty property = (MacroizedWidgetProperty)check.get(); - inline_editor = new TextField(property.getSpecification()); + // Allow editing newlines as '\n' + inline_editor = new TextField(property.getSpecification().replace("\n", "\\n")); // 'Managed' text field would assume some default size, // but we set the exact size in here inline_editor.setManaged(false); @@ -320,8 +321,9 @@ else if (widget instanceof GroupWidget) { if (! focused) { - if (!property.getSpecification().equals(inline_editor.getText())) - undo.execute(new SetMacroizedWidgetPropertyAction(property, inline_editor.getText())); + final String new_text = inline_editor.getText().replace("\\n", "\n"); + if (!property.getSpecification().equals(new_text)) + undo.execute(new SetMacroizedWidgetPropertyAction(property, new_text)); // Close when focus lost closeInlineEditor(); } @@ -329,7 +331,9 @@ else if (widget instanceof GroupWidget) inline_editor.setOnAction(event -> { - undo.execute(new SetMacroizedWidgetPropertyAction(property, inline_editor.getText())); + final String new_text = inline_editor.getText().replace("\\n", "\n"); + if (!property.getSpecification().equals(new_text)) + undo.execute(new SetMacroizedWidgetPropertyAction(property, new_text)); inline_editor.focusedProperty().removeListener(focused_listener); closeInlineEditor(); }); From 6852bd110a939a688f231ef0d633e9d6b7f3c61d Mon Sep 17 00:00:00 2001 From: kasemir Date: Mon, 18 Mar 2024 12:09:40 -0400 Subject: [PATCH 49/92] Inline editor: Focus listener was not removed when leaving focus ... because `focused_listener` could not be used inside the lambda that initialized the `focused_listener`. Now atomicref is used to hold that value, allowing its use inside the `focused_listener` --- .../editor/tracker/SelectedWidgetUITracker.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/tracker/SelectedWidgetUITracker.java b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/tracker/SelectedWidgetUITracker.java index bbe452cf83..4d95c72882 100644 --- a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/tracker/SelectedWidgetUITracker.java +++ b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/tracker/SelectedWidgetUITracker.java @@ -14,6 +14,7 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.RecursiveTask; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.stream.Collectors; @@ -317,24 +318,27 @@ else if (widget instanceof GroupWidget) PVAutocompleteMenu.INSTANCE.attachField(inline_editor); // On enter or lost focus, update the property. On Escape, just close. - final ChangeListener focused_listener = (prop, old, focused) -> + // Using atomic ref as holder so that focused_listener can remove itself + final AtomicReference> focused_listener = new AtomicReference<>(); + focused_listener.set((prop, old, focused) -> { if (! focused) { final String new_text = inline_editor.getText().replace("\\n", "\n"); if (!property.getSpecification().equals(new_text)) undo.execute(new SetMacroizedWidgetPropertyAction(property, new_text)); + inline_editor.focusedProperty().removeListener(focused_listener.get()); // Close when focus lost closeInlineEditor(); } - }; + }); inline_editor.setOnAction(event -> { final String new_text = inline_editor.getText().replace("\\n", "\n"); if (!property.getSpecification().equals(new_text)) undo.execute(new SetMacroizedWidgetPropertyAction(property, new_text)); - inline_editor.focusedProperty().removeListener(focused_listener); + inline_editor.focusedProperty().removeListener(focused_listener.get()); closeInlineEditor(); }); @@ -344,13 +348,13 @@ else if (widget instanceof GroupWidget) { case ESCAPE: event.consume(); - inline_editor.focusedProperty().removeListener(focused_listener); + inline_editor.focusedProperty().removeListener(focused_listener.get()); closeInlineEditor(); default: } }); - inline_editor.focusedProperty().addListener(focused_listener); + inline_editor.focusedProperty().addListener(focused_listener.get()); inline_editor.selectAll(); inline_editor.requestFocus(); From 5ea3f2d24f50aa98c9e1defbbe8f9ad171cbb603 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 19 Mar 2024 09:16:14 +0100 Subject: [PATCH 50/92] Bug fix save&restore client --- .../client/SaveAndRestoreJerseyClient.java | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java index 83427c4587..53287edd87 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java @@ -51,6 +51,9 @@ public class SaveAndRestoreJerseyClient implements org.phoebus.applications.save private static final ObjectMapper mapper = new ObjectMapper(); + /** + * Should be accessed through {@link #getClient()} to ensure proper usage of cached credentials, if available. + */ private static final Client client; private static HTTPBasicAuthFilter httpBasicAuthFilter; @@ -70,7 +73,14 @@ public class SaveAndRestoreJerseyClient implements org.phoebus.applications.save defaultClientConfig.getSingletons().add(jacksonJsonProvider); client = Client.create(defaultClientConfig); + } + public SaveAndRestoreJerseyClient() { + mapper.registerModule(new JavaTimeModule()); + mapper.setSerializationInclusion(Include.NON_NULL); + } + + private Client getClient(){ try { SecureStore store = new SecureStore(); ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE); @@ -85,11 +95,7 @@ public class SaveAndRestoreJerseyClient implements org.phoebus.applications.save } catch (Exception e) { logger.log(Level.WARNING, "Unable to retrieve credentials from secure store", e); } - } - - public SaveAndRestoreJerseyClient() { - mapper.registerModule(new JavaTimeModule()); - mapper.setSerializationInclusion(Include.NON_NULL); + return client; } @Override @@ -109,7 +115,7 @@ public Node getNode(String uniqueNodeId) { @Override public List getCompositeSnapshotReferencedNodes(String uniqueNodeId) { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/nodes"); + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/nodes"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -128,7 +134,7 @@ public List getCompositeSnapshotReferencedNodes(String uniqueNodeId) { @Override public List getCompositeSnapshotItems(String uniqueNodeId) { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/items"); + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/items"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -159,7 +165,7 @@ public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClient @Override public Node createNewNode(String parentNodeId, Node node) { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/node") + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/node") .queryParam("parentNodeId", parentNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(node, CONTENT_TYPE_JSON) @@ -183,7 +189,7 @@ public Node updateNode(Node nodeToUpdate) { @Override public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/node") + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/node") .queryParam("customTimeForMigration", customTimeForMigration ? "true" : "false"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) @@ -209,7 +215,7 @@ private T getCall(String relativeUrl, Class clazz) { } private ClientResponse getCall(String relativeUrl) { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + relativeUrl); + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + relativeUrl); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -227,7 +233,7 @@ private ClientResponse getCall(String relativeUrl) { @Override public void deleteNodes(List nodeIds) { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/node"); + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/node"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(nodeIds, CONTENT_TYPE_JSON) .delete(ClientResponse.class); @@ -254,7 +260,7 @@ public List getAllSnapshots() { @Override public Node moveNodes(List sourceNodeIds, String targetNodeId) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/move") + getClient().resource(Preferences.jmasarServiceUrl + "/move") .queryParam("to", targetNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) @@ -276,7 +282,7 @@ public Node moveNodes(List sourceNodeIds, String targetNodeId) { @Override public Node copyNodes(List sourceNodeIds, String targetNodeId) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/copy") + getClient().resource(Preferences.jmasarServiceUrl + "/copy") .queryParam("to", targetNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) @@ -298,7 +304,7 @@ public Node copyNodes(List sourceNodeIds, String targetNodeId) { @Override public String getFullPath(String uniqueNodeId) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/path/" + uniqueNodeId); + getClient().resource(Preferences.jmasarServiceUrl + "/path/" + uniqueNodeId); ClientResponse response = webResource.get(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -321,7 +327,7 @@ public ConfigurationData getConfigurationData(String nodeId) { @Override public Configuration createConfiguration(String parentNodeId, Configuration configuration) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/config") + getClient().resource(Preferences.jmasarServiceUrl + "/config") .queryParam("parentNodeId", parentNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(configuration, CONTENT_TYPE_JSON) @@ -340,7 +346,7 @@ public Configuration createConfiguration(String parentNodeId, Configuration conf @Override public Configuration updateConfiguration(Configuration configuration) { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/config"); + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/config"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(configuration, CONTENT_TYPE_JSON) @@ -366,7 +372,7 @@ public SnapshotData getSnapshotData(String nodeId) { @Override public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/snapshot") + getClient().resource(Preferences.jmasarServiceUrl + "/snapshot") .queryParam("parentNodeId", parentNodeId); ClientResponse response; try { @@ -391,7 +397,7 @@ public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) { @Override public Snapshot updateSnapshot(Snapshot snapshot) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/snapshot"); + getClient().resource(Preferences.jmasarServiceUrl + "/snapshot"); ClientResponse response; try { response = webResource.accept(CONTENT_TYPE_JSON) @@ -416,7 +422,7 @@ public Snapshot updateSnapshot(Snapshot snapshot) { @Override public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/composite-snapshot") + getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot") .queryParam("parentNodeId", parentNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(compositeSnapshot, CONTENT_TYPE_JSON) @@ -436,7 +442,7 @@ public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeS @Override public List checkCompositeSnapshotConsistency(List snapshotNodeIds) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/composite-snapshot-consistency-check"); + getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot-consistency-check"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(snapshotNodeIds, CONTENT_TYPE_JSON) .post(ClientResponse.class); @@ -455,7 +461,7 @@ public List checkCompositeSnapshotConsistency(List snapshotNodeI @Override public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot) { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/composite-snapshot"); + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(compositeSnapshot, CONTENT_TYPE_JSON) @@ -474,7 +480,7 @@ public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnap @Override public SearchResult search(MultivaluedMap searchParams) { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/search") + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/search") .queryParams(searchParams); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .get(ClientResponse.class); @@ -492,7 +498,7 @@ public SearchResult search(MultivaluedMap searchParams) { @Override public Filter saveFilter(Filter filter) { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/filter"); + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/filter"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(filter, CONTENT_TYPE_JSON) .put(ClientResponse.class); @@ -510,7 +516,7 @@ public Filter saveFilter(Filter filter) { @Override public List getAllFilters() { - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/filters"); + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/filters"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .get(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -530,7 +536,7 @@ public List getAllFilters() { public void deleteFilter(String name) { // Filter name may contain space chars, need to URL encode these. String filterName = name.replace(" ", "%20"); - WebResource webResource = client.resource(Preferences.jmasarServiceUrl + "/filter/" + filterName); + WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/filter/" + filterName); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .delete(ClientResponse.class); if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { @@ -554,7 +560,7 @@ public void deleteFilter(String name) { public List addTag(TagData tagData) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/tags"); + getClient().resource(Preferences.jmasarServiceUrl + "/tags"); ClientResponse response; try { response = webResource.accept(CONTENT_TYPE_JSON) @@ -585,7 +591,7 @@ public List addTag(TagData tagData) { */ public List deleteTag(TagData tagData) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/tags"); + getClient().resource(Preferences.jmasarServiceUrl + "/tags"); ClientResponse response; try { response = webResource.accept(CONTENT_TYPE_JSON) @@ -610,7 +616,7 @@ public List deleteTag(TagData tagData) { @Override public UserData authenticate(String userName, String password) { WebResource webResource = - client.resource(Preferences.jmasarServiceUrl + "/login") + getClient().resource(Preferences.jmasarServiceUrl + "/login") .queryParam("username", userName) .queryParam("password", password); ClientResponse response; From e294eaa87f37bf88f6b3015ad222e2aa9b4e596e Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 19 Mar 2024 10:12:57 +0100 Subject: [PATCH 51/92] Make save_credentials global for all scopes/applications --- .../org/phoebus/logbook/olog/ui/LogbookUIPreferences.java | 1 - .../logbook/olog/ui/write/LogEntryEditorController.java | 5 +++-- .../logbook/olog/ui/write/LogEntryUpdateController.java | 5 +++-- .../src/main/resources/log_olog_ui_preferences.properties | 3 --- .../java/org/phoebus/logbook/ui/LogbookUiPreferences.java | 1 - .../org/phoebus/logbook/ui/write/FieldsViewController.java | 3 ++- .../java/org/phoebus/logbook/ui/write/LogEntryModel.java | 6 ++++-- .../ui/src/main/resources/log_ui_preferences.properties | 3 --- core/ui/src/main/java/org/phoebus/ui/Preferences.java | 1 + .../ui/src/main/resources/phoebus_ui_preferences.properties | 6 ++++++ 10 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookUIPreferences.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookUIPreferences.java index 9769b5a394..9b2cc23d7f 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookUIPreferences.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookUIPreferences.java @@ -24,7 +24,6 @@ public class LogbookUIPreferences { @Preference public static String[] default_logbooks; @Preference public static String default_logbook_query; - @Preference public static boolean save_credentials; @Preference public static String calendar_view_item_stylesheet; @Preference public static String level_field_name; @Preference public static String markup_help; diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java index 664ad61ef5..365537a9d1 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java @@ -49,6 +49,7 @@ import org.phoebus.security.tokens.AuthenticationScope; import org.phoebus.security.tokens.ScopedAuthenticationToken; import org.phoebus.security.tokens.SimpleAuthenticationToken; +import org.phoebus.ui.Preferences; import org.phoebus.ui.dialog.ListSelectionPopOver; import org.phoebus.ui.javafx.ImageCache; import org.phoebus.util.time.TimestampFormats; @@ -258,7 +259,7 @@ public void initialize() { } }); }); - if (LogbookUIPreferences.save_credentials) { + if (Preferences.save_credentials) { fetchStoredUserCredentials(); } @@ -439,7 +440,7 @@ public void submit() { completionHandler.handleResult(result); } // Set username and password in secure store if submission of log entry completes successfully - if (LogbookUIPreferences.save_credentials) { + if (Preferences.save_credentials) { // Get the SecureStore. Store username and password. try { SecureStore store = new SecureStore(); diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryUpdateController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryUpdateController.java index eb7ce751f2..be2d7b9c9e 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryUpdateController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryUpdateController.java @@ -46,6 +46,7 @@ import org.phoebus.security.tokens.AuthenticationScope; import org.phoebus.security.tokens.ScopedAuthenticationToken; import org.phoebus.security.tokens.SimpleAuthenticationToken; +import org.phoebus.ui.Preferences; import org.phoebus.ui.dialog.ListSelectionPopOver; import org.phoebus.ui.javafx.ImageCache; import org.phoebus.util.time.TimestampFormats; @@ -241,7 +242,7 @@ public void initialize() { } }); }); - if (LogbookUIPreferences.save_credentials) { + if (Preferences.save_credentials) { fetchStoredUserCredentials(); } @@ -420,7 +421,7 @@ public void submit() { completionHandler.handleResult(result); } // Set username and password in secure store if submission of log entry completes successfully - if (LogbookUIPreferences.save_credentials) { + if (Preferences.save_credentials) { // Get the SecureStore. Store username and password. try { SecureStore store = new SecureStore(); diff --git a/app/logbook/olog/ui/src/main/resources/log_olog_ui_preferences.properties b/app/logbook/olog/ui/src/main/resources/log_olog_ui_preferences.properties index 527edeeb2f..92935f036f 100644 --- a/app/logbook/olog/ui/src/main/resources/log_olog_ui_preferences.properties +++ b/app/logbook/olog/ui/src/main/resources/log_olog_ui_preferences.properties @@ -8,9 +8,6 @@ default_logbooks=Scratch Pad # The default query for logbook applications default_logbook_query=desc=*&start=12 hours&end=now -# Whether or not to save user credentials to file so they only have to be entered once when making log entries. -save_credentials=false - # Stylesheet for the items in the log calendar view calendar_view_item_stylesheet=Agenda.css diff --git a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookUiPreferences.java b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookUiPreferences.java index 13b3d93ab4..6d3b34e1c3 100644 --- a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookUiPreferences.java +++ b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookUiPreferences.java @@ -24,7 +24,6 @@ public class LogbookUiPreferences { @Preference public static String[] default_logbooks; @Preference public static String default_logbook_query; - @Preference public static boolean save_credentials; @Preference public static String calendar_view_item_stylesheet; @Preference public static String level_field_name; diff --git a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/write/FieldsViewController.java b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/write/FieldsViewController.java index 04420d46e0..916a8014e2 100644 --- a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/write/FieldsViewController.java +++ b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/write/FieldsViewController.java @@ -33,6 +33,7 @@ import javafx.scene.paint.Color; import org.phoebus.logbook.ui.LogbookUiPreferences; import org.phoebus.logbook.ui.Messages; +import org.phoebus.ui.Preferences; import org.phoebus.util.time.TimestampFormats; import java.net.URL; @@ -147,7 +148,7 @@ else if (passwordField.getText().isEmpty()) }); userField.requestFocus(); - if (LogbookUiPreferences.save_credentials) + if (Preferences.save_credentials) { model.fetchStoredUserCredentials(); } diff --git a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/write/LogEntryModel.java b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/write/LogEntryModel.java index df4c944496..0cb4e56285 100644 --- a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/write/LogEntryModel.java +++ b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/write/LogEntryModel.java @@ -31,6 +31,7 @@ import org.phoebus.logbook.ui.LogbookUiPreferences; import org.phoebus.security.store.SecureStore; import org.phoebus.security.tokens.SimpleAuthenticationToken; +import org.phoebus.ui.Preferences; import javax.imageio.ImageIO; import java.io.File; @@ -84,7 +85,8 @@ public class LogEntryModel { private final SimpleBooleanProperty readyToSubmit; // Used internally. Backs read only property above. /** - * Property that allows the model to define when the application needs to update the username and password text fields. Only used if save_credentials=true + * Property that allows the model to define when the application needs to update the username and password text fields. + * Only used if save_credentials=true */ private final ReadOnlyBooleanProperty updateCredentialsProperty; // To be broadcast through getUpdateCredentialsProperty. private final SimpleBooleanProperty updateCredentials; // Used internally. Backs read only property above. @@ -517,7 +519,7 @@ public LogEntry submitEntry() throws Exception { // Sumission should be synchronous such that clients can intercept failures - if (LogbookUiPreferences.save_credentials) { + if (Preferences.save_credentials) { // Get the SecureStore. Store username and password. try { SecureStore store = new SecureStore(); diff --git a/app/logbook/ui/src/main/resources/log_ui_preferences.properties b/app/logbook/ui/src/main/resources/log_ui_preferences.properties index f282a7b8d5..b3d8c8c3d5 100644 --- a/app/logbook/ui/src/main/resources/log_ui_preferences.properties +++ b/app/logbook/ui/src/main/resources/log_ui_preferences.properties @@ -8,9 +8,6 @@ default_logbooks=Scratch Pad # The default query for logbook applications default_logbook_query=search=*&start=12 hours&end=now -# Whether or not to save user credentials to file so they only have to be entered once when making log entries. -save_credentials=false - # Stylesheet for the items in the log calendar view calendar_view_item_stylesheet=Agenda.css diff --git a/core/ui/src/main/java/org/phoebus/ui/Preferences.java b/core/ui/src/main/java/org/phoebus/ui/Preferences.java index 62c450669d..da1ac207fc 100644 --- a/core/ui/src/main/java/org/phoebus/ui/Preferences.java +++ b/core/ui/src/main/java/org/phoebus/ui/Preferences.java @@ -86,6 +86,7 @@ public class Preferences @Preference public static int[] alarm_area_panel_undefined_severity_background_color; /** cache_hint_for_picture_and_symbol_widgets */ @Preference public static String cache_hint_for_picture_and_symbol_widgets; + @Preference public static boolean save_credentials; static { diff --git a/core/ui/src/main/resources/phoebus_ui_preferences.properties b/core/ui/src/main/resources/phoebus_ui_preferences.properties index cb5180f426..a15ddab1ef 100644 --- a/core/ui/src/main/resources/phoebus_ui_preferences.properties +++ b/core/ui/src/main/resources/phoebus_ui_preferences.properties @@ -128,3 +128,9 @@ alarm_area_panel_undefined_severity_background_color=200,0,200,200 # default caching behavior is used (i.e., caching is DISABLED, # and the cache hint is set to "CacheHint.DEFAULT"). cache_hint_for_picture_and_symbol_widgets= + + +# Whether or not to save user credentials to file or memory so they only have to be entered once. Note that this +# applies to all scopes/applications prompting for credentials. +# See also setting org.phoebus.security/secure_store_target +save_credentials=false \ No newline at end of file From 7765c713ead35632bb81d181d31d92c6dfcecc37 Mon Sep 17 00:00:00 2001 From: kasemir Date: Wed, 20 Mar 2024 12:30:53 -0400 Subject: [PATCH 52/92] Default format 'precision' was ignored for arrays Now used because checking for DisplayProvider instead of just VNumber --- .../java/org/phoebus/ui/vtype/FormatOptionHandler.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/vtype/FormatOptionHandler.java b/core/ui/src/main/java/org/phoebus/ui/vtype/FormatOptionHandler.java index 90320f599e..2cb6097538 100644 --- a/core/ui/src/main/java/org/phoebus/ui/vtype/FormatOptionHandler.java +++ b/core/ui/src/main/java/org/phoebus/ui/vtype/FormatOptionHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015-2022 Oak Ridge National Laboratory. + * Copyright (c) 2015-2024 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -19,6 +19,7 @@ import org.epics.util.array.ListNumber; import org.epics.vtype.Display; +import org.epics.vtype.DisplayProvider; import org.epics.vtype.VBoolean; import org.epics.vtype.VDouble; import org.epics.vtype.VEnum; @@ -62,9 +63,9 @@ public static int actualPrecision (final VType value, int precision) { if (precision < 0) { - if (value instanceof VNumber) + if (value instanceof DisplayProvider) { - final NumberFormat format = ( (VNumber) value ).getDisplay().getFormat(); + final NumberFormat format = ( (DisplayProvider) value ).getDisplay().getFormat(); if (format instanceof DecimalFormat) precision = ( (DecimalFormat) format ).getMaximumFractionDigits(); } From ef8564a1e75e78a4dd0d12d9f3ad861357792552 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Fri, 22 Mar 2024 08:56:02 +0100 Subject: [PATCH 53/92] Fix NPE if save&restore not yet launched --- .../authentication/SecureStoreChangeHandlerImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java index f2177aed32..744f6e72fd 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java @@ -42,6 +42,10 @@ public void secureStoreChanged(List validTokens) { if (appDescriptor != null && appDescriptor instanceof SaveAndRestoreApplication) { SaveAndRestoreApplication saveAndRestoreApplication = (SaveAndRestoreApplication) appDescriptor; SaveAndRestoreInstance saveAndRestoreInstance = (SaveAndRestoreInstance) saveAndRestoreApplication.getInstance(); + // Save&restore app may not be launched (yet) + if(saveAndRestoreInstance == null){ + return; + } saveAndRestoreInstance.secureStoreChanged(validTokens); } } From 9062373a548cdf2537b25147c523f3ee556c8b7a Mon Sep 17 00:00:00 2001 From: kasemir Date: Fri, 22 Mar 2024 09:52:18 -0400 Subject: [PATCH 54/92] Ant: Add jackie dependencies --- services/alarm-server/build.xml | 2 ++ .../src/main/resources/alarm_server_logging.properties | 2 +- services/archive-engine/build.xml | 2 ++ .../archive-engine/src/main/resources/engine_logging.properties | 2 +- services/scan-server/build.xml | 2 ++ .../src/main/resources/scan_server_logging.properties | 2 +- 6 files changed, 9 insertions(+), 3 deletions(-) diff --git a/services/alarm-server/build.xml b/services/alarm-server/build.xml index adb0f7a593..a2cedf4de0 100644 --- a/services/alarm-server/build.xml +++ b/services/alarm-server/build.xml @@ -29,6 +29,8 @@ + + diff --git a/services/alarm-server/src/main/resources/alarm_server_logging.properties b/services/alarm-server/src/main/resources/alarm_server_logging.properties index 6d7fd50d06..3a5efd95eb 100644 --- a/services/alarm-server/src/main/resources/alarm_server_logging.properties +++ b/services/alarm-server/src/main/resources/alarm_server_logging.properties @@ -30,4 +30,4 @@ org.apache.kafka.level = WARNING org.phoebus.applications.alarm.level = INFO com.cosylab.epics.caj.level = WARNING org.phoebus.framework.rdb.level = WARNING -org.phoebus.pv.level = WARNING +org.phoebus.pv.level = CONFIG diff --git a/services/archive-engine/build.xml b/services/archive-engine/build.xml index 2a471e51fd..cba44eb5e6 100644 --- a/services/archive-engine/build.xml +++ b/services/archive-engine/build.xml @@ -26,6 +26,8 @@ + + diff --git a/services/archive-engine/src/main/resources/engine_logging.properties b/services/archive-engine/src/main/resources/engine_logging.properties index eebcc33b61..a1765a7c00 100644 --- a/services/archive-engine/src/main/resources/engine_logging.properties +++ b/services/archive-engine/src/main/resources/engine_logging.properties @@ -28,4 +28,4 @@ org.eclipse.jetty.server.AbstractConnector.level = WARNING com.cosylab.epics.caj.level = WARNING org.phoebus.framework.rdb.level = WARNING -org.phoebus.pv.level = WARNING +org.phoebus.pv.level = CONFIG diff --git a/services/scan-server/build.xml b/services/scan-server/build.xml index 5973e38ec1..fa4e25b52b 100644 --- a/services/scan-server/build.xml +++ b/services/scan-server/build.xml @@ -28,6 +28,8 @@ + + diff --git a/services/scan-server/src/main/resources/scan_server_logging.properties b/services/scan-server/src/main/resources/scan_server_logging.properties index 42fb8af83e..12df66c8ef 100644 --- a/services/scan-server/src/main/resources/scan_server_logging.properties +++ b/services/scan-server/src/main/resources/scan_server_logging.properties @@ -30,6 +30,6 @@ org.python.level = WARNING com.cosylab.epics.caj.level = WARNING org.phoebus.framework.rdb.level = WARNING -org.phoebus.pv.level = WARNING +org.phoebus.pv.level = CONFIG org.csstudio.scan.server.level = CONFIG From 728151dec12883ddf41e0e81c43e4ec80a79c48d Mon Sep 17 00:00:00 2001 From: georgweiss Date: Mon, 25 Mar 2024 19:43:32 +0100 Subject: [PATCH 55/92] New context menu items for DockItemWithInput tab --- .../filebrowser/FileBrowserController.java | 12 +- .../org/phoebus/ui/application/Messages.java | 4 + .../java/org/phoebus/ui/docking/DockItem.java | 92 +++-- .../phoebus/ui/docking/DockItemWithInput.java | 360 ++++++++++-------- .../ui/application/messages.properties | 4 + 5 files changed, 260 insertions(+), 212 deletions(-) diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 7577f277ac..15da2953cb 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -5,18 +5,8 @@ import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.scene.control.Alert; +import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.Button; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.Menu; -import javafx.scene.control.MenuItem; -import javafx.scene.control.SelectionMode; -import javafx.scene.control.SeparatorMenuItem; -import javafx.scene.control.TextField; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeTableColumn; -import javafx.scene.control.TreeTableView; import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; import javafx.scene.input.ContextMenuEvent; diff --git a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java index e9a8cdb981..a16789f778 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java @@ -20,6 +20,7 @@ public class Messages public static String AppRevision; public static String AppVersionHeader; public static String CloseAllTabs; + public static String CopyResourcePath; public static String DeleteLayouts; public static String DeleteLayoutsConfirmFmt; public static String DeleteLayoutsInfo; @@ -130,6 +131,9 @@ public class Messages public static String ScreenshotErrHdr; public static String ScreenshotErrMsg; public static String SelectTab; + public static String ShowIn; + public static String ShowInFileBrowserApp; + public static String ShowInNativeFileBrowser; public static String ShowStatusbar; public static String ShowToolbar; public static String Time12h; diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItem.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItem.java index e44a4ee891..8c3629cfdf 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItem.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItem.java @@ -137,6 +137,23 @@ public class DockItem extends Tab /** Called after tab was closed */ private List closed_callback = null; + private MenuItem info; + private MenuItem detach; + + private MenuItem split_horiz; + + private MenuItem split_vert; + + private MenuItem save_window; + + private MenuItem close; + + private MenuItem close_other; + + private MenuItem close_all; + + private ContextMenu menu; + /** Create dock item for instance of an application * @param applicationInstance {@link AppInstance} * @param content Content for this application instance @@ -224,19 +241,19 @@ public DockPane getDockPane() private void createContextMenu() { - final MenuItem info = new MenuItem(Messages.DockInfo, new ImageView(info_icon)); + info = new MenuItem(Messages.DockInfo, new ImageView(info_icon)); info.setOnAction(event -> showInfo()); - final MenuItem detach = new MenuItem(Messages.DockDetach, new ImageView(detach_icon)); + detach = new MenuItem(Messages.DockDetach, new ImageView(detach_icon)); detach.setOnAction(event -> detach()); - final MenuItem split_horiz = new MenuItem(Messages.DockSplitH, new ImageView(split_horiz_icon)); + split_horiz = new MenuItem(Messages.DockSplitH, new ImageView(split_horiz_icon)); split_horiz.setOnAction(event -> split(true)); - final MenuItem split_vert = new MenuItem(Messages.DockSplitV, new ImageView(split_vert_icon)); + split_vert = new MenuItem(Messages.DockSplitV, new ImageView(split_vert_icon)); split_vert.setOnAction(event -> split(false)); - final MenuItem save_window = new MenuItem(Messages.SaveLayoutOfContainingWindowAs, new ImageView(save_window_layout_icon)); + save_window = new MenuItem(Messages.SaveLayoutOfContainingWindowAs, new ImageView(save_window_layout_icon)); save_window.setOnAction(event -> { DockPane activeDockPane = getActiveDockPane(); List stagesContainingActiveDockPane = DockStage.getDockStages().stream().filter(stage -> getDockPanes(stage).contains(activeDockPane)).collect(Collectors.toList()); @@ -251,12 +268,12 @@ else if (stagesContainingActiveDockPane.size() == 0) { } }); - final MenuItem close = new MenuItem(Messages.DockClose, new ImageView(DockPane.close_icon)); + close = new MenuItem(Messages.DockClose, new ImageView(DockPane.close_icon)); ArrayList arrayList = new ArrayList(); arrayList.add(this); close.setOnAction(event -> close(arrayList)); - final MenuItem close_other = new MenuItem(Messages.DockCloseOthers, new ImageView(close_many_icon)); + close_other = new MenuItem(Messages.DockCloseOthers, new ImageView(close_many_icon)); close_other.setOnAction(event -> { // Close all other tabs in non-fixed panes of this window @@ -270,7 +287,7 @@ else if (stagesContainingActiveDockPane.size() == 0) { close(tabs); }); - final MenuItem close_all = new MenuItem(Messages.DockCloseAll, new ImageView(close_many_icon)); + close_all = new MenuItem(Messages.DockCloseAll, new ImageView(close_many_icon)); close_all.setOnAction(event -> { // Close all tabs in non-fixed panes of this window @@ -284,41 +301,44 @@ else if (stagesContainingActiveDockPane.size() == 0) { close(tabs); }); - final ContextMenu menu = new ContextMenu(info); - + menu = new ContextMenu(info); menu.setOnShowing(event -> { - menu.getItems().setAll(info); - - final boolean may_lock = AuthorizationService.hasAuthorization("lock_ui"); - final DockPane pane = getDockPane(); - if (pane.isFixed()) - { - if (may_lock) - menu.getItems().addAll(new NamePaneMenuItem(pane), new UnlockMenuItem(pane)); - } - else - { - menu.getItems().addAll(new SeparatorMenuItem(), - detach, - split_horiz, - split_vert); - - if (may_lock) - menu.getItems().addAll(new NamePaneMenuItem(pane), new LockMenuItem(pane)); - - menu.getItems().addAll(new SeparatorMenuItem(), - close, - close_other, - new SeparatorMenuItem(), - close_all); - } - menu.getItems().addAll(new SeparatorMenuItem(), save_window); + configureContextMenu(menu); }); name_tab.setContextMenu(menu); } + protected void configureContextMenu(ContextMenu menu){ + menu.getItems().setAll(info); + + final boolean may_lock = AuthorizationService.hasAuthorization("lock_ui"); + final DockPane pane = getDockPane(); + if (pane.isFixed()) + { + if (may_lock) + menu.getItems().addAll(new NamePaneMenuItem(pane), new UnlockMenuItem(pane)); + } + else + { + menu.getItems().addAll(new SeparatorMenuItem(), + detach, + split_horiz, + split_vert); + + if (may_lock) + menu.getItems().addAll(new NamePaneMenuItem(pane), new LockMenuItem(pane)); + + menu.getItems().addAll(new SeparatorMenuItem(), + close, + close_other, + new SeparatorMenuItem(), + close_all); + } + menu.getItems().addAll(new SeparatorMenuItem(), save_window); + } + /** @param tabs Tabs to prepare and then close */ private void close(final ArrayList tabs) { diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index fba63d64e6..0c01c67361 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -7,9 +7,35 @@ *******************************************************************************/ package org.phoebus.ui.docking; -import static org.phoebus.ui.application.PhoebusApplication.logger; +import javafx.application.Platform; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.layout.Region; +import javafx.stage.FileChooser.ExtensionFilter; +import javafx.stage.Window; +import org.apache.commons.io.FilenameUtils; +import org.phoebus.framework.jobs.JobManager; +import org.phoebus.framework.jobs.JobMonitor; +import org.phoebus.framework.jobs.JobRunnable; +import org.phoebus.framework.spi.AppInstance; +import org.phoebus.framework.util.ResourceParser; +import org.phoebus.framework.workbench.ApplicationService; +import org.phoebus.ui.application.Messages; +import org.phoebus.ui.dialog.DialogHelper; +import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; +import org.phoebus.ui.dialog.SaveAsDialog; +import org.phoebus.ui.javafx.ImageCache; +import java.awt.*; import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -23,54 +49,36 @@ import java.util.logging.Level; import java.util.stream.Collectors; -import javafx.scene.layout.Region; -import javafx.stage.Window; -import org.apache.commons.io.FilenameUtils; -import org.phoebus.framework.jobs.JobManager; -import org.phoebus.framework.jobs.JobMonitor; -import org.phoebus.framework.jobs.JobRunnable; -import org.phoebus.framework.spi.AppInstance; -import org.phoebus.framework.util.ResourceParser; -import org.phoebus.ui.application.Messages; -import org.phoebus.ui.dialog.DialogHelper; -import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; -import org.phoebus.ui.dialog.SaveAsDialog; - -import javafx.application.Platform; -import javafx.scene.Node; -import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.ButtonType; -import javafx.scene.control.Tab; -import javafx.scene.control.Tooltip; -import javafx.stage.FileChooser.ExtensionFilter; +import static org.phoebus.ui.application.PhoebusApplication.logger; -/** Item for a {@link DockPane} that has an 'input' file or URI. +/** + * Item for a {@link DockPane} that has an 'input' file or URI. * - *

While technically a {@link Tab}, - * only the methods declared in here and - * in {@link DockItem} should be called - * to assert compatibility with future updates. + *

While technically a {@link Tab}, + * only the methods declared in here and + * in {@link DockItem} should be called + * to assert compatibility with future updates. * - *

Tracks the current 'input' and the 'dirty' state. - * When the item becomes 'dirty', 'Save' or 'Save As' - * are supported via the provided list of file extensions - * and a 'save_handler'. - * User will be asked to save a dirty tab when the tab is closed. - * Saving can also be initiated from the 'File' menu. - * If the 'input' is null, 'Save' automatically - * invokes 'Save As' to prompt for a file name. + *

Tracks the current 'input' and the 'dirty' state. + * When the item becomes 'dirty', 'Save' or 'Save As' + * are supported via the provided list of file extensions + * and a 'save_handler'. + * User will be asked to save a dirty tab when the tab is closed. + * Saving can also be initiated from the 'File' menu. + * If the 'input' is null, 'Save' automatically + * invokes 'Save As' to prompt for a file name. * - * @author Kay Kasemir + * @author Kay Kasemir */ @SuppressWarnings("nls") -public class DockItemWithInput extends DockItem -{ +public class DockItemWithInput extends DockItem { private static final String DIRTY = "* "; private AtomicBoolean is_dirty = new AtomicBoolean(false); - /** The one item that should always be included in 'file_extensions' */ + /** + * The one item that should always be included in 'file_extensions' + */ public static final ExtensionFilter ALL_FILES = new ExtensionFilter(Messages.DockAll, "*.*"); private final ExtensionFilter[] file_extensions; @@ -79,37 +87,70 @@ public class DockItemWithInput extends DockItem private volatile URI input; - /** Create dock item + private final static Image copyToClipboardIcon = ImageCache.getImage(DockItem.class, "/icons/copy.png"); + + private final static Image showInIcon = ImageCache.getImage(DockItem.class, "/icons/fldr_obj.png"); + + /** + * Create dock item * - *

The 'save_handler' will be called to save the content. - * It will be called in a background job, because writing files - * might be slow. + *

The 'save_handler' will be called to save the content. + * It will be called in a background job, because writing files + * might be slow. * - *

When 'save_handler' is called, the 'input' will be set to a file-based URI. - * On success, or if for some reason there is nothing to save, - * the 'save_handler' returns. - * On error, the 'save_handler' throws an exception. + *

When 'save_handler' is called, the 'input' will be set to a file-based URI. + * On success, or if for some reason there is nothing to save, + * the 'save_handler' returns. + * On error, the 'save_handler' throws an exception. * - * @param application {@link AppInstance} - * @param content Initial content - * @param input URI for the input. May be null - * @param file_extensions File extensions for "Save As". May be null if never calling setDirty(true) - * @param save_handler Will be called to 'save' the content. May be null if never calling setDirty(true) + * @param application {@link AppInstance} + * @param content Initial content + * @param input URI for the input. May be null + * @param file_extensions File extensions for "Save As". May be null if never calling setDirty(true) + * @param save_handler Will be called to 'save' the content. May be null if never calling setDirty(true) */ public DockItemWithInput(final AppInstance application, final Node content, final URI input, final ExtensionFilter[] file_extensions, - final JobRunnable save_handler) - { + final JobRunnable save_handler) { super(application, content); - this.file_extensions = file_extensions; + this.file_extensions = file_extensions; this.save_handler = save_handler; setInput(input); + name_tab.getContextMenu().setOnShowing(e -> { + this.configureContextMenu(name_tab.getContextMenu()); + }); + } + + protected void configureContextMenu(ContextMenu menu) { + super.configureContextMenu(menu); + final Menu showInMenu = new Menu(Messages.ShowIn, new ImageView(showInIcon)); + final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp); + showInFileBrowser.setOnAction(e -> { + ApplicationService.createInstance("file_browser", new File(input.getPath()).getParentFile().toURI()); + }); + final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); + showInNativeFileBrowser.setOnAction(e -> { + try { + Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }); + final MenuItem copyResourceToClipboard = new MenuItem(Messages.CopyResourcePath, new ImageView(copyToClipboardIcon)); + copyResourceToClipboard.setOnAction(e -> { + final ClipboardContent content = new ClipboardContent(); + content.putString(input.getPath()); + Clipboard.getSystemClipboard().setContent(content); + }); + + showInMenu.getItems().addAll(showInFileBrowser, showInNativeFileBrowser); + name_tab.getContextMenu().getItems().add(1, showInMenu); + name_tab.getContextMenu().getItems().add(2, copyResourceToClipboard); } // Override to include 'dirty' tab @Override - public void setLabel(final String label) - { + public void setLabel(final String label) { name = label; if (isDirty()) name_tab.setText(DIRTY + label); @@ -119,15 +160,13 @@ public void setLabel(final String label) // Add 'input' @Override - protected void fillInformation(final StringBuilder info) - { + protected void fillInformation(final StringBuilder info) { super.fillInformation(info); info.append("\n"); info.append(Messages.DockInput).append(getInput()); } - private static String extract_name(String path) - { + private static String extract_name(String path) { if (path == null) return null; @@ -136,7 +175,7 @@ private static String extract_name(String path) if (sep < 0) sep = path.lastIndexOf('\\'); if (sep >= 0) - path = path.substring(sep+1); + path = path.substring(sep + 1); // Remove extension sep = path.lastIndexOf('.'); @@ -145,19 +184,19 @@ private static String extract_name(String path) return path.substring(0, sep); } - /** Set input + /** + * Set input * - *

Registers the input to be persisted and restored. - * The tab tooltip indicates complete input, - * while tab label will be set to basic name (sans path and extension). - * For custom name, call setLabel after updating input - * in Platform.runLater() + *

Registers the input to be persisted and restored. + * The tab tooltip indicates complete input, + * while tab label will be set to basic name (sans path and extension). + * For custom name, call setLabel after updating input + * in Platform.runLater() * - * @param input Input - * @see DockItemWithInput#setLabel(String) + * @param input Input + * @see DockItemWithInput#setLabel(String) */ - public void setInput(final URI input) - { + public void setInput(final URI input) { this.input = input; final String name = input == null ? null : extract_name(input.getPath()); @@ -166,8 +205,7 @@ public void setInput(final URI input) { if (input == null) name_tab.setTooltip(new Tooltip(Messages.DockNotSaved)); - else - { + else { String decodedInputURI = URLDecoder.decode(input.toString(), StandardCharsets.UTF_8); name_tab.setTooltip(new Tooltip(decodedInputURI)); setLabel(name); @@ -175,43 +213,48 @@ public void setInput(final URI input) }); } - /** @return Input, which may be null (OK to call from any thread) */ - public URI getInput() - { + /** + * @return Input, which may be null (OK to call from any thread) + */ + public URI getInput() { return input; } - /** @return Current 'dirty' state */ - public boolean isDirty() - { + /** + * @return Current 'dirty' state + */ + public boolean isDirty() { return is_dirty.get(); } - /** Update 'dirty' state. + /** + * Update 'dirty' state. + * + *

May be called from any thread * - *

May be called from any thread - * @param dirty Updated 'dirty' state + * @param dirty Updated 'dirty' state */ - public void setDirty(final boolean dirty) - { + public void setDirty(final boolean dirty) { if (is_dirty.getAndSet(dirty) == dirty) return; // Dirty state changed. Update label on UI thread Platform.runLater(() -> setLabel(name)); } - /** @return Is "Save As" supported, i.e. have file extensions and a save handler? */ - public boolean isSaveAsSupported() - { - return file_extensions != null && save_handler != null; + /** + * @return Is "Save As" supported, i.e. have file extensions and a save handler? + */ + public boolean isSaveAsSupported() { + return file_extensions != null && save_handler != null; } - /** Called when user tries to close the tab - * @return Should the tab close? Otherwise it stays open. + /** + * Called when user tries to close the tab + * + * @return Should the tab close? Otherwise it stays open. */ - public Future okToClose() - { - if (! isDirty()) + public Future okToClose() { + if (!isDirty()) return CompletableFuture.completedFuture(true); final FutureTask promptToSave = new FutureTask(() -> { @@ -229,7 +272,7 @@ public Future okToClose() Platform.runLater(promptToSave); try { - ButtonType result = (ButtonType)promptToSave.get(); + ButtonType result = (ButtonType) promptToSave.get(); // Cancel the close request if (result == ButtonType.CANCEL) return CompletableFuture.completedFuture(false); @@ -252,40 +295,38 @@ public Future okToClose() return done; } - /** Save the content of the item to its current 'input' + /** + * Save the content of the item to its current 'input' * - *

Called by the framework when user invokes the 'Save*' - * menu items or when a 'dirty' tab is closed. + *

Called by the framework when user invokes the 'Save*' + * menu items or when a 'dirty' tab is closed. * - *

Will never be called when the item remains clean, - * i.e. never called {@link DockItemWithInput#setDirty(boolean)}. + *

Will never be called when the item remains clean, + * i.e. never called {@link DockItemWithInput#setDirty(boolean)}. * - * @param monitor {@link JobMonitor} for reporting progress - * @return true on success + * @param monitor {@link JobMonitor} for reporting progress + * @return true on success */ - public final boolean save(final JobMonitor monitor, Window parentWindow) - { + public final boolean save(final JobMonitor monitor, Window parentWindow) { // 'final' because any save customization should be possible // inside the save_handler - monitor.beginTask(MessageFormat.format(Messages.Saving , input)); + monitor.beginTask(MessageFormat.format(Messages.Saving, input)); - try - { // If there is no file (input is null or for example http:), + try { // If there is no file (input is null or for example http:), // call save_as to prompt for file File file = ResourceParser.getFile(getInput()); if (file == null) return save_as(monitor, parentWindow); - if (file.exists() && !file.canWrite()) - { // Warn on UI thread that file is read-only + if (file.exists() && !file.canWrite()) { // Warn on UI thread that file is read-only final CompletableFuture response = new CompletableFuture<>(); Platform.runLater(() -> { final Alert prompt = new Alert(AlertType.CONFIRMATION); prompt.setTitle(Messages.SavingAlertTitle); prompt.setResizable(true); - prompt.setHeaderText(MessageFormat.format(Messages.SavingAlert , file.toString())); + prompt.setHeaderText(MessageFormat.format(Messages.SavingAlert, file.toString())); DialogHelper.positionDialog(prompt, getTabPane(), -200, -200); response.complete(prompt.showAndWait().orElse(ButtonType.CANCEL)); @@ -300,13 +341,11 @@ public final boolean save(final JobMonitor monitor, Window parentWindow) if (save_handler == null) throw new Exception("No save_handler provided for 'dirty' " + toString()); save_handler.run(monitor); - } - catch (Exception ex) - { + } catch (Exception ex) { logger.log(Level.WARNING, "Save error", ex); Platform.runLater(() -> - ExceptionDetailsErrorDialog.openError(Messages.SavingHdr, - Messages.SavingErr + getLabel(), ex)); + ExceptionDetailsErrorDialog.openError(Messages.SavingHdr, + Messages.SavingErr + getLabel(), ex)); return false; } @@ -315,43 +354,42 @@ public final boolean save(final JobMonitor monitor, Window parentWindow) return true; } - /** @param file_extensions {@link ExtensionFilter}s - * @return List of valid file extensions, ignoring "*.*" + /** + * @param file_extensions {@link ExtensionFilter}s + * @return List of valid file extensions, ignoring "*.*" */ - private static List getValidExtensions(final ExtensionFilter[] file_extensions) - { + private static List getValidExtensions(final ExtensionFilter[] file_extensions) { final List valid = new ArrayList<>(); for (ExtensionFilter filter : file_extensions) for (String ext : filter.getExtensions()) - if (! ext.equals("*.*")) - { + if (!ext.equals("*.*")) { final int sep = ext.lastIndexOf('.'); if (sep > 0) - valid.add(ext.substring(sep+1)); + valid.add(ext.substring(sep + 1)); } return valid; } - /** @param file File - * @param valid List of valid file extensions - * @return true if file has one of the valid extensions + /** + * @param file File + * @param valid List of valid file extensions + * @return true if file has one of the valid extensions */ - private static boolean checkFileExtension(final File file, final List valid) - { + private static boolean checkFileExtension(final File file, final List valid) { final String path = file.getPath(); final int sep = path.lastIndexOf('.'); if (sep < 0) return false; - final String ext = path.substring(sep+1); + final String ext = path.substring(sep + 1); return valid.contains(ext); } - /** @param file File - * @param valid List of valid file extensions - * @return File updated to the first valid file extension + /** + * @param file File + * @param valid List of valid file extensions + * @return File updated to the first valid file extension */ - private static File setFileExtension(final File file, final List valid) - { + private static File setFileExtension(final File file, final List valid) { String path = file.getPath(); // Remove existing extension final int sep = path.lastIndexOf('.'); @@ -363,27 +401,26 @@ private static File setFileExtension(final File file, final List valid) return new File(path); } - /** Prompt for new file, then save the content of the item that file. + /** + * Prompt for new file, then save the content of the item that file. * - *

Called by the framework when user invokes the 'Save As' - * menu item. + *

Called by the framework when user invokes the 'Save As' + * menu item. * - *

Will never be called when the item does not report - * {@link #isSaveAsSupported()}. + *

Will never be called when the item does not report + * {@link #isSaveAsSupported()}. * - * @param monitor {@link JobMonitor} for reporting progress - * @return true on success + * @param monitor {@link JobMonitor} for reporting progress + * @return true on success */ - public final boolean save_as(final JobMonitor monitor, Window parentWindow) - { + public final boolean save_as(final JobMonitor monitor, Window parentWindow) { // 'final' because any save customization should be possible // inside the save_handler - try - { + try { // Prompt for file final File initial = ResourceParser.getFile(getInput()); final File file = new SaveAsDialog().promptForFile(parentWindow, - Messages.SaveAs, initial, file_extensions); + Messages.SaveAs, initial, file_extensions); if (file == null) return false; @@ -392,16 +429,15 @@ public final boolean save_as(final JobMonitor monitor, Window parentWindow) final CompletableFuture actual_file = new CompletableFuture<>(); if (checkFileExtension(file, valid)) actual_file.complete(file); - else - { + else { // Suggest name with valid extension final File suggestion = setFileExtension(file, valid); // Prompt on UI thread final String prompt = MessageFormat.format(Messages.SaveAsPrompt, - file, - valid.stream().collect(Collectors.joining(", ")), - suggestion); + file, + valid.stream().collect(Collectors.joining(", ")), + suggestion); Runnable confirmFileExtension = () -> { @@ -424,8 +460,7 @@ else if (response == ButtonType.NO) if (Platform.isFxApplicationThread()) { confirmFileExtension.run(); - } - else { + } else { Platform.runLater(confirmFileExtension); } @@ -441,8 +476,7 @@ else if (response == ButtonType.NO) setInput(ResourceParser.getURI(actual_file.get())); // Save in that file return save(monitor, getTabPane().getScene().getWindow()); - } - else { + } else { CompletableFuture waitForDialogToClose = new CompletableFuture<>(); Platform.runLater(() -> { String filename = FilenameUtils.getName(newInput.getPath()); @@ -458,7 +492,7 @@ else if (response == ButtonType.NO) dialog.getDialogPane().setPrefSize(width, height); dialog.getDialogPane().setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); dialog.setResizable(false); - DialogHelper.positionDialog(dialog, getTabPane(), -width/2, -height/2); + DialogHelper.positionDialog(dialog, getTabPane(), -width / 2, -height / 2); dialog.showAndWait(); waitForDialogToClose.complete(true); }); @@ -466,23 +500,20 @@ else if (response == ButtonType.NO) waitForDialogToClose.get(); save_as(monitor, getTabPane().getScene().getWindow()); } - } - catch (Exception ex) - { + } catch (Exception ex) { logger.log(Level.WARNING, "Save-As error", ex); Platform.runLater(() -> - ExceptionDetailsErrorDialog.openError(Messages.SaveAsErrHdr, - Messages.SaveAsErrMsg + getLabel(), ex)); + ExceptionDetailsErrorDialog.openError(Messages.SaveAsErrHdr, + Messages.SaveAsErrMsg + getLabel(), ex)); } return false; } /** * {@inheritDoc} - * */ + */ @Override - final protected void handleClosed() - { + final protected void handleClosed() { // Do the same as in the parent class, DockItem.handleClosed... super.handleClosed(); @@ -492,8 +523,7 @@ final protected void handleClosed() } @Override - public String toString() - { + public String toString() { return "DockItemWithInput(\"" + getLabel() + "\", " + getInput() + ")"; } } \ No newline at end of file diff --git a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties index 2c9a3c4882..0d99f3bba3 100644 --- a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties +++ b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties @@ -6,6 +6,7 @@ AppVersion=${version} AppRevision=${revision} AppVersionHeader=CS Studio Version CloseAllTabs=Close All Tabs +CopyResourcePath=Copy resource path to clipboard DeleteLayouts=Delete Layouts... DeleteLayoutsConfirmFmt=Delete {0} selected layouts? DeleteLayoutsInfo=Select layouts to delete @@ -116,6 +117,9 @@ SavingHdr=Save error ScreenshotErrHdr=Screenshot error ScreenshotErrMsg=Cannot write screenshot SelectTab=Select Tab +ShowIn=Show in... +ShowInFileBrowserApp=File Browser App +ShowInNativeFileBrowser=Native File Browser ShowStatusbar=Show Status bar ShowToolbar=Show Toolbar Time12h=12 h From 94a80882a56302aa3447ba3ad925ce278f6629fc Mon Sep 17 00:00:00 2001 From: georgweiss Date: Mon, 25 Mar 2024 20:03:06 +0100 Subject: [PATCH 56/92] Add null check when restoring snapshot containing disconnected items --- .../ui/snapshot/SnapshotTableViewController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java index 267d4e0642..7557728093 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java @@ -353,7 +353,9 @@ public void restore(Snapshot snapshot, Consumer> completion) { for (SnapshotItem entry : snapshot.getSnapshotData().getSnapshotItems()) { TableEntry e = tableEntryItems.get(getPVKey(entry.getConfigPv().getPvName(), entry.getConfigPv().isReadOnly())); - boolean restorable = e.selectedProperty().get() && !e.readOnlyProperty().get() && + boolean restorable = e.selectedProperty().get() && + !e.readOnlyProperty().get() && + entry.getValue() != null && !entry.getValue().equals(VNoData.INSTANCE); if (restorable) { From cceba59044c3fef1efc62ca3deedae0923ff516b Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Wed, 27 Mar 2024 13:19:05 -0400 Subject: [PATCH 57/92] Stop all annunciation sounds when closing the Annunciator --- .../alarm/audio/annunciator/AudioAnnunciator.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java index 9494704d3e..678f5266de 100644 --- a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java +++ b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java @@ -11,6 +11,8 @@ import org.phoebus.applications.alarm.ui.annunciator.Annunciator; import org.phoebus.applications.alarm.ui.annunciator.AnnunciatorMessage; +import java.util.List; + /** * Annunciator class. Uses Audio files to annunciate passed messages. * @@ -56,5 +58,7 @@ public void speak(final AnnunciatorMessage message) { */ @Override public void shutdown() { + List.of(alarmSound, minorAlarmSound, majorAlarmSound, invalidAlarmSound, undefinedAlarmSound) + .forEach(sound -> sound.stop()); } } From a57579af8514b2447ad93afc02f641f1529562bc Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Wed, 27 Mar 2024 13:19:49 -0400 Subject: [PATCH 58/92] Call the shutdown before the thread join --- .../alarm/ui/annunciator/AnnunciatorController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java index 8904afbc1f..581a3c1828 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java @@ -168,11 +168,11 @@ public void shutdown() throws InterruptedException { // Send magic message that wakes annunciatorThread and causes it to exit to_annunciate.offer(LAST_MESSAGE); - // The thread should shutdown - process_thread.join(2000); - // Deallocate the annunciator's voice. loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().shutdown()); + // The thread should shutdown + process_thread.join(2000); + } } From 7bcc02ccdce0a4fc172d4e2dba86a69a5cac4a49 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 28 Mar 2024 12:17:12 -0400 Subject: [PATCH 59/92] Add a preference for the max duration of the alarm sound clip --- .../alarm/audio/annunciator/Preferences.java | 9 ++++++++- .../resources/audio_annunciator_preferences.properties | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java index 1619a10dab..cbbf2bf89e 100644 --- a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java +++ b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java @@ -35,6 +35,8 @@ public class Preferences { public static String undefined_alarm_sound_url; @Preference public static int volume; + @Preference + public static int max_alarm_duration; static { final PreferencesReader prefs = AnnotatedPreferences.initialize(AudioAnnunciator.class, Preferences.class, "/audio_annunciator_preferences.properties"); @@ -47,7 +49,12 @@ public class Preferences { private static String useLocalResourceIfUnspecified(String alarmResource) { if (alarmResource == null || alarmResource.isEmpty()) { - return Preferences.class.getResource("/sounds/mixkit-classic-alarm-995.wav").toString(); + // If only the alarm sound url is set, in a case where we want to use the same alarm sound for all severties + if (!alarm_sound_url.isEmpty()) { + return alarm_sound_url; + } else { + return Preferences.class.getResource("/sounds/mixkit-classic-alarm-995.wav").toString(); + } } else { return alarmResource; } diff --git a/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties b/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties index 019e9e02ae..c8383b2611 100644 --- a/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties +++ b/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties @@ -18,3 +18,6 @@ undefined_alarm_sound_url= # audio clip volume (0-100) volume=100 + +# max alarm Duration in seconds. +max_alarm_duration=10 \ No newline at end of file From f1d79c01b102698fac020c61c622e4a0e49b66e7 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 28 Mar 2024 12:17:50 -0400 Subject: [PATCH 60/92] use the media player to handle longer audio files ( stop time, memory, etc ) --- .../audio/annunciator/AudioAnnunciator.java | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java index 678f5266de..6e965524fb 100644 --- a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java +++ b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java @@ -8,6 +8,9 @@ package org.phoebus.applications.alarm.audio.annunciator; import javafx.scene.media.AudioClip; +import javafx.scene.media.Media; +import javafx.scene.media.MediaPlayer; +import javafx.util.Duration; import org.phoebus.applications.alarm.ui.annunciator.Annunciator; import org.phoebus.applications.alarm.ui.annunciator.AnnunciatorMessage; @@ -20,21 +23,27 @@ */ @SuppressWarnings("nls") public class AudioAnnunciator implements Annunciator { - private final AudioClip alarmSound; - private final AudioClip minorAlarmSound; - private final AudioClip majorAlarmSound; - private final AudioClip invalidAlarmSound; - private final AudioClip undefinedAlarmSound; + private final MediaPlayer alarmSound; + private final MediaPlayer minorAlarmSound; + private final MediaPlayer majorAlarmSound; + private final MediaPlayer invalidAlarmSound; + private final MediaPlayer undefinedAlarmSound; /** * Constructor */ public AudioAnnunciator() { - alarmSound = new AudioClip(Preferences.alarm_sound_url); - minorAlarmSound = new AudioClip(Preferences.minor_alarm_sound_url); - majorAlarmSound = new AudioClip(Preferences.major_alarm_sound_url); - invalidAlarmSound = new AudioClip(Preferences.invalid_alarm_sound_url); - undefinedAlarmSound = new AudioClip(Preferences.undefined_alarm_sound_url); + alarmSound = new MediaPlayer(new Media(Preferences.alarm_sound_url)); + minorAlarmSound = new MediaPlayer(new Media(Preferences.minor_alarm_sound_url)); + majorAlarmSound = new MediaPlayer(new Media(Preferences.major_alarm_sound_url)); + invalidAlarmSound = new MediaPlayer(new Media(Preferences.invalid_alarm_sound_url)); + undefinedAlarmSound = new MediaPlayer(new Media(Preferences.undefined_alarm_sound_url)); + // configure the media players for the different alarm sounds + List.of(alarmSound, minorAlarmSound, majorAlarmSound, invalidAlarmSound, undefinedAlarmSound) + .forEach(sound -> { + sound.setStopTime(Duration.seconds(Preferences.max_alarm_duration)); + sound.setVolume(Preferences.volume); + }); } /** @@ -45,11 +54,11 @@ public AudioAnnunciator() { @Override public void speak(final AnnunciatorMessage message) { switch (message.severity) { - case MINOR -> minorAlarmSound.play(Preferences.volume); - case MAJOR -> majorAlarmSound.play(Preferences.volume); - case INVALID -> invalidAlarmSound.play(Preferences.volume); - case UNDEFINED -> undefinedAlarmSound.play(Preferences.volume); - default -> alarmSound.play(Preferences.volume); + case MINOR -> minorAlarmSound.play(); + case MAJOR -> majorAlarmSound.play(); + case INVALID -> invalidAlarmSound.play(); + case UNDEFINED -> undefinedAlarmSound.play(); + default -> alarmSound.play(); } } @@ -59,6 +68,9 @@ public void speak(final AnnunciatorMessage message) { @Override public void shutdown() { List.of(alarmSound, minorAlarmSound, majorAlarmSound, invalidAlarmSound, undefinedAlarmSound) - .forEach(sound -> sound.stop()); + .forEach(sound -> { + sound.stop(); + sound.dispose(); + }); } } From 7274d6277147ae394ebd7034cc3ce239dda0b626 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 28 Mar 2024 14:09:08 -0400 Subject: [PATCH 61/92] Revert to the old cleanup/processor thread should be closed first --- .../alarm/ui/annunciator/AnnunciatorController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java index 581a3c1828..5288e6da13 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java @@ -168,11 +168,12 @@ public void shutdown() throws InterruptedException { // Send magic message that wakes annunciatorThread and causes it to exit to_annunciate.offer(LAST_MESSAGE); - // Deallocate the annunciator's voice. - loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().shutdown()); // The thread should shutdown process_thread.join(2000); + // Deallocate the annunciator's voice. + loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().shutdown()); + } } From 8e61ba6585082159ce04dd04c62d9a60caeff3e1 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 2 Apr 2024 11:29:07 +0200 Subject: [PATCH 62/92] Update to FileBrowserController to support highlighting of file --- .../filebrowser/FileBrowserController.java | 52 ++++++++++++++++++- .../phoebus/ui/docking/DockItemWithInput.java | 2 +- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 15da2953cb..ff3f318235 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -59,6 +59,13 @@ public class FileBrowserController { private final Menu openWith = new Menu(Messages.OpenWith, ImageCache.getImageView(PhoebusApplication.class, "/icons/fldr_obj.png")); private final ContextMenu contextMenu = new ContextMenu(); + /** + * A {@link File} object representing a file (i.e. not a directory) in case client calls + * {@link #setRoot(File)} using a file. If the {@link #setRoot(File)} call specifies a directory, + * this is set to null. + */ + private File fileToHighlight; + public FileBrowserController() { monitor = new DirectoryMonitor(this::handleFilesystemChanges); @@ -276,6 +283,34 @@ public void initialize() { contextMenu.getItems().addAll(open, openWith); treeView.setOnKeyPressed(this::handleKeys); + + // Listen for changes in expanded item count and then highlight file on change. + // The call to #highlightFile is put here as update of the TreeView is controlled + // by JavaFX whenever a new root is set. + treeView.expandedItemCountProperty().addListener((observableValue, oldValue, newValue) -> { + if(oldValue.intValue() > 0 && !newValue.equals(oldValue)){ + highlightFile(); + } + }); + } + + /** + * Highlights and scrolls to a file if {@link #setRoot(File)} was called with + * a file and not directory object. + */ + private void highlightFile(){ + if(fileToHighlight == null){ + return; + } + TreeItem root = treeView.getRoot(); + List children = root.getChildren(); + for(TreeItem child : children){ + if(((FileInfo)child.getValue()).file.equals(fileToHighlight)){ + treeView.getSelectionModel().select(child); + treeView.scrollTo(treeView.getSelectionModel().getSelectedIndex()); + return; + } + } } TreeTableView getView() @@ -476,9 +511,22 @@ public void setNewRoot() { setRoot(p.toFile()); } - /** @param directory Desired root directory */ - public void setRoot(final File directory) + /** + * Set a new root directory, or set a new root directory and then highlight the file. + * @param file A {@link File} object representing a directory or a file. In the latter case the + * file's parent is used to set the root of the {@link TreeView}. + */ + public void setRoot(final File file) { + File directory; + if(file.isFile()){ + directory = file.getParentFile(); + this.fileToHighlight = file; + } + else{ + directory = file; + this.fileToHighlight = null; + } monitor.setRoot(directory); path.setText(directory.toString()); treeView.setRoot(new FileTreeItem(monitor, directory)); diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 0c01c67361..1c29dd9b7b 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -126,7 +126,7 @@ protected void configureContextMenu(ContextMenu menu) { final Menu showInMenu = new Menu(Messages.ShowIn, new ImageView(showInIcon)); final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp); showInFileBrowser.setOnAction(e -> { - ApplicationService.createInstance("file_browser", new File(input.getPath()).getParentFile().toURI()); + ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); }); final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); showInNativeFileBrowser.setOnAction(e -> { From 95604460560331feb29103408937cce1f9fc1d2b Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Tue, 2 Apr 2024 09:41:15 -0400 Subject: [PATCH 63/92] Trying to avoid parallel plays of the annunciator audio --- .../audio/annunciator/AudioAnnunciator.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java index 6e965524fb..7d9600b967 100644 --- a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java +++ b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java @@ -54,14 +54,22 @@ public AudioAnnunciator() { @Override public void speak(final AnnunciatorMessage message) { switch (message.severity) { - case MINOR -> minorAlarmSound.play(); - case MAJOR -> majorAlarmSound.play(); - case INVALID -> invalidAlarmSound.play(); - case UNDEFINED -> undefinedAlarmSound.play(); - default -> alarmSound.play(); + case MINOR -> speakAlone(minorAlarmSound); + case MAJOR -> speakAlone(majorAlarmSound); + case INVALID -> speakAlone(invalidAlarmSound); + case UNDEFINED -> speakAlone(undefinedAlarmSound); + default -> speakAlone(alarmSound); } } + synchronized private void speakAlone(MediaPlayer alarm) { + List.of(alarmSound, minorAlarmSound, majorAlarmSound, invalidAlarmSound, undefinedAlarmSound) + .forEach(sound -> { + sound.stop(); + }); + alarm.play(); + } + /** * Deallocates the voice. */ From fa27f6ba172e3e657798934464413a4983e73f64 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 2 Apr 2024 20:21:01 +0200 Subject: [PATCH 64/92] Handle http(s) resources, add null check before configuring context menu --- .../applications/filebrowser/FileBrowser.java | 12 ++++-- .../filebrowser/FileBrowserController.java | 36 +++++++++-------- .../phoebus/ui/docking/DockItemWithInput.java | 39 +++++++++++-------- .../main/resources/static/CSSTUDIO-1316.bob | 31 +++++++++++++++ 4 files changed, 82 insertions(+), 36 deletions(-) create mode 100644 services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowser.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowser.java index deadaf19ee..d83506b943 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowser.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowser.java @@ -34,7 +34,7 @@ public class FileBrowser implements AppInstance private FileBrowserController controller; - FileBrowser(final AppDescriptor app, final File directory) + FileBrowser(final AppDescriptor app, final File file) { this.app = app; @@ -58,8 +58,14 @@ public class FileBrowser implements AppInstance final DockItem tab = new DockItem(this, content); DockPane.getActiveDockPane().addTab(tab); - if (controller != null && directory != null) - controller.setRoot(directory); + if (controller != null && file != null){ + if(file.isDirectory()){ + controller.setRoot(file); + } + else{ + controller.setRootAndHighlight(file); + } + } tab.addClosedNotification(controller::shutdown); } diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index ff3f318235..302ea9d90b 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -285,10 +285,10 @@ public void initialize() { treeView.setOnKeyPressed(this::handleKeys); // Listen for changes in expanded item count and then highlight file on change. - // The call to #highlightFile is put here as update of the TreeView is controlled - // by JavaFX whenever a new root is set. + // The call to #highlightFile is put here as update of the TreeView is executed + // asynchronously by JavaFX whenever a new root is set. treeView.expandedItemCountProperty().addListener((observableValue, oldValue, newValue) -> { - if(oldValue.intValue() > 0 && !newValue.equals(oldValue)){ + if(newValue.intValue() > 0){ highlightFile(); } }); @@ -511,25 +511,27 @@ public void setNewRoot() { setRoot(p.toFile()); } + /** @param directory Desired root directory */ + public void setRoot(final File directory) + { + monitor.setRoot(directory); + path.setText(directory.toString()); + treeView.setRoot(new FileTreeItem(monitor, directory)); + } + /** - * Set a new root directory, or set a new root directory and then highlight the file. - * @param file A {@link File} object representing a directory or a file. In the latter case the - * file's parent is used to set the root of the {@link TreeView}. + * Set a new root directory and highlight the file provided (unless it is a directory). + * @param file A {@link File} object representing a file (or directory). */ - public void setRoot(final File file) - { - File directory; - if(file.isFile()){ - directory = file.getParentFile(); - this.fileToHighlight = file; + public void setRootAndHighlight(final File file){ + if(file.isDirectory()){ + this.fileToHighlight = null; + setRoot(file); } else{ - directory = file; - this.fileToHighlight = null; + this.fileToHighlight = file; + setRoot(file.getParentFile()); } - monitor.setRoot(directory); - path.setText(directory.toString()); - treeView.setRoot(new FileTreeItem(monitor, directory)); } /** @return Root directory */ diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 1c29dd9b7b..e536b0be87 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -123,28 +123,35 @@ public DockItemWithInput(final AppInstance application, final Node content, fina protected void configureContextMenu(ContextMenu menu) { super.configureContextMenu(menu); - final Menu showInMenu = new Menu(Messages.ShowIn, new ImageView(showInIcon)); - final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp); - showInFileBrowser.setOnAction(e -> { - ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); - }); - final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); - showInNativeFileBrowser.setOnAction(e -> { - try { - Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - }); + if(input == null){ + return; + } + boolean isHttpResource = input.toString().toLowerCase().startsWith("http"); final MenuItem copyResourceToClipboard = new MenuItem(Messages.CopyResourcePath, new ImageView(copyToClipboardIcon)); copyResourceToClipboard.setOnAction(e -> { final ClipboardContent content = new ClipboardContent(); - content.putString(input.getPath()); + content.putString(isHttpResource ? input.toString() : input.getPath()); Clipboard.getSystemClipboard().setContent(content); }); - showInMenu.getItems().addAll(showInFileBrowser, showInNativeFileBrowser); - name_tab.getContextMenu().getItems().add(1, showInMenu); + if(!isHttpResource){ + final Menu showInMenu = new Menu(Messages.ShowIn, new ImageView(showInIcon)); + final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp); + showInFileBrowser.setOnAction(e -> { + ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); + }); + final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); + showInNativeFileBrowser.setOnAction(e -> { + try { + Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }); + showInMenu.getItems().addAll(showInFileBrowser, showInNativeFileBrowser); + name_tab.getContextMenu().getItems().add(1, showInMenu); + } + name_tab.getContextMenu().getItems().add(2, copyResourceToClipboard); } diff --git a/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob b/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob new file mode 100644 index 0000000000..963a73c631 --- /dev/null +++ b/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob @@ -0,0 +1,31 @@ + + + + Display + 920 + 300 + + LinearMeter + loc://x(10) + 200 + 340 + -100.0 + -100.0 + 100.0 + 100.0 + + + Action Button + + + loc://x + 1 + WritePV + + + 780 + 340 + 220 + 100 + + From 1c554e39a14a515c7f088fdabee64ab3c1912c59 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 3 Apr 2024 11:30:07 +0200 Subject: [PATCH 65/92] Adding some comments/Javadoc --- .../filebrowser/FileBrowserController.java | 8 ++++---- .../org/phoebus/ui/docking/DockItemWithInput.java | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 302ea9d90b..59fd9e34e9 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -61,8 +61,8 @@ public class FileBrowserController { /** * A {@link File} object representing a file (i.e. not a directory) in case client calls - * {@link #setRoot(File)} using a file. If the {@link #setRoot(File)} call specifies a directory, - * this is set to null. + * {@link #setRootAndHighlight(File)} using a file. If the {@link #setRootAndHighlight(File)} call + * specifies a directory, this is set to null. */ private File fileToHighlight; @@ -295,8 +295,8 @@ public void initialize() { } /** - * Highlights and scrolls to a file if {@link #setRoot(File)} was called with - * a file and not directory object. + * Highlights and scrolls to a file if {@link #setRootAndHighlight(File)} was called with + * a file and not directory {@link File} object. */ private void highlightFile(){ if(fileToHighlight == null){ diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index e536b0be87..776bd6bf4e 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -121,6 +121,18 @@ public DockItemWithInput(final AppInstance application, final Node content, fina }); } + /** + * Configures additional and optional items in the context menu if the resource filed is non-null: + *

    + *
  • Copy the resource to clipboard
  • + *
  • For file resources a sub-menu with items:
  • + *
      + *
    • Open and highlight file in Phoebus File Browser app
    • + *
    • Open file's parent directory in native file browser
    • + *
    + *
+ * @param menu The {@link ContextMenu} to update. + */ protected void configureContextMenu(ContextMenu menu) { super.configureContextMenu(menu); if(input == null){ From ec505859c19f6caf8bde38711a2d73fd3a0f110f Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 3 Apr 2024 11:32:17 +0200 Subject: [PATCH 66/92] Typos... --- .../main/java/org/phoebus/ui/docking/DockItemWithInput.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 776bd6bf4e..97434c2a2d 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -122,12 +122,12 @@ public DockItemWithInput(final AppInstance application, final Node content, fina } /** - * Configures additional and optional items in the context menu if the resource filed is non-null: - *
    + * Configures additional and optional items in the tab header context menu if the resource field is non-null: + * *
  • Copy the resource to clipboard
  • *
  • For file resources a sub-menu with items:
  • *
      - *
    • Open and highlight file in Phoebus File Browser app
    • + *
    • Open and highlight file in new File Browser instance
    • *
    • Open file's parent directory in native file browser
    • *
    *
From f918f3e91c4d5d06757894a03c391f90b86cc96d Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 3 Apr 2024 14:01:01 +0200 Subject: [PATCH 67/92] Desktop.isSupported call needed to avoid UI hand on (at least) Ubunbtu --- .../phoebus/ui/docking/DockItemWithInput.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 97434c2a2d..fd25d40a3e 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -152,15 +152,18 @@ protected void configureContextMenu(ContextMenu menu) { showInFileBrowser.setOnAction(e -> { ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); }); - final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); - showInNativeFileBrowser.setOnAction(e -> { - try { - Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - }); - showInMenu.getItems().addAll(showInFileBrowser, showInNativeFileBrowser); + showInMenu.getItems().add(showInFileBrowser); + if(Desktop.isDesktopSupported()){ + final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); + showInNativeFileBrowser.setOnAction(e -> { + try { + Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }); + showInMenu.getItems().add(showInNativeFileBrowser); + } name_tab.getContextMenu().getItems().add(1, showInMenu); } From e994c419315ad2816d70aa5925daec1b4384e6cf Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 3 Apr 2024 15:30:40 +0200 Subject: [PATCH 68/92] Removed 'Show in native file browser' as this does not work on Linux --- .../org/phoebus/ui/application/Messages.java | 2 -- .../phoebus/ui/docking/DockItemWithInput.java | 19 ++++--------------- .../ui/application/messages.properties | 4 +--- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java index a16789f778..85e440852f 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java @@ -131,9 +131,7 @@ public class Messages public static String ScreenshotErrHdr; public static String ScreenshotErrMsg; public static String SelectTab; - public static String ShowIn; public static String ShowInFileBrowserApp; - public static String ShowInNativeFileBrowser; public static String ShowStatusbar; public static String ShowToolbar; public static String Time12h; diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index fd25d40a3e..34893bed73 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -89,6 +89,8 @@ public class DockItemWithInput extends DockItem { private final static Image copyToClipboardIcon = ImageCache.getImage(DockItem.class, "/icons/copy.png"); + private final static Image fileBrowserIcon = ImageCache.getImage(DockItem.class, "/icons/filebrowser.png"); + private final static Image showInIcon = ImageCache.getImage(DockItem.class, "/icons/fldr_obj.png"); /** @@ -147,24 +149,11 @@ protected void configureContextMenu(ContextMenu menu) { }); if(!isHttpResource){ - final Menu showInMenu = new Menu(Messages.ShowIn, new ImageView(showInIcon)); - final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp); + final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp, new ImageView(fileBrowserIcon)); showInFileBrowser.setOnAction(e -> { ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); }); - showInMenu.getItems().add(showInFileBrowser); - if(Desktop.isDesktopSupported()){ - final MenuItem showInNativeFileBrowser = new MenuItem(Messages.ShowInNativeFileBrowser); - showInNativeFileBrowser.setOnAction(e -> { - try { - Desktop.getDesktop().open(new File(input.getPath()).getParentFile()); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - }); - showInMenu.getItems().add(showInNativeFileBrowser); - } - name_tab.getContextMenu().getItems().add(1, showInMenu); + name_tab.getContextMenu().getItems().add(1, showInFileBrowser); } name_tab.getContextMenu().getItems().add(2, copyResourceToClipboard); diff --git a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties index 0d99f3bba3..66d2cb0609 100644 --- a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties +++ b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties @@ -117,9 +117,7 @@ SavingHdr=Save error ScreenshotErrHdr=Screenshot error ScreenshotErrMsg=Cannot write screenshot SelectTab=Select Tab -ShowIn=Show in... -ShowInFileBrowserApp=File Browser App -ShowInNativeFileBrowser=Native File Browser +ShowInFileBrowserApp=Show in File Browser app ShowStatusbar=Show Status bar ShowToolbar=Show Toolbar Time12h=12 h From bb46dccf22267668944e3549a682c582f1a171d4 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 4 Apr 2024 15:14:36 +0200 Subject: [PATCH 69/92] Changes based on review feed-back --- .../filebrowser/FileBrowserController.java | 372 ++++++++---------- .../phoebus/ui/docking/DockItemWithInput.java | 24 +- .../main/resources/static/CSSTUDIO-1316.bob | 31 -- 3 files changed, 183 insertions(+), 244 deletions(-) delete mode 100644 services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 59fd9e34e9..2d0df0164d 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -3,10 +3,22 @@ import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.scene.control.*; +import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.TextField; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeTableColumn; +import javafx.scene.control.TreeTableView; import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; import javafx.scene.input.ContextMenuEvent; @@ -59,6 +71,8 @@ public class FileBrowserController { private final Menu openWith = new Menu(Messages.OpenWith, ImageCache.getImageView(PhoebusApplication.class, "/icons/fldr_obj.png")); private final ContextMenu contextMenu = new ContextMenu(); + private ExandedCountChangeListener exandedCountChangeListener; + /** * A {@link File} object representing a file (i.e. not a directory) in case client calls * {@link #setRootAndHighlight(File)} using a file. If the {@link #setRootAndHighlight(File)} call @@ -66,22 +80,17 @@ public class FileBrowserController { */ private File fileToHighlight; - public FileBrowserController() - { + public FileBrowserController() { monitor = new DirectoryMonitor(this::handleFilesystemChanges); } - private void handleFilesystemChanges(final File file, final DirectoryMonitor.Change change) - { + private void handleFilesystemChanges(final File file, final DirectoryMonitor.Change change) { // The notification might address a file that the file browser itself just added/renamed/removed, // and the file browser is already in the process of updating itself. // Wait a little to allow that to happen - try - { + try { Thread.sleep(1000); - } - catch (InterruptedException ex) - { + } catch (InterruptedException ex) { return; } @@ -94,11 +103,9 @@ else if (change == DirectoryMonitor.Change.REMOVED) assertTreeDoesntContain(treeView.getRoot(), file.toPath()); } - private void assertTreeContains(final TreeItem item, final Path file) - { + private void assertTreeContains(final TreeItem item, final Path file) { final Path dir = item.getValue().file.toPath(); - if (! file.startsWith(dir)) - { + if (!file.startsWith(dir)) { logger.log(Level.WARNING, "Cannot check for " + file + " within " + dir); return; } @@ -112,23 +119,20 @@ private void assertTreeContains(final TreeItem item, final Path file) logger.log(Level.FINE, () -> "Looking for " + sub + " in " + dir); for (TreeItem child : item.getChildren()) - if (sub.equals(child.getValue().file)) - { - logger.log(Level.FINE,"Found it!"); + if (sub.equals(child.getValue().file)) { + logger.log(Level.FINE, "Found it!"); if (sub.isDirectory()) assertTreeContains(child, file); return; } logger.log(Level.FINE, () -> "Forcing refresh of " + dir + " to show " + sub); - Platform.runLater(() -> ((FileTreeItem)item).forceRefresh()); + Platform.runLater(() -> ((FileTreeItem) item).forceRefresh()); } - private void refreshTreeItem(final TreeItem item, final Path file) - { + private void refreshTreeItem(final TreeItem item, final Path file) { final Path dir = item.getValue().file.toPath(); - if (dir.equals(file)) - { + if (dir.equals(file)) { logger.log(Level.FINE, () -> "Forcing refresh of " + item); Platform.runLater(() -> { @@ -140,8 +144,7 @@ private void refreshTreeItem(final TreeItem item, final Path file) return; } - if (! file.startsWith(dir)) - { + if (!file.startsWith(dir)) { logger.log(Level.WARNING, "Cannot refresh " + file + " within " + dir); return; } @@ -156,12 +159,10 @@ private void refreshTreeItem(final TreeItem item, final Path file) } - private void assertTreeDoesntContain(final TreeItem item, final Path file) - { + private void assertTreeDoesntContain(final TreeItem item, final Path file) { final Path dir = item.getValue().file.toPath(); logger.log(Level.FINE, () -> "Does " + dir + " still contain " + file + "?"); - if (! file.startsWith(dir)) - { + if (!file.startsWith(dir)) { logger.log(Level.FINE, "Can't!"); return; } @@ -169,16 +170,14 @@ private void assertTreeDoesntContain(final TreeItem item, final Path f final int dir_len = dir.getNameCount(); final File sub = new File(item.getValue().file, file.getName(dir_len).toString()); for (TreeItem child : item.getChildren()) - if (sub.equals(child.getValue().file)) - { + if (sub.equals(child.getValue().file)) { // Found file or sub path to it.. if (sub.isDirectory()) assertTreeDoesntContain(child, file); - else - { // Found the file still listed as a child of 'item', + else { // Found the file still listed as a child of 'item', // so refresh 'item' logger.log(Level.FINE, () -> "Forcing refresh of " + dir + " to hide " + sub); - Platform.runLater(() -> ((FileTreeItem)item).forceRefresh()); + Platform.runLater(() -> ((FileTreeItem) item).forceRefresh()); } return; } @@ -186,15 +185,15 @@ private void assertTreeDoesntContain(final TreeItem item, final Path f logger.log(Level.FINE, "Not found, all good"); } - /** Try to open resource, show error dialog on failure - * @param file Resource to open - * @param stage Stage to use to prompt for specific app. - * Otherwise null to use default app. + /** + * Try to open resource, show error dialog on failure + * + * @param file Resource to open + * @param stage Stage to use to prompt for specific app. + * Otherwise null to use default app. */ - private void openResource(final File file, final Stage stage) - { - if (! ApplicationLauncherService.openFile(file, stage != null, stage)) - { + private void openResource(final File file, final Stage stage) { + if (!ApplicationLauncherService.openFile(file, stage != null, stage)) { final Alert alert = new Alert(AlertType.ERROR); alert.setHeaderText(Messages.OpenAlert1 + file + Messages.OpenAlert2); DialogHelper.positionDialog(alert, treeView, -300, -200); @@ -202,17 +201,18 @@ private void openResource(final File file, final Stage stage) } } - /** Try to open all the currently selected resources */ - private void openSelectedResources() - { + /** + * Try to open all the currently selected resources + */ + private void openSelectedResources() { treeView.selectionModelProperty() .getValue() .getSelectedItems() .forEach(item -> - { - if (item.isLeaf()) - openResource(item.getValue().file, null); - }); + { + if (item.isLeaf()) + openResource(item.getValue().file, null); + }); } @FXML @@ -250,17 +250,15 @@ public void initialize() { // Available with, less space used for the TableMenuButton '+' on the right // so that the up/down column sort markers remain visible double available = treeView.getWidth() - 10; - if (name_col.isVisible()) - { + if (name_col.isVisible()) { // Only name visible? Use the space! if (!size_col.isVisible() && !time_col.isVisible()) name_col.setPrefWidth(available); else available -= name_col.getWidth(); } - if (size_col.isVisible()) - { - if (! time_col.isVisible()) + if (size_col.isVisible()) { + if (!time_col.isVisible()) size_col.setPrefWidth(available); else available -= size_col.getWidth(); @@ -283,128 +281,88 @@ public void initialize() { contextMenu.getItems().addAll(open, openWith); treeView.setOnKeyPressed(this::handleKeys); - - // Listen for changes in expanded item count and then highlight file on change. - // The call to #highlightFile is put here as update of the TreeView is executed - // asynchronously by JavaFX whenever a new root is set. - treeView.expandedItemCountProperty().addListener((observableValue, oldValue, newValue) -> { - if(newValue.intValue() > 0){ - highlightFile(); - } - }); - } - - /** - * Highlights and scrolls to a file if {@link #setRootAndHighlight(File)} was called with - * a file and not directory {@link File} object. - */ - private void highlightFile(){ - if(fileToHighlight == null){ - return; - } - TreeItem root = treeView.getRoot(); - List children = root.getChildren(); - for(TreeItem child : children){ - if(((FileInfo)child.getValue()).file.equals(fileToHighlight)){ - treeView.getSelectionModel().select(child); - treeView.scrollTo(treeView.getSelectionModel().getSelectedIndex()); - return; - } - } } - TreeTableView getView() - { + TreeTableView getView() { return treeView; } - private void handleKeys(final KeyEvent event) - { - switch(event.getCode()) - { - case ENTER: // Open - { - openSelectedResources(); - event.consume(); - break; - } - case F2: // Rename file - { - final ObservableList> items = treeView.selectionModelProperty().getValue().getSelectedItems(); - if (items.size() == 1) + private void handleKeys(final KeyEvent event) { + switch (event.getCode()) { + case ENTER: // Open { - final TreeItem item = items.get(0); - if (item.isLeaf()) - new RenameAction(treeView, item).fire(); + openSelectedResources(); + event.consume(); + break; } - event.consume(); - break; - } - case DELETE: // Delete - { - final ObservableList> items = treeView.selectionModelProperty().getValue().getSelectedItems(); - if (items.size() > 0) - new DeleteAction(treeView, items).fire(); - event.consume(); - break; - } - case C: // Copy - { - if (event.isShortcutDown()) + case F2: // Rename file { final ObservableList> items = treeView.selectionModelProperty().getValue().getSelectedItems(); - new CopyPath(items).fire(); + if (items.size() == 1) { + final TreeItem item = items.get(0); + if (item.isLeaf()) + new RenameAction(treeView, item).fire(); + } event.consume(); + break; } - break; - } - case V: // Paste - { - if (event.isShortcutDown()) + case DELETE: // Delete { - TreeItem item = treeView.selectionModelProperty().getValue().getSelectedItem(); - if (item == null) - item = treeView.getRoot(); - else if (item.isLeaf()) - item = item.getParent(); - new PasteFiles(treeView, item).fire(); + final ObservableList> items = treeView.selectionModelProperty().getValue().getSelectedItems(); + if (items.size() > 0) + new DeleteAction(treeView, items).fire(); event.consume(); + break; } - break; - } - case ESCAPE: // De-select - treeView.selectionModelProperty().get().clearSelection(); - break; - default: - if ((event.getCode().compareTo(KeyCode.A) >= 0 && event.getCode().compareTo(KeyCode.Z) <= 0) || - (event.getCode().compareTo(KeyCode.DIGIT0) >= 0 && event.getCode().compareTo(KeyCode.DIGIT9) <= 0)) + case C: // Copy { - // Move selection to first/next file that starts with that character - final String ch = event.getCode().getChar().toLowerCase(); - - final TreeItem selected = treeView.selectionModelProperty().getValue().getSelectedItem(); - final ObservableList> siblings; - int index; - if (selected != null) - { // Start after the selected item - siblings = selected.getParent().getChildren(); - index = siblings.indexOf(selected); + if (event.isShortcutDown()) { + final ObservableList> items = treeView.selectionModelProperty().getValue().getSelectedItems(); + new CopyPath(items).fire(); + event.consume(); } - else if (!treeView.getRoot().getChildren().isEmpty()) - { // Start at the root - siblings = treeView.getRoot().getChildren(); - index = -1; + break; + } + case V: // Paste + { + if (event.isShortcutDown()) { + TreeItem item = treeView.selectionModelProperty().getValue().getSelectedItem(); + if (item == null) + item = treeView.getRoot(); + else if (item.isLeaf()) + item = item.getParent(); + new PasteFiles(treeView, item).fire(); + event.consume(); } - else - break; - for (++index; index < siblings.size(); ++index) - if (siblings.get(index).getValue().file.getName().toLowerCase().startsWith(ch)) - { - treeView.selectionModelProperty().get().clearSelection(); - treeView.selectionModelProperty().get().select(siblings.get(index)); - break; - } + break; } + case ESCAPE: // De-select + treeView.selectionModelProperty().get().clearSelection(); + break; + default: + if ((event.getCode().compareTo(KeyCode.A) >= 0 && event.getCode().compareTo(KeyCode.Z) <= 0) || + (event.getCode().compareTo(KeyCode.DIGIT0) >= 0 && event.getCode().compareTo(KeyCode.DIGIT9) <= 0)) { + // Move selection to first/next file that starts with that character + final String ch = event.getCode().getChar().toLowerCase(); + + final TreeItem selected = treeView.selectionModelProperty().getValue().getSelectedItem(); + final ObservableList> siblings; + int index; + if (selected != null) { // Start after the selected item + siblings = selected.getParent().getChildren(); + index = siblings.indexOf(selected); + } else if (!treeView.getRoot().getChildren().isEmpty()) { // Start at the root + siblings = treeView.getRoot().getChildren(); + index = -1; + } else + break; + for (++index; index < siblings.size(); ++index) + if (siblings.get(index).getValue().file.getName().toLowerCase().startsWith(ch)) { + treeView.selectionModelProperty().get().clearSelection(); + treeView.selectionModelProperty().get().select(siblings.get(index)); + break; + } + } } } @@ -414,18 +372,15 @@ public void createContextMenu(ContextMenuEvent e) { contextMenu.getItems().clear(); - if (selectedItems.isEmpty()) - { + if (selectedItems.isEmpty()) { // Create directory at root contextMenu.getItems().addAll(new CreateDirectoryAction(treeView, treeView.getRoot())); // Paste files at root if (Clipboard.getSystemClipboard().hasFiles()) contextMenu.getItems().addAll(new PasteFiles(treeView, treeView.getRoot())); - } - else - { + } else { // allMatch() would return true for empty, so only check if there are items - if (selectedItems.stream().allMatch(item -> item.isLeaf())){ + if (selectedItems.stream().allMatch(item -> item.isLeaf())) { contextMenu.getItems().add(open); } @@ -433,7 +388,7 @@ public void createContextMenu(ContextMenuEvent e) { configureOpenWithMenuItem(selectedItems); if (selectedItems.size() == 1) { - if(file.isDirectory()){ + if (file.isDirectory()) { contextMenu.getItems().add(new SetBaseDirectory(file, this::setRoot)); contextMenu.getItems().add(new SeparatorMenuItem()); @@ -459,25 +414,21 @@ public void createContextMenu(ContextMenuEvent e) { contextMenu.getItems().add(new CopyPath(selectedItems)); contextMenu.getItems().add(new SeparatorMenuItem()); } - if (selectedItems.size() >= 1) - { + if (selectedItems.size() >= 1) { final TreeItem item = selectedItems.get(0); final boolean is_file = item.isLeaf(); - if (selectedItems.size() == 1) - { - if (is_file) - { + if (selectedItems.size() == 1) { + if (is_file) { // Create directory within the _parent_ contextMenu.getItems().addAll(new CreateDirectoryAction(treeView, item.getParent())); // Plain file can be duplicated, directory can't contextMenu.getItems().add(new DuplicateAction(treeView, item)); - } - else + } else // Within a directory, a new directory can be created contextMenu.getItems().addAll(new CreateDirectoryAction(treeView, item)); - contextMenu.getItems().addAll(new RenameAction(treeView, selectedItems.get(0))); + contextMenu.getItems().addAll(new RenameAction(treeView, selectedItems.get(0))); if (Clipboard.getSystemClipboard().hasFiles()) contextMenu.getItems().addAll(new PasteFiles(treeView, selectedItems.get(0))); @@ -492,15 +443,14 @@ public void createContextMenu(ContextMenuEvent e) { contextMenu.getItems().add(new RefreshAction(treeView, item)); } - if (selectedItems.size() == 1){ - contextMenu.getItems().addAll(new PropertiesAction(treeView, selectedItems.get(0))); + if (selectedItems.size() == 1) { + contextMenu.getItems().addAll(new PropertiesAction(treeView, selectedItems.get(0))); } contextMenu.show(treeView.getScene().getWindow(), e.getScreenX(), e.getScreenY()); } @FXML - public void handleMouseClickEvents(final MouseEvent event) - { + public void handleMouseClickEvents(final MouseEvent event) { if (event.getClickCount() == 2) openSelectedResources(); } @@ -511,9 +461,10 @@ public void setNewRoot() { setRoot(p.toFile()); } - /** @param directory Desired root directory */ - public void setRoot(final File directory) - { + /** + * @param directory Desired root directory + */ + public void setRoot(final File directory) { monitor.setRoot(directory); path.setText(directory.toString()); treeView.setRoot(new FileTreeItem(monitor, directory)); @@ -521,22 +472,24 @@ public void setRoot(final File directory) /** * Set a new root directory and highlight the file provided (unless it is a directory). + * * @param file A {@link File} object representing a file (or directory). */ - public void setRootAndHighlight(final File file){ - if(file.isDirectory()){ - this.fileToHighlight = null; + public void setRootAndHighlight(final File file) { + if (file.isDirectory()) { setRoot(file); - } - else{ + } else { + this.exandedCountChangeListener = new ExandedCountChangeListener(); this.fileToHighlight = file; + treeView.expandedItemCountProperty().addListener(exandedCountChangeListener); setRoot(file.getParentFile()); } } - /** @return Root directory */ - public File getRoot() - { + /** + * @return Root directory + */ + public File getRoot() { return treeView.getRoot().getValue().file; } @@ -558,9 +511,10 @@ public void browseNewRoot() { setRoot(newRootFile); } - /** Call when no longer needed */ - public void shutdown() - { + /** + * Call when no longer needed + */ + public void shutdown() { monitor.shutdown(); } @@ -572,10 +526,11 @@ public void shutdown() *
  • If all selected items are of same type, the Open With menu item will be added and the * the sub-menu items will open all items. This also covers the case when only one item is selected.
  • * + * * @param selectedItems List of items selected by user in the tree table view. */ - private void configureOpenWithMenuItem(List> selectedItems){ - if(!areSelectedFilesOfSameType(selectedItems)) { + private void configureOpenWithMenuItem(List> selectedItems) { + if (!areSelectedFilesOfSameType(selectedItems)) { openWith.getItems().clear(); return; } @@ -584,17 +539,15 @@ private void configureOpenWithMenuItem(List> selectedItems){ final File file = selectedItems.get(0).getValue().file; final URI resource = ResourceParser.getURI(file); final List applications = ApplicationService.getApplications(resource); - if (applications.size() > 0) - { + if (applications.size() > 0) { openWith.getItems().clear(); - for (AppResourceDescriptor app : applications) - { + for (AppResourceDescriptor app : applications) { final MenuItem open_app = new MenuItem(app.getDisplayName()); final URL icon_url = app.getIconURL(); if (icon_url != null) open_app.setGraphic(new ImageView(icon_url.toExternalForm())); open_app.setOnAction(event -> { - for(TreeItem item : selectedItems){ + for (TreeItem item : selectedItems) { URI u = ResourceParser.getURI(item.getValue().file); app.create(u); } @@ -608,20 +561,37 @@ private void configureOpenWithMenuItem(List> selectedItems){ /** * Examines the file selection to determine whether all files are of the same type. A type is * defined by the file extension (case-insensitive substring after last dot). + * * @param selectedItems Items selected by user in the tree table view * @return true if all selected files have same (case-insensitive) extension. */ - private boolean areSelectedFilesOfSameType(List> selectedItems){ + private boolean areSelectedFilesOfSameType(List> selectedItems) { File file = selectedItems.get(0).getValue().file; String firstExtension = file.getPath().substring(file.getPath().lastIndexOf(".") + 1).toLowerCase(); - for(int i = 1; i < selectedItems.size(); i++){ + for (int i = 1; i < selectedItems.size(); i++) { file = selectedItems.get(i).getValue().file; String nextExtension = file.getPath().substring(file.getPath().lastIndexOf(".") + 1).toLowerCase(); - if(!firstExtension.equals(nextExtension)){ + if (!firstExtension.equals(nextExtension)) { return false; } } return true; } + + private class ExandedCountChangeListener implements ChangeListener { + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) { + TreeItem root = treeView.getRoot(); + List children = root.getChildren(); + for (TreeItem child : children) { + if (((FileInfo) child.getValue()).file.equals(fileToHighlight)) { + treeView.getSelectionModel().select(child); + treeView.scrollTo(treeView.getSelectionModel().getSelectedIndex()); + treeView.expandedItemCountProperty().removeListener(exandedCountChangeListener); + return; + } + } + } + } } diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 34893bed73..234eea5a07 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -9,10 +9,13 @@ import javafx.application.Platform; import javafx.scene.Node; -import javafx.scene.control.*; +import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.Menu; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; +import javafx.scene.control.Tab; +import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; @@ -33,9 +36,7 @@ import org.phoebus.ui.dialog.SaveAsDialog; import org.phoebus.ui.javafx.ImageCache; -import java.awt.*; import java.io.File; -import java.io.IOException; import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -91,8 +92,6 @@ public class DockItemWithInput extends DockItem { private final static Image fileBrowserIcon = ImageCache.getImage(DockItem.class, "/icons/filebrowser.png"); - private final static Image showInIcon = ImageCache.getImage(DockItem.class, "/icons/fldr_obj.png"); - /** * Create dock item * @@ -126,29 +125,30 @@ public DockItemWithInput(final AppInstance application, final Node content, fina /** * Configures additional and optional items in the tab header context menu if the resource field is non-null: * - *
  • Copy the resource to clipboard
  • - *
  • For file resources a sub-menu with items:
  • + *
  • Copy the resource to clipboard
  • + *
  • For file resources a sub-menu with items:
  • *
      *
    • Open and highlight file in new File Browser instance
    • *
    • Open file's parent directory in native file browser
    • *
    * + * * @param menu The {@link ContextMenu} to update. */ protected void configureContextMenu(ContextMenu menu) { super.configureContextMenu(menu); - if(input == null){ + if (input == null) { return; } - boolean isHttpResource = input.toString().toLowerCase().startsWith("http"); + boolean isFileResource = input.getScheme().toLowerCase().startsWith("file"); final MenuItem copyResourceToClipboard = new MenuItem(Messages.CopyResourcePath, new ImageView(copyToClipboardIcon)); copyResourceToClipboard.setOnAction(e -> { final ClipboardContent content = new ClipboardContent(); - content.putString(isHttpResource ? input.toString() : input.getPath()); + content.putString(isFileResource ? input.toString() : input.getPath()); Clipboard.getSystemClipboard().setContent(content); }); - if(!isHttpResource){ + if (isFileResource) { final MenuItem showInFileBrowser = new MenuItem(Messages.ShowInFileBrowserApp, new ImageView(fileBrowserIcon)); showInFileBrowser.setOnAction(e -> { ApplicationService.createInstance("file_browser", new File(input.getPath()).toURI()); diff --git a/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob b/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob deleted file mode 100644 index 963a73c631..0000000000 --- a/services/save-and-restore/src/main/resources/static/CSSTUDIO-1316.bob +++ /dev/null @@ -1,31 +0,0 @@ - - - - Display - 920 - 300 - - LinearMeter - loc://x(10) - 200 - 340 - -100.0 - -100.0 - 100.0 - 100.0 - - - Action Button - - - loc://x - 1 - WritePV - - - 780 - 340 - 220 - 100 - - From b440591ae1a7629bffd737edbf237526b4abff32 Mon Sep 17 00:00:00 2001 From: kasemir Date: Thu, 4 Apr 2024 14:59:34 -0400 Subject: [PATCH 70/92] Scan 'script' command: Add exception message to error --- .../org/csstudio/scan/server/internal/JythonSupport.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/scan-server/src/main/java/org/csstudio/scan/server/internal/JythonSupport.java b/services/scan-server/src/main/java/org/csstudio/scan/server/internal/JythonSupport.java index 03eae23a60..41805648db 100644 --- a/services/scan-server/src/main/java/org/csstudio/scan/server/internal/JythonSupport.java +++ b/services/scan-server/src/main/java/org/csstudio/scan/server/internal/JythonSupport.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012-2020 Oak Ridge National Laboratory. + * Copyright (c) 2012-2024 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -211,6 +211,9 @@ public T loadClass(final Class type, final String class_name, final Strin public static String getExceptionMessage(final PyException ex) { final StringBuilder buf = new StringBuilder(); + if (ex.getLocalizedMessage() != null) + buf.append(" ").append(ex.getLocalizedMessage()); + if (ex.value instanceof PyString) buf.append(" ").append(ex.value.asString()); else if (ex.getCause() != null) From a77936d07c1992d89b909e94084aef59174caac8 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Fri, 5 Apr 2024 11:24:31 +0200 Subject: [PATCH 71/92] Typo --- .../filebrowser/FileBrowserController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 2d0df0164d..171c739395 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -71,7 +71,7 @@ public class FileBrowserController { private final Menu openWith = new Menu(Messages.OpenWith, ImageCache.getImageView(PhoebusApplication.class, "/icons/fldr_obj.png")); private final ContextMenu contextMenu = new ContextMenu(); - private ExandedCountChangeListener exandedCountChangeListener; + private ExpandedCountChangeListener expandedCountChangeListener; /** * A {@link File} object representing a file (i.e. not a directory) in case client calls @@ -479,9 +479,9 @@ public void setRootAndHighlight(final File file) { if (file.isDirectory()) { setRoot(file); } else { - this.exandedCountChangeListener = new ExandedCountChangeListener(); + this.expandedCountChangeListener = new ExpandedCountChangeListener(); this.fileToHighlight = file; - treeView.expandedItemCountProperty().addListener(exandedCountChangeListener); + treeView.expandedItemCountProperty().addListener(expandedCountChangeListener); setRoot(file.getParentFile()); } } @@ -579,7 +579,7 @@ private boolean areSelectedFilesOfSameType(List> selectedItem return true; } - private class ExandedCountChangeListener implements ChangeListener { + private class ExpandedCountChangeListener implements ChangeListener { @Override public void changed(ObservableValue observable, Object oldValue, Object newValue) { TreeItem root = treeView.getRoot(); @@ -588,7 +588,7 @@ public void changed(ObservableValue observable, Object oldValue, Object newValue if (((FileInfo) child.getValue()).file.equals(fileToHighlight)) { treeView.getSelectionModel().select(child); treeView.scrollTo(treeView.getSelectionModel().getSelectedIndex()); - treeView.expandedItemCountProperty().removeListener(exandedCountChangeListener); + treeView.expandedItemCountProperty().removeListener(expandedCountChangeListener); return; } } From 9829a02b4631c3315b68d632b03dd9f3fedbd7ce Mon Sep 17 00:00:00 2001 From: georgweiss Date: Fri, 5 Apr 2024 11:39:17 +0200 Subject: [PATCH 72/92] Removed need for class variable --- .../filebrowser/FileBrowserController.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java index 171c739395..bbf0d1fa73 100644 --- a/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java +++ b/app/filebrowser/src/main/java/org/phoebus/applications/filebrowser/FileBrowserController.java @@ -73,13 +73,6 @@ public class FileBrowserController { private ExpandedCountChangeListener expandedCountChangeListener; - /** - * A {@link File} object representing a file (i.e. not a directory) in case client calls - * {@link #setRootAndHighlight(File)} using a file. If the {@link #setRootAndHighlight(File)} call - * specifies a directory, this is set to null. - */ - private File fileToHighlight; - public FileBrowserController() { monitor = new DirectoryMonitor(this::handleFilesystemChanges); } @@ -479,8 +472,7 @@ public void setRootAndHighlight(final File file) { if (file.isDirectory()) { setRoot(file); } else { - this.expandedCountChangeListener = new ExpandedCountChangeListener(); - this.fileToHighlight = file; + this.expandedCountChangeListener = new ExpandedCountChangeListener(file); treeView.expandedItemCountProperty().addListener(expandedCountChangeListener); setRoot(file.getParentFile()); } @@ -580,6 +572,17 @@ private boolean areSelectedFilesOfSameType(List> selectedItem } private class ExpandedCountChangeListener implements ChangeListener { + + /** + * A {@link File} object representing a file (i.e. not a directory) in case client calls + * {@link #setRootAndHighlight(File)} using a file. If the {@link #setRootAndHighlight(File)} call + * specifies a directory, this is set to null. + */ + private File fileToHighlight; + + public ExpandedCountChangeListener(File fileToHighlight){ + this.fileToHighlight = fileToHighlight; + } @Override public void changed(ObservableValue observable, Object oldValue, Object newValue) { TreeItem root = treeView.getRoot(); From e15a7a3ea651744eefc21aea7da8961ac1c23f4e Mon Sep 17 00:00:00 2001 From: georgweiss Date: Fri, 5 Apr 2024 12:25:52 +0200 Subject: [PATCH 73/92] Update due to reversed condition --- .../src/main/java/org/phoebus/ui/docking/DockItemWithInput.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java index 234eea5a07..9c91ec9c0a 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockItemWithInput.java @@ -144,7 +144,7 @@ protected void configureContextMenu(ContextMenu menu) { final MenuItem copyResourceToClipboard = new MenuItem(Messages.CopyResourcePath, new ImageView(copyToClipboardIcon)); copyResourceToClipboard.setOnAction(e -> { final ClipboardContent content = new ClipboardContent(); - content.putString(isFileResource ? input.toString() : input.getPath()); + content.putString(isFileResource ? input.getPath() : input.toString()); Clipboard.getSystemClipboard().setContent(content); }); From fc9038f0c67b0c890f1f4d73cf70e2b31e57c470 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Tue, 9 Apr 2024 14:52:37 +0200 Subject: [PATCH 74/92] CSSTUDIO-2231 Initial implementation of new flag '-select_settings'. --- .../ui/application/PhoebusApplication.java | 120 +++++++++++++++++- 1 file changed, 113 insertions(+), 7 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index e2ca5467b4..14e3183b54 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -28,6 +28,8 @@ import java.util.logging.Logger; import java.util.stream.Collectors; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.scene.control.Alert; import javafx.scene.control.Button; @@ -36,6 +38,8 @@ import javafx.scene.control.CheckBox; import javafx.scene.control.CheckMenuItem; import javafx.scene.control.Dialog; +import javafx.scene.control.ListView; +import javafx.scene.control.ListView; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuButton; @@ -44,14 +48,26 @@ import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.ToolBar; import javafx.scene.control.Tooltip; -import javafx.scene.layout.*; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.text.Text; +import javafx.stage.Stage; +import javafx.stage.Window; import org.phoebus.framework.jobs.JobManager; import org.phoebus.framework.jobs.JobMonitor; import org.phoebus.framework.jobs.SubJobMonitor; import org.phoebus.framework.persistence.MementoTree; import org.phoebus.framework.persistence.XMLMementoTree; +import org.phoebus.framework.preferences.PropertyPreferenceLoader; import org.phoebus.framework.spi.AppDescriptor; import org.phoebus.framework.spi.AppResourceDescriptor; import org.phoebus.framework.util.ResourceParser; @@ -85,12 +101,6 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyCombination; -import javafx.scene.input.MouseEvent; -import javafx.stage.Stage; -import javafx.stage.Window; /** * Primary UI for a phoebus application @@ -289,14 +299,110 @@ public void start(final Stage initial_stage) throws Exception { // Show splash screen as soon as possible.. final Splash splash = Preferences.splash ? new Splash(initial_stage) : null; + Platform.setImplicitExit(false); + possiblySelectIniFile(application_parameters); + // .. then read saved state etc. in background job JobManager.schedule("Startup", monitor -> { final JobMonitor splash_monitor = new SplashJobMonitor(monitor, splash); backgroundStartup(splash_monitor, splash); + Platform.setImplicitExit(true); }); } + private void possiblySelectIniFile(CopyOnWriteArrayList application_parameters) { + if (application_parameters.contains("-select_settings")) { + int indexOfFlag = application_parameters.indexOf("-select_settings", 0); + if (indexOfFlag < 0) { + throw new RuntimeException("Error, this should never happen!"); + } + if (application_parameters.size() >= indexOfFlag) { + String iniFilesLocation_String = application_parameters.get(indexOfFlag + 1); + File iniFilesLocation_File = new File(iniFilesLocation_String); + if (iniFilesLocation_File.isDirectory()) { + List iniFilesInDirectory_List = Arrays.stream(iniFilesLocation_File.listFiles()).filter(file -> file.getAbsolutePath().endsWith(".ini")).collect(Collectors.toList()); + ObservableList iniFilesInDirectory_ObservableList = FXCollections.observableArrayList(iniFilesInDirectory_List); + + if (iniFilesInDirectory_List.size() > 0) { + Dialog iniFileSelectionDialog = new Dialog(); + iniFileSelectionDialog.setTitle("Select Phoebus configuration"); + iniFileSelectionDialog.setHeaderText("Select Phoebus configuration"); + iniFileSelectionDialog.setGraphic(null); + + iniFileSelectionDialog.setWidth(500); + iniFileSelectionDialog.setHeight(400); + iniFileSelectionDialog.setResizable(false); + + ListView listView = new ListView(iniFilesInDirectory_ObservableList); + listView.getSelectionModel().select(0); + + Runnable setReturnValueAndCloseDialog = () -> { + File selectedFile = (File) listView.getSelectionModel().getSelectedItem(); + if (selectedFile == null) { + selectedFile = (File) listView.getItems().get(0); + } + iniFileSelectionDialog.setResult(selectedFile); + iniFileSelectionDialog.close(); + }; + listView.setOnKeyPressed(keyEvent -> { + if (keyEvent.getCode() == KeyCode.ENTER) { + setReturnValueAndCloseDialog.run(); + } + }); + + iniFileSelectionDialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE); + Button closeButton = (Button) iniFileSelectionDialog.getDialogPane().lookupButton(ButtonType.CLOSE); + closeButton.setVisible(false); // In JavaFX, a button of type ButtonType.CLOSE must exist so that the "X"-button closes the window. + + Button okButton = new Button("OK"); + okButton.setOnAction(actionEvent -> setReturnValueAndCloseDialog.run()); + okButton.setPrefWidth(500); + + VBox vBox = new VBox(listView, okButton); + iniFileSelectionDialog.getDialogPane().setContent(vBox); + listView.requestFocus(); + + iniFileSelectionDialog.getDialogPane().addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> { + if (keyEvent.getCode() == KeyCode.ESCAPE) { + iniFileSelectionDialog.close(); + keyEvent.consume(); + } + }); + + iniFileSelectionDialog.setOnCloseRequest(dialogEvent -> { + Object currentResult = iniFileSelectionDialog.getResult(); + if (currentResult == null || !(currentResult instanceof File)) { + // Return null when closing the dialog by clicking the "X"-button or the ESC-key. + iniFileSelectionDialog.setResult(null); + } + }); + + Optional maybeSelectedFile = iniFileSelectionDialog.showAndWait(); + if (maybeSelectedFile.isPresent()) { + File selectedFile = maybeSelectedFile.get(); + try { + PropertyPreferenceLoader.load(new FileInputStream(selectedFile)); + } catch (Exception exception) { + logger.log(Level.SEVERE, "Error parsing Phoebus configuration '" + selectedFile.getAbsolutePath() + "': " + exception.getMessage()); + stop(); + } + } else { + // Selecting a configuration was cancelled either by pressing the "X"-button or by pressing the ESC-key. + stop(); + } + } else { + logger.log(Level.SEVERE, "Error during evaluation of the flag '-set_settings': the directory '" + iniFilesLocation_String + "' does not contain any .ini files!"); + stop(); + } + } else { + logger.log(Level.SEVERE, "Error during evaluation of the flag '-set_settings': the argument '" + iniFilesLocation_String + "' is not a directory!"); + stop(); + } + } + } + } + /** * Perform potentially slow startup task off the UI thread * From 032278d29795d645f04dd466c9e3809a68d6a12a Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 10 Apr 2024 10:25:22 +0200 Subject: [PATCH 75/92] CSSTUDIO-2231 Call possiblySelectIniFile() before preferences are initialized. --- .../java/org/phoebus/ui/application/PhoebusApplication.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index 14e3183b54..90c54b8db6 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -296,12 +296,12 @@ public void start(final Stage initial_stage) throws Exception { // Save original application parameters application_parameters.addAll(getParameters().getRaw()); + Platform.setImplicitExit(false); + possiblySelectIniFile(application_parameters); // possiblySelectIniFile() be called before preferences are initialized, to ensure that the selected configuration options are applied before old configuration options are loaded. + // Show splash screen as soon as possible.. final Splash splash = Preferences.splash ? new Splash(initial_stage) : null; - Platform.setImplicitExit(false); - possiblySelectIniFile(application_parameters); - // .. then read saved state etc. in background job JobManager.schedule("Startup", monitor -> { From e3d33cfb82810a6fde8eec7d5773c2516a28cd60 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 10 Apr 2024 10:30:40 +0200 Subject: [PATCH 76/92] CSSTUDIO-2231 Disallow the flag '-settings' when the flag '-select_settings' is also present. --- core/launcher/src/main/java/org/phoebus/product/Launcher.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/launcher/src/main/java/org/phoebus/product/Launcher.java b/core/launcher/src/main/java/org/phoebus/product/Launcher.java index 481c9934a7..0d2b182aa2 100644 --- a/core/launcher/src/main/java/org/phoebus/product/Launcher.java +++ b/core/launcher/src/main/java/org/phoebus/product/Launcher.java @@ -96,6 +96,9 @@ else if (cmd.equals("-logging")) } else if (cmd.equals("-settings")) { + if (args.contains("-select_settings")) { + throw new Exception("The flag '-settings' cannot be used in conjunction with the flag '-select_settings'."); + } if (! iter.hasNext()) throw new Exception("Missing -settings file name"); iter.remove(); From 9a608b51a8061de72affa010596ccc363e493d3f Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 10 Apr 2024 10:39:38 +0200 Subject: [PATCH 77/92] CSSTUDIO-2231 Add support for configuration files in XML format. Improve error handling. --- .../ui/application/PhoebusApplication.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index 90c54b8db6..d6a983ccfb 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -321,7 +321,7 @@ private void possiblySelectIniFile(CopyOnWriteArrayList application_para String iniFilesLocation_String = application_parameters.get(indexOfFlag + 1); File iniFilesLocation_File = new File(iniFilesLocation_String); if (iniFilesLocation_File.isDirectory()) { - List iniFilesInDirectory_List = Arrays.stream(iniFilesLocation_File.listFiles()).filter(file -> file.getAbsolutePath().endsWith(".ini")).collect(Collectors.toList()); + List iniFilesInDirectory_List = Arrays.stream(iniFilesLocation_File.listFiles()).filter(file -> file.getAbsolutePath().endsWith(".ini") || file.getAbsolutePath().endsWith(".xml")).collect(Collectors.toList()); ObservableList iniFilesInDirectory_ObservableList = FXCollections.observableArrayList(iniFilesInDirectory_List); if (iniFilesInDirectory_List.size() > 0) { @@ -382,9 +382,20 @@ private void possiblySelectIniFile(CopyOnWriteArrayList application_para if (maybeSelectedFile.isPresent()) { File selectedFile = maybeSelectedFile.get(); try { - PropertyPreferenceLoader.load(new FileInputStream(selectedFile)); - } catch (Exception exception) { - logger.log(Level.SEVERE, "Error parsing Phoebus configuration '" + selectedFile.getAbsolutePath() + "': " + exception.getMessage()); + FileInputStream selectedFile_FileInputStream = new FileInputStream(selectedFile); + try { + if (selectedFile.getAbsolutePath().endsWith(".xml")) { + java.util.prefs.Preferences.importPreferences(selectedFile_FileInputStream); + } + else { + PropertyPreferenceLoader.load(selectedFile_FileInputStream); + } + } catch (Exception exception) { + logger.log(Level.SEVERE, "Error parsing Phoebus configuration '" + selectedFile.getAbsolutePath() + "': " + exception.getMessage()); + stop(); + } + } catch (FileNotFoundException e) { + logger.log(Level.SEVERE, "Error loading Phoebus configuration '" + selectedFile.getAbsolutePath() + "': File does not exist!"); stop(); } } else { From a15c99ffe6be951faec4ca904138966c27a70972 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 10 Apr 2024 10:44:53 +0200 Subject: [PATCH 78/92] CSSTUDIO-2231 Add support for selecting a configuration by double-clicking on it. --- .../java/org/phoebus/ui/application/PhoebusApplication.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index d6a983ccfb..385c31e226 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -345,6 +345,11 @@ private void possiblySelectIniFile(CopyOnWriteArrayList application_para iniFileSelectionDialog.setResult(selectedFile); iniFileSelectionDialog.close(); }; + listView.setOnMouseClicked(mouseEvent -> { + if (mouseEvent.getClickCount() == 2) { + setReturnValueAndCloseDialog.run(); + } + }); listView.setOnKeyPressed(keyEvent -> { if (keyEvent.getCode() == KeyCode.ENTER) { setReturnValueAndCloseDialog.run(); From 9094fe5774571492e1a882140e9a24749414e2d5 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 10 Apr 2024 11:08:50 +0200 Subject: [PATCH 79/92] CSSTUDIO-2231 Remove duplicate import statement. --- .../main/java/org/phoebus/ui/application/PhoebusApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index 385c31e226..5e1bc1bab6 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -39,7 +39,6 @@ import javafx.scene.control.CheckMenuItem; import javafx.scene.control.Dialog; import javafx.scene.control.ListView; -import javafx.scene.control.ListView; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuButton; From 2d40fe31c4888cd190881e4fa964e28e206bff05 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 10 Apr 2024 11:12:22 +0200 Subject: [PATCH 80/92] CSSTUDIO-2231 Add explanatory comment. --- .../java/org/phoebus/ui/application/PhoebusApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index 5e1bc1bab6..824b4d3ed7 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -295,7 +295,7 @@ public void start(final Stage initial_stage) throws Exception { // Save original application parameters application_parameters.addAll(getParameters().getRaw()); - Platform.setImplicitExit(false); + Platform.setImplicitExit(false); // Avoids shutdown of Phoebus when the '-select_settings' option is used after the dialog to select configuration file has been closed. Platform.setImplicitExit(true) is called below to restore the option again. possiblySelectIniFile(application_parameters); // possiblySelectIniFile() be called before preferences are initialized, to ensure that the selected configuration options are applied before old configuration options are loaded. // Show splash screen as soon as possible.. From 5752ddae189c53df669ad04da592c25c58ea8e04 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 10 Apr 2024 11:15:15 +0200 Subject: [PATCH 81/92] CSSTUDIO-2231 Fix grammar. --- .../java/org/phoebus/ui/application/PhoebusApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index 824b4d3ed7..15d112eb5d 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -296,7 +296,7 @@ public void start(final Stage initial_stage) throws Exception { application_parameters.addAll(getParameters().getRaw()); Platform.setImplicitExit(false); // Avoids shutdown of Phoebus when the '-select_settings' option is used after the dialog to select configuration file has been closed. Platform.setImplicitExit(true) is called below to restore the option again. - possiblySelectIniFile(application_parameters); // possiblySelectIniFile() be called before preferences are initialized, to ensure that the selected configuration options are applied before old configuration options are loaded. + possiblySelectIniFile(application_parameters); // possiblySelectIniFile() must be called before preferences are initialized, to ensure that the selected configuration options are applied before old configuration options are loaded. // Show splash screen as soon as possible.. final Splash splash = Preferences.splash ? new Splash(initial_stage) : null; From e64f378d7c4eccc8454fa8848bb1a5342c1377a3 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Wed, 10 Apr 2024 11:16:41 +0200 Subject: [PATCH 82/92] CSSTUDIO-2231 Fix error message. --- .../java/org/phoebus/ui/application/PhoebusApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index 15d112eb5d..03340eb115 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -407,7 +407,7 @@ private void possiblySelectIniFile(CopyOnWriteArrayList application_para stop(); } } else { - logger.log(Level.SEVERE, "Error during evaluation of the flag '-set_settings': the directory '" + iniFilesLocation_String + "' does not contain any .ini files!"); + logger.log(Level.SEVERE, "Error during evaluation of the flag '-set_settings': the directory '" + iniFilesLocation_String + "' does not contain any .ini or .xml file(s)!"); stop(); } } else { From f56fc45ff56267d96c335bfba968579b732d2dfd Mon Sep 17 00:00:00 2001 From: Jem Bishop Date: Wed, 10 Apr 2024 11:28:19 +0100 Subject: [PATCH 83/92] update readme with new restore from server endpoints --- services/save-and-restore/doc/index.rst | 96 +++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/services/save-and-restore/doc/index.rst b/services/save-and-restore/doc/index.rst index 8d9641fa88..2295826908 100644 --- a/services/save-and-restore/doc/index.rst +++ b/services/save-and-restore/doc/index.rst @@ -723,6 +723,102 @@ Body: } ] +Server Restore Endpoints +---------------------------- + +Restore from snapshot items +""""""""""""""""""""""""""" + +**.../restore/items** + +Method: POST + +This endpoint allows you to send a list of ``SnapshotItem`` and the save-and-restore server +will set the values of the PVs in your system to the values supplied. +This allows restoring from clients which do not support EPICS access, for example web clients. + +Body: + +.. code-block:: JSON + + [ + { + "configPv": { + "pvName":"COUNTER10", + "readOnly":false + }, + "value":{ + "type":{ + "name":"VDouble", + "version":1 + }, + "value":11941.0, + "alarm":{ + "severity":"NONE", + "status":"NONE", + "name":"NO_ALARM" + }, + "time":{ + "unixSec":1664550284, + "nanoSec":870687555 + }, + "display":{ + "lowDisplay":0.0, + "highDisplay":0.0, + "units":"" + } + } + } + ] + +Return: A list of the snapshot items restored, and optionally the error message. +If there was no error in PV restoration then the error message is null. + +.. code-block:: JSON + + [ + { + "snapshotItem": { + "configPv": { + "pvName":"COUNTER10", + "readOnly":false + }, + "value":{ + "type":{ + "name":"VDouble", + "version":1 + }, + "value":11941.0, + "alarm":{ + "severity":"NONE", + "status":"NONE", + "name":"NO_ALARM" + }, + "time":{ + "unixSec":1664550284, + "nanoSec":870687555 + }, + "display":{ + "lowDisplay":0.0, + "highDisplay":0.0, + "units":"" + } + } + }, + "errorMsg": null + } + ] + +Restore from snapshot node +""""""""""""""""""""""""""" + +**.../restore/node?parentNodeId=** + +Method: POST + +This is the same as the endpoint to restore from snapshot items, however it uses snapshot items +from an existing node rather than providing them explicitly. It returns the same result. + Authentication and Authorization ================================ From 2fc606b7d6813c624833c186f39ed82c24d0f6cb Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Thu, 11 Apr 2024 11:30:11 +0200 Subject: [PATCH 84/92] Revert "CSSTUDIO-2231 Disallow the flag '-settings' when the flag '-select_settings' is also present." This reverts commit e3d33cfb82810a6fde8eec7d5773c2516a28cd60. --- core/launcher/src/main/java/org/phoebus/product/Launcher.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/launcher/src/main/java/org/phoebus/product/Launcher.java b/core/launcher/src/main/java/org/phoebus/product/Launcher.java index 0d2b182aa2..481c9934a7 100644 --- a/core/launcher/src/main/java/org/phoebus/product/Launcher.java +++ b/core/launcher/src/main/java/org/phoebus/product/Launcher.java @@ -96,9 +96,6 @@ else if (cmd.equals("-logging")) } else if (cmd.equals("-settings")) { - if (args.contains("-select_settings")) { - throw new Exception("The flag '-settings' cannot be used in conjunction with the flag '-select_settings'."); - } if (! iter.hasNext()) throw new Exception("Missing -settings file name"); iter.remove(); From 921f19d671666303358bfc709d3994b01c78d0fb Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Thu, 11 Apr 2024 11:30:20 +0200 Subject: [PATCH 85/92] CSSTUDIO-2231 Display error message when an error occurs. --- .../ui/application/PhoebusApplication.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index 03340eb115..af54a457b3 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -61,6 +61,7 @@ import javafx.scene.text.Text; import javafx.stage.Stage; import javafx.stage.Window; +import javafx.util.Pair; import org.phoebus.framework.jobs.JobManager; import org.phoebus.framework.jobs.JobMonitor; import org.phoebus.framework.jobs.SubJobMonitor; @@ -311,6 +312,23 @@ public void start(final Stage initial_stage) throws Exception { } private void possiblySelectIniFile(CopyOnWriteArrayList application_parameters) { + + Consumer> displayErrorMessageAndQuit = errorTitleAndErrorMessage -> { + + String errorTitle = errorTitleAndErrorMessage.getKey(); + String errorMessage = errorTitleAndErrorMessage.getValue(); + + logger.log(Level.SEVERE, errorMessage); + + Dialog errorDialog = new Alert(AlertType.ERROR); + errorDialog.setTitle(errorTitle); + errorDialog.setHeaderText(errorTitle); + errorDialog.setContentText(errorMessage + "\n\nPhoebus will quit."); + errorDialog.showAndWait(); + + stop(); + }; + if (application_parameters.contains("-select_settings")) { int indexOfFlag = application_parameters.indexOf("-select_settings", 0); if (indexOfFlag < 0) { @@ -395,24 +413,20 @@ private void possiblySelectIniFile(CopyOnWriteArrayList application_para PropertyPreferenceLoader.load(selectedFile_FileInputStream); } } catch (Exception exception) { - logger.log(Level.SEVERE, "Error parsing Phoebus configuration '" + selectedFile.getAbsolutePath() + "': " + exception.getMessage()); - stop(); + displayErrorMessageAndQuit.accept(new Pair("Error loading Phoebus configuration", "Error loading Phoebus configuration '" + selectedFile.getAbsolutePath() + "': " + exception.getMessage())); } } catch (FileNotFoundException e) { - logger.log(Level.SEVERE, "Error loading Phoebus configuration '" + selectedFile.getAbsolutePath() + "': File does not exist!"); - stop(); + displayErrorMessageAndQuit.accept(new Pair("Error loading Phoebus configuration", "Error loading Phoebus configuration '" + selectedFile.getAbsolutePath() + "': File does not exist!")); } } else { // Selecting a configuration was cancelled either by pressing the "X"-button or by pressing the ESC-key. stop(); } } else { - logger.log(Level.SEVERE, "Error during evaluation of the flag '-set_settings': the directory '" + iniFilesLocation_String + "' does not contain any .ini or .xml file(s)!"); - stop(); + displayErrorMessageAndQuit.accept(new Pair("Error during evaluation of the flag '-set_settings'", "Error during evaluation of the flag '-set_settings': the directory '" + iniFilesLocation_String + "' does not contain any .ini or .xml file(s)!")); } } else { - logger.log(Level.SEVERE, "Error during evaluation of the flag '-set_settings': the argument '" + iniFilesLocation_String + "' is not a directory!"); - stop(); + displayErrorMessageAndQuit.accept(new Pair("Error during evaluation of the flag '-set_settings'", "Error during evaluation of the flag '-set_settings': the argument '" + iniFilesLocation_String + "' is not a directory!")); } } } From c8e4f293c1a8acf1660f11b56051034cfc230da2 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Thu, 11 Apr 2024 11:58:12 +0200 Subject: [PATCH 86/92] CSSTUDIO-2231 Add labels to 'messages.properties'. Fix typo: '-set_settings' -> '-select_settings'. --- .../java/org/phoebus/ui/application/Messages.java | 7 +++++++ .../ui/application/PhoebusApplication.java | 15 ++++++++------- .../phoebus/ui/application/messages.properties | 7 +++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java index e9a8cdb981..60c72dab8f 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java @@ -40,11 +40,14 @@ public class Messages public static String DockSplitH; public static String DockSplitV; public static String Enjoy; + public static String ErrorDuringEvalutationOfTheFlagSelectSettings; + public static String ErrorLoadingPhoebusConfiguration; public static String Exit; public static String ExitContent; public static String ExitHdr; public static String ExitTitle; public static String File; + public static String FileDoesNotExist; public static String FileExists; public static String FixedTitle; public static String Help; @@ -90,6 +93,7 @@ public class Messages public static String MonitorTaskUi; public static String NamePane; public static String NamePaneHdr; + public static String OK; public static String Open; public static String OpenHdr; public static String OpenTitle; @@ -129,9 +133,12 @@ public class Messages public static String SavingHdr; public static String ScreenshotErrHdr; public static String ScreenshotErrMsg; + public static String SelectPhoebusConfiguration; public static String SelectTab; public static String ShowStatusbar; public static String ShowToolbar; + public static String TheArgumentIsNotADirectory; + public static String TheDirectoryDoesNotContainConfigurationFiles; public static String Time12h; public static String Time1d; public static String Time3d; diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index af54a457b3..3f977cb094 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -5,6 +5,7 @@ import java.io.FileNotFoundException; import java.lang.ref.WeakReference; import java.net.URI; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -343,8 +344,8 @@ private void possiblySelectIniFile(CopyOnWriteArrayList application_para if (iniFilesInDirectory_List.size() > 0) { Dialog iniFileSelectionDialog = new Dialog(); - iniFileSelectionDialog.setTitle("Select Phoebus configuration"); - iniFileSelectionDialog.setHeaderText("Select Phoebus configuration"); + iniFileSelectionDialog.setTitle(Messages.SelectPhoebusConfiguration); + iniFileSelectionDialog.setHeaderText(Messages.SelectPhoebusConfiguration); iniFileSelectionDialog.setGraphic(null); iniFileSelectionDialog.setWidth(500); @@ -377,7 +378,7 @@ private void possiblySelectIniFile(CopyOnWriteArrayList application_para Button closeButton = (Button) iniFileSelectionDialog.getDialogPane().lookupButton(ButtonType.CLOSE); closeButton.setVisible(false); // In JavaFX, a button of type ButtonType.CLOSE must exist so that the "X"-button closes the window. - Button okButton = new Button("OK"); + Button okButton = new Button(Messages.OK); okButton.setOnAction(actionEvent -> setReturnValueAndCloseDialog.run()); okButton.setPrefWidth(500); @@ -413,20 +414,20 @@ private void possiblySelectIniFile(CopyOnWriteArrayList application_para PropertyPreferenceLoader.load(selectedFile_FileInputStream); } } catch (Exception exception) { - displayErrorMessageAndQuit.accept(new Pair("Error loading Phoebus configuration", "Error loading Phoebus configuration '" + selectedFile.getAbsolutePath() + "': " + exception.getMessage())); + displayErrorMessageAndQuit.accept(new Pair(Messages.ErrorLoadingPhoebusConfiguration, Messages.ErrorLoadingPhoebusConfiguration + " '" + selectedFile.getAbsolutePath() + "': " + exception.getMessage())); } } catch (FileNotFoundException e) { - displayErrorMessageAndQuit.accept(new Pair("Error loading Phoebus configuration", "Error loading Phoebus configuration '" + selectedFile.getAbsolutePath() + "': File does not exist!")); + displayErrorMessageAndQuit.accept(new Pair(Messages.ErrorLoadingPhoebusConfiguration, Messages.ErrorLoadingPhoebusConfiguration + " '" + selectedFile.getAbsolutePath() + "': " + Messages.FileDoesNotExist)); } } else { // Selecting a configuration was cancelled either by pressing the "X"-button or by pressing the ESC-key. stop(); } } else { - displayErrorMessageAndQuit.accept(new Pair("Error during evaluation of the flag '-set_settings'", "Error during evaluation of the flag '-set_settings': the directory '" + iniFilesLocation_String + "' does not contain any .ini or .xml file(s)!")); + displayErrorMessageAndQuit.accept(new Pair(Messages.ErrorDuringEvalutationOfTheFlagSelectSettings, Messages.ErrorDuringEvalutationOfTheFlagSelectSettings + ": " + MessageFormat.format(Messages.TheDirectoryDoesNotContainConfigurationFiles, iniFilesLocation_String))); } } else { - displayErrorMessageAndQuit.accept(new Pair("Error during evaluation of the flag '-set_settings'", "Error during evaluation of the flag '-set_settings': the argument '" + iniFilesLocation_String + "' is not a directory!")); + displayErrorMessageAndQuit.accept(new Pair(Messages.ErrorDuringEvalutationOfTheFlagSelectSettings, Messages.ErrorDuringEvalutationOfTheFlagSelectSettings + ": " + MessageFormat.format(Messages.TheArgumentIsNotADirectory, iniFilesLocation_String))); } } } diff --git a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties index 2c9a3c4882..1c41683f3c 100644 --- a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties +++ b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties @@ -26,11 +26,14 @@ DockNotSaved= DockSplitH=Split Left/Right DockSplitV=Split Top/Bottom Enjoy=Enjoy CS-Studio! +ErrorDuringEvalutationOfTheFlagSelectSettings="Error during evaluation of the flag '-select_settings'" +ErrorLoadingPhoebusConfiguration=Error loading Phoebus configuration Exit=Exit ExitContent=Closing this window exits the application,\nclosing all other windows.\n ExitHdr=Close main window ExitTitle=Exit File=File +FileDoesNotExist=File does not exist! FileExists=File \"{0}\" already exists. Do you want to overwrite it? FixedTitle=CS-Studio Help=Help @@ -76,6 +79,7 @@ MonitorTaskTabs=Restore tabs MonitorTaskUi=Start UI NamePane=Name Pane NamePaneHdr=Assign a name to this pane.\nSome displays can be configured\nto appear in a named pane. +OK=OK Open=Open... OpenHdr=Select application for opening\n OpenTitle=Open @@ -115,9 +119,12 @@ SavingErr=Error saving SavingHdr=Save error ScreenshotErrHdr=Screenshot error ScreenshotErrMsg=Cannot write screenshot +SelectPhoebusConfiguration=Select Phoebus configuration SelectTab=Select Tab ShowStatusbar=Show Status bar ShowToolbar=Show Toolbar +TheArgumentIsNotADirectory="the argument '{0}' is not a directory!" +TheDirectoryDoesNotContainConfigurationFiles=the directory '{0}' does not contain any .ini or .xml file(s)! Time12h=12 h Time1d=1 day Time3d=3 days From d8c922a7c5c95a50f995d65070650b029690dc8e Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Thu, 11 Apr 2024 13:27:21 +0200 Subject: [PATCH 87/92] CSSTUDIO-2231 Fix format strings. --- .../org/phoebus/ui/application/messages.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties index 1c41683f3c..c144a5a2c3 100644 --- a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties +++ b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties @@ -26,7 +26,7 @@ DockNotSaved= DockSplitH=Split Left/Right DockSplitV=Split Top/Bottom Enjoy=Enjoy CS-Studio! -ErrorDuringEvalutationOfTheFlagSelectSettings="Error during evaluation of the flag '-select_settings'" +ErrorDuringEvalutationOfTheFlagSelectSettings=Error during evaluation of the flag '-select_settings' ErrorLoadingPhoebusConfiguration=Error loading Phoebus configuration Exit=Exit ExitContent=Closing this window exits the application,\nclosing all other windows.\n @@ -123,8 +123,8 @@ SelectPhoebusConfiguration=Select Phoebus configuration SelectTab=Select Tab ShowStatusbar=Show Status bar ShowToolbar=Show Toolbar -TheArgumentIsNotADirectory="the argument '{0}' is not a directory!" -TheDirectoryDoesNotContainConfigurationFiles=the directory '{0}' does not contain any .ini or .xml file(s)! +TheArgumentIsNotADirectory=the argument ''{0}'' is not a directory! +TheDirectoryDoesNotContainConfigurationFiles=the directory ''{0}'' does not contain any .ini or .xml file(s)! Time12h=12 h Time1d=1 day Time3d=3 days From bd0f1b45ac417566cb99a81fa59c558f49f25e81 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Thu, 11 Apr 2024 13:29:40 +0200 Subject: [PATCH 88/92] CSSTUDIO-2231 Add label to 'messages.properties'. --- core/ui/src/main/java/org/phoebus/ui/application/Messages.java | 1 + .../java/org/phoebus/ui/application/PhoebusApplication.java | 2 +- .../resources/org/phoebus/ui/application/messages.properties | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java index 60c72dab8f..22bb252a91 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/Messages.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/Messages.java @@ -98,6 +98,7 @@ public class Messages public static String OpenHdr; public static String OpenTitle; public static String OpenWith; + public static String PhoebusWillQuit; public static String ProgressTitle; public static String PVListAppName; public static String PVListJobName; diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index 3f977cb094..210ba67578 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -324,7 +324,7 @@ private void possiblySelectIniFile(CopyOnWriteArrayList application_para Dialog errorDialog = new Alert(AlertType.ERROR); errorDialog.setTitle(errorTitle); errorDialog.setHeaderText(errorTitle); - errorDialog.setContentText(errorMessage + "\n\nPhoebus will quit."); + errorDialog.setContentText(errorMessage + "\n\n" + Messages.PhoebusWillQuit); errorDialog.showAndWait(); stop(); diff --git a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties index c144a5a2c3..3f4b29c0c2 100644 --- a/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties +++ b/core/ui/src/main/resources/org/phoebus/ui/application/messages.properties @@ -84,6 +84,7 @@ Open=Open... OpenHdr=Select application for opening\n OpenTitle=Open OpenWith=Open With... +PhoebusWillQuit=Phoebus will quit. ProgressTitle=CS-Studio PVListAppName=PV List PVListJobName=List PVs From f91543526a016031d94f37a6fc2eaff819117036 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Thu, 11 Apr 2024 13:40:39 +0200 Subject: [PATCH 89/92] CSSTUDIO-2231 Fix off-by-one bug. --- .../java/org/phoebus/ui/application/PhoebusApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index 210ba67578..cdbd71a08c 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -335,7 +335,7 @@ private void possiblySelectIniFile(CopyOnWriteArrayList application_para if (indexOfFlag < 0) { throw new RuntimeException("Error, this should never happen!"); } - if (application_parameters.size() >= indexOfFlag) { + if (application_parameters.size() > indexOfFlag) { String iniFilesLocation_String = application_parameters.get(indexOfFlag + 1); File iniFilesLocation_File = new File(iniFilesLocation_String); if (iniFilesLocation_File.isDirectory()) { From cb5574ab3957d59595c84b5d83951a777073bd5f Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 11 Apr 2024 14:16:26 -0400 Subject: [PATCH 90/92] Remove multiple Annunciator SPI impl, use tts for backward compatibility --- phoebus-product/pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index ac3c6b6d1d..4815bb3803 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -192,12 +192,6 @@ 4.7.4-SNAPSHOT true
    - - org.phoebus - app-alarm-audio-annunciator - 4.7.4-SNAPSHOT - true - org.phoebus app-alarm-logging-ui From f829f3d78e95ea2618bdf4b2cb1dede752f1a1b5 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Fri, 12 Apr 2024 10:13:20 -0400 Subject: [PATCH 91/92] Fixing the loading of SPI contributions for Annunciators --- .../ui/annunciator/AnnunciatorController.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java index 5288e6da13..a550148a72 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java @@ -14,6 +14,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.phoebus.applications.alarm.model.SeverityLevel; import org.phoebus.framework.adapter.AdapterFactory; @@ -46,7 +47,7 @@ public class AnnunciatorController private final BlockingQueue to_annunciate = new LinkedBlockingQueue<>(); - private static ServiceLoader loader; + private final List annunciators; private final Thread process_thread = new Thread(this::processMessages, "Annunciator"); @@ -62,6 +63,10 @@ public AnnunciatorController(final int threshold, final Consumer loader = ServiceLoader.load(Annunciator.class); + annunciators = loader.stream().map(ServiceLoader.Provider::get).collect(Collectors.toList()); + // The thread should exit when requested by shutdown() call, but set to daemon so it dies // when program closes regardless. process_thread.setDaemon(true); @@ -84,8 +89,6 @@ private void processMessages() { final List batch = new ArrayList<>(); - loader = ServiceLoader.load(Annunciator.class); - // Process new messages until receiving LAST_MESSAGE while (true) { @@ -120,8 +123,8 @@ private void processMessages() { addToTable.accept(message); if (! muted) - loader.stream().forEach(annunciatorProvider -> { - annunciatorProvider.get().speak(message); + annunciators.stream().forEach(annunciator -> { + annunciator.speak(message); }); } } @@ -138,7 +141,9 @@ private void processMessages() addToTable.accept(message); if (! muted) { - loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().speak(message)); + annunciators.stream().forEach(annunciator -> { + annunciator.speak(message); + }); } } else @@ -154,7 +159,9 @@ private void processMessages() addToTable.accept(message); if (! muted) { - loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().speak(message)); + annunciators.stream().forEach(annunciator -> { + annunciator.speak(message); + }); } } } @@ -173,7 +180,8 @@ public void shutdown() throws InterruptedException process_thread.join(2000); // Deallocate the annunciator's voice. - loader.stream().forEach(annunciatorProvider -> annunciatorProvider.get().shutdown()); - + annunciators.stream().forEach(annunciator -> { + annunciator.shutdown(); + }); } } From 8c995c46ab10a13c6e841c2a9c098bb216dada62 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 17 Apr 2024 08:37:27 +0200 Subject: [PATCH 92/92] Optimize Apache Batik dependencies --- app/imageviewer/pom.xml | 23 +++----- core/ui/pom.xml | 23 +++----- dependencies/phoebus-target/pom.xml | 83 +++-------------------------- pom.xml | 2 +- 4 files changed, 22 insertions(+), 109 deletions(-) diff --git a/app/imageviewer/pom.xml b/app/imageviewer/pom.xml index 00dbab14ce..004c222597 100644 --- a/app/imageviewer/pom.xml +++ b/app/imageviewer/pom.xml @@ -19,23 +19,14 @@ org.apache.xmlgraphics - batik-svggen - ${batik.version} - - - org.apache.xmlgraphics - batik-transcoder - ${batik.version} - - - org.apache.xmlgraphics - batik-util - ${batik.version} - - - org.apache.xmlgraphics - batik-xml + batik-all ${batik.version} + + + xml-apis + xml-apis + + diff --git a/core/ui/pom.xml b/core/ui/pom.xml index af0d150eb1..6a0ce70dad 100644 --- a/core/ui/pom.xml +++ b/core/ui/pom.xml @@ -85,23 +85,14 @@ org.apache.xmlgraphics - batik-svggen - ${batik.version} - - - org.apache.xmlgraphics - batik-transcoder - ${batik.version} - - - org.apache.xmlgraphics - batik-util - ${batik.version} - - - org.apache.xmlgraphics - batik-xml + batik-all ${batik.version} + + + xml-apis + xml-apis + + diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml index 79499f62fc..b9864c4e8c 100644 --- a/dependencies/phoebus-target/pom.xml +++ b/dependencies/phoebus-target/pom.xml @@ -373,83 +373,14 @@ org.apache.xmlgraphics - batik-anim - ${batik.version} - - - org.apache.xmlgraphics - batik-awt-util - ${batik.version} - - - org.apache.xmlgraphics - batik-bridge - ${batik.version} - - - org.apache.xmlgraphics - batik-constants - ${batik.version} - - - org.apache.xmlgraphics - batik-css - ${batik.version} - - - org.apache.xmlgraphics - batik-dom - ${batik.version} - - - org.apache.xmlgraphics - batik-ext - ${batik.version} - - - org.apache.xmlgraphics - batik-gvt - ${batik.version} - - - org.apache.xmlgraphics - batik-i18n - ${batik.version} - - - org.apache.xmlgraphics - batik-parser - ${batik.version} - - - org.apache.xmlgraphics - batik-script - ${batik.version} - - - org.apache.xmlgraphics - batik-svg-dom - ${batik.version} - - - org.apache.xmlgraphics - batik-svggen - ${batik.version} - - - org.apache.xmlgraphics - batik-transcoder - ${batik.version} - - - org.apache.xmlgraphics - batik-util - ${batik.version} - - - org.apache.xmlgraphics - batik-xml + batik-all ${batik.version} + + + xml-apis + xml-apis + + org.apache.xmlgraphics diff --git a/pom.xml b/pom.xml index 9af7e5aeb6..d72281e9c9 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ 1.0.7 19 2.12.3 - 1.14 + 1.17 2.23.4 42.6.0