From 7d09f455f8213c85cef30a1c2a64c01ec1e9195f Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Thu, 11 Apr 2024 14:24:25 -0400
Subject: [PATCH 01/59] Archiver Datasource protoype module

---
 app/trends/archive-datasource/pom.xml | 25 +++++++++++++++++++++++++
 app/trends/pom.xml                    |  1 +
 2 files changed, 26 insertions(+)
 create mode 100644 app/trends/archive-datasource/pom.xml

diff --git a/app/trends/archive-datasource/pom.xml b/app/trends/archive-datasource/pom.xml
new file mode 100644
index 0000000000..f0ea19091d
--- /dev/null
+++ b/app/trends/archive-datasource/pom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>parent</artifactId>
+        <groupId>org.phoebus</groupId>
+        <version>4.7.4-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>app-trends-archive-datasource</artifactId>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.phoebus</groupId>
+            <artifactId>app-databrowser</artifactId>
+            <version>4.7.4-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/app/trends/pom.xml b/app/trends/pom.xml
index abdc14689a..83c350c27b 100644
--- a/app/trends/pom.xml
+++ b/app/trends/pom.xml
@@ -10,5 +10,6 @@
   <modules>
     <module>rich-adapters</module>
     <module>simple-adapters</module>
+    <module>archive-datasource</module>
   </modules>
 </project>

From 52ccde63edd93f89aac77eedd253be8468c4d7a5 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Thu, 11 Apr 2024 14:25:44 -0400
Subject: [PATCH 02/59] Create an archiver appliance reader - reusing
 databrowser code

---
 .../pv/archive/ArchiveReaderService.java      | 32 +++++++++++++++++++
 .../org/phoebus/pv/archive/Preferences.java   | 24 ++++++++++++++
 ...ppliance_datasource_preferences.properties |  5 +++
 3 files changed, 61 insertions(+)
 create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderService.java
 create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java
 create mode 100644 app/trends/archive-datasource/src/main/resources/appliance_datasource_preferences.properties

diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderService.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderService.java
new file mode 100644
index 0000000000..7a7f43674d
--- /dev/null
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderService.java
@@ -0,0 +1,32 @@
+package org.phoebus.pv.archive;
+
+import org.phoebus.archive.reader.ArchiveReader;
+import org.phoebus.archive.reader.appliance.ApplianceArchiveReader;
+
+public class ArchiveReaderService {
+
+    /**
+     * Singleton
+     */
+    private static final ArchiveReaderService INSTANCE = new ArchiveReaderService();
+
+    private final ArchiveReader reader;
+
+    public static ArchiveReaderService getService() {
+        return INSTANCE;
+    }
+
+    private ArchiveReaderService() {
+        // Might have to add support for multiple AA URL's
+        reader = createReader(Preferences.archive_url);
+    }
+
+    private ArchiveReader createReader(final String url) {
+        final ApplianceArchiveReader reader = new ApplianceArchiveReader(url, false, true);
+        return reader;
+    }
+
+    public ArchiveReader getReader() {
+        return reader;
+    }
+}
diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java
new file mode 100644
index 0000000000..231b3d8aa1
--- /dev/null
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java
@@ -0,0 +1,24 @@
+package org.phoebus.pv.archive;
+
+import org.csstudio.trends.databrowser3.Activator;
+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 archive_url;
+
+    static
+    {
+        final PreferencesReader prefs = AnnotatedPreferences.initialize(Activator.class, Preferences.class, "/appliance_datasource_preferences.properties");
+    }
+
+}
diff --git a/app/trends/archive-datasource/src/main/resources/appliance_datasource_preferences.properties b/app/trends/archive-datasource/src/main/resources/appliance_datasource_preferences.properties
new file mode 100644
index 0000000000..83b8ef61b9
--- /dev/null
+++ b/app/trends/archive-datasource/src/main/resources/appliance_datasource_preferences.properties
@@ -0,0 +1,5 @@
+# ----------------------------------------
+# Package org.phoebus.pv.archive
+# ----------------------------------------
+
+archive_url=http://localhost:10068/retrieval

From 02fc4f3671c7513b86c83b3693d72f630e4ed78f Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Thu, 11 Apr 2024 15:08:35 -0400
Subject: [PATCH 03/59] Simple archive retrieval datasource, constant

---
 .../pv/archive/retrieve/ArchivePV.java        | 61 ++++++++++++++++++
 .../pv/archive/retrieve/ArchivePVFactory.java | 62 +++++++++++++++++++
 .../services/org.phoebus.pv.PVFactory         |  1 +
 3 files changed, 124 insertions(+)
 create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
 create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java
 create mode 100644 app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory

diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
new file mode 100644
index 0000000000..19e4a83c85
--- /dev/null
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
@@ -0,0 +1,61 @@
+package org.phoebus.pv.archive.retrieve;
+
+import org.phoebus.archive.reader.ValueIterator;
+import org.phoebus.pv.PV;
+import org.phoebus.pv.archive.ArchiveReaderService;
+
+import java.time.Instant;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * A Connection to a PV in the archiver
+ *
+ * @author Kunal Shroff
+ */
+public class ArchivePV extends PV {
+
+    ArchiveReaderService service = ArchiveReaderService.getService();
+
+    /**
+     * Timer for archive updates
+     */
+    private final static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, target ->
+    {
+        final Thread thread = new Thread(target, "ArchivePV");
+        thread.setDaemon(true);
+        return thread;
+    });
+
+    public ArchivePV(String name) {
+        this(name, Instant.now());
+    }
+
+    public ArchivePV(String name, Instant instant) {
+        super(name);
+        try {
+            ValueIterator i = service.getReader().getRawValues(name, instant, instant);
+
+            if (i.hasNext()) {
+                notifyListenersOfValue(i.next());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void disconnected() {
+        notifyListenersOfDisconnect();
+    }
+
+    @Override
+    protected void close() {
+        super.close();
+    }
+
+    @Override
+    public boolean isReadonly() {
+        return true;
+    }
+
+}
diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java
new file mode 100644
index 0000000000..23ed1ca49b
--- /dev/null
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java
@@ -0,0 +1,62 @@
+package org.phoebus.pv.archive.retrieve;
+
+import org.phoebus.pv.PV;
+import org.phoebus.pv.PVFactory;
+
+import java.time.Instant;
+import java.util.logging.Logger;
+
+import static org.phoebus.util.time.TimestampFormats.DATETIME_FORMAT;
+import static org.phoebus.util.time.TimestampFormats.SECONDS_FORMAT;
+import static org.phoebus.util.time.TimestampFormats.MILLI_FORMAT;
+import static org.phoebus.util.time.TimestampFormats.FULL_FORMAT;
+
+/**
+ * A datasource for the retrieval of archived PV's
+ * @author Kunal Shroff
+ */
+public class ArchivePVFactory implements PVFactory
+{
+
+    final public static Logger logger = Logger.getLogger(ArchivePVFactory.class.getName());
+    final public static String TYPE = "archive";
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+
+    @Override
+    public PV createPV(String name, String base_name) throws Exception {
+        // Determine simulation function name and (optional) parameters
+        final String pvName, parameters;
+        int sep = base_name.indexOf('(');
+        if (sep < 0)
+        {
+            pvName = base_name;
+            parameters = "";
+        }
+        else
+        {
+            final int end = base_name.lastIndexOf(')');
+            if (end < 0)
+                throw new Exception("Missing closing bracket for parameters in '" + name + "'");
+            pvName = base_name.substring(0, sep);
+            parameters = base_name.substring(sep+1, end);
+        }
+
+        if(parameters.isEmpty()) {
+            return new ArchivePV(pvName);
+        } else {
+            Instant time;
+            switch (parameters.length()) {
+                case 16 -> time = Instant.from(DATETIME_FORMAT.parse(parameters));
+                case 19 -> time = Instant.from(SECONDS_FORMAT.parse(parameters));
+                case 23 -> time = Instant.from(MILLI_FORMAT.parse(parameters));
+                case 29 -> time = Instant.from(FULL_FORMAT.parse(parameters));
+                default -> throw new Exception("Time value defined in a unknown formatt, '" + parameters + "'");
+            }
+            return new ArchivePV(pvName, time);
+        }
+    }
+}
diff --git a/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory b/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory
new file mode 100644
index 0000000000..63ecd477bd
--- /dev/null
+++ b/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory
@@ -0,0 +1 @@
+org.phoebus.pv.archive.retrieve.ArchivePVFactory
\ No newline at end of file

From 6c6f24daee68ad63912fd1b5ffcb9a92d84eca6d Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Wed, 22 May 2024 15:08:59 -0400
Subject: [PATCH 04/59] Add the archive datasource 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 4815bb3803..24b256a3d6 100644
--- a/phoebus-product/pom.xml
+++ b/phoebus-product/pom.xml
@@ -240,6 +240,12 @@
             <version>4.7.4-SNAPSHOT</version>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.phoebus</groupId>
+            <artifactId>app-trends-archive-datasource</artifactId>
+            <version>4.7.4-SNAPSHOT</version>
+            <optional>true</optional>
+        </dependency>
         <dependency>
             <groupId>org.phoebus</groupId>
             <artifactId>app-channel-views</artifactId>

From 8f7b46d954705d86cd5c010e3262ebfa6aafedf1 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Wed, 22 May 2024 15:09:20 -0400
Subject: [PATCH 05/59] starting some documentation on the usage of the aa
 datasources

---
 app/trends/archive-datasource/doc/index.rst | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 app/trends/archive-datasource/doc/index.rst

diff --git a/app/trends/archive-datasource/doc/index.rst b/app/trends/archive-datasource/doc/index.rst
new file mode 100644
index 0000000000..c7515037d6
--- /dev/null
+++ b/app/trends/archive-datasource/doc/index.rst
@@ -0,0 +1,21 @@
+Alarm Datasource
+================
+
+Overview
+--------
+The archive datasource allows accessing historical data as a pv
+
+
+PV syntax
+---------
+
+The standard prefix for the datasource is ``archive://`` which can be omitted if configured as the default datasource.
+The archiver PV's are readonly and constants.
+
+archive://pv_name
+
+Retrieves the latest value in the archiver
+
+archive://pv_name(time)
+
+Retrieves the last value at or before the "time"

From 4ef8bb4e07c24fc092e38c7d49684b997e3ef1fa Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Fri, 31 May 2024 10:12:53 -0400
Subject: [PATCH 06/59] Fix the parent for the archive datasource module

---
 app/trends/archive-datasource/pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/trends/archive-datasource/pom.xml b/app/trends/archive-datasource/pom.xml
index f0ea19091d..6bda3f309e 100644
--- a/app/trends/archive-datasource/pom.xml
+++ b/app/trends/archive-datasource/pom.xml
@@ -3,7 +3,7 @@
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
-        <artifactId>parent</artifactId>
+        <artifactId>app-trends</artifactId>
         <groupId>org.phoebus</groupId>
         <version>4.7.4-SNAPSHOT</version>
     </parent>

From c252a8b227cb68d6b4222db04f34ef51e982f2ed Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Fri, 31 May 2024 11:07:22 -0400
Subject: [PATCH 07/59] Refactor archiver readers out of app-databrowser into
 independent module

---
 .../imports/ImportArchiveReaderFactory.java   |  25 ++---
 .../archive/vtype/TimestampHelper.java        | 100 ------------------
 ...us.archive.reader.spi.ArchiveReaderFactory |   5 -
 .../archive/vtype/TimestampHelperTest.java    |  91 ----------------
 .../phoebus/archive/reader/ArchiveReader.java |   0
 .../archive/reader/ArchiveReaders.java        |   0
 .../archive/reader/AveragedValueIterator.java |   0
 .../archive/reader/LinearValueIterator.java   |   0
 .../archive/reader/MergingValueIterator.java  |   0
 .../archive/reader/SpreadsheetIterator.java   |   0
 .../reader/UnknownChannelException.java       |   0
 .../phoebus/archive/reader/ValueIterator.java |   0
 .../appliance/ApplianceArchiveReader.java     |   0
 .../ApplianceArchiveReaderConstants.java      |   0
 .../ApplianceArchiveReaderFactory.java        |   0
 .../appliance/ApplianceMeanValueIterator.java |   0
 ...ianceNonNumericOptimizedValueIterator.java |   0
 .../ApplianceOptimizedValueIterator.java      |   0
 .../appliance/AppliancePreferences.java       |   0
 .../appliance/ApplianceRawValueIterator.java  |   0
 .../ApplianceStatisticsValueIterator.java     |   0
 .../appliance/ApplianceValueIterator.java     |   0
 .../appliance/ArchiverApplianceException.java |   0
 ...ArchiverApplianceInvalidTypeException.java |   0
 .../reader/appliance/IteratorListener.java    |   0
 .../reader/channelarchiver/SeverityInfo.java  |   0
 .../channelarchiver/ValueRequestIterator.java |   0
 .../channelarchiver/XMLRPCArchiveReader.java  |   0
 .../XMLRPCArchiveReaderFactory.java           |   0
 .../reader/channelarchiver/XmlRpc.java        |   0
 .../file/ArchiveFileBuffer.java               |   0
 .../file/ArchiveFileIndexReader.java          |   0
 .../file/ArchiveFileReader.java               |   0
 .../file/ArchiveFileReaderFactory.java        |   0
 .../file/ArchiveFileSampleReader.java         |   0
 .../channelarchiver/file/ArchiveFileTime.java |   0
 .../channelarchiver/file/CtrlInfoReader.java  |   0
 .../channelarchiver/file/DataFileEntry.java   |   0
 .../channelarchiver/file/DataHeader.java      |   0
 .../channelarchiver/file/ListIndexReader.java |   0
 .../channelarchiver/file/RTreeNode.java       |   0
 .../reader/rdb/AbstractRDBValueIterator.java  |   0
 .../archive/reader/rdb/RDBArchiveReader.java  |   0
 .../reader/rdb/RDBArchiveReaderFactory.java   |   0
 .../archive/reader/rdb/RDBPreferences.java    |   0
 .../archive/reader/rdb/RawSampleIterator.java |   0
 .../org/phoebus/archive/reader/rdb/SQL.java   |   0
 .../rdb/StoredProcedureValueIterator.java     |   0
 .../reader/spi/ArchiveReaderFactory.java      |   0
 .../reader/util/ChannelAccessStatusUtil.java  |   0
 .../appliance_preferences.properties          |   0
 .../archive_reader_rdb_preferences.properties |   0
 .../channelarchiver_preferences.properties    |   0
 .../archive/reader/DemoDataIterator.java      |   0
 .../reader/SpreadsheetIteratorUnitTest.java   |   0
 .../XMLRPCArchiveReaderDemo.java              |   0
 56 files changed, 13 insertions(+), 208 deletions(-)
 delete mode 100644 app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java
 delete mode 100644 app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory
 delete mode 100644 app/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/ArchiveReader.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/ArchiveReaders.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/MergingValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/SpreadsheetIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/UnknownChannelException.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/ValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderConstants.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderFactory.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceNonNumericOptimizedValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/AppliancePreferences.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceRawValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceException.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceInvalidTypeException.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/IteratorListener.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/SeverityInfo.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/ValueRequestIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReader.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderFactory.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/XmlRpc.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileBuffer.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileIndexReader.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReader.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReaderFactory.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileTime.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/CtrlInfoReader.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataFileEntry.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataHeader.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ListIndexReader.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/RTreeNode.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/AbstractRDBValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReaderFactory.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/RawSampleIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/SQL.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/StoredProcedureValueIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/spi/ArchiveReaderFactory.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/resources/appliance_preferences.properties (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/resources/archive_reader_rdb_preferences.properties (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/resources/channelarchiver_preferences.properties (100%)
 rename app/{databrowser => trends/archive-reader}/src/test/java/org/phoebus/archive/reader/DemoDataIterator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/test/java/org/phoebus/archive/reader/SpreadsheetIteratorUnitTest.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/test/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderDemo.java (100%)

diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java
index 8eb68bcd5b..539728d809 100644
--- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java
+++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java
@@ -7,13 +7,13 @@
  ******************************************************************************/
 package org.csstudio.trends.databrowser3.imports;
 
-import java.util.Collection;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.csstudio.trends.databrowser3.model.ArchiveDataSource;
 import org.phoebus.archive.reader.ArchiveReader;
 import org.phoebus.archive.reader.spi.ArchiveReaderFactory;
 
+import java.util.concurrent.ConcurrentHashMap;
+// TODO resolve the dependency between this archive reader and the app-databrowser
+//import org.csstudio.trends.databrowser3.model.ArchiveDataSource;
+
 /** Factory for {@link ArchiveReader} that imports data from file
  *  @author Kay Kasemir
  */
@@ -103,12 +103,13 @@ private ArchiveReader doCreateReader(final String url)
         }
     }
 
-    /** Removed cached data for given archive data sources
-     *  @param sources {@link ArchiveDataSource}[]
-     */
-    public static void removeCachedArchives(final Collection<ArchiveDataSource> sources)
-    {
-        for (ArchiveDataSource source : sources)
-            cache.remove(source.getUrl());
-    }
+    // TODO resolve the dependency without creating a cyclic dependency with app-databrowser
+//    /** Removed cached data for given archive data sources
+//     *  @param sources {@link ArchiveDataSource}[]
+//     */
+//    public static void removeCachedArchives(final Collection<ArchiveDataSource> sources)
+//    {
+//        for (ArchiveDataSource source : sources)
+//            cache.remove(source.getUrl());
+//    }
 }
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java b/app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java
deleted file mode 100644
index 49f6638374..0000000000
--- a/app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*******************************************************************************
- * 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.archive.vtype;
-
-import java.sql.Timestamp;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.concurrent.TimeUnit;
-
-/** Time stamp helper */
-public class TimestampHelper
-{
-    /** Round time to next multiple of given duration
-     *  @param time Original time stamp
-     *  @param duration Duration to use for rounding
-     *  @return Time stamp rounded up to next multiple of duration
-     */
-    public static Instant roundUp(final Instant time, final Duration duration)
-    {
-        return roundUp(time, duration.getSeconds());
-    }
-
-    /** Seconds per hour */
-    final public static long SECS_PER_HOUR = TimeUnit.HOURS.toSeconds(1);
-    /** Seconds per minute */
-    final public static long SECS_PER_MINUTE = TimeUnit.MINUTES.toSeconds(1);
-    /** Seconds per day */
-    final public static long SECS_PER_DAY = TimeUnit.DAYS.toSeconds(1);
-
-    /** Round time to next multiple of given seconds
-     *  @param time Original time stamp
-     *  @param seconds Seconds to use for rounding
-     *  @return Time stamp rounded up to next multiple of seconds
-     */
-    public static Instant roundUp(final Instant time, final long seconds)
-    {
-        if (seconds <= 0)
-            return time;
-
-        // Directly round seconds within an hour
-        if (seconds <= SECS_PER_HOUR)
-        {
-            long secs = time.getEpochSecond();
-            if (time.getNano() > 0)
-                ++secs;
-            final long periods = secs / seconds;
-            secs = (periods + 1) * seconds;
-            return Instant.ofEpochSecond(secs, 0);
-        }
-
-        // When rounding "2012/01/19 12:23:14" by 2 hours,
-        // the user likely expects "2012/01/19 14:00:00"
-        // because 12.xx rounded up by 2 is 14.
-        //
-        // In other words, rounding by 2 should result in an even hour,
-        // but this is in the user's time zone.
-        // When rounding based on the epoch seconds, which could differ
-        // by an odd number of hours from the local time zone, rounding by
-        // 2 hours could result in odd-numbered hours in local time.
-        //
-        // The addition of leap seconds can further confuse matters,
-        // so perform computations that go beyond an hour in local time,
-        // relative to midnight of the given time stamp.
-        final ZonedDateTime local = ZonedDateTime.ofInstant(time, ZoneId.systemDefault());
-        final ZonedDateTime midnight = ZonedDateTime.of(local.getYear(), local.getMonthValue(), local.getDayOfMonth(),
-                                                        0, 0, 0, 0, local.getZone());
-
-        // Round the HH:MM within the day
-        long secs = local.getHour()* SECS_PER_HOUR +
-                    local.getMinute() * SECS_PER_MINUTE;
-        final long periods = secs / seconds;
-        secs = (periods + 1) * seconds;
-
-        // Create time for rounded HH:MM
-        return midnight.toInstant().plus(Duration.ofSeconds(secs));
-    }
-
-    /** @param time Instant
-     *  @return SQL time stamp
-     */
-    public static Timestamp toSQLTimestamp(final Instant time)
-    {
-        return java.sql.Timestamp.from(time);
-    }
-
-    /** @param time SQL time stamp
-     *  @return Instant
-     */
-    public static Instant fromSQLTimestamp(final Timestamp time)
-    {
-        return time.toInstant();
-    }
-}
diff --git a/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory b/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory
deleted file mode 100644
index 7c0cb8427b..0000000000
--- a/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory
+++ /dev/null
@@ -1,5 +0,0 @@
-org.phoebus.archive.reader.appliance.ApplianceArchiveReaderFactory
-org.phoebus.archive.reader.rdb.RDBArchiveReaderFactory
-org.phoebus.archive.reader.channelarchiver.XMLRPCArchiveReaderFactory
-org.phoebus.archive.reader.channelarchiver.file.ArchiveFileReaderFactory
-org.csstudio.trends.databrowser3.imports.ImportArchiveReaderFactory
diff --git a/app/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java b/app/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java
deleted file mode 100644
index cfc68a31cb..0000000000
--- a/app/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*******************************************************************************
- * 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.archive.vtype;
-
-import org.junit.jupiter.api.Test;
-import org.phoebus.util.time.TimeDuration;
-import org.phoebus.util.time.TimestampFormats;
-
-import java.time.Instant;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-/** JUnit test of {@link TimestampHelper}
- *  @author Kay Kasemir
- */
-@SuppressWarnings("nls")
-public class TimestampHelperTest
-{
-    @Test
-    public void testRoundUp() throws Exception
-    {
-        final Instant orig = Instant.from(TimestampFormats.SECONDS_FORMAT.parse("2012-01-19 12:23:14"));
-        String text = TimestampFormats.SECONDS_FORMAT.format(orig);
-        System.out.println(text);
-        assertThat(text, equalTo("2012-01-19 12:23:14"));
-
-        Instant time;
-
-        // Round within a few seconds
-        time = TimestampHelper.roundUp(orig, 10);
-        text = TimestampFormats.SECONDS_FORMAT.format(time);
-        System.out.println(text);
-        assertThat(text, equalTo("2012-01-19 12:23:20"));
-
-        time = TimestampHelper.roundUp(orig, TimeDuration.ofSeconds(30));
-        text = TimestampFormats.SECONDS_FORMAT.format(time);
-        System.out.println(text);
-        assertThat(text, equalTo("2012-01-19 12:23:30"));
-
-        // .. to minute
-        time = TimestampHelper.roundUp(orig, 60);
-        text = TimestampFormats.SECONDS_FORMAT.format(time);
-        System.out.println(text);
-        assertThat(text, equalTo("2012-01-19 12:24:00"));
-
-        // .. to hours
-        time = TimestampHelper.roundUp(orig, TimeDuration.ofHours(1.0));
-        text = TimestampFormats.SECONDS_FORMAT.format(time);
-        System.out.println(text);
-        assertThat(text, equalTo("2012-01-19 13:00:00"));
-
-        time = TimestampHelper.roundUp(orig, 2L*60*60);
-        text = TimestampFormats.SECONDS_FORMAT.format(time);
-        System.out.println(text);
-        assertThat(text, equalTo("2012-01-19 14:00:00"));
-
-        // .. full day(s)
-        assertThat(24L*60*60, equalTo(TimestampHelper.SECS_PER_DAY));
-
-        time = TimestampHelper.roundUp(orig, TimestampHelper.SECS_PER_DAY);
-        text = TimestampFormats.SECONDS_FORMAT.format(time);
-        System.out.println(text);
-        assertThat(text, equalTo("2012-01-20 00:00:00"));
-
-        time = TimestampHelper.roundUp(orig, 3*TimestampHelper.SECS_PER_DAY);
-        text = TimestampFormats.SECONDS_FORMAT.format(time);
-        System.out.println(text);
-        assertThat(text, equalTo("2012-01-22 00:00:00"));
-
-        // Into next month
-        time = TimestampHelper.roundUp(orig, 13*TimestampHelper.SECS_PER_DAY);
-        text = TimestampFormats.SECONDS_FORMAT.format(time);
-        System.out.println(text);
-        assertThat(text, equalTo("2012-02-01 00:00:00"));
-
-        // .. full day(s)
-        assertThat(24L*60*60, equalTo(TimestampHelper.SECS_PER_DAY));
-
-        // 1.5 days
-        time = TimestampHelper.roundUp(orig, (3*TimestampHelper.SECS_PER_DAY)/2);
-        text = TimestampFormats.SECONDS_FORMAT.format(time);
-        System.out.println(text);
-        assertThat(text, equalTo("2012-01-20 12:00:00"));
-    }
-}
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/ArchiveReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ArchiveReader.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/ArchiveReader.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ArchiveReader.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/ArchiveReaders.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ArchiveReaders.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/ArchiveReaders.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ArchiveReaders.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/MergingValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/MergingValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/MergingValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/MergingValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/SpreadsheetIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/SpreadsheetIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/SpreadsheetIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/SpreadsheetIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/UnknownChannelException.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/UnknownChannelException.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/UnknownChannelException.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/UnknownChannelException.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/ValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/ValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderConstants.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderConstants.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderConstants.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderConstants.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderFactory.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderFactory.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderFactory.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderFactory.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceNonNumericOptimizedValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceNonNumericOptimizedValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceNonNumericOptimizedValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceNonNumericOptimizedValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/AppliancePreferences.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/AppliancePreferences.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/AppliancePreferences.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/AppliancePreferences.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceRawValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceRawValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceRawValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceRawValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceException.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceException.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceException.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceException.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceInvalidTypeException.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceInvalidTypeException.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceInvalidTypeException.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceInvalidTypeException.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/IteratorListener.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/IteratorListener.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/IteratorListener.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/IteratorListener.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/SeverityInfo.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/SeverityInfo.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/SeverityInfo.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/SeverityInfo.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/ValueRequestIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/ValueRequestIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/ValueRequestIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/ValueRequestIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReader.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReader.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReader.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderFactory.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderFactory.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderFactory.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderFactory.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XmlRpc.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XmlRpc.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XmlRpc.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XmlRpc.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileBuffer.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileBuffer.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileBuffer.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileBuffer.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileIndexReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileIndexReader.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileIndexReader.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileIndexReader.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReader.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReader.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReader.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReaderFactory.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReaderFactory.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReaderFactory.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReaderFactory.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileTime.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileTime.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileTime.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileTime.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/CtrlInfoReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/CtrlInfoReader.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/CtrlInfoReader.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/CtrlInfoReader.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataFileEntry.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataFileEntry.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataFileEntry.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataFileEntry.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataHeader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataHeader.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataHeader.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataHeader.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ListIndexReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ListIndexReader.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ListIndexReader.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ListIndexReader.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/RTreeNode.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/RTreeNode.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/RTreeNode.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/RTreeNode.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/AbstractRDBValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/AbstractRDBValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/AbstractRDBValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/AbstractRDBValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReaderFactory.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReaderFactory.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReaderFactory.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReaderFactory.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RawSampleIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RawSampleIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RawSampleIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RawSampleIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/SQL.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/SQL.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/SQL.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/SQL.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/StoredProcedureValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/StoredProcedureValueIterator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/StoredProcedureValueIterator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/StoredProcedureValueIterator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/spi/ArchiveReaderFactory.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/spi/ArchiveReaderFactory.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/spi/ArchiveReaderFactory.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/spi/ArchiveReaderFactory.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java
diff --git a/app/databrowser/src/main/resources/appliance_preferences.properties b/app/trends/archive-reader/src/main/resources/appliance_preferences.properties
similarity index 100%
rename from app/databrowser/src/main/resources/appliance_preferences.properties
rename to app/trends/archive-reader/src/main/resources/appliance_preferences.properties
diff --git a/app/databrowser/src/main/resources/archive_reader_rdb_preferences.properties b/app/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties
similarity index 100%
rename from app/databrowser/src/main/resources/archive_reader_rdb_preferences.properties
rename to app/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties
diff --git a/app/databrowser/src/main/resources/channelarchiver_preferences.properties b/app/trends/archive-reader/src/main/resources/channelarchiver_preferences.properties
similarity index 100%
rename from app/databrowser/src/main/resources/channelarchiver_preferences.properties
rename to app/trends/archive-reader/src/main/resources/channelarchiver_preferences.properties
diff --git a/app/databrowser/src/test/java/org/phoebus/archive/reader/DemoDataIterator.java b/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/DemoDataIterator.java
similarity index 100%
rename from app/databrowser/src/test/java/org/phoebus/archive/reader/DemoDataIterator.java
rename to app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/DemoDataIterator.java
diff --git a/app/databrowser/src/test/java/org/phoebus/archive/reader/SpreadsheetIteratorUnitTest.java b/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/SpreadsheetIteratorUnitTest.java
similarity index 100%
rename from app/databrowser/src/test/java/org/phoebus/archive/reader/SpreadsheetIteratorUnitTest.java
rename to app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/SpreadsheetIteratorUnitTest.java
diff --git a/app/databrowser/src/test/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderDemo.java b/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderDemo.java
similarity index 100%
rename from app/databrowser/src/test/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderDemo.java
rename to app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderDemo.java

From 05b9e40909c40a8cdd4564c519b5303bf43e7bf2 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Fri, 31 May 2024 11:08:16 -0400
Subject: [PATCH 08/59] moved to core-util with other utility classes for
 handling time

---
 .../phoebus/util/time/TimestampHelper.java    | 100 ++++++++++++++++++
 .../util/time/TimestampHelperTest.java        |  92 ++++++++++++++++
 2 files changed, 192 insertions(+)
 create mode 100644 core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java
 create mode 100644 core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java

diff --git a/core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java b/core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java
new file mode 100644
index 0000000000..aa03c40bed
--- /dev/null
+++ b/core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * 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.util.time;
+
+import java.sql.Timestamp;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.concurrent.TimeUnit;
+
+/** Time stamp helper */
+public class TimestampHelper
+{
+    /** Round time to next multiple of given duration
+     *  @param time Original time stamp
+     *  @param duration Duration to use for rounding
+     *  @return Time stamp rounded up to next multiple of duration
+     */
+    public static Instant roundUp(final Instant time, final Duration duration)
+    {
+        return roundUp(time, duration.getSeconds());
+    }
+
+    /** Seconds per hour */
+    final public static long SECS_PER_HOUR = TimeUnit.HOURS.toSeconds(1);
+    /** Seconds per minute */
+    final public static long SECS_PER_MINUTE = TimeUnit.MINUTES.toSeconds(1);
+    /** Seconds per day */
+    final public static long SECS_PER_DAY = TimeUnit.DAYS.toSeconds(1);
+
+    /** Round time to next multiple of given seconds
+     *  @param time Original time stamp
+     *  @param seconds Seconds to use for rounding
+     *  @return Time stamp rounded up to next multiple of seconds
+     */
+    public static Instant roundUp(final Instant time, final long seconds)
+    {
+        if (seconds <= 0)
+            return time;
+
+        // Directly round seconds within an hour
+        if (seconds <= SECS_PER_HOUR)
+        {
+            long secs = time.getEpochSecond();
+            if (time.getNano() > 0)
+                ++secs;
+            final long periods = secs / seconds;
+            secs = (periods + 1) * seconds;
+            return Instant.ofEpochSecond(secs, 0);
+        }
+
+        // When rounding "2012/01/19 12:23:14" by 2 hours,
+        // the user likely expects "2012/01/19 14:00:00"
+        // because 12.xx rounded up by 2 is 14.
+        //
+        // In other words, rounding by 2 should result in an even hour,
+        // but this is in the user's time zone.
+        // When rounding based on the epoch seconds, which could differ
+        // by an odd number of hours from the local time zone, rounding by
+        // 2 hours could result in odd-numbered hours in local time.
+        //
+        // The addition of leap seconds can further confuse matters,
+        // so perform computations that go beyond an hour in local time,
+        // relative to midnight of the given time stamp.
+        final ZonedDateTime local = ZonedDateTime.ofInstant(time, ZoneId.systemDefault());
+        final ZonedDateTime midnight = ZonedDateTime.of(local.getYear(), local.getMonthValue(), local.getDayOfMonth(),
+                                                        0, 0, 0, 0, local.getZone());
+
+        // Round the HH:MM within the day
+        long secs = local.getHour()* SECS_PER_HOUR +
+                    local.getMinute() * SECS_PER_MINUTE;
+        final long periods = secs / seconds;
+        secs = (periods + 1) * seconds;
+
+        // Create time for rounded HH:MM
+        return midnight.toInstant().plus(Duration.ofSeconds(secs));
+    }
+
+    /** @param time Instant
+     *  @return SQL time stamp
+     */
+    public static Timestamp toSQLTimestamp(final Instant time)
+    {
+        return java.sql.Timestamp.from(time);
+    }
+
+    /** @param time SQL time stamp
+     *  @return Instant
+     */
+    public static Instant fromSQLTimestamp(final Timestamp time)
+    {
+        return time.toInstant();
+    }
+}
diff --git a/core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java b/core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java
new file mode 100644
index 0000000000..86bc71edee
--- /dev/null
+++ b/core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * 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.util.time;
+
+import org.junit.jupiter.api.Test;
+import org.phoebus.util.time.TimeDuration;
+import org.phoebus.util.time.TimestampFormats;
+import org.phoebus.util.time.TimestampHelper;
+
+import java.time.Instant;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/** JUnit test of {@link TimestampHelper}
+ *  @author Kay Kasemir
+ */
+@SuppressWarnings("nls")
+public class TimestampHelperTest
+{
+    @Test
+    public void testRoundUp() throws Exception
+    {
+        final Instant orig = Instant.from(TimestampFormats.SECONDS_FORMAT.parse("2012-01-19 12:23:14"));
+        String text = TimestampFormats.SECONDS_FORMAT.format(orig);
+        System.out.println(text);
+        assertThat(text, equalTo("2012-01-19 12:23:14"));
+
+        Instant time;
+
+        // Round within a few seconds
+        time = TimestampHelper.roundUp(orig, 10);
+        text = TimestampFormats.SECONDS_FORMAT.format(time);
+        System.out.println(text);
+        assertThat(text, equalTo("2012-01-19 12:23:20"));
+
+        time = TimestampHelper.roundUp(orig, TimeDuration.ofSeconds(30));
+        text = TimestampFormats.SECONDS_FORMAT.format(time);
+        System.out.println(text);
+        assertThat(text, equalTo("2012-01-19 12:23:30"));
+
+        // .. to minute
+        time = TimestampHelper.roundUp(orig, 60);
+        text = TimestampFormats.SECONDS_FORMAT.format(time);
+        System.out.println(text);
+        assertThat(text, equalTo("2012-01-19 12:24:00"));
+
+        // .. to hours
+        time = TimestampHelper.roundUp(orig, TimeDuration.ofHours(1.0));
+        text = TimestampFormats.SECONDS_FORMAT.format(time);
+        System.out.println(text);
+        assertThat(text, equalTo("2012-01-19 13:00:00"));
+
+        time = TimestampHelper.roundUp(orig, 2L*60*60);
+        text = TimestampFormats.SECONDS_FORMAT.format(time);
+        System.out.println(text);
+        assertThat(text, equalTo("2012-01-19 14:00:00"));
+
+        // .. full day(s)
+        assertThat(24L*60*60, equalTo(TimestampHelper.SECS_PER_DAY));
+
+        time = TimestampHelper.roundUp(orig, TimestampHelper.SECS_PER_DAY);
+        text = TimestampFormats.SECONDS_FORMAT.format(time);
+        System.out.println(text);
+        assertThat(text, equalTo("2012-01-20 00:00:00"));
+
+        time = TimestampHelper.roundUp(orig, 3*TimestampHelper.SECS_PER_DAY);
+        text = TimestampFormats.SECONDS_FORMAT.format(time);
+        System.out.println(text);
+        assertThat(text, equalTo("2012-01-22 00:00:00"));
+
+        // Into next month
+        time = TimestampHelper.roundUp(orig, 13*TimestampHelper.SECS_PER_DAY);
+        text = TimestampFormats.SECONDS_FORMAT.format(time);
+        System.out.println(text);
+        assertThat(text, equalTo("2012-02-01 00:00:00"));
+
+        // .. full day(s)
+        assertThat(24L*60*60, equalTo(TimestampHelper.SECS_PER_DAY));
+
+        // 1.5 days
+        time = TimestampHelper.roundUp(orig, (3*TimestampHelper.SECS_PER_DAY)/2);
+        text = TimestampFormats.SECONDS_FORMAT.format(time);
+        System.out.println(text);
+        assertThat(text, equalTo("2012-01-20 12:00:00"));
+    }
+}

From b93d705d176e7ead1b19f4550a1e28597ec841ce Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Fri, 31 May 2024 11:09:12 -0400
Subject: [PATCH 09/59] Moving the very databrowser specific reader back to
 app-databrowser

---
 .../imports/ImportArchiveReaderFactory.java   | 21 +++++++++----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java
index 539728d809..c2aeb636b1 100644
--- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java
+++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java
@@ -10,9 +10,9 @@
 import org.phoebus.archive.reader.ArchiveReader;
 import org.phoebus.archive.reader.spi.ArchiveReaderFactory;
 
+import java.util.Collection;
 import java.util.concurrent.ConcurrentHashMap;
-// TODO resolve the dependency between this archive reader and the app-databrowser
-//import org.csstudio.trends.databrowser3.model.ArchiveDataSource;
+import org.csstudio.trends.databrowser3.model.ArchiveDataSource;
 
 /** Factory for {@link ArchiveReader} that imports data from file
  *  @author Kay Kasemir
@@ -103,13 +103,12 @@ private ArchiveReader doCreateReader(final String url)
         }
     }
 
-    // TODO resolve the dependency without creating a cyclic dependency with app-databrowser
-//    /** Removed cached data for given archive data sources
-//     *  @param sources {@link ArchiveDataSource}[]
-//     */
-//    public static void removeCachedArchives(final Collection<ArchiveDataSource> sources)
-//    {
-//        for (ArchiveDataSource source : sources)
-//            cache.remove(source.getUrl());
-//    }
+    /** Removed cached data for given archive data sources
+     *  @param sources {@link ArchiveDataSource}[]
+     */
+    public static void removeCachedArchives(final Collection<ArchiveDataSource> sources)
+    {
+        for (ArchiveDataSource source : sources)
+            cache.remove(source.getUrl());
+    }
 }

From 57e20143e99ed77b3a7dc601d88eff474b70e8bc Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Fri, 31 May 2024 11:09:47 -0400
Subject: [PATCH 10/59] Add the SPI for the various archive readers

---
 .../org.phoebus.archive.reader.spi.ArchiveReaderFactory       | 1 +
 .../org.phoebus.archive.reader.spi.ArchiveReaderFactory       | 4 ++++
 2 files changed, 5 insertions(+)
 create mode 100644 app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory
 create mode 100644 app/trends/archive-reader/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory

diff --git a/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory b/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory
new file mode 100644
index 0000000000..3db81f793e
--- /dev/null
+++ b/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory
@@ -0,0 +1 @@
+org.csstudio.trends.databrowser3.imports.ImportArchiveReaderFactory
diff --git a/app/trends/archive-reader/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory b/app/trends/archive-reader/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory
new file mode 100644
index 0000000000..737fe3cf43
--- /dev/null
+++ b/app/trends/archive-reader/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory
@@ -0,0 +1,4 @@
+org.phoebus.archive.reader.appliance.ApplianceArchiveReaderFactory
+org.phoebus.archive.reader.rdb.RDBArchiveReaderFactory
+org.phoebus.archive.reader.channelarchiver.XMLRPCArchiveReaderFactory
+org.phoebus.archive.reader.channelarchiver.file.ArchiveFileReaderFactory

From 024250acdadc8a2016b28c0adb7390ab6988647b Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 3 Jun 2024 12:10:23 -0400
Subject: [PATCH 11/59] Move the archive vtype package to the new archive
 reader module

---
 .../main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java   | 0
 .../main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java   | 0
 .../main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java    | 0
 .../java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java    | 0
 .../java/org/phoebus/archive/vtype/StatisticsAccumulator.java     | 0
 .../main/java/org/phoebus/archive/vtype/StringVTypeFormat.java    | 0
 .../src/main/java/org/phoebus/archive/vtype/Style.java            | 0
 .../src/main/java/org/phoebus/archive/vtype/VTypeFormat.java      | 0
 .../src/main/java/org/phoebus/archive/vtype/VTypeHelper.java      | 0
 9 files changed, 0 insertions(+), 0 deletions(-)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/StatisticsAccumulator.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/StringVTypeFormat.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/Style.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/VTypeFormat.java (100%)
 rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/VTypeHelper.java (100%)

diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/StatisticsAccumulator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/StatisticsAccumulator.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/StatisticsAccumulator.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/StatisticsAccumulator.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/StringVTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/StringVTypeFormat.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/StringVTypeFormat.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/StringVTypeFormat.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/Style.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/Style.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/Style.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/Style.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/VTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/VTypeFormat.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/VTypeFormat.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/VTypeFormat.java
diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/VTypeHelper.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/VTypeHelper.java
similarity index 100%
rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/VTypeHelper.java
rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/VTypeHelper.java

From bab8778deba8211ad4f4a0141799070ca4957b89 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 3 Jun 2024 12:14:00 -0400
Subject: [PATCH 12/59] Move the equivalent_pv_prefixes to RDB preference (only
 use case found)

---
 .../archive/reader/rdb/RDBArchiveReader.java  |  2 +-
 .../archive/reader/rdb/RDBPreferences.java    |  3 ++-
 .../archive_reader_rdb_preferences.properties | 21 +++++++++++++++++++
 3 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java
index ede39a0d38..3966a422b5 100644
--- a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java
+++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java
@@ -340,7 +340,7 @@ int getChannelID(final String name) throws UnknownChannelException, Exception
                 if (RDBPreferences.timeout_secs > 0)
                     statement.setQueryTimeout(RDBPreferences.timeout_secs);
                 // Loop over variants
-                for (String variant : PVPool.getNameVariants(name, org.csstudio.trends.databrowser3.preferences.Preferences.equivalent_pv_prefixes))
+                for (String variant : PVPool.getNameVariants(name, RDBPreferences.equivalent_pv_prefixes))
                 {
                     statement.setString(1, variant);
                     try (final ResultSet result = statement.executeQuery())
diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java
index 6bf5d1602d..abe5caa48d 100644
--- a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java
+++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java
@@ -22,9 +22,10 @@ public class RDBPreferences
     @Preference static String stored_procedure;
     @Preference static String starttime_function;
     @Preference static int fetch_size;
+    @Preference static String[] equivalent_pv_prefixes;
 
     static
     {
-    	AnnotatedPreferences.initialize(RDBPreferences.class, "/archive_reader_rdb_preferences.properties");
+        AnnotatedPreferences.initialize(RDBPreferences.class, "/archive_reader_rdb_preferences.properties");
     }
 }
diff --git a/app/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties b/app/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties
index 71a36e7c40..a542079bcc 100644
--- a/app/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties
+++ b/app/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties
@@ -56,3 +56,24 @@ starttime_function=
 # Tests resulted in a speed increase up to fetch sizes of 1000.
 # On the other hand, bigger numbers can result in java.lang.OutOfMemoryError.
 fetch_size=1000
+
+# With EPICS IOCs from release 7 on, the PVs
+# "xxx", "ca://xxx" and "pva://xxx" all refer
+# to the same record "xxx" on the IOC.
+#
+# When the plot requests "pva://xxx", the archive might still
+# trace that channel as "ca://xxx" or "xxx".
+# Alternatively, the archive might already track the channel
+# as "pva://xxx" while data browser plots still use "ca://xxx"
+# or just "xxx".
+# This preference setting instructs the data browser
+# to try all equivalent variants. If any types are listed,
+# just "xxx" without any prefix will also be checked in addition
+# to the listed types.
+#
+# The default of setting of "ca, pva" supports the seamless
+# transition between the key protocols.
+#
+# When `equivalent_pv_prefixes` is empty,
+# the PV name is used as is without looking for any equivalent names.
+equivalent_pv_prefixes=ca, pva

From 3d6b1dd9e9cb5604db14eff2f6152edf6c6b58a7 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 3 Jun 2024 13:09:34 -0400
Subject: [PATCH 13/59] Fix import of TimestampHelper

---
 .../java/org/phoebus/archive/reader/AveragedValueIterator.java  | 2 +-
 .../java/org/phoebus/archive/reader/LinearValueIterator.java    | 2 +-
 .../archive/reader/appliance/ApplianceArchiveReader.java        | 2 +-
 .../archive/reader/appliance/ApplianceMeanValueIterator.java    | 2 +-
 .../reader/appliance/ApplianceOptimizedValueIterator.java       | 2 +-
 .../reader/appliance/ApplianceStatisticsValueIterator.java      | 2 +-
 .../archive/reader/appliance/ApplianceValueIterator.java        | 2 +-
 7 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java
index 64f8b12af7..31d31145d7 100644
--- a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java
+++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java
@@ -22,10 +22,10 @@
 import org.epics.vtype.VStatistics;
 import org.epics.vtype.VType;
 import org.phoebus.archive.vtype.StatisticsAccumulator;
-import org.phoebus.archive.vtype.TimestampHelper;
 import org.phoebus.archive.vtype.VTypeHelper;
 import org.phoebus.pv.TimeHelper;
 import org.phoebus.util.time.TimestampFormats;
+import org.phoebus.util.time.TimestampHelper;
 
 /** Averaging sample iterator.
  *
diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java
index fde8622b37..0b2d3400a3 100644
--- a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java
+++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java
@@ -18,10 +18,10 @@
 import org.epics.vtype.VStatistics;
 import org.epics.vtype.VType;
 import org.phoebus.archive.vtype.StatisticsAccumulator;
-import org.phoebus.archive.vtype.TimestampHelper;
 import org.phoebus.archive.vtype.VTypeHelper;
 import org.phoebus.pv.TimeHelper;
 import org.phoebus.util.time.TimeDuration;
+import org.phoebus.util.time.TimestampHelper;
 
 /** {@link ValueIterator} that performs linear interpolation
  *
diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java
index f42e16d846..1887885028 100644
--- a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java
+++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java
@@ -21,8 +21,8 @@
 import org.phoebus.archive.reader.ArchiveReader;
 import org.phoebus.archive.reader.UnknownChannelException;
 import org.phoebus.archive.reader.ValueIterator;
-import org.phoebus.archive.vtype.TimestampHelper;
 import org.phoebus.ui.text.RegExHelper;
+import org.phoebus.util.time.TimestampHelper;
 
 /**
  * Appliance archive reader which reads data from EPICS archiver appliance.
diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java
index b32d487851..4303995af9 100644
--- a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java
+++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java
@@ -8,10 +8,10 @@
 import org.epics.archiverappliance.retrieval.client.EpicsMessage;
 import org.epics.archiverappliance.retrieval.client.GenMsgIterator;
 import org.epics.vtype.Display;
-import org.phoebus.archive.vtype.TimestampHelper;
 
 import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo;
 import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadType;
+import org.phoebus.util.time.TimestampHelper;
 
 /**
  *
diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java
index f757fcba1d..91dbd5b750 100644
--- a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java
+++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java
@@ -14,11 +14,11 @@
 import org.epics.vtype.VNumber;
 import org.epics.vtype.VStatistics;
 import org.epics.vtype.VType;
-import org.phoebus.archive.vtype.TimestampHelper;
 import org.phoebus.pv.TimeHelper;
 
 import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo;
 import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadType;
+import org.phoebus.util.time.TimestampHelper;
 
 /**
  *
diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java
index 579eb63822..2140a62ce9 100644
--- a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java
+++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java
@@ -12,10 +12,10 @@
 import org.epics.vtype.Display;
 import org.epics.vtype.VStatistics;
 import org.epics.vtype.VType;
-import org.phoebus.archive.vtype.TimestampHelper;
 import org.phoebus.pv.TimeHelper;
 
 import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadType;
+import org.phoebus.util.time.TimestampHelper;
 
 /**
  *
diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java
index c070522fe2..d7feba56c5 100644
--- a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java
+++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java
@@ -32,7 +32,6 @@
 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;
 
 import com.google.protobuf.ByteString;
@@ -41,6 +40,7 @@
 import edu.stanford.slac.archiverappliance.PB.EPICSEvent.FieldValue;
 import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo;
 import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadType;
+import org.phoebus.util.time.TimestampHelper;
 
 /**
  *

From 9b7f03ad6f37f23c8a65629daa0a2af2c6ba5ae4 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 3 Jun 2024 13:43:22 -0400
Subject: [PATCH 14/59] remove the unused equivalent pv prefix preference from
 databrowser

---
 .../csstudio/trends/databrowser3/preferences/Preferences.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java
index 9e154a754d..18c52e22a1 100644
--- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java
+++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java
@@ -87,8 +87,8 @@ public static class TimePreset
     @Preference public static boolean use_default_archives;
     /** Setting */
     @Preference public static boolean drop_failed_archives;
-    /** Setting */
-    @Preference public static String[]  equivalent_pv_prefixes;
+//    /** Setting */
+//    @Preference public static String[]  equivalent_pv_prefixes;
     /** Setting */
     @Preference public static boolean use_trace_names;
     /** Setting */

From ed7bb6d68e7813adbc8538cec00e612f90774f24 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 3 Jun 2024 13:43:52 -0400
Subject: [PATCH 15/59] Update the pom files for the new trend modules

---
 app/databrowser/pom.xml           | 13 ++----
 app/trends/archive-reader/pom.xml | 69 +++++++++++++++++++++++++++++++
 app/trends/pom.xml                |  1 +
 3 files changed, 74 insertions(+), 9 deletions(-)
 create mode 100644 app/trends/archive-reader/pom.xml

diff --git a/app/databrowser/pom.xml b/app/databrowser/pom.xml
index d0139b4640..9aee48ab32 100644
--- a/app/databrowser/pom.xml
+++ b/app/databrowser/pom.xml
@@ -56,24 +56,19 @@
     </dependency>
     <dependency>
       <groupId>org.phoebus</groupId>
-      <artifactId>app-rtplot</artifactId>
+      <artifactId>app-trends-archive-reader</artifactId>
       <version>4.7.4-SNAPSHOT</version>
     </dependency>
     <dependency>
-      <groupId>com.google.protobuf</groupId>
-      <artifactId>protobuf-java</artifactId>
-      <version>3.21.9</version>
+      <groupId>org.phoebus</groupId>
+      <artifactId>app-rtplot</artifactId>
+      <version>4.7.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.epics</groupId>
       <artifactId>epics-util</artifactId>
       <version>${epics.util.version}</version>
     </dependency>
-    <dependency>
-      <groupId>org.epics</groupId>
-      <artifactId>pbrawclient</artifactId>
-      <version>0.0.10</version>
-    </dependency>
     <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-math3 -->
     <dependency>
       <groupId>org.apache.commons</groupId>
diff --git a/app/trends/archive-reader/pom.xml b/app/trends/archive-reader/pom.xml
new file mode 100644
index 0000000000..21ce6b584f
--- /dev/null
+++ b/app/trends/archive-reader/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>app-trends</artifactId>
+        <groupId>org.phoebus</groupId>
+        <version>4.7.4-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>app-trends-archive-reader</artifactId>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.phoebus</groupId>
+            <artifactId>core-pv</artifactId>
+            <version>4.7.4-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.phoebus</groupId>
+            <artifactId>core-types</artifactId>
+            <version>4.7.4-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.phoebus</groupId>
+            <artifactId>core-util</artifactId>
+            <version>4.7.4-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+            <version>3.21.9</version>
+        </dependency>
+        <dependency>
+            <groupId>org.epics</groupId>
+            <artifactId>epics-util</artifactId>
+            <version>${epics.util.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.epics</groupId>
+            <artifactId>pbrawclient</artifactId>
+            <version>0.0.10</version>
+        </dependency>
+        <dependency>
+            <groupId>org.phoebus</groupId>
+            <artifactId>core-ui</artifactId>
+            <version>4.7.4-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-all</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/app/trends/pom.xml b/app/trends/pom.xml
index 83c350c27b..617c89db42 100644
--- a/app/trends/pom.xml
+++ b/app/trends/pom.xml
@@ -11,5 +11,6 @@
     <module>rich-adapters</module>
     <module>simple-adapters</module>
     <module>archive-datasource</module>
+    <module>archive-reader</module>
   </modules>
 </project>

From 1eb9e3d786e9af0a91d074a8f0202b61e8e96f19 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 3 Jun 2024 14:00:23 -0400
Subject: [PATCH 16/59] Undo the deleted preference ( needed by TS archive
 datasource )

---
 .../trends/databrowser3/preferences/Preferences.java         | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java
index 18c52e22a1..18780336ba 100644
--- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java
+++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java
@@ -87,8 +87,9 @@ public static class TimePreset
     @Preference public static boolean use_default_archives;
     /** Setting */
     @Preference public static boolean drop_failed_archives;
-//    /** Setting */
-//    @Preference public static String[]  equivalent_pv_prefixes;
+    /** Setting */
+    @Deprecated
+    @Preference public static String[]  equivalent_pv_prefixes;
     /** Setting */
     @Preference public static boolean use_trace_names;
     /** Setting */

From 1fd892bf6c96033d18abf340537582eb54d87e94 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 3 Jun 2024 14:27:28 -0400
Subject: [PATCH 17/59] Add ant build scripts for the new trends modules

---
 app/trends/archive-datasource/build.xml | 19 +++++++++++++++++++
 app/trends/archive-reader/build.xml     | 19 +++++++++++++++++++
 2 files changed, 38 insertions(+)
 create mode 100644 app/trends/archive-datasource/build.xml
 create mode 100644 app/trends/archive-reader/build.xml

diff --git a/app/trends/archive-datasource/build.xml b/app/trends/archive-datasource/build.xml
new file mode 100644
index 0000000000..707746f875
--- /dev/null
+++ b/app/trends/archive-datasource/build.xml
@@ -0,0 +1,19 @@
+<project default="app-trends-rich-adapters">
+  <import file="../../../dependencies/ant_settings.xml"/>
+
+  <target name="app-trends-archive-datasource">
+    <mkdir dir="${classes}"/>
+    <javac destdir="${classes}" debug="${debug}">
+      <src path="${src}"/>
+      <classpath>
+        <path refid="app-classpath"/>
+      </classpath>
+    </javac>
+  	
+    <jar destfile="${build}/app-trends-archive-datasource-${version}.jar">
+      <fileset dir="${classes}"/>
+      <fileset dir="${resources}"/>
+    </jar>
+  </target>
+	
+</project>
diff --git a/app/trends/archive-reader/build.xml b/app/trends/archive-reader/build.xml
new file mode 100644
index 0000000000..4ea1950c56
--- /dev/null
+++ b/app/trends/archive-reader/build.xml
@@ -0,0 +1,19 @@
+<project default="app-trends-rich-adapters">
+  <import file="../../../dependencies/ant_settings.xml"/>
+
+  <target name="app-trends-archive-reader">
+    <mkdir dir="${classes}"/>
+    <javac destdir="${classes}" debug="${debug}">
+      <src path="${src}"/>
+      <classpath>
+        <path refid="app-classpath"/>
+      </classpath>
+    </javac>
+
+    <jar destfile="${build}/app-trends-archive-reader-${version}.jar">
+      <fileset dir="${classes}"/>
+      <fileset dir="${resources}"/>
+    </jar>
+  </target>
+
+</project>

From d9930a9e3fa49c0374204c9417d88c0feddc378c Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 3 Jun 2024 15:47:43 -0400
Subject: [PATCH 18/59] Fix typo in documentation

---
 app/trends/archive-datasource/doc/index.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/trends/archive-datasource/doc/index.rst b/app/trends/archive-datasource/doc/index.rst
index c7515037d6..3e11c6ac3b 100644
--- a/app/trends/archive-datasource/doc/index.rst
+++ b/app/trends/archive-datasource/doc/index.rst
@@ -1,5 +1,5 @@
-Alarm Datasource
-================
+Archive Datasource
+==================
 
 Overview
 --------

From 0d0295e0ac657a4af2f8f5ecbe2f5dc995112b8a Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Tue, 4 Jun 2024 09:17:55 -0400
Subject: [PATCH 19/59] Fix the preference loading

---
 .../src/main/java/org/phoebus/pv/archive/Preferences.java      | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java
index 231b3d8aa1..3c0da79c2d 100644
--- a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java
@@ -1,6 +1,5 @@
 package org.phoebus.pv.archive;
 
-import org.csstudio.trends.databrowser3.Activator;
 import org.phoebus.framework.preferences.AnnotatedPreferences;
 import org.phoebus.framework.preferences.Preference;
 import org.phoebus.framework.preferences.PreferencesReader;
@@ -18,7 +17,7 @@ public class Preferences
 
     static
     {
-        final PreferencesReader prefs = AnnotatedPreferences.initialize(Activator.class, Preferences.class, "/appliance_datasource_preferences.properties");
+        final PreferencesReader prefs = AnnotatedPreferences.initialize(Preferences.class, "/appliance_datasource_preferences.properties");
     }
 
 }

From 20b987f1089b8e2e38f503a460c6fddd3f7c3ccb Mon Sep 17 00:00:00 2001
From: Abraham Wolk <abraham.wolk@ess.eu>
Date: Mon, 10 Jun 2024 09:22:48 +0200
Subject: [PATCH 20/59] Revert "CSSTUDIO-1987 Remove unused code."

This reverts commit ffe810a8b56103ca4fb6eb9b9cf1e24fdc92a909.
---
 .../runtime/app/DockItemRepresentation.java   | 20 +++++++++++++++++++
 .../builder/runtime/script/ScriptUtil.java    | 18 +++++++++++++++++
 2 files changed, 38 insertions(+)

diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java
index fc23c0dce4..5a13862a88 100644
--- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java
+++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java
@@ -141,4 +141,24 @@ public void representModel(final Parent model_parent, final DisplayModel model)
             app_instance.trackCurrentModel(model);
         super.representModel(model_parent, model);
     }
+
+    @Override
+    public void closeWindow(final DisplayModel model) throws Exception
+    {
+        // Is called from ScriptUtil, i.e. scripts, from background thread
+        final Parent model_parent = Objects.requireNonNull(model.getUserData(Widget.USER_DATA_TOOLKIT_PARENT));
+        if (model_parent.getProperties().get(DisplayRuntimeInstance.MODEL_PARENT_DISPLAY_RUNTIME) == app_instance)
+        {
+            // Prepare-to-close, which might take time and must be called off the UI thread
+            final DisplayRuntimeInstance instance = (DisplayRuntimeInstance) app_instance.getRepresentation().getModelParent().getProperties().get(DisplayRuntimeInstance.MODEL_PARENT_DISPLAY_RUNTIME);
+            if (instance != null)
+                instance.getDockItem().prepareToClose();
+            else
+                logger.log(Level.SEVERE, "Missing DisplayRuntimeInstance to prepare closing", new Exception("Stack Trace"));
+            // 'close' on the UI thread
+            execute(() -> app_instance.close());
+        }
+        else
+            throw new Exception("Wrong model");
+    }
 }
diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/script/ScriptUtil.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/script/ScriptUtil.java
index 600fff19bd..7f6b4b2a60 100644
--- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/script/ScriptUtil.java
+++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/script/ScriptUtil.java
@@ -141,6 +141,24 @@ public static void openDisplay(final Widget widget, final String file, final Str
         ActionUtil.handleAction(widget, open);
     }
 
+    /** Close a display
+     *
+     *  @param widget Widget within the display to close
+     */
+    public static void closeDisplay(final Widget widget)
+    {
+        try
+        {
+            final DisplayModel model = widget.getTopDisplayModel();
+            final ToolkitRepresentation<Object, Object> toolkit = ToolkitRepresentation.getToolkit(model);
+            toolkit.closeWindow(model);
+        }
+        catch (Throwable ex)
+        {
+            logger.log(Level.WARNING, "Cannot close display", ex);
+        }
+    }
+
     // ====================
     // public alert dialog utils
 

From a633b75bc7ab35eaee34d37f3568e576bc0f0f9b Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Wed, 12 Jun 2024 16:46:12 -0400
Subject: [PATCH 21/59] Implement In-Memory Preferences

---
 .../preferences/InMemoryPreferences.java      | 111 ++++++++++++++++++
 .../preferences/PhoebusPreferenceService.java |   4 +-
 2 files changed, 113 insertions(+), 2 deletions(-)
 create mode 100644 core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java

diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java b/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java
new file mode 100644
index 0000000000..7c600d5c73
--- /dev/null
+++ b/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.phoebus.framework.preferences;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.prefs.AbstractPreferences;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+
+/** Java Preferences that are held in memory, not persisted
+ *  @author Kay Kasemir
+ */
+class InMemoryPreferences extends AbstractPreferences
+{
+    /** Preferences for both "user" and "system" */
+    private static final InMemoryPreferences prefs = new InMemoryPreferences(null, "");
+    
+    /** Settings for this node in the preferences hierarchy */
+    private ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
+
+    /** @return User preferences */
+    public static Preferences getUserRoot()
+    {
+        return prefs;
+    }
+
+    /** @return System preferences */
+    public static Preferences getSystemRoot()
+    {
+        return prefs;
+    }
+    
+    InMemoryPreferences(final InMemoryPreferences parent, final String name)
+    {
+        super(parent, name);
+    }
+    
+    /** @inheritDoc */
+    @Override
+    protected void putSpi(String key, String value)
+    {
+        cache.put(key, value);
+    }
+
+    /** @inheritDoc */
+    @Override
+    protected String getSpi(String key)
+    {
+        return cache.get(key);
+    }
+
+    /** @inheritDoc */
+    @Override
+    protected void removeSpi(String key)
+    {
+        cache.remove(key);        
+    }
+
+    /** @inheritDoc */
+    @Override
+    protected void removeNodeSpi() throws BackingStoreException
+    {
+        // Nothing to remove in the file system
+        cache.clear();
+    }
+
+    /** @inheritDoc */
+    @Override
+    protected String[] keysSpi() throws BackingStoreException
+    {
+        return cache.keySet().toArray(new String[cache.size()]);        
+    }
+
+    /** @inheritDoc */
+    @Override
+    protected String[] childrenNamesSpi() throws BackingStoreException
+    {
+        // This method need not return the names of any nodes already cached
+        // by the AbstractPreferences
+        return new String[0];
+    }
+
+    /** @inheritDoc */
+    @Override
+    protected AbstractPreferences childSpi(String name)
+    {
+        // AbstractPreferences guaranteed that the named node has not been returned
+        // by a previous invocation of this method or {@link #getChild(String)},
+        // so only called once and can then create the one and only new child
+        return new InMemoryPreferences(this, name);
+    }
+
+    /** @inheritDoc */
+    @Override
+    protected void syncSpi() throws BackingStoreException
+    {
+        // Nothing to sync
+    }
+
+    /** @inheritDoc */
+    @Override
+    protected void flushSpi() throws BackingStoreException
+    {
+        // Nothing to sync
+    }
+}
diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java b/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java
index fe4f6fb453..d99e175dd5 100644
--- a/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java
+++ b/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java
@@ -22,13 +22,13 @@ public static Preferences userNodeForClass(final Class<?> clazz)
     @Override
     public Preferences userRoot()
     {
-        return FileSystemPreferences.getUserRoot();
+        return InMemoryPreferences.getUserRoot();
     }
 
     @Override
     public Preferences systemRoot()
     {
-        return FileSystemPreferences.getSystemRoot();
+        return InMemoryPreferences.getSystemRoot();
     }
 
 }

From 9c43b68372bba798be580d38cd7599d3085dd920 Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Thu, 13 Jun 2024 09:44:40 -0400
Subject: [PATCH 22/59] R3emove unused file system preferences

---
 .../phoebus/framework/preferences/Base64.java | 261 -----
 .../FilePreferencesXmlSupport.java            | 426 --------
 .../preferences/FileSystemPreferences.java    | 964 ------------------
 3 files changed, 1651 deletions(-)
 delete mode 100644 core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java
 delete mode 100644 core/framework/src/main/java/org/phoebus/framework/preferences/FilePreferencesXmlSupport.java
 delete mode 100644 core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java

diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java b/core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java
deleted file mode 100644
index 288ff8941f..0000000000
--- a/core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright 2000-2005 Sun Microsystems, Inc.  All Rights Reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Sun designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Sun in the LICENSE file that accompanied this code.
- *
- * This code 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
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
- * CA 95054 USA or visit www.sun.com if you need additional information or
- * have any questions.
- */
-
-package org.phoebus.framework.preferences;
-
-/**
- * Static methods for translating Base64 encoded strings to byte arrays
- * and vice-versa.
- *
- * @author  Josh Bloch
- * @see     Preferences
- * @since   1.4
- */
-class Base64 {
-    /**
-     * Translates the specified byte array into a Base64 string as per
-     * Preferences.put(byte[]).
-     */
-    static String byteArrayToBase64(byte[] a) {
-        return byteArrayToBase64(a, false);
-    }
-
-    /**
-     * Translates the specified byte array into an "alternate representation"
-     * Base64 string.  This non-standard variant uses an alphabet that does
-     * not contain the uppercase alphabetic characters, which makes it
-     * suitable for use in situations where case-folding occurs.
-     */
-    static String byteArrayToAltBase64(byte[] a) {
-        return byteArrayToBase64(a, true);
-    }
-
-    private static String byteArrayToBase64(byte[] a, boolean alternate) {
-        int aLen = a.length;
-        int numFullGroups = aLen/3;
-        int numBytesInPartialGroup = aLen - 3*numFullGroups;
-        int resultLen = 4*((aLen + 2)/3);
-        StringBuffer result = new StringBuffer(resultLen);
-        char[] intToAlpha = (alternate ? intToAltBase64 : intToBase64);
-
-        // Translate all full groups from byte array elements to Base64
-        int inCursor = 0;
-        for (int i=0; i<numFullGroups; i++) {
-            int byte0 = a[inCursor++] & 0xff;
-            int byte1 = a[inCursor++] & 0xff;
-            int byte2 = a[inCursor++] & 0xff;
-            result.append(intToAlpha[byte0 >> 2]);
-            result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
-            result.append(intToAlpha[(byte1 << 2)&0x3f | (byte2 >> 6)]);
-            result.append(intToAlpha[byte2 & 0x3f]);
-        }
-
-        // Translate partial group if present
-        if (numBytesInPartialGroup != 0) {
-            int byte0 = a[inCursor++] & 0xff;
-            result.append(intToAlpha[byte0 >> 2]);
-            if (numBytesInPartialGroup == 1) {
-                result.append(intToAlpha[(byte0 << 4) & 0x3f]);
-                result.append("==");
-            } else {
-                // assert numBytesInPartialGroup == 2;
-                int byte1 = a[inCursor++] & 0xff;
-                result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
-                result.append(intToAlpha[(byte1 << 2)&0x3f]);
-                result.append('=');
-            }
-        }
-        // assert inCursor == a.length;
-        // assert result.length() == resultLen;
-        return result.toString();
-    }
-
-    /**
-     * This array is a lookup table that translates 6-bit positive integer
-     * index values into their "Base64 Alphabet" equivalents as specified
-     * in Table 1 of RFC 2045.
-     */
-    private static final char intToBase64[] = {
-        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
-        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
-        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
-        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
-    };
-
-    /**
-     * This array is a lookup table that translates 6-bit positive integer
-     * index values into their "Alternate Base64 Alphabet" equivalents.
-     * This is NOT the real Base64 Alphabet as per in Table 1 of RFC 2045.
-     * This alternate alphabet does not use the capital letters.  It is
-     * designed for use in environments where "case folding" occurs.
-     */
-    private static final char intToAltBase64[] = {
-        '!', '"', '#', '$', '%', '&', '\'', '(', ')', ',', '-', '.', ':',
-        ';', '<', '>', '@', '[', ']', '^',  '`', '_', '{', '|', '}', '~',
-        'a', 'b', 'c', 'd', 'e', 'f', 'g',  'h', 'i', 'j', 'k', 'l', 'm',
-        'n', 'o', 'p', 'q', 'r', 's', 't',  'u', 'v', 'w', 'x', 'y', 'z',
-        '0', '1', '2', '3', '4', '5', '6',  '7', '8', '9', '+', '?'
-    };
-
-    /**
-     * Translates the specified Base64 string (as per Preferences.get(byte[]))
-     * into a byte array.
-     *
-     * @throw IllegalArgumentException if <tt>s</tt> is not a valid Base64
-     *        string.
-     */
-    static byte[] base64ToByteArray(String s) {
-        return base64ToByteArray(s, false);
-    }
-
-    /**
-     * Translates the specified "alternate representation" Base64 string
-     * into a byte array.
-     *
-     * @throw IllegalArgumentException or ArrayOutOfBoundsException
-     *        if <tt>s</tt> is not a valid alternate representation
-     *        Base64 string.
-     */
-    static byte[] altBase64ToByteArray(String s) {
-        return base64ToByteArray(s, true);
-    }
-
-    private static byte[] base64ToByteArray(String s, boolean alternate) {
-        byte[] alphaToInt = (alternate ?  altBase64ToInt : base64ToInt);
-        int sLen = s.length();
-        int numGroups = sLen/4;
-        if (4*numGroups != sLen)
-            throw new IllegalArgumentException(
-                "String length must be a multiple of four.");
-        int missingBytesInLastGroup = 0;
-        int numFullGroups = numGroups;
-        if (sLen != 0) {
-            if (s.charAt(sLen-1) == '=') {
-                missingBytesInLastGroup++;
-                numFullGroups--;
-            }
-            if (s.charAt(sLen-2) == '=')
-                missingBytesInLastGroup++;
-        }
-        byte[] result = new byte[3*numGroups - missingBytesInLastGroup];
-
-        // Translate all full groups from base64 to byte array elements
-        int inCursor = 0, outCursor = 0;
-        for (int i=0; i<numFullGroups; i++) {
-            int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt);
-            int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt);
-            int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt);
-            int ch3 = base64toInt(s.charAt(inCursor++), alphaToInt);
-            result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4));
-            result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2));
-            result[outCursor++] = (byte) ((ch2 << 6) | ch3);
-        }
-
-        // Translate partial group, if present
-        if (missingBytesInLastGroup != 0) {
-            int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt);
-            int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt);
-            result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4));
-
-            if (missingBytesInLastGroup == 1) {
-                int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt);
-                result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2));
-            }
-        }
-        // assert inCursor == s.length()-missingBytesInLastGroup;
-        // assert outCursor == result.length;
-        return result;
-    }
-
-    /**
-     * Translates the specified character, which is assumed to be in the
-     * "Base 64 Alphabet" into its equivalent 6-bit positive integer.
-     *
-     * @throw IllegalArgumentException or ArrayOutOfBoundsException if
-     *        c is not in the Base64 Alphabet.
-     */
-    private static int base64toInt(char c, byte[] alphaToInt) {
-        int result = alphaToInt[c];
-        if (result < 0)
-            throw new IllegalArgumentException("Illegal character " + c);
-        return result;
-    }
-
-    /**
-     * This array is a lookup table that translates unicode characters
-     * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
-     * into their 6-bit positive integer equivalents.  Characters that
-     * are not in the Base64 alphabet but fall within the bounds of the
-     * array are translated to -1.
-     */
-    private static final byte base64ToInt[] = {
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
-        55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
-        5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
-        24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
-        35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
-    };
-
-    /**
-     * This array is the analogue of base64ToInt, but for the nonstandard
-     * variant that avoids the use of uppercase alphabetic characters.
-     */
-    private static final byte altBase64ToInt[] = {
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1,
-        2, 3, 4, 5, 6, 7, 8, -1, 62, 9, 10, 11, -1 , 52, 53, 54, 55, 56, 57,
-        58, 59, 60, 61, 12, 13, 14, -1, 15, 63, 16, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-        -1, -1, -1, 17, -1, 18, 19, 21, 20, 26, 27, 28, 29, 30, 31, 32, 33,
-        34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
-        51, 22, 23, 24, 25
-    };
-
-    public static void main(String args[]) {
-        int numRuns  = Integer.parseInt(args[0]);
-        int numBytes = Integer.parseInt(args[1]);
-        java.util.Random rnd = new java.util.Random();
-        for (int i=0; i<numRuns; i++) {
-            for (int j=0; j<numBytes; j++) {
-                byte[] arr = new byte[j];
-                for (int k=0; k<j; k++)
-                    arr[k] = (byte)rnd.nextInt();
-
-                String s = byteArrayToBase64(arr);
-                byte [] b = base64ToByteArray(s);
-                if (!java.util.Arrays.equals(arr, b))
-                    System.out.println("Dismal failure!");
-
-                s = byteArrayToAltBase64(arr);
-                b = altBase64ToByteArray(s);
-                if (!java.util.Arrays.equals(arr, b))
-                    System.out.println("Alternate dismal failure!");
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/FilePreferencesXmlSupport.java b/core/framework/src/main/java/org/phoebus/framework/preferences/FilePreferencesXmlSupport.java
deleted file mode 100644
index 942a99e257..0000000000
--- a/core/framework/src/main/java/org/phoebus/framework/preferences/FilePreferencesXmlSupport.java
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Copyright 2002-2006 Sun Microsystems, Inc.  All Rights Reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Sun designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Sun in the LICENSE file that accompanied this code.
- *
- * This code 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
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
- * CA 95054 USA or visit www.sun.com if you need additional information or
- * have any questions.
- */
-
-package org.phoebus.framework.preferences;
-
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.prefs.BackingStoreException;
-import java.util.prefs.InvalidPreferencesFormatException;
-import java.util.prefs.Preferences;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
-import org.w3c.dom.DOMImplementation;
-import org.w3c.dom.Document;
-import org.w3c.dom.DocumentType;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-import org.xml.sax.EntityResolver;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-/**
- * XML Support for java.util.prefs. Methods to import and export preference
- * nodes and subtrees.
- *
- * @author Josh Bloch and Mark Reinhold
- * @see Preferences
- * @since 1.4
- */
-class FilePreferencesXmlSupport {
-    // The required DTD URI for exported preferences
-    private static final String PREFS_DTD_URI = "http://java.sun.com/dtd/preferences.dtd";
-
-    // The actual DTD corresponding to the URI
-    private static final String PREFS_DTD = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
-
-            "<!-- DTD for preferences -->" +
-
-            "<!ELEMENT preferences (root) >" + "<!ATTLIST preferences" + " EXTERNAL_XML_VERSION CDATA \"0.0\"  >" +
-
-            "<!ELEMENT root (map, node*) >" + "<!ATTLIST root" + "          type (system|user) #REQUIRED >" +
-
-            "<!ELEMENT node (map, node*) >" + "<!ATTLIST node" + "          name CDATA #REQUIRED >" +
-
-            "<!ELEMENT map (entry*) >" + "<!ATTLIST map" + "  MAP_XML_VERSION CDATA \"0.0\"  >"
-            + "<!ELEMENT entry EMPTY >" + "<!ATTLIST entry" + "          key CDATA #REQUIRED"
-            + "          value CDATA #REQUIRED >";
-    /**
-     * Version number for the format exported preferences files.
-     */
-    private static final String EXTERNAL_XML_VERSION = "1.0";
-
-    /*
-     * Version number for the internal map files.
-     */
-    private static final String MAP_XML_VERSION = "1.0";
-
-    /**
-     * Export the specified preferences node and, if subTree is true, all subnodes,
-     * to the specified output stream. Preferences are exported as an XML document
-     * conforming to the definition in the Preferences spec.
-     *
-     * @throws IOException
-     *             if writing to the specified output stream results in an
-     *             <tt>IOException</tt>.
-     * @throws BackingStoreException
-     *             if preference data cannot be read from backing store.
-     * @throws IllegalStateException
-     *             if this node (or an ancestor) has been removed with the
-     *             {@link #removeNode()} method.
-     */
-    static void export(OutputStream os, final Preferences p, boolean subTree)
-            throws IOException, BackingStoreException {
-        if (((FileSystemPreferences) p).isRemoved())
-            throw new IllegalStateException("Node has been removed");
-        Document doc = createPrefsDoc("preferences");
-        Element preferences = doc.getDocumentElement();
-        preferences.setAttribute("EXTERNAL_XML_VERSION", EXTERNAL_XML_VERSION);
-        Element xmlRoot = (Element) preferences.appendChild(doc.createElement("root"));
-        xmlRoot.setAttribute("type", (p.isUserNode() ? "user" : "system"));
-
-        // Get bottom-up list of nodes from p to root, excluding root
-        List ancestors = new ArrayList();
-
-        for (Preferences kid = p, dad = kid.parent(); dad != null; kid = dad, dad = kid.parent()) {
-            ancestors.add(kid);
-        }
-        Element e = xmlRoot;
-        for (int i = ancestors.size() - 1; i >= 0; i--) {
-            e.appendChild(doc.createElement("map"));
-            e = (Element) e.appendChild(doc.createElement("node"));
-            e.setAttribute("name", ((Preferences) ancestors.get(i)).name());
-        }
-        putPreferencesInXml(e, doc, p, subTree);
-
-        writeDoc(doc, os);
-    }
-
-    /**
-     * Put the preferences in the specified Preferences node into the specified XML
-     * element which is assumed to represent a node in the specified XML document
-     * which is assumed to conform to PREFS_DTD. If subTree is true, create children
-     * of the specified XML node conforming to all of the children of the specified
-     * Preferences node and recurse.
-     *
-     * @throws BackingStoreException
-     *             if it is not possible to read the preferences or children out of
-     *             the specified preferences node.
-     */
-    private static void putPreferencesInXml(Element elt, Document doc, Preferences prefs, boolean subTree)
-            throws BackingStoreException {
-        Preferences[] kidsCopy = null;
-        String[] kidNames = null;
-
-        // Node is locked to export its contents and get a
-        // copy of children, then lock is released,
-        // and, if subTree = true, recursive calls are made on children
-
-        // to remove a node we need an exclusive lock
-
-        // to remove a node we need an exclusive lock
-        if (!((FileSystemPreferences) prefs).lockFile(false))
-            throw (new BackingStoreException("Couldn't get file lock."));
-        try {
-            // Check if this node was concurrently removed. If yes
-            // remove it from XML Document and return.
-            if (((FileSystemPreferences) prefs).isRemoved()) {
-                elt.getParentNode().removeChild(elt);
-                return;
-            }
-            // Put map in xml element
-            String[] keys = prefs.keys();
-            Element map = (Element) elt.appendChild(doc.createElement("map"));
-            for (int i = 0; i < keys.length; i++) {
-                Element entry = (Element) map.appendChild(doc.createElement("entry"));
-                entry.setAttribute("key", keys[i]);
-                // NEXT STATEMENT THROWS NULL PTR EXC INSTEAD OF ASSERT FAIL
-                entry.setAttribute("value", prefs.get(keys[i], null));
-            }
-            // Recurse if appropriate
-            if (subTree) {
-                /* Get a copy of kids while lock is held */
-                kidNames = prefs.childrenNames();
-                kidsCopy = new Preferences[kidNames.length];
-                for (int i = 0; i < kidNames.length; i++)
-                    kidsCopy[i] = prefs.node(kidNames[i]);
-            }
-        } finally {
-            ((FileSystemPreferences) prefs).unlockFile();
-        }
-        if (subTree) {
-            for (int i = 0; i < kidNames.length; i++) {
-                Element xmlKid = (Element) elt.appendChild(doc.createElement("node"));
-                xmlKid.setAttribute("name", kidNames[i]);
-                putPreferencesInXml(xmlKid, doc, kidsCopy[i], subTree);
-            }
-        }
-    }
-
-    /**
-     * Import preferences from the specified input stream, which is assumed to
-     * contain an XML document in the format described in the Preferences spec.
-     *
-     * @throws IOException
-     *             if reading from the specified output stream results in an
-     *             <tt>IOException</tt>.
-     * @throws InvalidPreferencesFormatException
-     *             Data on input stream does not constitute a valid XML document
-     *             with the mandated document type.
-     */
-    static void importPreferences(InputStream is) throws IOException, InvalidPreferencesFormatException {
-        try {
-            Document doc = loadPrefsDoc(is);
-            String xmlVersion = doc.getDocumentElement().getAttribute("EXTERNAL_XML_VERSION");
-            if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0)
-                throw new InvalidPreferencesFormatException("Exported preferences file format version " + xmlVersion
-                        + " is not supported. This java installation can read" + " versions " + EXTERNAL_XML_VERSION
-                        + " or older. You may need" + " to install a newer version of JDK.");
-
-            Element xmlRoot = (Element) doc.getDocumentElement().getChildNodes().item(0);
-            Preferences prefsRoot = (xmlRoot.getAttribute("type").equals("user") ? Preferences.userRoot()
-                    : Preferences.systemRoot());
-            ImportSubtree(prefsRoot, xmlRoot);
-        } catch (SAXException | BackingStoreException e) {
-            throw new InvalidPreferencesFormatException(e);
-        }
-    }
-
-    /**
-     * Create a new prefs XML document.
-     */
-    private static Document createPrefsDoc(String qname) {
-        try {
-            DOMImplementation di = DocumentBuilderFactory.newInstance().newDocumentBuilder().getDOMImplementation();
-            DocumentType dt = di.createDocumentType(qname, null, PREFS_DTD_URI);
-            return di.createDocument(null, qname, dt);
-        } catch (ParserConfigurationException e) {
-            throw new AssertionError(e);
-        }
-    }
-
-    /**
-     * Load an XML document from specified input stream, which must have the
-     * requisite DTD URI.
-     */
-    private static Document loadPrefsDoc(InputStream in) throws SAXException, IOException {
-        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
-        dbf.setIgnoringElementContentWhitespace(true);
-        dbf.setValidating(true);
-        dbf.setCoalescing(true);
-        dbf.setIgnoringComments(true);
-        try {
-            DocumentBuilder db = dbf.newDocumentBuilder();
-            db.setEntityResolver(new Resolver());
-            db.setErrorHandler(new EH());
-            return db.parse(new InputSource(in));
-        } catch (ParserConfigurationException e) {
-            throw new AssertionError(e);
-        }
-    }
-
-    /**
-     * Write XML document to the specified output stream.
-     */
-    private static final void writeDoc(Document doc, OutputStream out) throws IOException {
-        try {
-            TransformerFactory tf = TransformerFactory.newInstance();
-            try {
-                tf.setAttribute("indent-number", Integer.valueOf(2));
-            } catch (IllegalArgumentException iae) {
-                // Ignore the IAE. Should not fail the writeout even the
-                // transformer provider does not support "indent-number".
-            }
-            Transformer t = tf.newTransformer();
-            t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());
-            t.setOutputProperty(OutputKeys.INDENT, "yes");
-            // Transformer resets the "indent" info if the "result" is a StreamResult with
-            // an OutputStream object embedded, creating a Writer object on top of that
-            // OutputStream object however works.
-            t.transform(new DOMSource(doc), new StreamResult(new BufferedWriter(new OutputStreamWriter(out, "UTF-8"))));
-        } catch (TransformerException e) {
-            throw new AssertionError(e);
-        }
-    }
-
-    /**
-     * Recursively traverse the specified preferences node and store the described
-     * preferences into the system or current user preferences tree, as appropriate.
-     * @throws BackingStoreException 
-     */
-    private static void ImportSubtree(Preferences prefsNode, Element xmlNode) throws BackingStoreException {
-        NodeList xmlKids = xmlNode.getChildNodes();
-        int numXmlKids = xmlKids.getLength();
-        /*
-         * We first lock the node, import its contents and get child nodes. Then we
-         * unlock the node and go to children Since some of the children might have been
-         * concurrently deleted we check for this.
-         */
-        Preferences[] prefsKids;
-        /* Lock the node */
-        // to remove a node we need an exclusive lock
-        if (!((FileSystemPreferences) prefsNode).lockFile(false))
-            throw (new BackingStoreException("Couldn't get file lock."));
-        try {
-            // If removed, return silently
-            if (((FileSystemPreferences) prefsNode).isRemoved())
-                return;
-
-            // Import any preferences at this node
-            Element firstXmlKid = (Element) xmlKids.item(0);
-            ImportPrefs(prefsNode, firstXmlKid);
-            prefsKids = new Preferences[numXmlKids - 1];
-
-            // Get involved children
-            for (int i = 1; i < numXmlKids; i++) {
-                Element xmlKid = (Element) xmlKids.item(i);
-                prefsKids[i - 1] = prefsNode.node(xmlKid.getAttribute("name"));
-            }
-        } finally {
-            // unlocked the node
-            ((FileSystemPreferences) prefsNode).unlockFile();
-        }
-
-        // import children
-        for (int i = 1; i < numXmlKids; i++)
-            ImportSubtree(prefsKids[i - 1], (Element) xmlKids.item(i));
-    }
-
-    /**
-     * Import the preferences described by the specified XML element (a map from a
-     * preferences document) into the specified preferences node.
-     */
-    private static void ImportPrefs(Preferences prefsNode, Element map) {
-        NodeList entries = map.getChildNodes();
-        for (int i = 0, numEntries = entries.getLength(); i < numEntries; i++) {
-            Element entry = (Element) entries.item(i);
-            prefsNode.put(entry.getAttribute("key"), entry.getAttribute("value"));
-        }
-    }
-
-    /**
-     * Export the specified Map<String,String> to a map document on the specified
-     * OutputStream as per the prefs DTD. This is used as the internal
-     * (undocumented) format for FileSystemPrefs.
-     *
-     * @throws IOException
-     *             if writing to the specified output stream results in an
-     *             <tt>IOException</tt>.
-     */
-    static void exportMap(OutputStream os, Map map) throws IOException {
-        Document doc = createPrefsDoc("map");
-        Element xmlMap = doc.getDocumentElement();
-        xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);
-
-        for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
-            Map.Entry e = (Map.Entry) i.next();
-            Element xe = (Element) xmlMap.appendChild(doc.createElement("entry"));
-            xe.setAttribute("key", (String) e.getKey());
-            xe.setAttribute("value", (String) e.getValue());
-        }
-
-        writeDoc(doc, os);
-    }
-
-    /**
-     * Import Map from the specified input stream, which is assumed to contain a map
-     * document as per the prefs DTD. This is used as the internal (undocumented)
-     * format for FileSystemPrefs. The key-value pairs specified in the XML document
-     * will be put into the specified Map. (If this Map is empty, it will contain
-     * exactly the key-value pairs int the XML-document when this method returns.)
-     *
-     * @throws IOException
-     *             if reading from the specified output stream results in an
-     *             <tt>IOException</tt>.
-     * @throws InvalidPreferencesFormatException
-     *             Data on input stream does not constitute a valid XML document
-     *             with the mandated document type.
-     */
-    static void importMap(InputStream is, Map m) throws IOException, InvalidPreferencesFormatException {
-        try {
-            Document doc = loadPrefsDoc(is);
-            Element xmlMap = doc.getDocumentElement();
-            // check version
-            String mapVersion = xmlMap.getAttribute("MAP_XML_VERSION");
-            if (mapVersion.compareTo(MAP_XML_VERSION) > 0)
-                throw new InvalidPreferencesFormatException("Preferences map file format version " + mapVersion
-                        + " is not supported. This java installation can read" + " versions " + MAP_XML_VERSION
-                        + " or older. You may need" + " to install a newer version of JDK.");
-
-            NodeList entries = xmlMap.getChildNodes();
-            for (int i = 0, numEntries = entries.getLength(); i < numEntries; i++) {
-                Element entry = (Element) entries.item(i);
-                m.put(entry.getAttribute("key"), entry.getAttribute("value"));
-            }
-        } catch (SAXException e) {
-            throw new InvalidPreferencesFormatException(e);
-        }
-    }
-
-    private static class Resolver implements EntityResolver {
-        public InputSource resolveEntity(String pid, String sid) throws SAXException {
-            if (sid.equals(PREFS_DTD_URI)) {
-                InputSource is;
-                is = new InputSource(new StringReader(PREFS_DTD));
-                is.setSystemId(PREFS_DTD_URI);
-                return is;
-            }
-            throw new SAXException("Invalid system identifier: " + sid);
-        }
-    }
-
-    private static class EH implements ErrorHandler {
-        public void error(SAXParseException x) throws SAXException {
-            throw x;
-        }
-
-        public void fatalError(SAXParseException x) throws SAXException {
-            throw x;
-        }
-
-        public void warning(SAXParseException x) throws SAXException {
-            throw x;
-        }
-    }
-}
\ No newline at end of file
diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java b/core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java
deleted file mode 100644
index 534ed13bdb..0000000000
--- a/core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java
+++ /dev/null
@@ -1,964 +0,0 @@
-/*
- * Copyright 2000-2006 Sun Microsystems, Inc.  All Rights Reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Sun designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Sun in the LICENSE file that accompanied this code.
- *
- * This code 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
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
- * CA 95054 USA or visit www.sun.com if you need additional information or
- * have any questions.
- */
-
-package org.phoebus.framework.preferences;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileLock;
-import java.nio.file.Files;
-import java.nio.file.StandardCopyOption;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.TreeMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.prefs.AbstractPreferences;
-import java.util.prefs.BackingStoreException;
-import java.util.prefs.InvalidPreferencesFormatException;
-import java.util.prefs.Preferences;
-
-import org.phoebus.framework.workbench.Locations;
-
-/**
- * Preferences implementation for Unix. Preferences are stored in the file
- * system, with one directory per preferences node. All of the preferences at
- * each node are stored in a single file. Atomic file system operations (e.g.
- * File.renameTo) are used to ensure integrity. An in-memory cache of the
- * "explored" portion of the tree is maintained for performance, and written
- * back to the disk periodically. File-locking is used to ensure reasonable
- * behavior when multiple VMs are running at the same time. (The file lock is
- * obtained only for sync(), flush() and removeNode().)
- *
- * @author Josh Bloch
- * @see Preferences
- * @since 1.4
- */
-
-@SuppressWarnings({"unchecked", "nls"})
-class FileSystemPreferences extends AbstractPreferences {
-
-    /**
-     * Sync interval in seconds.
-     */
-    private static final int SYNC_INTERVAL = Math.max(1,
-            Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction<String>() {
-                @Override
-                public String run() {
-                    return System.getProperty("java.util.prefs.syncInterval", "30");
-                }
-            })));
-
-    /**
-     * Returns logger for error messages. Backing store exceptions are logged at
-     * WARNING level.
-     */
-    private static Logger getLogger() {
-        return Logger.getLogger("java.util.prefs");
-    }
-
-    /**
-     * Directory for system preferences.
-     */
-    private static File systemRootDir;
-
-    /*
-     * Flag, indicating whether systemRoot directory is writable
-     */
-    private static boolean isSystemRootWritable;
-
-    /**
-     * Directory for user preferences.
-     */
-    private static File userRootDir;
-
-    /*
-     * Flag, indicating whether userRoot directory is writable
-     */
-    private static boolean isUserRootWritable;
-
-    /**
-     * The user root.
-     */
-    static Preferences userRoot = null;
-
-    static synchronized Preferences getUserRoot() {
-        if (userRoot == null) {
-            setupUserRoot();
-            userRoot = new FileSystemPreferences(true);
-        }
-        return userRoot;
-    }
-
-    private static void setupUserRoot() {
-        AccessController.doPrivileged(new PrivilegedAction() {
-            @Override
-            public Object run() {
-
-                // If Phoebus locations have been set,
-                // place the user preferences in the user directory that also stores mementos etc.
-                // Otherwise fall back to original FileSystemPreferences behavior
-                if (System.getProperty(Locations.PHOEBUS_USER, "").length() > 0)
-                    userRootDir = new File(System.getProperty(Locations.PHOEBUS_USER), ".userPrefs");
-                else
-                    userRootDir = new File(System.getProperty("java.util.prefs.userRoot", System.getProperty("user.home")),
-                        ".phoebus/.userPrefs");
-
-                // Attempt to create root dir if it does not yet exist.
-                if (!userRootDir.exists()) {
-                    if (userRootDir.mkdirs()) {
-                        getLogger().info("Created user preferences directory.");
-                    } else
-                        getLogger().warning(
-                                "Couldn't create user preferences" + " directory. User preferences are unusable.");
-                }
-                isUserRootWritable = userRootDir.canWrite();
-                String USER_NAME = System.getProperty("user.name");
-                userLockFile = new File(userRootDir, ".user.lock." + USER_NAME);
-                userRootModFile = new File(userRootDir, ".userRootModFile." + USER_NAME);
-                if (!userRootModFile.exists())
-                    try {
-                        // create if does not exist.
-                        userRootModFile.createNewFile();
-                    } catch (IOException e) {
-                        getLogger().warning(e.toString());
-                    }
-                userRootModTime = userRootModFile.lastModified();
-                return null;
-            }
-        });
-    }
-
-    /**
-     * The system root.
-     */
-    static Preferences systemRoot;
-
-    static synchronized Preferences getSystemRoot() {
-        if (systemRoot == null) {
-            setupSystemRoot();
-            systemRoot = new FileSystemPreferences(false);
-        }
-        return systemRoot;
-    }
-
-    private static void setupSystemRoot() {
-        AccessController.doPrivileged(new PrivilegedAction() {
-            @Override
-            public Object run() {
-                String systemPrefsDirName = System.getProperty("java.util.prefs.systemRoot", "/etc/.java");
-                systemRootDir = new File(systemPrefsDirName, ".systemPrefs");
-                // Attempt to create root dir if it does not yet exist.
-                if (!systemRootDir.exists()) {
-                    // system root does not exist in /etc/.java
-                    // Switching to java.home
-                    systemRootDir = new File(System.getProperty("java.home"), ".systemPrefs");
-                    if (!systemRootDir.exists()) {
-                        if (systemRootDir.mkdirs()) {
-                            getLogger().info("Created system preferences directory " + "in java.home.");
-                        } else {
-                            getLogger().warning("Could not create " + "system preferences directory. System "
-                                    + "preferences are unusable.");
-                        }
-                    }
-                }
-                isSystemRootWritable = systemRootDir.canWrite();
-                systemLockFile = new File(systemRootDir, ".system.lock");
-                systemRootModFile = new File(systemRootDir, ".systemRootModFile");
-                if (!systemRootModFile.exists() && isSystemRootWritable)
-                    try {
-                        // create if does not exist.
-                        systemRootModFile.createNewFile();
-                    } catch (IOException e) {
-                        getLogger().warning(e.toString());
-                    }
-                systemRootModTime = systemRootModFile.lastModified();
-                return null;
-            }
-        });
-    }
-
-    /**
-     * The lock file for the user tree.
-     */
-    static File userLockFile;
-
-    /**
-     * The lock file for the system tree.
-     */
-    static File systemLockFile;
-
-    /**
-     * Unix lock handle for userRoot. Zero, if unlocked.
-     */
-
-    private static RandomAccessFile userRootLockFile = null;
-    private static FileLock userRootLockHandle = null;
-
-    /**
-     * Unix lock handle for systemRoot. Zero, if unlocked.
-     */
-
-    private static RandomAccessFile systemRootLockFile = null;
-    private static FileLock systemRootLockHandle = null;
-
-    /**
-     * The directory representing this preference node. There is no guarantee that
-     * this directory exits, as another VM can delete it at any time that it (the
-     * other VM) holds the file-lock. While the root node cannot be deleted, it may
-     * not yet have been created, or the underlying directory could have been
-     * deleted accidentally.
-     */
-    private final File dir;
-
-    /**
-     * The file representing this preference node's preferences. The file format is
-     * undocumented, and subject to change from release to release, but I'm sure
-     * that you can figure it out if you try real hard.
-     */
-    private final File prefsFile;
-
-    /**
-     * A temporary file used for saving changes to preferences. As part of the sync
-     * operation, changes are first saved into this file, and then atomically
-     * renamed to prefsFile. This results in an atomic state change from one valid
-     * set of preferences to another. The the file-lock is held for the duration of
-     * this transformation.
-     */
-    private final File tmpFile;
-
-    /**
-     * File, which keeps track of global modifications of userRoot.
-     */
-    private static File userRootModFile;
-
-    /**
-     * Flag, which indicated whether userRoot was modified by another VM
-     */
-    private static boolean isUserRootModified = false;
-
-    /**
-     * Keeps track of userRoot modification time. This time is reset to zero after
-     * UNIX reboot, and is increased by 1 second each time userRoot is modified.
-     */
-    private static long userRootModTime;
-
-    /*
-     * File, which keeps track of global modifications of systemRoot
-     */
-    private static File systemRootModFile;
-    /*
-     * Flag, which indicates whether systemRoot was modified by another VM
-     */
-    private static boolean isSystemRootModified = false;
-
-    /**
-     * Keeps track of systemRoot modification time. This time is reset to zero after
-     * system reboot, and is increased by 1 second each time systemRoot is modified.
-     */
-    private static long systemRootModTime;
-
-    /**
-     * Locally cached preferences for this node (includes uncommitted changes). This
-     * map is initialized with from disk when the first get or put operation occurs
-     * on this node. It is synchronized with the corresponding disk file (prefsFile)
-     * by the sync operation. The initial value is read *without* acquiring the
-     * file-lock.
-     */
-    private Map prefsCache = null;
-
-    /**
-     * The last modification time of the file backing this node at the time that
-     * prefCache was last synchronized (or initially read). This value is set
-     * *before* reading the file, so it's conservative; the actual timestamp could
-     * be (slightly) higher. A value of zero indicates that we were unable to
-     * initialize prefsCache from the disk, or have not yet attempted to do so. (If
-     * prefsCache is non-null, it indicates the former; if it's null, the latter.)
-     */
-    private long lastSyncTime = 0;
-
-    /**
-     * Unix error code for locked file.
-     */
-    private static final int EAGAIN = 11;
-
-    /**
-     * Unix error code for denied access.
-     */
-    private static final int EACCES = 13;
-
-    /**
-     * A list of all uncommitted preference changes. The elements in this list are
-     * of type PrefChange. If this node is concurrently modified on disk by another
-     * VM, the two sets of changes are merged when this node is sync'ed by
-     * overwriting our prefsCache with the preference map last written out to disk
-     * (by the other VM), and then replaying this change log against that map. The
-     * resulting map is then written back to the disk.
-     */
-    final List changeLog = new ArrayList();
-
-    /**
-     * Represents a change to a preference.
-     */
-    private abstract class Change {
-        /**
-         * Reapplies the change to prefsCache.
-         */
-        abstract void replay();
-    };
-
-    /**
-     * Represents a preference put.
-     */
-    private class Put extends Change {
-        String key, value;
-
-        Put(String key, String value) {
-            this.key = key;
-            this.value = value;
-        }
-
-        @Override
-        void replay() {
-            prefsCache.put(key, value);
-        }
-    }
-
-    /**
-     * Represents a preference remove.
-     */
-    private class Remove extends Change {
-        String key;
-
-        Remove(String key) {
-            this.key = key;
-        }
-
-        @Override
-        void replay() {
-            prefsCache.remove(key);
-        }
-    }
-
-    /**
-     * Represents the creation of this node.
-     */
-    private class NodeCreate extends Change {
-        /**
-         * Performs no action, but the presence of this object in changeLog will force
-         * the node and its ancestors to be made permanent at the next sync.
-         */
-        @Override
-        void replay() {
-        }
-    }
-
-    /**
-     * NodeCreate object for this node.
-     */
-    NodeCreate nodeCreate = null;
-
-    /**
-     * Replay changeLog against prefsCache.
-     */
-    private void replayChanges() {
-        for (int i = 0, n = changeLog.size(); i < n; i++)
-            ((Change) changeLog.get(i)).replay();
-    }
-
-    private static Timer syncTimer = new Timer(true); // Daemon Thread
-
-    static {
-        // Add periodic timer task to periodically sync cached prefs
-        syncTimer.schedule(new TimerTask() {
-            @Override
-            public void run() {
-                syncWorld();
-            }
-        }, SYNC_INTERVAL * 1000, SYNC_INTERVAL * 1000);
-
-        // Add shutdown hook to flush cached prefs on normal termination
-        AccessController.doPrivileged(new PrivilegedAction() {
-            @Override
-            public Object run() {
-                Runtime.getRuntime().addShutdownHook(new Thread() {
-                    @Override
-                    public void run() {
-                        syncTimer.cancel();
-                        syncWorld();
-                    }
-                });
-                return null;
-            }
-        });
-    }
-
-    private static void syncWorld() {
-        /*
-         * Synchronization necessary because userRoot and systemRoot are lazily
-         * initialized.
-         */
-        Preferences userRt;
-        Preferences systemRt;
-        synchronized (FileSystemPreferences.class) {
-            userRt = userRoot;
-            systemRt = systemRoot;
-        }
-
-        try {
-            if (userRt != null)
-                userRt.flush();
-        } catch (BackingStoreException e) {
-            getLogger().warning("Couldn't flush user prefs: " + e);
-        }
-
-        try {
-            if (systemRt != null)
-                systemRt.flush();
-        } catch (BackingStoreException e) {
-            getLogger().warning("Couldn't flush system prefs: " + e);
-        }
-    }
-
-    private final boolean isUserNode;
-
-    /**
-     * Special constructor for roots (both user and system). This constructor will
-     * only be called twice, by the static initializer.
-     */
-    private FileSystemPreferences(boolean user) {
-        super(null, "");
-        isUserNode = user;
-        dir = (user ? userRootDir : systemRootDir);
-        prefsFile = new File(dir, "prefs.xml");
-        tmpFile = new File(dir, "prefs.tmp");
-    }
-
-    /**
-     * Construct a new FileSystemPreferences instance with the specified parent node
-     * and name. This constructor, called from childSpi, is used to make every node
-     * except for the two //roots.
-     */
-    private FileSystemPreferences(FileSystemPreferences parent, String name) {
-        super(parent, name);
-        isUserNode = parent.isUserNode;
-        dir = new File(parent.dir, dirName(name));
-        prefsFile = new File(dir, "prefs.xml");
-        tmpFile = new File(dir, "prefs.tmp");
-        AccessController.doPrivileged(new PrivilegedAction() {
-            @Override
-            public Object run() {
-                newNode = !dir.exists();
-                return null;
-            }
-        });
-        if (newNode) {
-            // These 2 things guarantee node will get wrtten at next flush/sync
-            prefsCache = new TreeMap();
-            nodeCreate = new NodeCreate();
-            changeLog.add(nodeCreate);
-        }
-    }
-
-    @Override
-    public boolean isUserNode() {
-        return isUserNode;
-    }
-
-    @Override
-    protected void putSpi(String key, String value) {
-        initCacheIfNecessary();
-        changeLog.add(new Put(key, value));
-        prefsCache.put(key, value);
-    }
-
-    @Override
-    protected String getSpi(String key) {
-        initCacheIfNecessary();
-        return (String) prefsCache.get(key);
-    }
-
-    @Override
-    protected void removeSpi(String key) {
-        initCacheIfNecessary();
-        changeLog.add(new Remove(key));
-        prefsCache.remove(key);
-    }
-
-    /**
-     * Initialize prefsCache if it has yet to be initialized. When this method
-     * returns, prefsCache will be non-null. If the data was successfully read from
-     * the file, lastSyncTime will be updated. If prefsCache was null, but it was
-     * impossible to read the file (because it didn't exist or for any other reason)
-     * prefsCache will be initialized to an empty, modifiable Map, and lastSyncTime
-     * remain zero.
-     */
-    private void initCacheIfNecessary() {
-        if (prefsCache != null)
-            return;
-
-        try {
-            loadCache();
-        } catch (Exception e) {
-            // assert lastSyncTime == 0;
-            prefsCache = new TreeMap();
-        }
-    }
-
-    /**
-     * Attempt to load prefsCache from the backing store. If the attempt succeeds,
-     * lastSyncTime will be updated (the new value will typically correspond to the
-     * data loaded into the map, but it may be less, if another VM is updating this
-     * node concurrently). If the attempt fails, a BackingStoreException is thrown
-     * and both prefsCache and lastSyncTime are unaffected by the call.
-     */
-    private void loadCache() throws BackingStoreException {
-        try {
-            AccessController.doPrivileged(new PrivilegedExceptionAction() {
-                @Override
-                public Object run() throws BackingStoreException {
-                    Map m = new TreeMap();
-                    long newLastSyncTime = 0;
-                    try {
-                        newLastSyncTime = prefsFile.lastModified();
-                        FileInputStream fis = new FileInputStream(prefsFile);
-                        FilePreferencesXmlSupport.importMap(fis, m);
-                        fis.close();
-                    } catch (Exception e) {
-                        if (e instanceof InvalidPreferencesFormatException) {
-                            getLogger().warning("Invalid preferences format in " + prefsFile.getPath());
-                            prefsFile.renameTo(new File(prefsFile.getParentFile(), "IncorrectFormatPrefs.xml"));
-                            m = new TreeMap();
-                        } else if (e instanceof FileNotFoundException) {
-                            getLogger().warning("Prefs file removed in background " + prefsFile.getPath());
-                        } else {
-                            throw new BackingStoreException(e);
-                        }
-                    }
-                    // Attempt succeeded; update state
-                    prefsCache = m;
-                    lastSyncTime = newLastSyncTime;
-                    return null;
-                }
-            });
-        } catch (PrivilegedActionException e) {
-            throw (BackingStoreException) e.getException();
-        }
-    }
-
-    /**
-     * Attempt to write back prefsCache to the backing store. If the attempt
-     * succeeds, lastSyncTime will be updated (the new value will correspond exactly
-     * to the data thust written back, as we hold the file lock, which prevents a
-     * concurrent write. If the attempt fails, a BackingStoreException is thrown and
-     * both the backing store (prefsFile) and lastSyncTime will be unaffected by
-     * this call. This call will NEVER leave prefsFile in a corrupt state.
-     */
-    private void writeBackCache() throws BackingStoreException {
-        try {
-            AccessController.doPrivileged(new PrivilegedExceptionAction() {
-                @Override
-                public Object run() throws BackingStoreException {
-                    try {
-                        if (!dir.exists() && !dir.mkdirs())
-                            throw new BackingStoreException(dir + " create failed.");
-                        FileOutputStream fos = new FileOutputStream(tmpFile);
-                        FilePreferencesXmlSupport.exportMap(fos, prefsCache);
-                        fos.close();
-                        Files.move(tmpFile.toPath(), prefsFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
-                    } catch (Exception e) {
-                        throw new BackingStoreException(e);
-                    }
-                    return null;
-                }
-            });
-        } catch (PrivilegedActionException e) {
-            throw (BackingStoreException) e.getException();
-        }
-    }
-
-    @Override
-    protected String[] keysSpi() {
-        initCacheIfNecessary();
-        return (String[]) prefsCache.keySet().toArray(new String[prefsCache.size()]);
-    }
-
-    @Override
-    protected String[] childrenNamesSpi() {
-        return (String[]) AccessController.doPrivileged(new PrivilegedAction() {
-            @Override
-            public Object run() {
-                List result = new ArrayList();
-                File[] dirContents = dir.listFiles();
-                if (dirContents != null) {
-                    for (int i = 0; i < dirContents.length; i++)
-                        if (dirContents[i].isDirectory())
-                            result.add(nodeName(dirContents[i].getName()));
-                }
-                return result.toArray(EMPTY_STRING_ARRAY);
-            }
-        });
-    }
-
-    private static final String[] EMPTY_STRING_ARRAY = new String[0];
-
-    @Override
-    protected AbstractPreferences childSpi(String name) {
-        return new FileSystemPreferences(this, name);
-    }
-
-    @Override
-    public void removeNode() throws BackingStoreException {
-        synchronized (isUserNode() ? userLockFile : systemLockFile) {
-            // to remove a node we need an exclusive lock
-            if (!lockFile(false))
-                throw (new BackingStoreException("Couldn't get file lock."));
-            try {
-                super.removeNode();
-            } finally {
-                unlockFile();
-            }
-        }
-    }
-
-    /**
-     * Called with file lock held (in addition to node locks).
-     */
-    @Override
-    protected void removeNodeSpi() throws BackingStoreException {
-        try {
-            AccessController.doPrivileged(new PrivilegedExceptionAction() {
-                @Override
-                public Object run() throws BackingStoreException {
-                    if (changeLog.contains(nodeCreate)) {
-                        changeLog.remove(nodeCreate);
-                        nodeCreate = null;
-                        return null;
-                    }
-                    if (!dir.exists())
-                        return null;
-                    prefsFile.delete();
-                    tmpFile.delete();
-                    // dir should be empty now. If it's not, empty it
-                    File[] junk = dir.listFiles();
-                    if (junk.length != 0) {
-                        getLogger().warning("Found extraneous files when removing node: " + Arrays.asList(junk));
-                        for (int i = 0; i < junk.length; i++)
-                            junk[i].delete();
-                    }
-                    if (!dir.delete())
-                        throw new BackingStoreException("Couldn't delete dir: " + dir);
-                    return null;
-                }
-            });
-        } catch (PrivilegedActionException e) {
-            throw (BackingStoreException) e.getException();
-        }
-    }
-
-    @Override
-    public synchronized void sync() throws BackingStoreException {
-        boolean userNode = isUserNode();
-        boolean shared;
-
-        if (userNode) {
-            shared = false; /* use exclusive lock for user prefs */
-        } else {
-            /*
-             * if can write to system root, use exclusive lock. otherwise use shared lock.
-             */
-            shared = !isSystemRootWritable;
-        }
-        synchronized (isUserNode() ? userLockFile : systemLockFile) {
-            if (!lockFile(shared))
-                throw (new BackingStoreException("Couldn't get file lock."));
-            final Long newModTime = (Long) AccessController.doPrivileged(new PrivilegedAction() {
-                @Override
-                public Object run() {
-                    long nmt;
-                    if (isUserNode()) {
-                        nmt = userRootModFile.lastModified();
-                        isUserRootModified = userRootModTime == nmt;
-                    } else {
-                        nmt = systemRootModFile.lastModified();
-                        isSystemRootModified = systemRootModTime == nmt;
-                    }
-                    return Long.valueOf(nmt);
-                }
-            });
-            try {
-                super.sync();
-                AccessController.doPrivileged(new PrivilegedAction() {
-                    @Override
-                    public Object run() {
-                        if (isUserNode()) {
-                            userRootModTime = newModTime.longValue() + 1000;
-                            userRootModFile.setLastModified(userRootModTime);
-                        } else {
-                            systemRootModTime = newModTime.longValue() + 1000;
-                            systemRootModFile.setLastModified(systemRootModTime);
-                        }
-                        return null;
-                    }
-                });
-            } finally {
-                unlockFile();
-            }
-        }
-    }
-
-    @Override
-    protected void syncSpi() throws BackingStoreException {
-        try {
-            AccessController.doPrivileged(new PrivilegedExceptionAction() {
-                @Override
-                public Object run() throws BackingStoreException {
-                    syncSpiPrivileged();
-                    return null;
-                }
-            });
-        } catch (PrivilegedActionException e) {
-            throw (BackingStoreException) e.getException();
-        }
-    }
-
-    private void syncSpiPrivileged() throws BackingStoreException {
-        if (isRemoved())
-            throw new IllegalStateException("Node has been removed");
-        if (prefsCache == null)
-            return; // We've never been used, don't bother syncing
-        long lastModifiedTime;
-        if ((isUserNode() ? isUserRootModified : isSystemRootModified)) {
-            lastModifiedTime = prefsFile.lastModified();
-            if (lastModifiedTime != lastSyncTime) {
-                // Prefs at this node were externally modified; read in node and
-                // playback any local mods since last sync
-                loadCache();
-                replayChanges();
-                lastSyncTime = lastModifiedTime;
-            }
-        } else if (lastSyncTime != 0 && !dir.exists()) {
-            // This node was removed in the background. Playback any changes
-            // against a virgin (empty) Map.
-            prefsCache = new TreeMap();
-            replayChanges();
-        }
-        if (!changeLog.isEmpty()) {
-            writeBackCache(); // Creates directory & file if necessary
-            /*
-             * Attempt succeeded; it's barely possible that the call to lastModified might
-             * fail (i.e., return 0), but this would not be a disaster, as lastSyncTime is
-             * allowed to lag.
-             */
-            lastModifiedTime = prefsFile.lastModified();
-            /*
-             * If lastSyncTime did not change, or went back increment by 1 second. Since we
-             * hold the lock lastSyncTime always monotonically encreases in the atomic
-             * sense.
-             */
-            if (lastSyncTime <= lastModifiedTime) {
-                lastSyncTime = lastModifiedTime + 1000;
-                prefsFile.setLastModified(lastSyncTime);
-            }
-            changeLog.clear();
-        }
-    }
-
-    @Override
-    public void flush() throws BackingStoreException {
-        if (isRemoved())
-            return;
-        sync();
-    }
-
-    @Override
-    protected void flushSpi() throws BackingStoreException {
-        // assert false;
-    }
-
-    @Override
-    protected boolean isRemoved() {
-        return super.isRemoved();
-    }
-
-    /**
-     * Returns true if the specified character is appropriate for use in Unix
-     * directory names. A character is appropriate if it's a printable ASCII
-     * character (> 0x1f && < 0x7f) and unequal to slash ('/', 0x2f), dot ('.',
-     * 0x2e), or underscore ('_', 0x5f).
-     */
-    private static boolean isDirChar(char ch) {
-        return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.' && ch != '_';
-    }
-
-    /**
-     * Returns the directory name corresponding to the specified node name.
-     * Generally, this is just the node name. If the node name includes
-     * inappropriate characters (as per isDirChar) it is translated to Base64. with
-     * the underscore character ('_', 0x5f) prepended.
-     */
-    private static String dirName(String nodeName) {
-        for (int i = 0, n = nodeName.length(); i < n; i++)
-            if (!isDirChar(nodeName.charAt(i)))
-                return "_" + Base64.byteArrayToAltBase64(byteArray(nodeName));
-        return nodeName;
-    }
-
-    /**
-     * Translate a string into a byte array by translating each character into two
-     * bytes, high-byte first ("big-endian").
-     */
-    private static byte[] byteArray(String s) {
-        int len = s.length();
-        byte[] result = new byte[2 * len];
-        for (int i = 0, j = 0; i < len; i++) {
-            char c = s.charAt(i);
-            result[j++] = (byte) (c >> 8);
-            result[j++] = (byte) c;
-        }
-        return result;
-    }
-
-    /**
-     * Returns the node name corresponding to the specified directory name. (Inverts
-     * the transformation of dirName(String).
-     */
-    private static String nodeName(String dirName) {
-        if (dirName.charAt(0) != '_')
-            return dirName;
-        byte a[] = Base64.altBase64ToByteArray(dirName.substring(1));
-        StringBuffer result = new StringBuffer(a.length / 2);
-        for (int i = 0; i < a.length;) {
-            int highByte = a[i++] & 0xff;
-            int lowByte = a[i++] & 0xff;
-            result.append((char) ((highByte << 8) | lowByte));
-        }
-        return result.toString();
-    }
-
-    /**
-     * Try to acquire the appropriate file lock (user or system). If the initial
-     * attempt fails, several more attempts are made using an exponential backoff
-     * strategy. If all attempts fail, this method returns false.
-     *
-     * @throws SecurityException
-     *             if file access denied.
-     */
-    boolean lockFile(boolean shared) throws SecurityException {
-        boolean usernode = isUserNode();
-        int errorCode = 0;
-        File lockFile = (usernode ? userLockFile : systemLockFile);
-        long sleepTime = INIT_SLEEP_TIME;
-        for (int i = 0; i < MAX_ATTEMPTS; i++) {
-            try {
-                RandomAccessFile file = new RandomAccessFile(lockFile.getCanonicalPath(), "rw");
-                FileLock lock = file.getChannel().lock();
-
-                if (lock != null) {
-                    if (usernode) {
-                        userRootLockFile = file;
-                        userRootLockHandle = lock;
-                    } else {
-                        systemRootLockFile = file;
-                        systemRootLockHandle = lock;
-                    }
-                    return true;
-                }
-            } catch (IOException e) {
-            }
-
-            try {
-                Thread.sleep(sleepTime);
-            } catch (InterruptedException e) {
-                checkLockFile0ErrorCode(errorCode);
-                return false;
-            }
-            sleepTime *= 2;
-        }
-        checkLockFile0ErrorCode(errorCode);
-        return false;
-    }
-
-    /**
-     * Checks if unlockFile0() returned an error. Throws a SecurityException, if
-     * access denied. Logs a warning otherwise.
-     */
-    private void checkLockFile0ErrorCode(int errorCode) throws SecurityException {
-        if (errorCode == EACCES)
-            throw new SecurityException(
-                    "Could not lock " + (isUserNode() ? "User prefs." : "System prefs.") + " Lock file access denied.");
-        if (errorCode != EAGAIN)
-            getLogger().warning("Could not lock " + (isUserNode() ? "User prefs. " : "System prefs.")
-                    + " Unix error code " + errorCode + ".");
-    }
-
-    /**
-     * Initial time between lock attempts, in ms. The time is doubled after each
-     * failing attempt (except the first).
-     */
-    private static int INIT_SLEEP_TIME = 50;
-
-    /**
-     * Maximum number of lock attempts.
-     */
-    private static int MAX_ATTEMPTS = 5;
-
-    /**
-     * Release the the appropriate file lock (user or system).
-     *
-     * @throws SecurityException
-     *             if file access denied.
-     */
-    void unlockFile() {
-        boolean usernode = isUserNode();
-        RandomAccessFile rootLockFile = (usernode ? userRootLockFile : systemRootLockFile);
-        FileLock lockHandle = (usernode ? userRootLockHandle : systemRootLockHandle);
-        try {
-            lockHandle.close();
-        } catch (IOException e) {
-            getLogger().warning("Unlock: zero lockHandle for " + (usernode ? "user" : "system") + " preferences.)");
-            return;
-        } finally {
-            try {
-                rootLockFile.close();
-            } catch (IOException e) {
-                getLogger().log(Level.WARNING, "Cannot close lock file " + rootLockFile, e);
-            }
-        }
-
-    }
-}
\ No newline at end of file

From 57bcc055767e420474865c78526ea8a04e013acc Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Thu, 13 Jun 2024 09:52:22 -0400
Subject: [PATCH 23/59] Documentation, cleanup

---
 .../preferences/PhoebusPreferenceService.java |  2 +-
 .../java/org/phoebus/ui/help/OpenAbout.java   | 46 +------------------
 docs/source/preferences.rst                   | 39 ++++++----------
 3 files changed, 17 insertions(+), 70 deletions(-)

diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java b/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java
index d99e175dd5..fe9b303ccc 100644
--- a/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java
+++ b/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java
@@ -4,7 +4,7 @@
 import java.util.prefs.PreferencesFactory;
 
 /**
- * A service which enabled the use of the file backed implementation of java {@link Preferences}. 
+ * A service which enabled the use of the in-memory implementation of java {@link Preferences}. 
  * @author Kunal Shroff
  *
  */
diff --git a/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java b/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java
index 6583817b53..0add9484a5 100644
--- a/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java
+++ b/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2017-2020 Oak Ridge National Laboratory.
+ * Copyright (c) 2017-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
@@ -11,34 +11,27 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileInputStream;
 import java.util.Arrays;
 import java.util.List;
 import java.util.logging.Level;
 
-import org.phoebus.framework.jobs.JobManager;
-import org.phoebus.framework.preferences.PropertyPreferenceLoader;
 import org.phoebus.framework.preferences.PropertyPreferenceWriter;
 import org.phoebus.framework.workbench.ApplicationService;
 import org.phoebus.framework.workbench.Locations;
 import org.phoebus.ui.application.Messages;
 import org.phoebus.ui.dialog.DialogHelper;
-import org.phoebus.ui.dialog.OpenFileDialog;
 import org.phoebus.ui.docking.DockPane;
 import org.phoebus.ui.javafx.ImageCache;
 import org.phoebus.ui.javafx.ReadOnlyTextCell;
 import org.phoebus.ui.spi.MenuEntry;
 
-import javafx.application.Platform;
 import javafx.beans.property.SimpleStringProperty;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
-import javafx.geometry.Pos;
 import javafx.scene.Node;
 import javafx.scene.control.Alert;
 import javafx.scene.control.Alert.AlertType;
 import javafx.scene.control.Button;
-import javafx.scene.control.ButtonType;
 import javafx.scene.control.Tab;
 import javafx.scene.control.TabPane;
 import javafx.scene.control.TableCell;
@@ -47,10 +40,8 @@
 import javafx.scene.control.TextArea;
 import javafx.scene.control.Tooltip;
 import javafx.scene.image.Image;
-import javafx.scene.layout.HBox;
 import javafx.scene.layout.Priority;
 import javafx.scene.layout.VBox;
-import javafx.stage.FileChooser.ExtensionFilter;
 
 /** Menu entry to open 'about'
  *  @author Kay Kasemir
@@ -216,44 +207,11 @@ private Node createDetailSection()
         area = new TextArea(prefs_buf.toString());
         area.setEditable(false);
 
-        final Button import_prefs = new Button("Import Preferences");
-        import_prefs.setOnAction(event -> import_preferences());
-        final HBox bottom_row = new HBox(import_prefs);
-        bottom_row.setAlignment(Pos.BASELINE_RIGHT);
-
         VBox.setVgrow(area, Priority.ALWAYS);
 
-        final Tab prefs = new Tab(Messages.HelpAboutPrefs, new VBox(5, area, bottom_row));
+        final Tab prefs = new Tab(Messages.HelpAboutPrefs, area);
 
         final TabPane tabs = new TabPane(apps, envs, props, prefs);
         return tabs;
     }
-
-    /** Prompt for settings.ini to import, then offer restart */
-    private void import_preferences()
-    {
-        final DockPane parent = DockPane.getActiveDockPane();
-
-        final ExtensionFilter[] ini = new ExtensionFilter[] { new ExtensionFilter("Preference settings.ini", List.of("*.ini")) };
-        final File file = new OpenFileDialog().promptForFile(parent.getScene().getWindow(), Messages.Open, null, ini);
-        if (file == null)
-            return;
-
-        JobManager.schedule("Load preferences", monitor ->
-        {
-            PropertyPreferenceLoader.load(new FileInputStream(file));
-            Platform.runLater(() ->
-            {
-                final Alert restart = new Alert(AlertType.CONFIRMATION);
-                restart.setHeaderText("Restart to activate loaded settings");
-                restart.setContentText("For performance reasons, preference settings are only loaded once on startup.\n" +
-                                       "Exit application so you can then start it again?");
-                restart.getDialogPane().setPrefSize(500, 300);
-                restart.setResizable(true);
-                DialogHelper.positionDialog(restart, parent, -400, -300);
-                if (restart.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK)
-                    System.exit(0);
-            });
-        });
-    }
 }
diff --git a/docs/source/preferences.rst b/docs/source/preferences.rst
index 67357bd0a6..df70bd01c9 100644
--- a/docs/source/preferences.rst
+++ b/docs/source/preferences.rst
@@ -64,22 +64,15 @@ that lists all preference settings in the same format that is used by the
 ``settings.ini`` file. You can copy settings that you need to change
 from the display into your settings file.
 
-The same details pane that lists current preference settings also
-offers an ``Import Preferences`` button for loading a ``settings.ini``
-file. You may use that as an alternative to the command line ``-settings ..`` option,
-but note that settings loaded via this button only become effective
-after a restart.
-
-Settings loaded via either the ``-settings ..`` command line option
-or the ``Import Preferences`` button are stored in the user location (see :ref:`locations`).
-They remain effective until different settings are loaded or the user location is deleted.
-It is therefore not necessary to always run the application with the same
-``-settings ..`` command line option. Just invoking with the command line option
-once or using the ``Import Preferences`` button once suffices to load settings.
-In practice, however, it is advisable to include the ``-settings ..`` command line option
-in a site-specific application start script.
-This way, new users do not need to remember to once start with the option,
-and existing users will benefit from changes to the settings file.
+Settings loaded via the ``-settings ..`` command line option
+only remain effective while the application is running.
+It is therefore necessary to always run the application with the same
+``-settings ..`` command line option to get the same results.
+In practice, it is advisable to include the ``-settings ..`` command line option
+in a site-specific application start script or add them to a site-specific
+product as detailed below.
+This way, new users do not need to remember any command line settings
+because they are applied in the launcher script or bundled into the product.
 
 Conceptually, preference settings are meant to hold critical configuration
 parameters like the control system network configuration.
@@ -94,7 +87,7 @@ like context menus or configuration dialogs.
 When you package phoebus for distribution at your site, you can also place
 a file ``settings.ini`` in the installation location (see :ref:`locations`).
 At startup, Phoebus will automatically load the file ``settings.ini``
-from the installation location, eliminating the need for your users
+from the installation location, eliminating the need for your users or a launcher script
 to add the ``-settings ..`` command line option.
 
 
@@ -145,7 +138,7 @@ In your application code, you can most conveniently access them like this::
 
 
 The ``AnnotatedPreferences`` helper will read your ``*preferences.properties``,
-apply updates from ``java.util.prefs.Preferences``, and then set the values
+apply updates from ``java.util.prefs.Preferences`` that have been added via ``-settings ..``, and then set the values
 of all static fields annotated with ``@Preference``.
 It handles basic types like ``int``, ``long``, ``double``, ``boolean``, ``String``,
 ``File``. It can also parse comma-separated items into ``int[]`` or ``String[]``.
@@ -169,10 +162,6 @@ returns a ``PreferencesReader``, or you could directly use that lower level API
     // and parse as desired.
 
 The ``PreferencesReader`` loads defaults from the property file,
-then allows overrides via the ``java.util.prefs.Preferences`` API.
-By default, the user settings are stored in a ``.phoebus`` folder
-in the home directory.
-This location can be changed by setting the Java property ``phoebus.user``.
-
-In the future, a preference UI might be added, but as mentioned
-the preference settings are not meant to be adjusted by end users.
+then allows overrides via the ``java.util.prefs.Preferences`` API
+that is used when loading a ``settings.ini`` in the installation location 
+and by the ``-settings ..`` provided on the command line.

From 7e174e795f5728fd945e7f2938a144fabc36ee77 Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Thu, 13 Jun 2024 09:54:04 -0400
Subject: [PATCH 24/59] Documentation

---
 docs/source/locations.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/source/locations.rst b/docs/source/locations.rst
index 2c0ee43a56..4eace4c04e 100644
--- a/docs/source/locations.rst
+++ b/docs/source/locations.rst
@@ -19,7 +19,7 @@ set when starting the product.
 
 ``phoebus.user``:
    Location where phoebus keeps the memento
-   and preferences.
+   and saved layouts.
    Defaults to ``.phoebus`` in the user's home directory.
 
 Site-Specific Branding and Settings

From 4cc1642f0269ff07edcccd2807f55eaf962a2523 Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Thu, 13 Jun 2024 10:01:14 -0400
Subject: [PATCH 25/59] Simplify locking

---
 .../framework/preferences/InMemoryPreferences.java       | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java b/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java
index 7c600d5c73..e0cea922f1 100644
--- a/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java
+++ b/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java
@@ -7,7 +7,8 @@
  *******************************************************************************/
 package org.phoebus.framework.preferences;
 
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.prefs.AbstractPreferences;
 import java.util.prefs.BackingStoreException;
 import java.util.prefs.Preferences;
@@ -21,8 +22,12 @@ class InMemoryPreferences extends AbstractPreferences
     private static final InMemoryPreferences prefs = new InMemoryPreferences(null, "");
     
     /** Settings for this node in the preferences hierarchy */
-    private ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
+    private Map<String, String> cache = new HashMap<>();
 
+    // Javadoc for all ..Spi calls includes
+    // "This method is invoked with the lock on this node held."
+    // so no need for ConcurrentHashMap or our own locking
+    
     /** @return User preferences */
     public static Preferences getUserRoot()
     {

From 1deac1c16a192aca812bff5589f9406ff4632bc7 Mon Sep 17 00:00:00 2001
From: lcaouen <loic.caouen@cea.fr>
Date: Fri, 14 Jun 2024 16:54:50 +0200
Subject: [PATCH 26/59] 
 https://github.com/ControlSystemStudio/phoebus/issues/3047

Add Swagger-ui to phoebus-alarm-logger
---
 services/alarm-logger/pom.xml                 |  7 ++++
 .../alarm/logging/rest/SearchController.java  | 36 +++++++++++++++++--
 2 files changed, 40 insertions(+), 3 deletions(-)

diff --git a/services/alarm-logger/pom.xml b/services/alarm-logger/pom.xml
index 7007ddef65..4e98977b0f 100644
--- a/services/alarm-logger/pom.xml
+++ b/services/alarm-logger/pom.xml
@@ -141,6 +141,13 @@
       <scope>test</scope>
     </dependency>
 
+    <dependency>
+      <groupId>org.springdoc</groupId>
+      <artifactId>springdoc-openapi-ui</artifactId>
+      <version>1.7.0</version>
+    </dependency>
+
+
   </dependencies>
 
   <profiles>
diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java
index 614483b024..68cba562b8 100644
--- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java
+++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java
@@ -3,6 +3,9 @@
 import co.elastic.clients.elasticsearch.ElasticsearchClient;
 import co.elastic.clients.elasticsearch._types.ElasticsearchVersionInfo;
 import co.elastic.clients.elasticsearch.core.InfoResponse;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Schema;
+
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.phoebus.alarm.logging.AlarmLoggingService;
@@ -25,6 +28,12 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+
 /**
  * A REST service for querying the alarm message history
  *
@@ -32,6 +41,7 @@
  */
 @RestController
 @SuppressWarnings("unused")
+@Tag(name = "Search controller")
 public class SearchController {
 
     static final Logger logger = Logger.getLogger(SearchController.class.getName());
@@ -44,6 +54,7 @@ public class SearchController {
     /**
      * @return Information about the alarm logging service
      */
+    @Operation(summary = "Get Service Info")
     @GetMapping
     public String info() {
 
@@ -74,22 +85,41 @@ public String info() {
         }
     }
 
+    @Operation(summary = "Search alarms")
+    @Parameters({
+        @Parameter(name = "pv", description = "PV name", schema = @Schema(type = "string"), required = false, example = "*"),
+        @Parameter(name = "severity", description = "Alarm severity", schema = @Schema(type = "string"), required = false, example = "*"),
+        @Parameter(name = "message", description = "Alarm message", schema = @Schema(type = "string"), required = false, example = "*"),
+        @Parameter(name = "current_severity", description = "PV severity", schema = @Schema(type = "string"), required = false, example = "*"),
+        @Parameter(name = "current_message", description = "PV message", schema = @Schema(type = "string"), required = false, example = "*"),
+        @Parameter(name = "user", description = "User", schema = @Schema(type = "string"), required = false, example = "*"),
+        @Parameter(name = "host", description = "Host", schema = @Schema(type = "string"), required = false, example = "*"),
+        @Parameter(name = "command", description = "Command", schema = @Schema(type = "string"), required = false, example = "*"),
+        @Parameter(name = "start", description = "Start time", schema = @Schema(type = "string"), required = false, example = "2024-06-12"),
+        @Parameter(name = "end", description = "End time", schema = @Schema(type = "string"), required = false, example = "2024-06-14"),
+    })
     @RequestMapping(value = "/search/alarm", method = RequestMethod.GET)
-    public List<AlarmLogMessage> search(@RequestParam Map<String, String> allRequestParams) {
+    public List<AlarmLogMessage> search(@Parameter(hidden = true) @RequestParam Map<String, String> allRequestParams) {
         List<AlarmLogMessage> result = AlarmLogSearchUtil.search(ElasticClientHelper.getInstance().getClient(), allRequestParams);
         return result;
     }
 
+    @Operation(summary = "Search alarms by PV name")
     @RequestMapping(value = "/search/alarm/pv/{pv}", method = RequestMethod.GET)
-    public List<AlarmLogMessage> searchPv(@PathVariable String pv) {
+    public List<AlarmLogMessage> searchPv(@Parameter(description = "PV name") @PathVariable String pv) {
         Map<String, String> searchParameters = new HashMap<>();
         searchParameters.put("pv", pv);
         List<AlarmLogMessage> result = AlarmLogSearchUtil.search(ElasticClientHelper.getInstance().getClient(), searchParameters);
         return result;
     }
 
+    @Operation(summary = "Search alarm config")
+    @Schema(name = "config", example = "/Accelerator/compteur", required = true)
+    @Parameters({
+        @Parameter(name = "config", description = "Config path", schema = @Schema(type = "string"), required = false, example = "/Accelerator/pvname"),
+    })
     @RequestMapping(value = "/search/alarm/config", method = RequestMethod.GET)
-    public List<AlarmLogMessage> searchConfig(@RequestParam Map<String, String> allRequestParams) {
+    public List<AlarmLogMessage> searchConfig(@Parameter(hidden = true) @RequestParam Map<String, String> allRequestParams) {
         if(allRequestParams == null ||
                 allRequestParams.isEmpty() ||
                 !allRequestParams.containsKey("config") ||

From d66790be2e2370feb932d86ce7aa7664f2abef5b Mon Sep 17 00:00:00 2001
From: lcaouen <loic.caouen@cea.fr>
Date: Fri, 14 Jun 2024 16:57:45 +0200
Subject: [PATCH 27/59] 
 https://github.com/ControlSystemStudio/phoebus/issues/3047

Add Swagger-ui to phoebus-alarm-logger
---
 .../java/org/phoebus/alarm/logging/rest/SearchController.java   | 2 --
 1 file changed, 2 deletions(-)

diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java
index 68cba562b8..95ab4aba3b 100644
--- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java
+++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java
@@ -3,8 +3,6 @@
 import co.elastic.clients.elasticsearch.ElasticsearchClient;
 import co.elastic.clients.elasticsearch._types.ElasticsearchVersionInfo;
 import co.elastic.clients.elasticsearch.core.InfoResponse;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.media.Schema;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;

From 60614783d47c344798d4c5717b3242bf6cde6602 Mon Sep 17 00:00:00 2001
From: Conor Schofield <conorschofield@lbl.gov>
Date: Fri, 14 Jun 2024 16:20:24 -0400
Subject: [PATCH 28/59] Adding Maven Action to fix github builds

---
 .github/workflows/build.yml        | 7 ++++---
 .github/workflows/build_latest.yml | 5 +++--
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8a0b5dc04c..6f11682178 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,9 +11,10 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v2
-    - name: Set up JDK 17
-      uses: actions/setup-java@v1
+    - name: Setup Maven and Java Action
+      uses: s4u/setup-maven-action@v1.13.0
       with:
         java-version: '17'
+        maven-version: '3.9.6'
     - name: Build
-      run: mvn --batch-mode install
\ No newline at end of file
+      run: mvn --batch-mode install -DskipTests
\ No newline at end of file
diff --git a/.github/workflows/build_latest.yml b/.github/workflows/build_latest.yml
index 5e5e0749e3..0779cb277c 100644
--- a/.github/workflows/build_latest.yml
+++ b/.github/workflows/build_latest.yml
@@ -15,10 +15,11 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
     - uses: actions/checkout@v2
-    - name: Set up JDK 17
-      uses: actions/setup-java@v1
+    - name: Setup Maven and Java Action
+      uses: s4u/setup-maven-action@v1.13.0
       with:
         java-version: '17'
+        maven-version: '3.9.6'
     - name: Build
       run: mvn --batch-mode install -DskipTests
 

From 490cfc4999e641fc0fa57a2d4d9a7ce5d21f25ac Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 17 Jun 2024 11:40:29 -0400
Subject: [PATCH 29/59] Create a core utility class based on the google.guava
 String package

---
 .../java/org/phoebus/util/text/Strings.java   | 20 ++++++++++
 .../org/phoebus/util/text/StringsTest.java    | 39 +++++++++++++++++++
 2 files changed, 59 insertions(+)
 create mode 100644 core/util/src/main/java/org/phoebus/util/text/Strings.java
 create mode 100644 core/util/src/test/java/org/phoebus/util/text/StringsTest.java

diff --git a/core/util/src/main/java/org/phoebus/util/text/Strings.java b/core/util/src/main/java/org/phoebus/util/text/Strings.java
new file mode 100644
index 0000000000..03328eb7da
--- /dev/null
+++ b/core/util/src/main/java/org/phoebus/util/text/Strings.java
@@ -0,0 +1,20 @@
+package org.phoebus.util.text;
+
+import java.util.Objects;
+
+public class Strings {
+
+    /**
+     * Based on the the google String.isNullOrEmpty.
+     *
+     * @param str
+     * @return true if the string is null or empty
+     */
+    public static boolean isNullOrEmpty(String str) {
+        if(Objects.isNull(str) || str.isBlank()) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/core/util/src/test/java/org/phoebus/util/text/StringsTest.java b/core/util/src/test/java/org/phoebus/util/text/StringsTest.java
new file mode 100644
index 0000000000..95595b3505
--- /dev/null
+++ b/core/util/src/test/java/org/phoebus/util/text/StringsTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.phoebus.util.text;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Unit test for {@link Strings}.
+ *
+ * Based on the tests in google guava @author Kevin Bourrillion
+ */
+public class StringsTest {
+
+
+  @Test
+  public void testIsNullOrEmpty() {
+    assertTrue(Strings.isNullOrEmpty(null));
+    assertTrue(Strings.isNullOrEmpty(""));
+    assertFalse(Strings.isNullOrEmpty("a"));
+  }
+
+}
\ No newline at end of file

From 3896ec59dc234dbae739bc9d27dc555f7f893c8b Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 17 Jun 2024 11:42:31 -0400
Subject: [PATCH 30/59] Refactor the use of google Strings with the Phoebus
 core-util version

---
 .../src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java  | 2 --
 1 file changed, 2 deletions(-)

diff --git a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java
index c8c20dac81..ebf34db574 100644
--- a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java
+++ b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java
@@ -11,8 +11,6 @@
 
 import org.phoebus.util.time.TimeParser;
 
-import com.google.common.base.Strings;
-
 public class LogbookQueryUtil {
 
     // Ordered search keys

From dee5a41528f704d5a722201b720e855956cc9e92 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 17 Jun 2024 11:42:41 -0400
Subject: [PATCH 31/59] Refactor the use of google Strings with the Phoebus
 core-util version

---
 .../src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java   | 1 +
 1 file changed, 1 insertion(+)

diff --git a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java
index ebf34db574..64c5741dea 100644
--- a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java
+++ b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java
@@ -9,6 +9,7 @@
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
+import org.phoebus.util.text.Strings;
 import org.phoebus.util.time.TimeParser;
 
 public class LogbookQueryUtil {

From af4c161684b0144fc5c87d069fa3281694faf815 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 17 Jun 2024 11:47:04 -0400
Subject: [PATCH 32/59] [cont] Refactor use of google Strings with the Phoebus
 core-util version

---
 .../phoebus/logbook/olog/ui/AdvancedSearchViewController.java  | 2 +-
 .../java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java  | 2 +-
 .../java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java     | 2 +-
 .../java/org/phoebus/logbook/olog/ui/LogbookQueryUtil.java     | 3 +--
 .../java/org/phoebus/logbook/olog/ui/SearchParameters.java     | 2 +-
 5 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java
index 19871335af..e97816cd58 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java
@@ -18,7 +18,6 @@
 
 package org.phoebus.logbook.olog.ui;
 
-import com.google.common.base.Strings;
 import javafx.application.Platform;
 import javafx.beans.property.SimpleBooleanProperty;
 import javafx.beans.value.ChangeListener;
@@ -42,6 +41,7 @@
 import org.phoebus.ui.dialog.ListSelectionPopOver;
 import org.phoebus.ui.dialog.PopOver;
 import org.phoebus.ui.time.TimeRelativeIntervalPane;
+import org.phoebus.util.text.Strings;
 import org.phoebus.util.time.TimeParser;
 import org.phoebus.util.time.TimeRelativeInterval;
 import org.phoebus.util.time.TimestampFormats;
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java
index 4fa8ca4194..3eb48128d3 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java
@@ -1,6 +1,5 @@
 package org.phoebus.logbook.olog.ui;
 
-import com.google.common.base.Strings;
 import javafx.scene.image.Image;
 import org.phoebus.framework.spi.AppInstance;
 import org.phoebus.framework.spi.AppResourceDescriptor;
@@ -9,6 +8,7 @@
 import org.phoebus.logbook.LogService;
 import org.phoebus.logbook.LogbookPreferences;
 import org.phoebus.ui.javafx.ImageCache;
+import org.phoebus.util.text.Strings;
 
 import java.net.URI;
 import java.util.logging.Logger;
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 c50ca88d73..deaadb5baf 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
@@ -1,11 +1,11 @@
 package org.phoebus.logbook.olog.ui;
 
-import com.google.common.base.Strings;
 import javafx.scene.image.Image;
 import org.phoebus.framework.spi.AppInstance;
 import org.phoebus.framework.spi.AppResourceDescriptor;
 import org.phoebus.logbook.*;
 import org.phoebus.ui.javafx.ImageCache;
+import org.phoebus.util.text.Strings;
 
 import java.net.URI;
 import java.util.logging.Logger;
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookQueryUtil.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookQueryUtil.java
index 097e623b9d..99d9802e75 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookQueryUtil.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookQueryUtil.java
@@ -1,7 +1,6 @@
 package org.phoebus.logbook.olog.ui;
 
-import com.google.common.base.Strings;
-import org.checkerframework.checker.units.qual.K;
+import org.phoebus.util.text.Strings;
 import org.phoebus.util.time.TimeParser;
 
 import java.net.URI;
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SearchParameters.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SearchParameters.java
index 795e515a42..f50ba2e095 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SearchParameters.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SearchParameters.java
@@ -18,13 +18,13 @@
 
 package org.phoebus.logbook.olog.ui;
 
-import com.google.common.base.Strings;
 import javafx.beans.InvalidationListener;
 import javafx.beans.property.SimpleBooleanProperty;
 import javafx.beans.property.SimpleStringProperty;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import org.phoebus.logbook.olog.ui.LogbookQueryUtil.Keys;
+import org.phoebus.util.text.Strings;
 
 import java.util.ArrayList;
 import java.util.HashMap;

From 8b53d7ca302e275442bd9172fb57e78a1bea2a70 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 17 Jun 2024 11:59:10 -0400
Subject: [PATCH 33/59] replacing the use of google guava with functionality
 from core java

---
 app/channel/channelfinder/pom.xml             |  5 ---
 .../ChannelFinderClientImpl.java              |  3 +-
 app/logbook/inmemory/pom.xml                  |  5 ---
 .../logbook/InMemoryLogClient.java            | 37 +++++++++++++++----
 app/logbook/olog/ui/pom.xml                   |  5 ---
 .../olog/ui/AttachmentsViewController.java    |  3 +-
 app/logbook/ui/pom.xml                        |  5 ---
 7 files changed, 33 insertions(+), 30 deletions(-)

diff --git a/app/channel/channelfinder/pom.xml b/app/channel/channelfinder/pom.xml
index 03ab9ec901..df93acc4e0 100644
--- a/app/channel/channelfinder/pom.xml
+++ b/app/channel/channelfinder/pom.xml
@@ -39,10 +39,5 @@
         <artifactId>jersey-client</artifactId>
         <version>1.19</version>
     </dependency>
-    <dependency>
-        <groupId>com.google.guava</groupId>
-        <artifactId>guava</artifactId>
-        <version>${guava.version}</version>
-    </dependency>
   </dependencies>
 </project>
diff --git a/app/channel/channelfinder/src/main/java/org/phoebus/channelfinder/ChannelFinderClientImpl.java b/app/channel/channelfinder/src/main/java/org/phoebus/channelfinder/ChannelFinderClientImpl.java
index b833bd9bca..8c212a9701 100644
--- a/app/channel/channelfinder/src/main/java/org/phoebus/channelfinder/ChannelFinderClientImpl.java
+++ b/app/channel/channelfinder/src/main/java/org/phoebus/channelfinder/ChannelFinderClientImpl.java
@@ -11,7 +11,6 @@
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.base.Joiner;
 import com.sun.jersey.api.client.Client;
 import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
@@ -1002,7 +1001,7 @@ public Collection<Channel> findByTag(String pattern) throws ChannelFinderExcepti
     public Collection<Channel> findByProperty(String property, String... pattern) throws ChannelFinderException {
         Map<String, String> propertyPatterns = new HashMap<String, String>();
         if (pattern.length > 0) {
-            propertyPatterns.put(property, Joiner.on(",").join(pattern)); //$NON-NLS-1$
+            propertyPatterns.put(property, Arrays.stream(pattern).collect(Collectors.joining(","))); //$NON-NLS-1$
         } else {
             propertyPatterns.put(property, "*"); //$NON-NLS-1$
         }
diff --git a/app/logbook/inmemory/pom.xml b/app/logbook/inmemory/pom.xml
index 448a2effac..63e28235f4 100644
--- a/app/logbook/inmemory/pom.xml
+++ b/app/logbook/inmemory/pom.xml
@@ -12,10 +12,5 @@
       <artifactId>core-logbook</artifactId>
       <version>4.7.4-SNAPSHOT</version>
     </dependency>
-    <dependency>
-        <groupId>com.google.guava</groupId>
-        <artifactId>guava</artifactId>
-        <version>${guava.version}</version>
-    </dependency>
   </dependencies>
 </project>
diff --git a/app/logbook/inmemory/src/main/java/org/phoebus/applications/logbook/InMemoryLogClient.java b/app/logbook/inmemory/src/main/java/org/phoebus/applications/logbook/InMemoryLogClient.java
index a9f8f9eb9a..468e545180 100644
--- a/app/logbook/inmemory/src/main/java/org/phoebus/applications/logbook/InMemoryLogClient.java
+++ b/app/logbook/inmemory/src/main/java/org/phoebus/applications/logbook/InMemoryLogClient.java
@@ -1,24 +1,47 @@
 package org.phoebus.applications.logbook;
 
-import java.io.*;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
 import java.net.URLConnection;
+import java.nio.file.CopyOption;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.time.Instant;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import org.phoebus.logbook.*;
+import org.phoebus.logbook.Attachment;
+import org.phoebus.logbook.AttachmentImpl;
+import org.phoebus.logbook.LogClient;
+import org.phoebus.logbook.LogEntry;
+import org.phoebus.logbook.LogEntryImpl;
 import org.phoebus.logbook.LogEntryImpl.LogEntryBuilder;
-
-import com.google.common.io.Files;
+import org.phoebus.logbook.Logbook;
+import org.phoebus.logbook.LogbookException;
+import org.phoebus.logbook.LogbookImpl;
+import org.phoebus.logbook.Property;
+import org.phoebus.logbook.PropertyImpl;
+import org.phoebus.logbook.SearchResult;
+import org.phoebus.logbook.Tag;
+import org.phoebus.logbook.TagImpl;
 
 /**
  * A logbook which maintains logentries in memory. It is mainly for testing and debugging purpose.
  */
-public class InMemoryLogClient implements LogClient{
+public class InMemoryLogClient implements LogClient {
     private static Logger logger = Logger.getLogger(InMemoryLogClient.class.getName());
     private final AtomicInteger logIdCounter;
     private final Map<Long, LogEntry> logEntries;
@@ -105,7 +128,7 @@ public LogEntry set(LogEntry log) {
                     ext = file.getName().substring(i);
                 }
                 File tempFile = File.createTempFile(prefix, ext);
-                Files.copy(file, tempFile);
+                Files.copy(file.toPath(), tempFile.toPath());
                 tempFile.deleteOnExit();
                 String mimeType = URLConnection.guessContentTypeFromName(tempFile.getName());
                 return AttachmentImpl.of(tempFile, mimeType != null ? mimeType : ext, false);
diff --git a/app/logbook/olog/ui/pom.xml b/app/logbook/olog/ui/pom.xml
index 3adee8c0f0..9df3fa0dc3 100644
--- a/app/logbook/olog/ui/pom.xml
+++ b/app/logbook/olog/ui/pom.xml
@@ -40,11 +40,6 @@
             <artifactId>core-security</artifactId>
             <version>4.7.4-SNAPSHOT</version>
         </dependency>
-        <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava</artifactId>
-            <version>${guava.version}</version>
-        </dependency>
         <dependency>
             <groupId>org.jfxtras</groupId>
             <artifactId>jfxtras-agenda</artifactId>
diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AttachmentsViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AttachmentsViewController.java
index 07beeea29a..5a791d621e 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AttachmentsViewController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AttachmentsViewController.java
@@ -60,6 +60,7 @@
 import java.io.IOException;
 import java.net.URI;
 import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -306,7 +307,7 @@ private void copyAttachment(File targetFolder, Attachment attachment) {
             if (targetFile.exists()) {
                 throw new Exception("Target file " + targetFile.getAbsolutePath() + " exists");
             }
-            Files.copy(attachment.getFile().toPath(), targetFile.toPath());
+            Files.copy(attachment.getFile().toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
         } catch (Exception e) {
             ExceptionDetailsErrorDialog.openError(splitPane.getParent(), Messages.FileSave, Messages.FileSaveFailed, e);
         }
diff --git a/app/logbook/ui/pom.xml b/app/logbook/ui/pom.xml
index 049e5dd541..530e494d43 100644
--- a/app/logbook/ui/pom.xml
+++ b/app/logbook/ui/pom.xml
@@ -32,11 +32,6 @@
             <artifactId>core-security</artifactId>
             <version>4.7.4-SNAPSHOT</version>
         </dependency>
-        <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava</artifactId>
-            <version>${guava.version}</version>
-        </dependency>
         <dependency>
             <groupId>org.jfxtras</groupId>
             <artifactId>jfxtras-agenda</artifactId>

From d2a382e316d34ed11ff4d1da599ce7712f622556 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Tue, 18 Jun 2024 10:19:07 -0400
Subject: [PATCH 34/59] Replacing use of guava ImmutableLists with
 Collections.unmodifiableList

---
 app/databrowser-json/pom.xml                  |  6 --
 .../reader/json/internal/JsonVTypeReader.java | 64 +++++++++----------
 dependencies/phoebus-target/pom.xml           |  5 --
 3 files changed, 31 insertions(+), 44 deletions(-)

diff --git a/app/databrowser-json/pom.xml b/app/databrowser-json/pom.xml
index 9fffffa15d..bab56bf956 100644
--- a/app/databrowser-json/pom.xml
+++ b/app/databrowser-json/pom.xml
@@ -39,12 +39,6 @@
       <version>${jackson.version}</version>
     </dependency>
 
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-      <version>${guava.version}</version>
-    </dependency>
-
     <dependency>
       <groupId>org.epics</groupId>
       <artifactId>epics-util</artifactId>
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
index 4febe23c57..499f947610 100644
--- 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
@@ -11,10 +11,6 @@
 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;
@@ -43,6 +39,8 @@
 import java.math.BigInteger;
 import java.text.NumberFormat;
 import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -103,12 +101,12 @@ public static VType readValue(
                     parser.getTokenLocation());
         }
         Display display = null;
-        ImmutableDoubleArray double_value = null;
+        List<Double> double_value = null;
         EnumDisplay enum_display = null;
-        ImmutableIntArray enum_value = null;
+        List<Integer> enum_value = null;
         String field_name = null;
         boolean found_value = false;
-        ImmutableLongArray long_value = null;
+        List<Long> long_value = null;
         Double maximum = null;
         Double minimum = null;
         String quality = null;
@@ -280,13 +278,12 @@ public static VType readValue(
                 if (display == null) {
                     display = Display.none();
                 }
-                if (double_value.length() == 1) {
+                if (double_value.size() == 1) {
                     return VDouble.of(
                             double_value.get(0), alarm, time, display);
                 } else {
                     return VDoubleArray.of(
-                            CollectionNumbers.toListDouble(
-                                    double_value.toArray()),
+                            toListDouble(double_value),
                             alarm,
                             time,
                             display);
@@ -296,7 +293,7 @@ public static VType readValue(
                 // 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) {
+                for (var i = 0; i < enum_value.size(); ++i) {
                     final var value = enum_value.get(i);
                     min_value = Math.min(min_value, value);
                     max_value = Math.max(max_value, value);
@@ -321,7 +318,7 @@ public static VType readValue(
                             Range.undefined(),
                             "",
                             NumberFormats.precisionFormat(0));
-                    if (enum_value.length() == 1) {
+                    if (enum_value.size() == 1) {
                         return VInt.of(
                                 enum_value.get(0),
                                 alarm,
@@ -335,7 +332,7 @@ public static VType readValue(
                                 display);
                     }
                 }
-                if (enum_value.length() == 1) {
+                if (enum_value.size() == 1) {
                     return VEnum.of(
                             enum_value.get(0), enum_display, alarm, time);
                 } else {
@@ -366,7 +363,7 @@ display. getAlarmRange(),
                             NumberFormats.precisionFormat(0),
                             display.getDescription());
                 }
-                if (long_value.length() == 1) {
+                if (long_value.size() == 1) {
                     return VLong.of(long_value.get(0), alarm, time, display);
                 } else {
                     return VLongArray.of(
@@ -386,7 +383,7 @@ display. getAlarmRange(),
                             "Mandatory field is missing in object.",
                             parser.getTokenLocation());
                 }
-                if (double_value.length() == 1) {
+                if (double_value.size() == 1) {
                     return VStatistics.of(
                             double_value.get(0),
                             Double.NaN,
@@ -459,9 +456,10 @@ private static boolean readBooleanValue(final JsonParser parser)
         return parser.getBooleanValue();
     }
 
-    private static ImmutableDoubleArray readDoubleArray(
+    private static List<Double> readDoubleArray(
             final JsonParser parser) throws IOException {
-        final var array_builder = ImmutableDoubleArray.builder(1);
+
+        final List<Double> values = new ArrayList<>();
         var token = parser.getCurrentToken();
         if (token != JsonToken.START_ARRAY) {
             throw new JsonParseException(
@@ -477,9 +475,9 @@ private static ImmutableDoubleArray readDoubleArray(
             if (token == JsonToken.END_ARRAY) {
                 break;
             }
-            array_builder.add(readDoubleValue(parser));
+            values.add(readDoubleValue(parser));
         }
-        return array_builder.build();
+        return Collections.unmodifiableList(values);
     }
 
     private static double readDoubleValue(final JsonParser parser)
@@ -515,9 +513,9 @@ private static Instant readInstant(final JsonParser parser)
         return bigIntegerToTimestamp(parser.getBigIntegerValue());
     }
 
-    private static ImmutableIntArray readIntArray(final JsonParser parser)
+    private static List<Integer> readIntArray(final JsonParser parser)
             throws IOException {
-        final var array_builder = ImmutableIntArray.builder(1);
+        final List<Integer> values = new ArrayList<>();
         var token = parser.getCurrentToken();
         if (token != JsonToken.START_ARRAY) {
             throw new JsonParseException(
@@ -533,9 +531,9 @@ private static ImmutableIntArray readIntArray(final JsonParser parser)
             if (token == JsonToken.END_ARRAY) {
                 break;
             }
-            array_builder.add(readIntValue(parser));
+            values.add(readIntValue(parser));
         }
-        return array_builder.build();
+        return Collections.unmodifiableList(values);
     }
 
     private static int readIntValue(final JsonParser parser)
@@ -551,9 +549,9 @@ private static int readIntValue(final JsonParser parser)
         return parser.getIntValue();
     }
 
-    private static ImmutableLongArray readLongArray(final JsonParser parser)
+    private static List<Long> readLongArray(final JsonParser parser)
             throws IOException {
-        final var array_builder = ImmutableLongArray.builder(1);
+        final List<Long> values = new ArrayList<>();
         var token = parser.getCurrentToken();
         if (token != JsonToken.START_ARRAY) {
             throw new JsonParseException(
@@ -569,9 +567,9 @@ private static ImmutableLongArray readLongArray(final JsonParser parser)
             if (token == JsonToken.END_ARRAY) {
                 break;
             }
-            array_builder.add(readLongValue(parser));
+            values.add(readLongValue(parser));
         }
-        return array_builder.build();
+        return Collections.unmodifiableList(values);
     }
 
     private static long readLongValue(final JsonParser parser)
@@ -888,7 +886,7 @@ private static double stringToSpecialDouble(
         };
     }
 
-    private static ListDouble toListDouble(final ImmutableDoubleArray array) {
+    private static ListDouble toListDouble(final List<Double> array) {
         return new ListDouble() {
             @Override
             public double getDouble(int index) {
@@ -897,12 +895,12 @@ public double getDouble(int index) {
 
             @Override
             public int size() {
-                return array.length();
+                return array.size();
             }
         };
     }
 
-    private static ListInteger toListInteger(final ImmutableIntArray array) {
+    private static ListInteger toListInteger(final List<Integer> array) {
         return new ListInteger() {
             @Override
             public int getInt(int index) {
@@ -911,12 +909,12 @@ public int getInt(int index) {
 
             @Override
             public int size() {
-                return array.length();
+                return array.size();
             }
         };
     }
 
-    private static ListLong toListLong(final ImmutableLongArray array) {
+    private static ListLong toListLong(final List<Long> array) {
         return new ListLong() {
             @Override
             public long getLong(int index) {
@@ -925,7 +923,7 @@ public long getLong(int index) {
 
             @Override
             public int size() {
-                return array.length();
+                return array.size();
             }
         };
     }
diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml
index a545106292..9c9b495ce9 100644
--- a/dependencies/phoebus-target/pom.xml
+++ b/dependencies/phoebus-target/pom.xml
@@ -217,11 +217,6 @@
       <artifactId>javax.activation</artifactId>
       <version>1.2.0</version>
     </dependency>
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-      <version>${guava.version}</version>
-    </dependency>
     <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
     <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>

From 5b5ed1fd82079a9acac11d9699997cf40cff216d Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Tue, 18 Jun 2024 10:49:18 -0400
Subject: [PATCH 35/59] Fixing the unit test dependencies to guava

---
 .../reader/json/HttpServerTestBase.java       | 22 ++++++++++++-------
 1 file changed, 14 insertions(+), 8 deletions(-)

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
index afa7d7437b..aaad15aa28 100644
--- 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
@@ -8,8 +8,6 @@
 
 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;
@@ -23,10 +21,12 @@
 import java.net.URI;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * Base class for tests that need an HTTP server.
@@ -62,12 +62,18 @@ public record HttpRequest(
      */
     public static Map<String, String> parseQueryString(
             final String query_string) {
-        return Maps.transformValues(
-                Splitter
-                        .on('&')
-                        .withKeyValueSeparator('=')
-                        .split(query_string),
-                (value) -> URLDecoder.decode(value, StandardCharsets.UTF_8));
+
+        return Arrays.stream(query_string.split("&"))
+                .collect(Collectors.toMap(
+                        k -> k.split("=")[0],
+                        k -> URLDecoder.decode(k.split("=")[1], StandardCharsets.UTF_8)));
+//
+//        return Maps.transformValues(
+//                Splitter
+//                        .on('&')
+//                        .withKeyValueSeparator('=')
+//                        .split(query_string),
+//                (value) -> URLDecoder.decode(value, StandardCharsets.UTF_8));
     }
 
     /**

From c4963ae8ec9a60ab5a75e16e127b386973424108 Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Tue, 18 Jun 2024 15:15:14 -0400
Subject: [PATCH 36/59] Fix ant build broken by #3048

---
 dependencies/phoebus-target/pom.xml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml
index a545106292..099ad4d1de 100644
--- a/dependencies/phoebus-target/pom.xml
+++ b/dependencies/phoebus-target/pom.xml
@@ -472,6 +472,11 @@
       <version>${spring.boot-version}</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+       <groupId>org.springdoc</groupId>
+       <artifactId>springdoc-openapi-ui</artifactId>
+       <version>1.7.0</version>
+     </dependency>
 
     <dependency>
       <groupId>javax.validation</groupId>

From 01a260c9621bd81b15151619edae8a17a297c8b3 Mon Sep 17 00:00:00 2001
From: Evan Daykin <daykin@frib.msu.edu>
Date: Wed, 19 Jun 2024 17:50:58 -0400
Subject: [PATCH 37/59] Add a 'methodCalled' event that can be fired with
 varargs for listeners to see the call stack at the time of firing

---
 .../builder/representation/ToolkitListener.java    |  3 +++
 .../representation/ToolkitRepresentation.java      | 14 ++++++++++++++
 .../runtime/app/DisplayRuntimeInstance.java        |  2 ++
 3 files changed, 19 insertions(+)

diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java
index 90360b9007..2a0341e52b 100644
--- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java
+++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java
@@ -44,4 +44,7 @@ public interface ToolkitListener
      *  @param value The value
      */
     default public void handleWrite(Widget widget, Object value) {};
+
+    default public void handleMethodCalled(Object... user_args) {};
+
 }
diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java
index 81da7b057e..f5fc3a0fa1 100644
--- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java
+++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java
@@ -689,6 +689,20 @@ public void fireWrite(final Widget widget, final Object value)
         }
     }
 
+    public void fireMethodCall(Object... user_args) {
+        for (final ToolkitListener listener : listeners)
+        {
+            try
+            {
+                listener.handleMethodCalled(user_args);
+            }
+            catch (final Throwable ex)
+            {
+                logger.log(Level.WARNING, "Failure when firing method-call event for " + Thread.currentThread().getStackTrace()[1].getMethodName(), ex);
+            }
+        }
+    };
+
     /** Close the toolkit's "window" that displays a model
      *  @param model Model that has been represented in this toolkit
      *  @throws Exception on error
diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java
index ddca7128e8..a0887f0bd9 100644
--- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java
+++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java
@@ -294,6 +294,8 @@ public void loadDisplayFile(final DisplayInfo info)
         // another instance
         dock_item.setInput(info.toURI());
 
+        representation.fireMethodCall((Object)display_info);
+
         // Now that old model is no longer represented,
         // show info.
         // Showing this info before disposeModel()

From 84da2a3c45cd2f88890c9a411ab2fd690d679e39 Mon Sep 17 00:00:00 2001
From: Evan Daykin <daykin@frib.msu.edu>
Date: Thu, 20 Jun 2024 16:13:23 -0400
Subject: [PATCH 38/59] Only fire event after successful display load, grabbing
 application thread's stack trace

---
 .../display/builder/runtime/app/DisplayRuntimeInstance.java  | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java
index a0887f0bd9..0ce90879da 100644
--- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java
+++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java
@@ -276,7 +276,7 @@ void close()
     }
 
     /** @return Current display info or <code>null</code> */
-    DisplayInfo getDisplayInfo()
+    public DisplayInfo getDisplayInfo()
     {
         return display_info.orElse(null);
     }
@@ -294,7 +294,7 @@ public void loadDisplayFile(final DisplayInfo info)
         // another instance
         dock_item.setInput(info.toURI());
 
-        representation.fireMethodCall((Object)display_info);
+        StackTraceElement[] applicationThreadStackTrace = Thread.currentThread().getStackTrace();
 
         // Now that old model is no longer represented,
         // show info.
@@ -324,6 +324,7 @@ public void loadDisplayFile(final DisplayInfo info)
                 {
                     representation.awaitRepresentation(30, TimeUnit.SECONDS);
                     representation_init.run();
+                    representation.fireMethodCall(info, applicationThreadStackTrace);
                     logger.log(Level.FINE, "Done with representing model of " + info.getPath());
                 }
                 catch (TimeoutException | InterruptedException ex)

From 89c9591d219d2384cdcf19c57321b1f4d988e648 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Thu, 20 Jun 2024 16:43:51 -0400
Subject: [PATCH 39/59] Update the widget info to allow simpler selection of
 PV's

---
 .../javafx/WidgetInfoDialog.java              | 90 +++++++++++++++----
 .../types/TimeStampedProcessVariable.java     |  3 +
 2 files changed, 76 insertions(+), 17 deletions(-)

diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/WidgetInfoDialog.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/WidgetInfoDialog.java
index b685b56ce7..5adf641745 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/WidgetInfoDialog.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/WidgetInfoDialog.java
@@ -18,7 +18,14 @@
 import java.util.Objects;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-
+import java.util.stream.Collectors;
+
+import javafx.event.EventHandler;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.SelectionMode;
+import javafx.scene.control.cell.PropertyValueFactory;
+import javafx.scene.input.ContextMenuEvent;
 import org.csstudio.display.builder.model.DisplayModel;
 import org.csstudio.display.builder.model.Widget;
 import org.csstudio.display.builder.model.WidgetDescriptor;
@@ -31,8 +38,12 @@
 import org.epics.vtype.AlarmSeverity;
 import org.epics.vtype.VNumberArray;
 import org.epics.vtype.VType;
+import org.phoebus.core.types.ProcessVariable;
 import org.phoebus.core.vtypes.VTypeHelper;
+import org.phoebus.framework.adapter.AdapterService;
 import org.phoebus.framework.macros.Macros;
+import org.phoebus.framework.selection.SelectionService;
+import org.phoebus.ui.application.ContextMenuService;
 import org.phoebus.ui.dialog.DialogHelper;
 import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
 import org.phoebus.ui.javafx.ReadOnlyTextCell;
@@ -61,6 +72,7 @@
 import javafx.scene.layout.Priority;
 import javafx.scene.layout.VBox;
 import javafx.stage.FileChooser;
+import org.phoebus.ui.spi.ContextMenuEntry;
 
 /** Dialog for displaying widget information
  *  @author Kay Kasemir
@@ -75,16 +87,14 @@ public class WidgetInfoDialog extends Dialog<Boolean>
     private DisplayWidgetStats stats;
 
     /** PV info */
-    public static class NameStateValue
+    public static class NameStateValue extends ProcessVariable
     {
-        /** PV Name */
-        public final String name;
         /** State, incl. read-only or writable? */
-        public final String state;
+        private final String state;
         /** Last known value */
-        public final VType value;
+        private final VType value;
         /** Path to Widget within display that uses the PV */
-        public final String path;
+        private final String path;
 
         /** @param name PV Name
          *  @param state State, incl. read-only or writable?
@@ -93,11 +103,23 @@ public static class NameStateValue
          */
         public NameStateValue(final String name, final String state, final VType value, final String path)
         {
-            this.name = name;
+            super(name);
             this.state = state;
             this.value = value;
             this.path = path;
         }
+
+        public String getState() {
+            return state;
+        }
+
+        public VType getValue() {
+            return value;
+        }
+
+        public String getPath() {
+            return path;
+        }
     }
 
     /** Cell with text colored based on alarm severity */
@@ -209,8 +231,8 @@ private void exportToCSV(File file){
 
         buffer.append("PVS (name, state, value, widget path)").append(System.lineSeparator())
                 .append(horizontalRuler).append(System.lineSeparator());
-        pvs.stream().sorted(Comparator.comparing(pv -> pv.name)).forEach(pv -> {
-            buffer.append(pv.name).append(itemSeparator)
+        pvs.stream().sorted(Comparator.comparing(pv -> pv.getName())).forEach(pv -> {
+            buffer.append(pv.getName()).append(itemSeparator)
                     .append(pv.state)
                     .append(itemSeparator)
                     .append(getPVValue(pv.value))
@@ -292,16 +314,13 @@ private Tab createPVs(final Collection<NameStateValue> pvs)
     {
         // Use text field to allow users to copy the name, value to clipboard
         final TableColumn<NameStateValue, String> name = new TableColumn<>(Messages.WidgetInfoDialog_Name);
-        name.setCellFactory(col -> new ReadOnlyTextCell<>());
-        name.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue().name));
+        name.setCellValueFactory(new PropertyValueFactory<NameStateValue, String>("name"));
 
         final TableColumn<NameStateValue, String> state = new TableColumn<>(Messages.WidgetInfoDialog_State);
-        state.setCellFactory(col -> new ReadOnlyTextCell<>());
-        state.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue().state));
+        state.setCellValueFactory(new PropertyValueFactory<NameStateValue, String>("state"));
 
         final TableColumn<NameStateValue, String> path = new TableColumn<>(Messages.WidgetInfoDialog_Path);
-        path.setCellFactory(col -> new ReadOnlyTextCell<>());
-        path.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue().path));
+        path.setCellValueFactory(new PropertyValueFactory<NameStateValue, String>("path"));
 
         final TableColumn<NameStateValue, String> value = new TableColumn<>(Messages.WidgetInfoDialog_Value);
         value.setCellFactory(col -> new AlarmColoredCell());
@@ -312,7 +331,7 @@ private Tab createPVs(final Collection<NameStateValue> pvs)
         });
 
         final ObservableList<NameStateValue> pv_data = FXCollections.observableArrayList(pvs);
-        pv_data.sort(Comparator.comparing(a -> a.name));
+        pv_data.sort(Comparator.comparing(a -> a.getName()));
         final TableView<NameStateValue> table = new TableView<>(pv_data);
         table.getColumns().add(name);
         table.getColumns().add(state);
@@ -320,6 +339,43 @@ private Tab createPVs(final Collection<NameStateValue> pvs)
         table.getColumns().add(path);
         table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
 
+        table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
+        table.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
+            if (newSelection != null) {
+                SelectionService.getInstance().setSelection(table, table.getSelectionModel().getSelectedItems());
+            }
+        });
+
+        table.setOnContextMenuRequested(event -> {
+
+            final ContextMenu contextMenu = new ContextMenu();
+
+            List<ContextMenuEntry> contextEntries = ContextMenuService.getInstance().listSupportedContextMenuEntries();
+
+            contextEntries.forEach(entry -> {
+                MenuItem item = new MenuItem(entry.getName(), new ImageView(entry.getIcon()));
+                item.setOnAction(e -> {
+                    try {
+                        ObservableList<NameStateValue> old = table.getSelectionModel().getSelectedItems();
+
+                        List<Object> selectedPVs = SelectionService.getInstance().getSelection().getSelections().stream().map(s -> {
+                            return AdapterService.adapt(s, entry.getSupportedType()).get();
+                        }).collect(Collectors.toList());
+                        // set the selection
+                        SelectionService.getInstance().setSelection(table, selectedPVs);
+                        entry.call(SelectionService.getInstance().getSelection());
+                        // reset the selection
+                        SelectionService.getInstance().setSelection(table, old);
+                    } catch (Exception ex) {
+                        //logger.log(Level.WARNING, "Failed to execute action " + entry.getName(), ex);
+                    }
+                });
+                contextMenu.getItems().add(item);
+            });
+
+            table.setContextMenu(contextMenu);
+        });
+
         return new Tab(Messages.WidgetInfoDialog_TabPVs, table);
     }
 
diff --git a/core/types/src/main/java/org/phoebus/core/types/TimeStampedProcessVariable.java b/core/types/src/main/java/org/phoebus/core/types/TimeStampedProcessVariable.java
index f407f7558d..87566c421f 100644
--- a/core/types/src/main/java/org/phoebus/core/types/TimeStampedProcessVariable.java
+++ b/core/types/src/main/java/org/phoebus/core/types/TimeStampedProcessVariable.java
@@ -2,6 +2,9 @@
 
 import java.time.Instant;
 
+/**
+ * A PV with a Timestamp
+ */
 @SuppressWarnings("nls")
 public class TimeStampedProcessVariable extends ProcessVariable {
 

From c76143d38ccf2b205ba473456695de79969a8a99 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Fri, 21 Jun 2024 13:04:58 -0400
Subject: [PATCH 40/59] Remove unused Executor

---
 .../org/phoebus/pv/archive/retrieve/ArchivePV.java     | 10 ----------
 1 file changed, 10 deletions(-)

diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
index 19e4a83c85..b10e9e4c5d 100644
--- a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
@@ -17,16 +17,6 @@ public class ArchivePV extends PV {
 
     ArchiveReaderService service = ArchiveReaderService.getService();
 
-    /**
-     * Timer for archive updates
-     */
-    private final static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, target ->
-    {
-        final Thread thread = new Thread(target, "ArchivePV");
-        thread.setDaemon(true);
-        return thread;
-    });
-
     public ArchivePV(String name) {
         this(name, Instant.now());
     }

From 9416075617cb0c88def66766daa75776d3b8521d Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 24 Jun 2024 14:47:30 -0400
Subject: [PATCH 41/59] Use the complete name to ensure PVPool create proper
 references

---
 .../pv/archive/retrieve/ArchivePV.java        | 19 ++++++++---
 .../pv/archive/retrieve/ArchivePVFactory.java | 32 ++++++++++++++-----
 2 files changed, 39 insertions(+), 12 deletions(-)

diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
index b10e9e4c5d..6540be32d7 100644
--- a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
@@ -17,12 +17,23 @@ public class ArchivePV extends PV {
 
     ArchiveReaderService service = ArchiveReaderService.getService();
 
-    public ArchivePV(String name) {
-        this(name, Instant.now());
+    /**
+     *
+     * @param completeName
+     * @param name
+     */
+    public ArchivePV(String completeName, String name) {
+        this(completeName, name, Instant.now());
     }
 
-    public ArchivePV(String name, Instant instant) {
-        super(name);
+    /**
+     *
+     * @param completeName
+     * @param name
+     * @param instant
+     */
+    public ArchivePV(String completeName, String name, Instant instant) {
+        super(completeName);
         try {
             ValueIterator i = service.getReader().getRawValues(name, instant, instant);
 
diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java
index 23ed1ca49b..d5083f1736 100644
--- a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java
@@ -3,8 +3,12 @@
 import org.phoebus.pv.PV;
 import org.phoebus.pv.PVFactory;
 
+import java.time.DateTimeException;
 import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
 import java.util.logging.Logger;
+import java.util.stream.Collectors;
 
 import static org.phoebus.util.time.TimestampFormats.DATETIME_FORMAT;
 import static org.phoebus.util.time.TimestampFormats.SECONDS_FORMAT;
@@ -46,17 +50,29 @@ public PV createPV(String name, String base_name) throws Exception {
         }
 
         if(parameters.isEmpty()) {
-            return new ArchivePV(pvName);
+            return new ArchivePV(name, pvName);
         } else {
             Instant time;
-            switch (parameters.length()) {
-                case 16 -> time = Instant.from(DATETIME_FORMAT.parse(parameters));
-                case 19 -> time = Instant.from(SECONDS_FORMAT.parse(parameters));
-                case 23 -> time = Instant.from(MILLI_FORMAT.parse(parameters));
-                case 29 -> time = Instant.from(FULL_FORMAT.parse(parameters));
-                default -> throw new Exception("Time value defined in a unknown formatt, '" + parameters + "'");
+            List<String> parameterList = Arrays.stream(parameters.split(","))
+                                                .map(String::strip)
+                                                .collect(Collectors.toList());
+            if(parameterList.size() == 1) {
+                String timeParameter = parameterList.get(0);
+                try {
+                    switch (parameterList.get(0).length()) {
+                        case 16 -> time = Instant.from(DATETIME_FORMAT.parse(timeParameter));
+                        case 19 -> time = Instant.from(SECONDS_FORMAT.parse(timeParameter));
+                        case 23 -> time = Instant.from(MILLI_FORMAT.parse(timeParameter));
+                        case 29 -> time = Instant.from(FULL_FORMAT.parse(timeParameter));
+                        default -> throw new Exception("Time value defined in a unknown format, '" + timeParameter + "'");
+                    }
+                    return new ArchivePV(name, pvName, time);
+                } catch (DateTimeException e) {
+                    throw new Exception("Time value defined in a unknown format, '" + timeParameter + "'");
+                }
+            } else {
+                throw new Exception("Incorrect number of parameters defined '" + name + "'");
             }
-            return new ArchivePV(pvName, time);
         }
     }
 }

From 999739d7b8c5376382114276713d9e71939db9c8 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 24 Jun 2024 14:47:59 -0400
Subject: [PATCH 42/59] Basic "replay" archive datasource

---
 app/trends/archive-datasource/pom.xml         |  2 +-
 .../phoebus/pv/archive/replay/ReplayPV.java   | 93 +++++++++++++++++++
 .../pv/archive/replay/ReplayPVFactory.java    | 64 +++++++++++++
 .../services/org.phoebus.pv.PVFactory         |  3 +-
 4 files changed, 160 insertions(+), 2 deletions(-)
 create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java
 create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java

diff --git a/app/trends/archive-datasource/pom.xml b/app/trends/archive-datasource/pom.xml
index 6bda3f309e..988864edd9 100644
--- a/app/trends/archive-datasource/pom.xml
+++ b/app/trends/archive-datasource/pom.xml
@@ -18,7 +18,7 @@
     <dependencies>
         <dependency>
             <groupId>org.phoebus</groupId>
-            <artifactId>app-databrowser</artifactId>
+            <artifactId>app-trends-archive-reader</artifactId>
             <version>4.7.4-SNAPSHOT</version>
         </dependency>
     </dependencies>
diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java
new file mode 100644
index 0000000000..a69298c5fd
--- /dev/null
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java
@@ -0,0 +1,93 @@
+package org.phoebus.pv.archive.replay;
+
+import org.phoebus.archive.reader.ValueIterator;
+import org.phoebus.pv.PV;
+import org.phoebus.pv.archive.ArchiveReaderService;
+
+import java.time.Instant;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+
+/** Base for replay PVs
+ *
+ *  @author Kunal Shroff, Kay Kasemir, based on similar code in org.csstudio.utility.pv and diirt
+ */
+@SuppressWarnings("nls")
+public class ReplayPV extends PV
+{
+    ArchiveReaderService service = ArchiveReaderService.getService();
+
+    /** Timer for replaying updates */
+    private final static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, target ->
+    {
+        final Thread thread = new Thread(target, "ReplayPV");
+        thread.setDaemon(true);
+        return thread;
+    });
+
+    /** Task that was submitted for periodic updates */
+    private ScheduledFuture<?> task;
+
+    private ValueIterator i;
+
+    /**
+     * 
+     * @param completeName
+     * @param name
+     * @param start
+     * @param end
+     */
+    public ReplayPV(String completeName, final String name, Instant start, Instant end)
+    {
+        super(completeName);
+
+        // ReplayPV PVs are read-only
+        notifyListenersOfPermissions(true);
+
+        try {
+            i = service.getReader().getRawValues(name, start, end);
+
+            if (i.hasNext()) {
+                notifyListenersOfValue(i.next());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        start(.01);
+    }
+
+    /** Start periodic updates
+     *  @param update_seconds Update period in seconds
+     */
+    protected void start(final double update_seconds)
+    {
+        // Limit rate to 100 Hz
+        final long milli = Math.round(Math.max(update_seconds, 0.01) * 1000);
+        task = executor.scheduleAtFixedRate(this::update, milli, milli, TimeUnit.MILLISECONDS);
+    }
+
+    /** Called by periodic timer */
+    protected void update() {
+        try {
+            if (i.hasNext()) {
+                notifyListenersOfValue(i.next());
+            } else {
+                close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            close();
+        }
+    }
+
+    @Override
+    protected void close()
+    {
+        if (! task.cancel(false))
+            logger.log(Level.WARNING, "Cannot cancel updates for " + getName());
+        super.close();
+    }
+}
diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java
new file mode 100644
index 0000000000..a979534525
--- /dev/null
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java
@@ -0,0 +1,64 @@
+package org.phoebus.pv.archive.replay;
+
+import org.phoebus.pv.PV;
+import org.phoebus.pv.PVFactory;
+import org.phoebus.pv.archive.retrieve.ArchivePVFactory;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * A datasource for the replaying archived PV's
+ * @author Kunal Shroff
+ */
+public class ReplayPVFactory implements PVFactory
+{
+    final public static Logger logger = Logger.getLogger(ReplayPVFactory.class.getName());
+    final public static String TYPE = "replay";
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+
+    @Override
+    public PV createPV(String name, String base_name) throws Exception {
+        // Determine simulation function name and (optional) parameters
+        final String pvName;
+        int sep = base_name.indexOf('(');
+        if (sep < 0)
+        {
+            pvName = base_name;
+            return new ReplayPV(name, pvName, Instant.now().minusSeconds(300), Instant.now());
+        }
+        else
+        {
+            final int end = base_name.lastIndexOf(')');
+            if (end < 0)
+                throw new Exception("Missing closing bracket for parameters in '" + name + "'");
+            pvName = base_name.substring(0, sep);
+            final List<String> parameters = Arrays
+                    .stream(base_name.substring(sep+1, end).split(","))
+                    .map(String::strip)
+                    .collect(Collectors.toList());
+            if (parameters.size() == 2) {
+                // start and end
+
+
+            } else if (parameters.size() == 3 ) {
+                // start, end, and rate
+
+            } else {
+                throw new Exception("Incorrect number of parameters defined," + "'" + name + "'" +
+                        " the replay datasource supports start, end, and optionally a rate parameter.");
+
+            }
+        }
+
+        return new ReplayPV(name, pvName, Instant.now(), Instant.now().minusSeconds(300));
+    }
+}
diff --git a/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory b/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory
index 63ecd477bd..72db0eb20d 100644
--- a/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory
+++ b/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory
@@ -1 +1,2 @@
-org.phoebus.pv.archive.retrieve.ArchivePVFactory
\ No newline at end of file
+org.phoebus.pv.archive.retrieve.ArchivePVFactory
+org.phoebus.pv.archive.replay.ReplayPVFactory
\ No newline at end of file

From 82f93bf7498feaeeee14d367be85ab8ef649d797 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 24 Jun 2024 16:48:24 -0400
Subject: [PATCH 43/59] Argument parsing for replay archive datasource

---
 .../phoebus/pv/archive/ArchiveReaderUtil.java | 40 +++++++++++++++++++
 .../phoebus/pv/archive/replay/ReplayPV.java   | 22 ++++++++--
 .../pv/archive/replay/ReplayPVFactory.java    | 18 +++++++--
 3 files changed, 72 insertions(+), 8 deletions(-)
 create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderUtil.java

diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderUtil.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderUtil.java
new file mode 100644
index 0000000000..07fa49a8c9
--- /dev/null
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderUtil.java
@@ -0,0 +1,40 @@
+package org.phoebus.pv.archive;
+
+import java.time.DateTimeException;
+import java.time.Instant;
+
+import static org.phoebus.util.time.TimestampFormats.DATETIME_FORMAT;
+import static org.phoebus.util.time.TimestampFormats.FULL_FORMAT;
+import static org.phoebus.util.time.TimestampFormats.MILLI_FORMAT;
+import static org.phoebus.util.time.TimestampFormats.SECONDS_FORMAT;
+
+public class ArchiveReaderUtil {
+
+    /**
+     * A utility method to parse a subset of supported time formats used by the archive datasources
+     * Support formats include
+     *     FULL_PATTERN = "yyyy-MM-dd HH:mm:ss.nnnnnnnnn";
+     *     MILLI_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS";
+     *     SECONDS_PATTERN = "yyyy-MM-dd HH:mm:ss";
+     *     DATETIME_PATTERN = "yyyy-MM-dd HH:mm";
+     *
+     * @param timeValue
+     * @return
+     */
+    public static Instant parseSupportedTimeFormat(String timeValue) throws Exception {
+        Instant time;
+        try {
+            switch (timeValue.length()) {
+                case 16 -> time = Instant.from(DATETIME_FORMAT.parse(timeValue));
+                case 19 -> time = Instant.from(SECONDS_FORMAT.parse(timeValue));
+                case 23 -> time = Instant.from(MILLI_FORMAT.parse(timeValue));
+                case 29 -> time = Instant.from(FULL_FORMAT.parse(timeValue));
+                default -> throw new Exception("Time value defined in a unknown format, '" + timeValue + "'");
+            }
+        } catch (
+                DateTimeException e) {
+            throw new Exception("Time value defined in a unknown format, '" + timeValue + "'");
+        }
+        return time;
+    }
+}
diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java
index a69298c5fd..1ca5704d6a 100644
--- a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java
@@ -34,13 +34,26 @@ public class ReplayPV extends PV
     private ValueIterator i;
 
     /**
-     * 
+     *
      * @param completeName
      * @param name
      * @param start
      * @param end
      */
     public ReplayPV(String completeName, final String name, Instant start, Instant end)
+    {
+        this(completeName, name, start, end, .1);
+    }
+
+    /**
+     *
+     * @param completeName
+     * @param name
+     * @param start
+     * @param end
+     * @param update
+     */
+    public ReplayPV(String completeName, final String name, Instant start, Instant end, double update)
     {
         super(completeName);
 
@@ -56,7 +69,7 @@ public ReplayPV(String completeName, final String name, Instant start, Instant e
         } catch (Exception e) {
             e.printStackTrace();
         }
-        start(.01);
+        start(update);
     }
 
     /** Start periodic updates
@@ -70,7 +83,8 @@ protected void start(final double update_seconds)
     }
 
     /** Called by periodic timer */
-    protected void update() {
+    protected void update()
+    {
         try {
             if (i.hasNext()) {
                 notifyListenersOfValue(i.next());
@@ -86,7 +100,7 @@ protected void update() {
     @Override
     protected void close()
     {
-        if (! task.cancel(false))
+        if (!task.isDone() && !task.cancel(false))
             logger.log(Level.WARNING, "Cannot cancel updates for " + getName());
         super.close();
     }
diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java
index a979534525..b436feb776 100644
--- a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java
@@ -11,6 +11,8 @@
 import java.util.logging.Logger;
 import java.util.stream.Collectors;
 
+import static org.phoebus.pv.archive.ArchiveReaderUtil.parseSupportedTimeFormat;
+
 /**
  * A datasource for the replaying archived PV's
  * @author Kunal Shroff
@@ -45,20 +47,28 @@ public PV createPV(String name, String base_name) throws Exception {
                     .stream(base_name.substring(sep+1, end).split(","))
                     .map(String::strip)
                     .collect(Collectors.toList());
+
+            Instant startTime;
+            Instant endTime;
             if (parameters.size() == 2) {
                 // start and end
-
+                startTime = parseSupportedTimeFormat(parameters.get(0));
+                endTime = parseSupportedTimeFormat(parameters.get(1));
+                return new ReplayPV(name, pvName, startTime, endTime);
 
             } else if (parameters.size() == 3 ) {
                 // start, end, and rate
+                startTime = parseSupportedTimeFormat(parameters.get(0));
+                endTime = parseSupportedTimeFormat(parameters.get(1));
+                double rate = Double.parseDouble(parameters.get(2));
+
+                return new ReplayPV(name, pvName, startTime, endTime, rate);
 
             } else {
                 throw new Exception("Incorrect number of parameters defined," + "'" + name + "'" +
                         " the replay datasource supports start, end, and optionally a rate parameter.");
-
             }
-        }
 
-        return new ReplayPV(name, pvName, Instant.now(), Instant.now().minusSeconds(300));
+        }
     }
 }

From aeaf7a7fedbd2c89065ba5c4b6d83a5988687e5d Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Mon, 24 Jun 2024 16:52:59 -0400
Subject: [PATCH 44/59] Fix some formatting and logging ( more cleanup might be
 req )

---
 .../phoebus/pv/archive/replay/ReplayPV.java    |  2 +-
 .../pv/archive/replay/ReplayPVFactory.java     |  6 ++++--
 .../phoebus/pv/archive/retrieve/ArchivePV.java | 18 ++++++++++++------
 .../pv/archive/retrieve/ArchivePVFactory.java  | 17 ++++++++++++-----
 4 files changed, 29 insertions(+), 14 deletions(-)

diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java
index 1ca5704d6a..c071f96667 100644
--- a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java
@@ -92,7 +92,7 @@ protected void update()
                 close();
             }
         } catch (Exception e) {
-            e.printStackTrace();
+            logger.log(Level.WARNING, "failed to update pv: " + getName() , e);
             close();
         }
     }
diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java
index b436feb776..df177b2fa9 100644
--- a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java
@@ -23,12 +23,14 @@ public class ReplayPVFactory implements PVFactory
     final public static String TYPE = "replay";
 
     @Override
-    public String getType() {
+    public String getType()
+    {
         return TYPE;
     }
 
     @Override
-    public PV createPV(String name, String base_name) throws Exception {
+    public PV createPV(String name, String base_name) throws Exception
+    {
         // Determine simulation function name and (optional) parameters
         final String pvName;
         int sep = base_name.indexOf('(');
diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
index 6540be32d7..32617fb8d3 100644
--- a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java
@@ -7,6 +7,7 @@
 import java.time.Instant;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.logging.Level;
 
 /**
  * A Connection to a PV in the archiver
@@ -22,7 +23,8 @@ public class ArchivePV extends PV {
      * @param completeName
      * @param name
      */
-    public ArchivePV(String completeName, String name) {
+    public ArchivePV(String completeName, String name)
+    {
         this(completeName, name, Instant.now());
     }
 
@@ -32,7 +34,8 @@ public ArchivePV(String completeName, String name) {
      * @param name
      * @param instant
      */
-    public ArchivePV(String completeName, String name, Instant instant) {
+    public ArchivePV(String completeName, String name, Instant instant)
+    {
         super(completeName);
         try {
             ValueIterator i = service.getReader().getRawValues(name, instant, instant);
@@ -41,21 +44,24 @@ public ArchivePV(String completeName, String name, Instant instant) {
                 notifyListenersOfValue(i.next());
             }
         } catch (Exception e) {
-            e.printStackTrace();
+            logger.log(Level.WARNING, "failed to create pv: " + getName() , e);
         }
     }
 
-    public void disconnected() {
+    public void disconnected()
+    {
         notifyListenersOfDisconnect();
     }
 
     @Override
-    protected void close() {
+    protected void close()
+    {
         super.close();
     }
 
     @Override
-    public boolean isReadonly() {
+    public boolean isReadonly()
+    {
         return true;
     }
 
diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java
index d5083f1736..ea819e6278 100644
--- a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java
+++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java
@@ -26,7 +26,8 @@ public class ArchivePVFactory implements PVFactory
     final public static String TYPE = "archive";
 
     @Override
-    public String getType() {
+    public String getType()
+    {
         return TYPE;
     }
 
@@ -49,14 +50,18 @@ public PV createPV(String name, String base_name) throws Exception {
             parameters = base_name.substring(sep+1, end);
         }
 
-        if(parameters.isEmpty()) {
+        if(parameters.isEmpty())
+        {
             return new ArchivePV(name, pvName);
-        } else {
+        }
+        else
+        {
             Instant time;
             List<String> parameterList = Arrays.stream(parameters.split(","))
                                                 .map(String::strip)
                                                 .collect(Collectors.toList());
-            if(parameterList.size() == 1) {
+            if(parameterList.size() == 1)
+            {
                 String timeParameter = parameterList.get(0);
                 try {
                     switch (parameterList.get(0).length()) {
@@ -70,7 +75,9 @@ public PV createPV(String name, String base_name) throws Exception {
                 } catch (DateTimeException e) {
                     throw new Exception("Time value defined in a unknown format, '" + timeParameter + "'");
                 }
-            } else {
+            }
+            else
+            {
                 throw new Exception("Incorrect number of parameters defined '" + name + "'");
             }
         }

From 14bd26c8c1545fc6fd412f65f68a2b833145ee4e Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Tue, 25 Jun 2024 11:32:08 -0400
Subject: [PATCH 45/59] Adding socumentation for the archive datasources

---
 app/trends/archive-datasource/doc/index.rst | 30 +++++++++++++--------
 1 file changed, 19 insertions(+), 11 deletions(-)

diff --git a/app/trends/archive-datasource/doc/index.rst b/app/trends/archive-datasource/doc/index.rst
index 3e11c6ac3b..5d63610b6b 100644
--- a/app/trends/archive-datasource/doc/index.rst
+++ b/app/trends/archive-datasource/doc/index.rst
@@ -1,21 +1,29 @@
-Archive Datasource
-==================
+Archive Datasources
+===================
 
 Overview
 --------
-The archive datasource allows accessing historical data as a pv
+The archive datasources allow accessing historical data as a PV. There are two types of archive datasources:
 
+1. `archive`: Retrieves a single archived value at a particular instant of time.
+2. `replay`: Creates a PV that recreates changes in values based on data from the archive.
 
-PV syntax
----------
+Archive PV Syntax
+-----------------
 
-The standard prefix for the datasource is ``archive://`` which can be omitted if configured as the default datasource.
-The archiver PV's are readonly and constants.
+The prefix for the datasource is ``archive://``, which can be omitted if configured as the default datasource.
+The archiver PVs are read-only and constant.
 
-archive://pv_name
+- `archive://pv_name`: Retrieves the latest value in the archiver.
+- `archive://pv_name(time)`: Retrieves the last value at or before the specified "time".
 
-Retrieves the latest value in the archiver
+Replay PV Syntax
+----------------
 
-archive://pv_name(time)
+The prefix for this datasource is ``replay://``.
+The replay PVs are read-only and constant.
+
+- `replay://pv_name`: Retrieves the last 5 minutes of data for this PV from the archiver and replays them at 10Hz.
+- `replay://pv_name(start, end)`: Recreates the PV value changes using the data from the archiver between the specific start and end times.
+- `replay://pv_name(start, end, update_rate)`: Recreates the PV value changes using the data from the archiver between the specified start and end times. Updates occur at the rate specified by `update_rate` (a value defined in seconds).
 
-Retrieves the last value at or before the "time"

From e6db4fd62ab76522eb3b0c4de1f01cdf6a3a4248 Mon Sep 17 00:00:00 2001
From: Lars Johansson <lars.johansson@ess.eu>
Date: Wed, 26 Jun 2024 11:23:52 +0200
Subject: [PATCH 46/59] Drill down to alarm sub tree displays from alarm area
 panel

Add query parameter to alarm URI.
---
 .../applications/alarm/ui/AlarmURI.java       |  39 ++++-
 .../alarm/ui/area/AlarmAreaView.java          |  34 ++--
 .../alarm/ui/area/OpenTreeViewAction.java     |   6 +-
 .../alarm/ui/tree/AlarmTreeInstance.java      |  19 ++-
 .../alarm/ui/tree/AlarmTreeView.java          |  17 +-
 .../applications/alarm/AlarmURITest.java      | 146 ++++++++++++++++--
 6 files changed, 221 insertions(+), 40 deletions(-)

diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java
index 3441135480..bffe9557a5 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java
@@ -23,6 +23,9 @@ public class AlarmURI
     /** URI schema used to refer to an alarm config */
     public static final String SCHEMA = "alarm";
 
+    public static final String DELIMITER_QUERY_PARAMETERS = "&";
+    public static final String DELIMITER_QUERY_PARAMETER_VALUE = "=";
+
     /** @param server Kafka server host:port
      *  @param config_name Alarm configuration root
      *  @return URI used to access that alarm configuration, "alarm://host:port/config_name"
@@ -32,9 +35,18 @@ public static URI createURI(final String server, final String config_name)
         return URI.create(SCHEMA + "://" + server + "/" + config_name);
     }
 
+    /** @param server Kafka server host:port
+     *  @param config_name Alarm configuration root
+     *  @param rawQuery raw query for URI
+     *  @return URI used to access that alarm configuration, "alarm://host:port/config_name"
+     */
+    public static URI createURI(final String server, final String config_name, String rawQuery) {
+        return URI.create(SCHEMA + "://" + server + "/" + config_name + "?" + rawQuery);
+    }
+
     /** Parse alarm configuration parameters from URI
-     *  @param resource "alarm://localhost:9092/Accelerator"
-     *  @return [ "localhost:9092", "Accelerator" ]
+     *  @param resource "alarm://localhost:9092/Accelerator" or "alarm://localhost:9092/Accelerator?param=value"
+     *  @return ["localhost:9092", "Accelerator", null] or ["localhost:9092", "Accelerator", "param=value"]
      *  @throws Exception on error
      */
     public static String[] parseAlarmURI(final URI resource) throws Exception
@@ -55,10 +67,31 @@ public static String[] parseAlarmURI(final URI resource) throws Exception
         if (port < 0)
             port = 9092;
         String config_name = resource.getPath();
+        String rawQuery = resource.getRawQuery();
         if (config_name.startsWith("/"))
             config_name = config_name.substring(1);
         if (config_name.isEmpty())
             throw new Exception("Missing alarm config name in " + resource + ", expecting " + SCHEMA + "://{host}:{port}/{config_name}");
-        return new String[] { resource.getHost() + ":" + port, config_name };
+        return new String[] { resource.getHost() + ":" + port, config_name, rawQuery };
     }
+
+    /**
+     * Extract raw query parameter value for given parameter.
+     * @param resource "alarm://localhost:9092/Accelerator" or "alarm://localhost:9092/Accelerator?param=value"
+     * @param queryParameter name of query parameter for which to extract value
+     * @return parameter value, null or "value" for examples above
+     */
+    public static String getRawQueryParameterValue(URI resource, String queryParameter) {
+        String[] queryParametersValues = resource.getRawQuery() != null ? resource.getRawQuery().split(AlarmURI.DELIMITER_QUERY_PARAMETERS) : null;
+        if (queryParametersValues != null) {
+            for (String queryParameterValue : queryParametersValues) {
+                if (queryParameterValue.startsWith(queryParameter)) {
+                    String[] parameterValue = queryParameterValue.split(AlarmURI.DELIMITER_QUERY_PARAMETER_VALUE);
+                    return parameterValue != null && parameterValue.length == 2 ? parameterValue[1] : null;
+                }
+            }
+        }
+        return null;
+    }
+
 }
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java
index 6bc83d1a10..32b5f96b92 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java
@@ -9,6 +9,8 @@
 
 import static org.phoebus.applications.alarm.AlarmSystem.logger;
 
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -35,12 +37,10 @@
 import org.phoebus.ui.javafx.UpdateThrottle;
 
 import javafx.application.Platform;
-import javafx.collections.ObservableList;
 import javafx.geometry.Insets;
 import javafx.geometry.Pos;
 import javafx.scene.control.ContextMenu;
 import javafx.scene.control.Label;
-import javafx.scene.control.MenuItem;
 import javafx.scene.paint.Color;
 import javafx.scene.shape.StrokeLineCap;
 import javafx.scene.shape.StrokeLineJoin;
@@ -104,8 +104,6 @@ public AlarmAreaView(final AlarmClient model)
 
         areaFilter = new AreaFilter(AlarmSystem.alarm_area_level);
         model.addListener(this);
-
-        createContextMenu();
     }
 
     // AlarmClientModelListener
@@ -205,6 +203,23 @@ private void recreateItems(final List<String> items)
         for (String item_name : items)
         {
             final Label view_item = newAreaLabel(item_name);
+
+            // context menu content for alarm model item instead of alarm area
+            // link to item in tree view
+            view_item.setOnContextMenuRequested(event -> {
+                // need to clear and repopulate context menu since alarm area is recreated multiple times
+                // (but number of times unknown)
+                for (int i=menu.getItems().size()-1; i>0; i--) {
+                    if (menu.getItems().get(i).getClass().equals(OpenTreeViewAction.class)) {
+                        menu.getItems().remove(i);
+                    }
+                }
+
+                OpenTreeViewAction otva = new OpenTreeViewAction(alarmConfigName, "itemName=" + URLEncoder.encode(item_name, StandardCharsets.UTF_8));
+                menu.getItems().add(otva);
+                menu.show(this.getScene().getWindow(), event.getScreenX(), event.getScreenY());
+            });
+
             itemViewMap.put(item_name, view_item);
             updateItem(item_name);
             final int column = index % AlarmSystem.alarm_area_column_count;
@@ -241,19 +256,10 @@ private void updateItem(final String item_name)
         view_item.setStyle("-fx-alignment: center; -fx-border-color: black; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-insets: 1; -fx-background-radius: 10; -fx-text-fill: " + JFXUtil.webRGB(AlarmUI.getAlarmAreaPanelColor(severityLevel)) + ";  -fx-background-color: " + JFXUtil.webRGB(AlarmUI.getAlarmAreaPanelBackgroundColor(severityLevel)));
     }
 
-    private void createContextMenu()
-    {
-        final ObservableList<MenuItem> menu_items = menu.getItems();
-
-        menu_items.add(new OpenTreeViewAction(alarmConfigName));
-        this.setOnContextMenuRequested(event ->
-            menu.show(this.getScene().getWindow(), event.getScreenX(), event.getScreenY())
-        );
-    }
-
     /** @return Context menu */
     public ContextMenu getMenu()
     {
         return menu;
     }
+
 }
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java
index fd99a80747..c0d7440937 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java
@@ -26,11 +26,12 @@ public class OpenTreeViewAction extends MenuItem
     /**
      * Constructor
      * @param alarmConfigName The alarm configuration name
+     * @param alarmRawQuery raw query for alarm (null if no such information is available)
      */
-    public OpenTreeViewAction(String alarmConfigName)
+    public OpenTreeViewAction(String alarmConfigName, String alarmRawQuery)
     {
         final AlarmTreeMenuEntry entry = new AlarmTreeMenuEntry();
-        entry.setResource(AlarmURI.createURI(AlarmSystem.server, alarmConfigName));
+        entry.setResource(AlarmURI.createURI(AlarmSystem.server, alarmConfigName, alarmRawQuery));
         setText(entry.getName());
         setGraphic(new ImageView(entry.getIcon()));
         setOnAction(event ->
@@ -45,4 +46,5 @@ public OpenTreeViewAction(String alarmConfigName)
             }
         });
     }
+
 }
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java
index a05ee7bb6a..a4e7a71720 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java
@@ -10,6 +10,8 @@
 import static org.phoebus.applications.alarm.AlarmSystem.logger;
 
 import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.CompletableFuture;
 import java.util.logging.Level;
 
@@ -32,6 +34,8 @@
 @SuppressWarnings("nls")
 class AlarmTreeInstance implements AppInstance
 {
+    public static final String ITEM_NAME = "itemName";
+
     private final AlarmTreeApplication app;
 
     private String server = null, config_name = null;
@@ -46,7 +50,12 @@ public AlarmTreeInstance(final AlarmTreeApplication app, final URI input) throws
     {
         this.app = app;
 
-        tab = new DockItemWithInput(this, create(input), input, null, null);
+        // split input with/without (raw) query
+        final URI resource = new URI(input.getScheme(), input.getUserInfo(), input.getHost(), input.getPort(), input.getPath(), null, null);
+        String itemName = AlarmURI.getRawQueryParameterValue(input, ITEM_NAME);
+        itemName = itemName != null ? URLDecoder.decode(itemName, StandardCharsets.UTF_8) : null;
+
+        tab = new DockItemWithInput(this, create(resource, itemName), resource, null, null);
         Platform.runLater(() -> tab.setLabel(config_name + " " + app.getDisplayName()));
         tab.addCloseCheck(() ->
         {
@@ -65,10 +74,11 @@ public AppDescriptor getAppDescriptor()
     /** Create UI for input, starts alarm client
      *
      *  @param input Alarm URI, will be parsed into `server` and `config_name`
+     *  @param itemName item name that may be expanded or given focus
      *  @return Alarm UI
      *  @throws Exception
      */
-    private Node create(final URI input) throws Exception
+    private Node create(final URI input, String itemName) throws Exception
     {
         final String[] parsed = AlarmURI.parseAlarmURI(input);
         server = parsed[0];
@@ -77,7 +87,7 @@ private Node create(final URI input) throws Exception
         try
         {
             client = new AlarmClient(server, config_name, AlarmSystem.kafka_properties);
-            final AlarmTreeView tree_view = new AlarmTreeView(client);
+            final AlarmTreeView tree_view = new AlarmTreeView(client, itemName);
             client.start();
 
             if (AlarmSystem.config_names.length > 0)
@@ -104,7 +114,8 @@ private void changeConfig(final String new_config_name)
         {
             // Use same server name, but new config_name
             final URI new_input = AlarmURI.createURI(server, new_config_name);
-            tab.setContent(create(new_input));
+            // no need for initial item name
+            tab.setContent(create(new_input, null));
             tab.setInput(new_input);
             Platform.runLater(() -> tab.setLabel(config_name + " " + app.getDisplayName()));
         }
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java
index e7c0c4a413..db1a7f9480 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java
@@ -88,6 +88,7 @@ public class AlarmTreeView extends BorderPane implements AlarmClientListener
 
     /** Model with current alarm tree, sends updates */
     private final AlarmClient model;
+    private final String itemName;
 
     /** Latch for initially pausing model listeners
      *
@@ -150,7 +151,10 @@ public class AlarmTreeView extends BorderPane implements AlarmClientListener
     //     constant performance?
 
     /** @param model Model to represent. Must <u>not</u> be running, yet */
-    public AlarmTreeView(final AlarmClient model)
+    public AlarmTreeView(final AlarmClient model) {
+        this(model, null);
+    }
+    public AlarmTreeView(final AlarmClient model, String itemName)
     {
         if (model.isRunning())
             throw new IllegalStateException();
@@ -159,6 +163,7 @@ public AlarmTreeView(final AlarmClient model)
         changing.setBackground(new Background(new BackgroundFill(Color.BLUE, CornerRadii.EMPTY, Insets.EMPTY)));
 
         this.model = model;
+        this.itemName = itemName;
 
         tree_view.setShowRoot(false);
         tree_view.setCellFactory(view -> new AlarmTreeViewCell());
@@ -203,6 +208,16 @@ private void startup()
             // Represent model that should by now be fairly complete
             tree_view.setRoot(createViewItem(model.getRoot()));
 
+            // expand tree item if is matches item name
+            if (tree_view.getRoot() != null && itemName != null) {
+                for (TreeItem treeItem : tree_view.getRoot().getChildren()) {
+                    if (String.valueOf(treeItem.getValue()).startsWith(itemName)) {
+                        expandAlarms(treeItem);
+                        break;
+                    }
+                }
+            }
+
             // Set change indicator so that it clears when there are no more changes
             indicateChange();
             showServerState(model.isServerAlive());
diff --git a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java
index 50682f48e5..1238717cef 100644
--- a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java
+++ b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java
@@ -11,6 +11,8 @@
 import org.phoebus.applications.alarm.ui.AlarmURI;
 
 import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -21,39 +23,151 @@
  *  @author Kay Kasemir
  */
 @SuppressWarnings("nls")
-public class AlarmURITest
-{
+public class AlarmURITest {
+
     @Test
-	public void testCreateURI()
-	{
-        final URI uri = AlarmURI.createURI("localhost:9092", "Accelerator");
-        System.out.println(uri);
+	public void createURI() {
+        URI uri = AlarmURI.createURI("localhost:9092", "Accelerator");
 		assertThat(uri.toString(), equalTo("alarm://localhost:9092/Accelerator"));
+
+        uri = AlarmURI.createURI("localhost:9092", "Accelerator", "param=value");
+        assertThat(uri.toString(), equalTo("alarm://localhost:9092/Accelerator?param=value"));
 	}
 
     @Test
-    public void testParseURI() throws Exception
-    {
+    public void parseAlarmURI() throws Exception {
+        // create with URI.create
+        // with / without default port
         String[] parsed = AlarmURI.parseAlarmURI(URI.create("alarm://localhost:9092/Accelerator"));
-        assertThat(parsed.length, equalTo(2));
+        assertThat(parsed.length, equalTo(3));
         assertThat(parsed[0], equalTo("localhost:9092"));
         assertThat(parsed[1], equalTo("Accelerator"));
+        assertThat(parsed[2], equalTo(null));
 
-        // Default port
         parsed = AlarmURI.parseAlarmURI(URI.create("alarm://host.my.site/Test"));
-        assertThat(parsed.length, equalTo(2));
+        assertThat(parsed.length, equalTo(3));
+        assertThat(parsed[0], equalTo("host.my.site:9092"));
+        assertThat(parsed[1], equalTo("Test"));
+        assertThat(parsed[2], equalTo(null));
+
+        parsed = AlarmURI.parseAlarmURI(URI.create("alarm://host.my.site/Test?param"));
+        assertThat(parsed.length, equalTo(3));
+        assertThat(parsed[0], equalTo("host.my.site:9092"));
+        assertThat(parsed[1], equalTo("Test"));
+        assertThat(parsed[2], equalTo("param"));
+
+        parsed = AlarmURI.parseAlarmURI(URI.create("alarm://host.my.site/Test?param=value"));
+        assertThat(parsed.length, equalTo(3));
+        assertThat(parsed[0], equalTo("host.my.site:9092"));
+        assertThat(parsed[1], equalTo("Test"));
+        assertThat(parsed[2], equalTo("param=value"));
+
+        parsed = AlarmURI.parseAlarmURI(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+        assertThat(parsed.length, equalTo(3));
+        assertThat(parsed[0], equalTo("host.my.site:9092"));
+        assertThat(parsed[1], equalTo("Test"));
+        assertThat(parsed[2], equalTo("param=" + URLEncoder.encode("abc def")));
+
+        parsed = AlarmURI.parseAlarmURI(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"));
+        assertThat(parsed.length, equalTo(3));
+        assertThat(parsed[0], equalTo("host.my.site:9092"));
+        assertThat(parsed[1], equalTo("Test"));
+        assertThat(parsed[2], equalTo("param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"));
+
+        // create with AlarmURI.createURI
+        // with / without default port
+        parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("localhost:9092", "Accelerator"));
+        assertThat(parsed.length, equalTo(3));
+        assertThat(parsed[0], equalTo("localhost:9092"));
+        assertThat(parsed[1], equalTo("Accelerator"));
+        assertThat(parsed[2], equalTo(null));
+
+        parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("host.my.site", "Test"));
+        assertThat(parsed.length, equalTo(3));
+        assertThat(parsed[0], equalTo("host.my.site:9092"));
+        assertThat(parsed[1], equalTo("Test"));
+        assertThat(parsed[2], equalTo(null));
+
+        parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("host.my.site", "Test", "param"));
+        assertThat(parsed.length, equalTo(3));
+        assertThat(parsed[0], equalTo("host.my.site:9092"));
+        assertThat(parsed[1], equalTo("Test"));
+        assertThat(parsed[2], equalTo("param"));
+
+        parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("host.my.site", "Test", "param=value"));
+        assertThat(parsed.length, equalTo(3));
+        assertThat(parsed[0], equalTo("host.my.site:9092"));
+        assertThat(parsed[1], equalTo("Test"));
+        assertThat(parsed[2], equalTo("param=value"));
+
+        parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+        assertThat(parsed.length, equalTo(3));
+        assertThat(parsed[0], equalTo("host.my.site:9092"));
+        assertThat(parsed[1], equalTo("Test"));
+        assertThat(parsed[2], equalTo("param=" + URLEncoder.encode("abc def")));
+
+        parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"));
+        assertThat(parsed.length, equalTo(3));
         assertThat(parsed[0], equalTo("host.my.site:9092"));
         assertThat(parsed[1], equalTo("Test"));
+        assertThat(parsed[2], equalTo("param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"));
 
-        try
-        {
+        try {
             AlarmURI.parseAlarmURI(URI.create("alarm://server_but_no_config"));
             fail("Didn't catch missing config name");
-        }
-        catch (Exception ex)
-        {
+        } catch (Exception ex) {
             // Expected
             assertThat(ex.getMessage(), containsString("expecting"));
         }
     }
+
+    @Test
+    public void getRawQueryParameterValue() throws Exception {
+        // create with URI.create
+        // with / without default port
+        String value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://localhost:9092/Accelerator"), "param");
+        assertThat(value, equalTo(null));
+
+        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test"), "param");
+        assertThat(value, equalTo(null));
+
+        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param"), "param");
+        assertThat(value, equalTo(null));
+
+        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=value"), "param");
+        assertThat(value, equalTo("value"));
+
+        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)), "param");
+        assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+
+        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"), "param");
+        assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+
+        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"), "param2");
+        assertThat(value, equalTo("value"));
+
+        // create with AlarmURI.createURI
+        // with / without default port
+        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("localhost:9092", "Accelerator"), "param");
+        assertThat(value, equalTo(null));
+
+        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test"), "param");
+        assertThat(value, equalTo(null));
+
+        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param"), "param");
+        assertThat(value, equalTo(null));
+
+        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=value"), "param");
+        assertThat(value, equalTo("value"));
+
+        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)), "param");
+        assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+
+        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"), "param");
+        assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+
+        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"), "param2");
+        assertThat(value, equalTo("value"));
+    }
+
 }

From 4e6419ff34ef7eea9fdd4f9f99879ee38d482e34 Mon Sep 17 00:00:00 2001
From: Kunal Shroff <kunalshroff9@gmail.com>
Date: Thu, 27 Jun 2024 10:37:48 -0400
Subject: [PATCH 47/59] Add the archive readers to the common product

---
 phoebus-product/pom.xml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml
index 24b256a3d6..ab311a58af 100644
--- a/phoebus-product/pom.xml
+++ b/phoebus-product/pom.xml
@@ -116,6 +116,11 @@
             <artifactId>app-databrowser</artifactId>
             <version>4.7.4-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.phoebus</groupId>
+            <artifactId>app-trends-archive-reader</artifactId>
+            <version>4.7.4-SNAPSHOT</version>
+        </dependency>
         <dependency>
             <groupId>org.phoebus</groupId>
             <artifactId>app-databrowser-json</artifactId>

From 1e729ed567830b3327198245b52dc5825b0245a2 Mon Sep 17 00:00:00 2001
From: Abraham Wolk <abraham.wolk@ess.eu>
Date: Fri, 28 Jun 2024 12:08:41 +0200
Subject: [PATCH 48/59] CSSTUDIO-2472 Bugfix: Fix freezes of Display Runtime
 occurring when rapidly changing between embedded displays.

---
 .../EmbeddedDisplayRepresentation.java        | 136 +++++++++---------
 .../javafx/widgets/JFXBaseRepresentation.java |   1 -
 .../widgets/plots/XYPlotRepresentation.java   |   1 -
 .../representation/WidgetRepresentation.java  |   4 -
 4 files changed, 65 insertions(+), 77 deletions(-)

diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
index e6b032542d..1cd4e049e8 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
@@ -15,6 +15,7 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.logging.Level;
 
+import javafx.application.Platform;
 import org.csstudio.display.builder.model.DirtyFlag;
 import org.csstudio.display.builder.model.DisplayModel;
 import org.csstudio.display.builder.model.UntypedWidgetPropertyListener;
@@ -256,7 +257,7 @@ private void fileChanged(final WidgetProperty<?> property, final Object old_valu
 
     /** Update to the next pending display
      *
-     *  <p>Synchronized to serialize the background threads.
+     *  <p>Executed on the JavaFX Application Thread to serialize the background threads.
      *
      *  <p>Example: Displays A, B, C are requested in quick succession.
      *
@@ -270,65 +271,56 @@ private void fileChanged(final WidgetProperty<?> property, final Object old_valu
      *  As thread C finally continues, it finds pending_display_and_group empty.
      *  --> Showing A, then C, skipping B.
      */
-    private synchronized void updatePendingDisplay(final JobMonitor monitor)
-    {
-        try
-        {
-            final DisplayAndGroup handle = pending_display_and_group.getAndSet(null);
-            if (handle == null)
-            {
-                // System.out.println("Nothing to handle");
-                return;
-            }
-            if (inner == null)
-            {
-                // System.out.println("Aborted: " + handle);
-                return;
-            }
+    private void updatePendingDisplay(final JobMonitor monitor) {
+        Platform.runLater(() -> {
+            try {
+                final DisplayAndGroup handle = pending_display_and_group.getAndSet(null);
+                if (handle == null) {
+                    // System.out.println("Nothing to handle");
+                    return;
+                }
+                if (inner == null) {
+                    // System.out.println("Aborted: " + handle);
+                    return;
+                }
 
-            monitor.beginTask("Load " + handle);
-            try
-            {   // Load new model (potentially slow)
-                final DisplayModel new_model = loadDisplayModel(model_widget, handle);
+                monitor.beginTask("Load " + handle);
+                try {   // Load new model (potentially slow)
+                    final DisplayModel new_model = loadDisplayModel(model_widget, handle);
 
-                // Stop (old) runtime
-                // EmbeddedWidgetRuntime tracks this property to start/stop the embedded model's runtime
-                model_widget.runtimePropEmbeddedModel().setValue(null);
+                    // Stop (old) runtime
+                    // EmbeddedWidgetRuntime tracks this property to start/stop the embedded model's runtime
+                    model_widget.runtimePropEmbeddedModel().setValue(null);
 
-                // Atomically update the 'active' model
-                final DisplayModel old_model = active_content_model.getAndSet(new_model);
-                new_model.propBackgroundColor().addUntypedPropertyListener(backgroundChangedListener);
+                    // Atomically update the 'active' model
+                    final DisplayModel old_model = active_content_model.getAndSet(new_model);
+                    new_model.propBackgroundColor().addUntypedPropertyListener(backgroundChangedListener);
 
-                if (old_model != null)
-                {   // Dispose old model
+                    if (old_model != null) {   // Dispose old model
+                        final Future<Object> completion = toolkit.submit(() ->
+                        {
+                            toolkit.disposeRepresentation(old_model);
+                            return null;
+                        });
+                        checkCompletion(model_widget, completion, "timeout disposing old representation");
+                    }
+                    // Represent new model on UI thread
+                    toolkit.onRepresentationStarted();
                     final Future<Object> completion = toolkit.submit(() ->
                     {
-                        toolkit.disposeRepresentation(old_model);
+                        representContent(new_model);
                         return null;
                     });
-                    checkCompletion(model_widget, completion, "timeout disposing old representation");
+                    checkCompletion(model_widget, completion, "timeout representing new content");
+                    // Allow EmbeddedWidgetRuntime to start the new runtime
+                    model_widget.runtimePropEmbeddedModel().setValue(new_model);
+                } catch (Exception ex) {
+                    logger.log(Level.WARNING, "Failed to handle embedded display " + handle, ex);
                 }
-                // Represent new model on UI thread
-                toolkit.onRepresentationStarted();
-                final Future<Object> completion = toolkit.submit(() ->
-                {
-                    representContent(new_model);
-                    return null;
-                });
-                checkCompletion(model_widget, completion, "timeout representing new content");
-
-                // Allow EmbeddedWidgetRuntime to start the new runtime
-                model_widget.runtimePropEmbeddedModel().setValue(new_model);
+            } finally {
+                toolkit.onRepresentationFinished();
             }
-            catch (Exception ex)
-            {
-                logger.log(Level.WARNING, "Failed to handle embedded display " + handle, ex);
-            }
-        }
-        finally
-        {
-            toolkit.onRepresentationFinished();
-        }
+        });
     }
 
     /** @param content_model Model to represent */
@@ -471,26 +463,28 @@ else if (inner.getHeight() != 0.0 && inner.getWidth() != 0.0)
     }
 
     @Override
-    public void dispose()
-    {
-        // When the file name is changed, updatePendingDisplay()
-        // will atomically update the active_content_model,
-        // represent the new model, and then set runtimePropEmbeddedModel.
-        //
-        // Fetching the embedded model from active_content_model
-        // could dispose a representation that hasn't been represented, yet.
-        // Fetching the embedded model from runtimePropEmbeddedModel
-        // could fail to dispose what's just now being represented.
-        //
-        // --> Very unlikely to happen because runtime has been stopped,
-        //     so nothing is changing the file name right now.
-        final DisplayModel em = active_content_model.getAndSet(null);
-        model_widget.runtimePropEmbeddedModel().setValue(null);
-
-        if (inner != null  &&  em != null)
-            toolkit.disposeRepresentation(em);
-        inner = null;
-
-        super.dispose();
+    public void dispose() {
+        Platform.runLater(() -> {
+            // When the file name is changed, updatePendingDisplay()
+            // will atomically update the active_content_model,
+            // represent the new model, and then set runtimePropEmbeddedModel.
+            //
+            // Fetching the embedded model from active_content_model
+            // could dispose a representation that hasn't been represented, yet.
+            // Fetching the embedded model from runtimePropEmbeddedModel
+            // could fail to dispose what's just now being represented.
+            //
+            // --> Very unlikely to happen because runtime has been stopped,
+            //     so nothing is changing the file name right now.
+
+            final DisplayModel em = active_content_model.getAndSet(null);
+            model_widget.runtimePropEmbeddedModel().setValue(null);
+
+            if (inner != null && em != null)
+                toolkit.disposeRepresentation(em);
+            inner = null;
+
+            super.dispose();
+        });
     }
 }
diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java
index 3ba4f67132..40f03b8a0c 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java
@@ -251,7 +251,6 @@ public void dispose()
             logger.log(Level.WARNING, "Missing JFX parent for " + model_widget);
         else
             JFXRepresentation.getChildren(parent).remove(jfx_node);
-        jfx_node = null;
     }
 
     /** Get parent that would be used for child-widgets.
diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/plots/XYPlotRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/plots/XYPlotRepresentation.java
index 9ae909fced..11105cf3fa 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/plots/XYPlotRepresentation.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/plots/XYPlotRepresentation.java
@@ -736,6 +736,5 @@ public void dispose()
     {
         super.dispose();
         plot.dispose();
-        plot = null;
     }
 }
diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/WidgetRepresentation.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/WidgetRepresentation.java
index e4a8018ce0..cc9515bf1e 100644
--- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/WidgetRepresentation.java
+++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/WidgetRepresentation.java
@@ -86,9 +86,5 @@ public void initialize(final ToolkitRepresentation<TWP, TW> toolkit,
     void destroy()
     {
         dispose();
-
-        // Speedup GC
-        model_widget = null;
-        toolkit = null;
     }
 }

From 61aa0ceed4c8f04fba201f18a18d4c00d752c7c1 Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Fri, 28 Jun 2024 10:32:52 -0400
Subject: [PATCH 49/59] Fix ant

---
 app/databrowser-timescale/build.xml | 1 +
 app/databrowser/build.xml           | 1 +
 app/trends/archive-reader/build.xml | 2 +-
 build.xml                           | 2 ++
 4 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/app/databrowser-timescale/build.xml b/app/databrowser-timescale/build.xml
index f7c0c637d4..a7871b647d 100644
--- a/app/databrowser-timescale/build.xml
+++ b/app/databrowser-timescale/build.xml
@@ -8,6 +8,7 @@
       <classpath>
         <path refid="app-classpath"/>
         <pathelement path="../databrowser/${build}/app-databrowser-${version}.jar"/>
+        <pathelement path="../trends/archive-reader/${build}/app-trends-archive-reader-${version}.jar"/>
       </classpath>
     </javac>
   	
diff --git a/app/databrowser/build.xml b/app/databrowser/build.xml
index 402e587b29..58a661cb05 100644
--- a/app/databrowser/build.xml
+++ b/app/databrowser/build.xml
@@ -8,6 +8,7 @@
       <src path="${test}"/>
       <classpath>
         <path refid="app-classpath"/>
+        <pathelement path="../trends/archive-reader/${build}/app-trends-archive-reader-${version}.jar"/>
         <pathelement path="../rtplot/${build}/app-rtplot-${version}.jar"/>
       </classpath>
     </javac>
diff --git a/app/trends/archive-reader/build.xml b/app/trends/archive-reader/build.xml
index 4ea1950c56..d086b50cb0 100644
--- a/app/trends/archive-reader/build.xml
+++ b/app/trends/archive-reader/build.xml
@@ -1,4 +1,4 @@
-<project default="app-trends-rich-adapters">
+<project default="app-trends-archive-reader">
   <import file="../../../dependencies/ant_settings.xml"/>
 
   <target name="app-trends-archive-reader">
diff --git a/build.xml b/build.xml
index f9c6667abc..0ee3e74f6b 100644
--- a/build.xml
+++ b/build.xml
@@ -33,6 +33,7 @@
     <ant target="clean" dir="app/pvtree"/>
     <ant target="clean" dir="app/pvtable"/>
     <ant target="clean" dir="app/rtplot"/>
+    <ant target="clean" dir="app/trends/archive-reader"/>
     <ant target="clean" dir="app/databrowser"/>
     <ant target="clean" dir="app/databrowser-timescale"/>
     <ant target="clean" dir="app/trends/rich-adapters"/>
@@ -117,6 +118,7 @@
     <ant dir="app/pvtree"/>
     <ant dir="app/pvtable"/>
     <ant dir="app/rtplot"/>
+    <ant dir="app/trends/archive-reader"/>
     <ant dir="app/databrowser"/>
     <ant dir="app/databrowser-timescale"/>
     <ant dir="app/trends/rich-adapters"/>

From 53ebe6bc2c3bb18643e24296ae9660fccf7382b2 Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Fri, 28 Jun 2024 10:56:50 -0400
Subject: [PATCH 50/59] Fix eclipse

---
 app/databrowser-timescale/.classpath         |  1 +
 app/databrowser/.classpath                   |  1 +
 app/display/representation-javafx/.classpath |  1 +
 app/trends/archive-reader/.classpath         | 14 ++++++++++++++
 app/trends/archive-reader/.project           | 17 +++++++++++++++++
 dependencies/phoebus-target/.classpath       | 12 ++++++------
 6 files changed, 40 insertions(+), 6 deletions(-)
 create mode 100644 app/trends/archive-reader/.classpath
 create mode 100644 app/trends/archive-reader/.project

diff --git a/app/databrowser-timescale/.classpath b/app/databrowser-timescale/.classpath
index 83b0af9e8b..68a49ce44a 100644
--- a/app/databrowser-timescale/.classpath
+++ b/app/databrowser-timescale/.classpath
@@ -10,5 +10,6 @@
 	<classpathentry combineaccessrules="false" kind="src" path="/core-pv"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/core-util"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/app-databrowser"/>
+        <classpathentry combineaccessrules="false" kind="src" path="/app-trends-archive-reader"/>
 	<classpathentry kind="output" path="target/classes"/>
 </classpath>
diff --git a/app/databrowser/.classpath b/app/databrowser/.classpath
index ea5d410a5d..fdb5eaefc4 100644
--- a/app/databrowser/.classpath
+++ b/app/databrowser/.classpath
@@ -13,6 +13,7 @@
 	<classpathentry combineaccessrules="false" kind="src" path="/core-vtype"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/core-util"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/app-rtplot"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/app-trends-archive-reader"/>
 	<classpathentry kind="src" path="/app-email-ui"/>
 	<classpathentry kind="src" path="/app-logbook-ui"/>
 	<classpathentry kind="output" path="target/classes"/>
diff --git a/app/display/representation-javafx/.classpath b/app/display/representation-javafx/.classpath
index b4875b35dc..f4a0fd70dd 100644
--- a/app/display/representation-javafx/.classpath
+++ b/app/display/representation-javafx/.classpath
@@ -10,6 +10,7 @@
 	<classpathentry combineaccessrules="false" kind="src" path="/core-framework"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/core-ui"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/core-util"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/core-types"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/core-vtype"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/app-rtplot"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/app-display-model"/>
diff --git a/app/trends/archive-reader/.classpath b/app/trends/archive-reader/.classpath
new file mode 100644
index 0000000000..0b6599fa79
--- /dev/null
+++ b/app/trends/archive-reader/.classpath
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry kind="src" output="target/classes" path="src/main/resources"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+        <classpathentry combineaccessrules="false" kind="src" path="/phoebus-target"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/core-framework"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/core-types"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/core-vtype"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/core-pv"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/core-util"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/core-ui"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/app/trends/archive-reader/.project b/app/trends/archive-reader/.project
new file mode 100644
index 0000000000..4e243167d2
--- /dev/null
+++ b/app/trends/archive-reader/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>app-trends-archive-reader</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/dependencies/phoebus-target/.classpath b/dependencies/phoebus-target/.classpath
index 529c1da696..276b6b4c62 100644
--- a/dependencies/phoebus-target/.classpath
+++ b/dependencies/phoebus-target/.classpath
@@ -42,11 +42,11 @@
     <classpathentry exported="true" kind="lib" path="target/lib/epics-pvaccess-5.1.9.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/epics-pvdata-6.1.9.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/epics-util-1.0.7.jar"/>
-    <classpathentry exported="true" kind="lib" path="target/lib/error_prone_annotations-2.26.1.jar"/>
-    <classpathentry exported="true" kind="lib" path="target/lib/failureaccess-1.0.2.jar"/>
+    <classpathentry exported="true" kind="lib" path="target/lib/error_prone_annotations-2.3.4.jar"/>
+    <classpathentry exported="true" kind="lib" path="target/lib/failureaccess-1.0.1.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/freetts-1.2.2.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/geronimo-spec-ejb-2.1-rc2.jar"/>
-    <classpathentry exported="true" kind="lib" path="target/lib/guava-33.2.0-jre.jar"/>
+    <classpathentry exported="true" kind="lib" path="target/lib/guava-29.0-jre.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/hamcrest-2.2.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/hamcrest-all-1.3.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/hamcrest-core-1.3.jar"/>
@@ -56,7 +56,7 @@
     <classpathentry exported="true" kind="lib" path="target/lib/httpcore-nio-4.4.12.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/install-jars-4.7.4-SNAPSHOT.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/istack-commons-runtime-3.0.5.jar"/>
-    <classpathentry exported="true" kind="lib" path="target/lib/j2objc-annotations-3.0.0.jar"/>
+    <classpathentry exported="true" kind="lib" path="target/lib/j2objc-annotations-1.3.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/jackson-annotations-2.12.3.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/jackson-core-2.12.3.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/jackson-databind-2.12.3.jar"/>
@@ -125,7 +125,7 @@
     <classpathentry exported="true" kind="lib" path="target/lib/mx4j-3.0.1.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/mx4j-impl-2.1.1.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/mx4j-jmx-2.1.1.jar"/>
-    <classpathentry exported="true" kind="lib" path="target/lib/mysql-connector-java-8.0.30.jar"/>
+    <classpathentry exported="true" kind="lib" path="target/lib/mysql-connector-j-8.4.0.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/nanocontainer-remoting-1.0-RC-1.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/objenesis-2.6.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/ojdbc8-12.2.0.1.jar"/>
@@ -135,7 +135,7 @@
     <classpathentry exported="true" kind="lib" path="target/lib/parsson-1.0.0.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/pbrawclient-0.0.10.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/picocontainer-1.2.jar"/>
-    <classpathentry exported="true" kind="lib" path="target/lib/postgresql-42.6.0.jar"/>
+    <classpathentry exported="true" kind="lib" path="target/lib/postgresql-42.6.2.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/protobuf-java-3.21.9.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/proxytoys-0.1.jar"/>
     <classpathentry exported="true" kind="lib" path="target/lib/py4j-0.10.2.1.jar"/>

From e4db60bb8ac54310ec5f48b394e9220ba7620508 Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Fri, 28 Jun 2024 11:03:59 -0400
Subject: [PATCH 51/59] Eclipse product: Add archive reader

---
 phoebus-product/.classpath | 1 +
 1 file changed, 1 insertion(+)

diff --git a/phoebus-product/.classpath b/phoebus-product/.classpath
index d4138ff3a5..0ee9f7c14a 100644
--- a/phoebus-product/.classpath
+++ b/phoebus-product/.classpath
@@ -43,6 +43,7 @@
     <classpathentry combineaccessrules="false" kind="src" path="/app-display-adapters"/>
     <classpathentry combineaccessrules="false" kind="src" path="/app-databrowser"/>
     <classpathentry combineaccessrules="false" kind="src" path="/app-trends-rich-adapters"/>
+    <classpathentry combineaccessrules="false" kind="src" path="/app-trends-archive-reader"/>
     <classpathentry combineaccessrules="false" kind="src" path="/app-rtplot"/>
     <classpathentry combineaccessrules="false" kind="src" path="/app-pvtable"/>
     <classpathentry combineaccessrules="false" kind="src" path="/app-email-ui"/>

From aa59a78ad88c0e6e8df9f7d015c0c11ababa489b Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Fri, 28 Jun 2024 12:56:07 -0400
Subject: [PATCH 52/59] RDB Archive: Correct writing of 'enum' metadata

`Display.displayOf(VEnum)` or `..(VString)` returns a non-`null`
display, which prevented the writing of enum metadata.
Now VEnum is detected and its labels are written.
---
 services/archive-engine/.classpath            |  2 +
 .../archive/writer/rdb/RDBArchiveWriter.java  | 54 ++++++++++++-------
 2 files changed, 37 insertions(+), 19 deletions(-)

diff --git a/services/archive-engine/.classpath b/services/archive-engine/.classpath
index 2225c2c8b6..12e57c1a54 100644
--- a/services/archive-engine/.classpath
+++ b/services/archive-engine/.classpath
@@ -7,6 +7,8 @@
 	<classpathentry combineaccessrules="false" kind="src" path="/core-framework"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/core-util"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/core-pv"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/core-pv-ca"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/core-pv-pva"/>
         <classpathentry combineaccessrules="false" kind="src" path="/core-vtype"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/phoebus-target"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
diff --git a/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java b/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java
index 02eda778c4..157c6e02c2 100644
--- a/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java
+++ b/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2011-2020 Oak Ridge National Laboratory.
+ * Copyright (c) 2011-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
@@ -178,11 +178,18 @@ public WriteChannel getChannel(final String name) throws Exception
     @Override
     public void addSample(final WriteChannel channel, final VType sample) throws Exception
     {
-        final RDBWriteChannel rdb_channel = (RDBWriteChannel) channel;
-        writeMetaData(rdb_channel, sample);
-        batchSample(rdb_channel, sample);
-        batched_channel.add(rdb_channel);
-        batched_samples.add(sample);
+        try
+        {
+            final RDBWriteChannel rdb_channel = (RDBWriteChannel) channel;
+            writeMetaData(rdb_channel, sample);
+            batchSample(rdb_channel, sample);
+            batched_channel.add(rdb_channel);
+            batched_samples.add(sample);
+        }
+        catch (Exception ex)
+        {   // Wrap with channel info
+            throw new Exception("Cannot add sample for " + channel, ex);
+        }
     }
 
     /** Write meta data if it was never written or has changed
@@ -191,12 +198,33 @@ public void addSample(final WriteChannel channel, final VType sample) throws Exc
      */
     private void writeMetaData(final RDBWriteChannel channel, final VType sample) throws Exception
     {
+        // Three cases: Enum, numeric, string.
+        //
         // Note that Strings have no meta data. But we don't know at this point
         // if it's really a string channel, or of this is just a special
         // string value like "disconnected".
         // In order to not delete any existing meta data,
-        // we just do nothing for strings
+        // we just do nothing for strings.
+        if (sample instanceof VString)
+            return;
+
+        if (sample instanceof VEnum)
+        {
+            final List<String> labels = ((VEnum)sample).getDisplay().getChoices();
+            if (MetaDataHelper.equals(labels, channel.getMetadata()))
+                return;
+
+            // Clear numeric meta data, set enumerated in RDB
+            NumericMetaDataHelper.delete(connection, sql, channel);
+            EnumMetaDataHelper.delete(connection, sql, channel);
+            EnumMetaDataHelper.insert(connection, sql, channel, labels);
+            channel.setMetaData(labels);
 
+            return;
+        }
+
+        // Note that Display.displayOf(VEnum) or ..(VString) will return a non-null display,
+        // but we already handled those cases
         final Display display = Display.displayOf(sample);
         if (display != null)
         {
@@ -209,18 +237,6 @@ private void writeMetaData(final RDBWriteChannel channel, final VType sample) th
             NumericMetaDataHelper.insert(connection, sql, channel, display);
             channel.setMetaData(display);
         }
-        else if (sample instanceof VEnum)
-        {
-            final List<String> labels = ((VEnum)sample).getDisplay().getChoices();
-            if (MetaDataHelper.equals(labels, channel.getMetadata()))
-                return;
-
-            // Clear numeric meta data, set enumerated in RDB
-            NumericMetaDataHelper.delete(connection, sql, channel);
-            EnumMetaDataHelper.delete(connection, sql, channel);
-            EnumMetaDataHelper.insert(connection, sql, channel, labels);
-            channel.setMetaData(labels);
-        }
     }
 
     private static Instant getTimestamp(final VType value)

From c3a53dd5529d1938c2acb730034dd218e61cf035 Mon Sep 17 00:00:00 2001
From: kasemir <ky9@ornl.gov>
Date: Fri, 28 Jun 2024 13:21:49 -0400
Subject: [PATCH 53/59] typo

---
 .../org/csstudio/archive/writer/rdb/RDBArchiveWriter.java     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java b/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java
index 157c6e02c2..38c54216ee 100644
--- a/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java
+++ b/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java
@@ -198,10 +198,10 @@ public void addSample(final WriteChannel channel, final VType sample) throws Exc
      */
     private void writeMetaData(final RDBWriteChannel channel, final VType sample) throws Exception
     {
-        // Three cases: Enum, numeric, string.
+        // Three cases: String, enum, numeric.
         //
         // Note that Strings have no meta data. But we don't know at this point
-        // if it's really a string channel, or of this is just a special
+        // if it's really a string channel, or if this is just a special
         // string value like "disconnected".
         // In order to not delete any existing meta data,
         // we just do nothing for strings.

From d2f099eaf4465783008453052f10dc68dc553ec8 Mon Sep 17 00:00:00 2001
From: Lars Johansson <lars.johansson@ess.eu>
Date: Tue, 2 Jul 2024 11:26:44 +0200
Subject: [PATCH 54/59] Refactor handling of parameter names and values for raw
 query of alarm URI

---
 .../applications/alarm/ui/AlarmURI.java       | 42 ++++++++-----
 .../alarm/ui/area/AlarmAreaView.java          |  3 +-
 .../alarm/ui/area/OpenTreeViewAction.java     | 14 +++--
 .../alarm/ui/tree/AlarmTreeInstance.java      |  4 +-
 .../alarm/ui/tree/AlarmTreeView.java          |  7 ++-
 .../applications/alarm/AlarmURITest.java      | 61 +++++++++----------
 6 files changed, 75 insertions(+), 56 deletions(-)

diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java
index bffe9557a5..5fede08730 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java
@@ -7,7 +7,11 @@
  *******************************************************************************/
 package org.phoebus.applications.alarm.ui;
 
+import com.google.common.collect.ImmutableMap;
+
 import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
 
 /** Alarm URI helpers
  *
@@ -23,25 +27,27 @@ public class AlarmURI
     /** URI schema used to refer to an alarm config */
     public static final String SCHEMA = "alarm";
 
-    public static final String DELIMITER_QUERY_PARAMETERS = "&";
-    public static final String DELIMITER_QUERY_PARAMETER_VALUE = "=";
+    /** Nme to use for item as query parameter */
+    public static final String QUERY_PARAMETER_ITEM_NAME = "itemName";
 
     /** @param server Kafka server host:port
      *  @param config_name Alarm configuration root
      *  @return URI used to access that alarm configuration, "alarm://host:port/config_name"
      */
-    public static URI createURI(final String server, final String config_name)
-    {
-        return URI.create(SCHEMA + "://" + server + "/" + config_name);
+    public static URI createURI(final String server, final String config_name) {
+        return createURI(server, config_name, null);
     }
 
     /** @param server Kafka server host:port
      *  @param config_name Alarm configuration root
      *  @param rawQuery raw query for URI
-     *  @return URI used to access that alarm configuration, "alarm://host:port/config_name"
+     *  @return URI used to access that alarm configuration, "alarm://host:port/config_name?rawQuery"
      */
     public static URI createURI(final String server, final String config_name, String rawQuery) {
-        return URI.create(SCHEMA + "://" + server + "/" + config_name + "?" + rawQuery);
+        if (rawQuery != null  && rawQuery.length() > 0)
+            return URI.create(SCHEMA + "://" + server + "/" + config_name + "?" + rawQuery);
+        else
+            return URI.create(SCHEMA + "://" + server + "/" + config_name);
     }
 
     /** Parse alarm configuration parameters from URI
@@ -76,22 +82,26 @@ public static String[] parseAlarmURI(final URI resource) throws Exception
     }
 
     /**
-     * Extract raw query parameter value for given parameter.
+     * Return a map with parameter names and values as key-value pairs for raw query of given resource.
      * @param resource "alarm://localhost:9092/Accelerator" or "alarm://localhost:9092/Accelerator?param=value"
-     * @param queryParameter name of query parameter for which to extract value
-     * @return parameter value, null or "value" for examples above
+     * @return map with parameter names as keys and parameter values as values for raw query
      */
-    public static String getRawQueryParameterValue(URI resource, String queryParameter) {
-        String[] queryParametersValues = resource.getRawQuery() != null ? resource.getRawQuery().split(AlarmURI.DELIMITER_QUERY_PARAMETERS) : null;
+    public static Map<String, String> getRawQueryParametersValues(URI resource) {
+        Map<String, String> map = new HashMap<>();
+        String[] queryParametersValues = resource.getRawQuery() != null ? resource.getRawQuery().split("&") : null;
         if (queryParametersValues != null) {
             for (String queryParameterValue : queryParametersValues) {
-                if (queryParameterValue.startsWith(queryParameter)) {
-                    String[] parameterValue = queryParameterValue.split(AlarmURI.DELIMITER_QUERY_PARAMETER_VALUE);
-                    return parameterValue != null && parameterValue.length == 2 ? parameterValue[1] : null;
+                String[] parameterValue = queryParameterValue.split("=");
+                if (parameterValue != null) {
+                    if (parameterValue.length == 2) {
+                        map.put(parameterValue[0], parameterValue[1]);
+                    } else if (parameterValue.length == 1) {
+                        map.put(parameterValue[0], null);
+                    }
                 }
             }
         }
-        return null;
+        return map;
     }
 
 }
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java
index 32b5f96b92..63f5762453 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java
@@ -33,6 +33,7 @@
 import org.phoebus.applications.alarm.model.AlarmTreeItem;
 import org.phoebus.applications.alarm.model.SeverityLevel;
 import org.phoebus.applications.alarm.ui.AlarmUI;
+import org.phoebus.applications.alarm.ui.AlarmURI;
 import org.phoebus.ui.javafx.JFXUtil;
 import org.phoebus.ui.javafx.UpdateThrottle;
 
@@ -215,7 +216,7 @@ private void recreateItems(final List<String> items)
                     }
                 }
 
-                OpenTreeViewAction otva = new OpenTreeViewAction(alarmConfigName, "itemName=" + URLEncoder.encode(item_name, StandardCharsets.UTF_8));
+                OpenTreeViewAction otva = new OpenTreeViewAction(alarmConfigName, AlarmURI.QUERY_PARAMETER_ITEM_NAME + "=" + URLEncoder.encode(item_name, StandardCharsets.UTF_8));
                 menu.getItems().add(otva);
                 menu.show(this.getScene().getWindow(), event.getScreenX(), event.getScreenY());
             });
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java
index c0d7440937..fcae14aae3 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java
@@ -20,16 +20,22 @@
  *  @author Evan Smith
  */
 @SuppressWarnings("nls")
-public class OpenTreeViewAction extends MenuItem
-{
+public class OpenTreeViewAction extends MenuItem {
+
+    /**
+     * Constructor
+     * @param alarmConfigName The alarm configuration name
+     */
+    public OpenTreeViewAction(String alarmConfigName) {
+        this(alarmConfigName, null);
+    }
 
     /**
      * Constructor
      * @param alarmConfigName The alarm configuration name
      * @param alarmRawQuery raw query for alarm (null if no such information is available)
      */
-    public OpenTreeViewAction(String alarmConfigName, String alarmRawQuery)
-    {
+    public OpenTreeViewAction(String alarmConfigName, String alarmRawQuery) {
         final AlarmTreeMenuEntry entry = new AlarmTreeMenuEntry();
         entry.setResource(AlarmURI.createURI(AlarmSystem.server, alarmConfigName, alarmRawQuery));
         setText(entry.getName());
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java
index a4e7a71720..f08fe1baa6 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java
@@ -34,8 +34,6 @@
 @SuppressWarnings("nls")
 class AlarmTreeInstance implements AppInstance
 {
-    public static final String ITEM_NAME = "itemName";
-
     private final AlarmTreeApplication app;
 
     private String server = null, config_name = null;
@@ -52,7 +50,7 @@ public AlarmTreeInstance(final AlarmTreeApplication app, final URI input) throws
 
         // split input with/without (raw) query
         final URI resource = new URI(input.getScheme(), input.getUserInfo(), input.getHost(), input.getPort(), input.getPath(), null, null);
-        String itemName = AlarmURI.getRawQueryParameterValue(input, ITEM_NAME);
+        String itemName = AlarmURI.getRawQueryParametersValues(input).get(AlarmURI.QUERY_PARAMETER_ITEM_NAME);
         itemName = itemName != null ? URLDecoder.decode(itemName, StandardCharsets.UTF_8) : null;
 
         tab = new DockItemWithInput(this, create(resource, itemName), resource, null, null);
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java
index db1a7f9480..83a3efd6aa 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java
@@ -154,6 +154,11 @@ public class AlarmTreeView extends BorderPane implements AlarmClientListener
     public AlarmTreeView(final AlarmClient model) {
         this(model, null);
     }
+
+    /**
+     * @param model Model to represent. Must <u>not</u> be running, yet
+     * @param itemName item name that may be expanded or given focus
+     */
     public AlarmTreeView(final AlarmClient model, String itemName)
     {
         if (model.isRunning())
@@ -211,7 +216,7 @@ private void startup()
             // expand tree item if is matches item name
             if (tree_view.getRoot() != null && itemName != null) {
                 for (TreeItem treeItem : tree_view.getRoot().getChildren()) {
-                    if (String.valueOf(treeItem.getValue()).startsWith(itemName)) {
+                    if (((AlarmTreeItem) treeItem.getValue()).getName().equals(itemName)) {
                         expandAlarms(treeItem);
                         break;
                     }
diff --git a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java
index 1238717cef..0bdd4618eb 100644
--- a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java
+++ b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java
@@ -7,12 +7,14 @@
  *******************************************************************************/
 package org.phoebus.applications.alarm;
 
+import com.google.common.collect.ImmutableMap;
 import org.junit.jupiter.api.Test;
 import org.phoebus.applications.alarm.ui.AlarmURI;
 
 import java.net.URI;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
+import java.util.Map;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -122,52 +124,49 @@ public void parseAlarmURI() throws Exception {
     }
 
     @Test
-    public void getRawQueryParameterValue() throws Exception {
+    public void getRawQueryParametersValues() throws Exception {
         // create with URI.create
         // with / without default port
-        String value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://localhost:9092/Accelerator"), "param");
-        assertThat(value, equalTo(null));
+        //ImmutableMap<String, String> immutableMap = AlarmURI.getRawQueryParametersValues(URI.create("alarm://localhost:9092/Accelerator"));
+        Map<String, String> map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://localhost:9092/Accelerator"));
+        assertThat(map.get("param"), equalTo(null));
 
-        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test"), "param");
-        assertThat(value, equalTo(null));
+        map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test"));
+        assertThat(map.get("param"), equalTo(null));
 
-        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param"), "param");
-        assertThat(value, equalTo(null));
+        map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test?param"));
+        assertThat(map.get("param"), equalTo(null));
 
-        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=value"), "param");
-        assertThat(value, equalTo("value"));
+        map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test?param=value"));
+        assertThat(map.get("param"), equalTo("value"));
 
-        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)), "param");
-        assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+        map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+        assertThat(map.get("param"), equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
 
-        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"), "param");
-        assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
-
-        value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"), "param2");
-        assertThat(value, equalTo("value"));
+        map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"));
+        assertThat(map.get("param"), equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+        assertThat(map.get("param2"), equalTo("value"));
 
         // create with AlarmURI.createURI
         // with / without default port
-        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("localhost:9092", "Accelerator"), "param");
-        assertThat(value, equalTo(null));
-
-        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test"), "param");
-        assertThat(value, equalTo(null));
+        map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("localhost:9092", "Accelerator"));
+        assertThat(map.get("param"), equalTo(null));
 
-        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param"), "param");
-        assertThat(value, equalTo(null));
+        map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test"));
+        assertThat(map.get("param"), equalTo(null));
 
-        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=value"), "param");
-        assertThat(value, equalTo("value"));
+        map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test", "param"));
+        assertThat(map.get("param"), equalTo(null));
 
-        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)), "param");
-        assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+        map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test", "param=value"));
+        assertThat(map.get("param"), equalTo("value"));
 
-        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"), "param");
-        assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+        map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+        assertThat(map.get("param"), equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
 
-        value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"), "param2");
-        assertThat(value, equalTo("value"));
+        map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "&param2=value"));
+        assertThat(map.get("param"), equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8)));
+        assertThat(map.get("param2"), equalTo("value"));
     }
 
 }

From 73a1c1c27409650aac5d793dd0e17370e7711858 Mon Sep 17 00:00:00 2001
From: Lars Johansson <lars.johansson@ess.eu>
Date: Tue, 2 Jul 2024 17:10:23 +0200
Subject: [PATCH 55/59] Removed import statements

---
 .../main/java/org/phoebus/applications/alarm/ui/AlarmURI.java   | 2 --
 .../test/java/org/phoebus/applications/alarm/AlarmURITest.java  | 1 -
 2 files changed, 3 deletions(-)

diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java
index 5fede08730..f3046c8fce 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java
@@ -7,8 +7,6 @@
  *******************************************************************************/
 package org.phoebus.applications.alarm.ui;
 
-import com.google.common.collect.ImmutableMap;
-
 import java.net.URI;
 import java.util.HashMap;
 import java.util.Map;
diff --git a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java
index 0bdd4618eb..37d96ca6fd 100644
--- a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java
+++ b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java
@@ -7,7 +7,6 @@
  *******************************************************************************/
 package org.phoebus.applications.alarm;
 
-import com.google.common.collect.ImmutableMap;
 import org.junit.jupiter.api.Test;
 import org.phoebus.applications.alarm.ui.AlarmURI;
 

From e432998913ebc555eeeafcad1d6aba213f15f481 Mon Sep 17 00:00:00 2001
From: Evan Daykin <daykin@frib.msu.edu>
Date: Wed, 3 Jul 2024 10:40:44 -0400
Subject: [PATCH 56/59] core changes required for UX Analytics plugin

Docstring, remove unused imports

re-add imports which were necessary at compile time
---
 .../display/builder/representation/ToolkitListener.java   | 5 +++++
 .../builder/runtime/app/DisplayRuntimeInstance.java       | 8 +++++++-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java
index 2a0341e52b..7164b691f1 100644
--- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java
+++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java
@@ -45,6 +45,11 @@ public interface ToolkitListener
      */
     default public void handleWrite(Widget widget, Object value) {};
 
+    /**
+     * A method was called from the UI that other listeners might be interested in.
+     * @param user_args Zero or more objects relevant to what was called.
+     *      Case-specific Implementations should expect and check these.
+     */
     default public void handleMethodCalled(Object... user_args) {};
 
 }
diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java
index 0ce90879da..3c01b44f37 100644
--- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java
+++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java
@@ -11,6 +11,7 @@
 
 
 import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.Callable;
@@ -286,9 +287,12 @@ public DisplayInfo getDisplayInfo()
      */
     public void loadDisplayFile(final DisplayInfo info)
     {
+        DisplayInfo old_info = display_info.orElse(null);
         // If already executing another display, shut it down
         disposeModel();
 
+        ArrayList<DisplayInfo> dst_src = new ArrayList<>();
+
         // Set input ASAP so that other requests to open this
         // resource will find this instance and not start
         // another instance
@@ -324,7 +328,9 @@ public void loadDisplayFile(final DisplayInfo info)
                 {
                     representation.awaitRepresentation(30, TimeUnit.SECONDS);
                     representation_init.run();
-                    representation.fireMethodCall(info, applicationThreadStackTrace);
+                    dst_src.add(info);
+                    dst_src.add(old_info);
+                    representation.fireMethodCall(dst_src, applicationThreadStackTrace);
                     logger.log(Level.FINE, "Done with representing model of " + info.getPath());
                 }
                 catch (TimeoutException | InterruptedException ex)

From 86b178605375bf649f4d4673afbeeb7625a136b2 Mon Sep 17 00:00:00 2001
From: Abraham Wolk <abraham.wolk@ess.eu>
Date: Thu, 4 Jul 2024 11:15:18 +0200
Subject: [PATCH 57/59] CSSTUDIO-2472 Revert changes to
 "EmbeddedDisplayRepresentation".

---
 .../EmbeddedDisplayRepresentation.java        | 136 +++++++++---------
 1 file changed, 71 insertions(+), 65 deletions(-)

diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
index 1cd4e049e8..e6b032542d 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
@@ -15,7 +15,6 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.logging.Level;
 
-import javafx.application.Platform;
 import org.csstudio.display.builder.model.DirtyFlag;
 import org.csstudio.display.builder.model.DisplayModel;
 import org.csstudio.display.builder.model.UntypedWidgetPropertyListener;
@@ -257,7 +256,7 @@ private void fileChanged(final WidgetProperty<?> property, final Object old_valu
 
     /** Update to the next pending display
      *
-     *  <p>Executed on the JavaFX Application Thread to serialize the background threads.
+     *  <p>Synchronized to serialize the background threads.
      *
      *  <p>Example: Displays A, B, C are requested in quick succession.
      *
@@ -271,56 +270,65 @@ private void fileChanged(final WidgetProperty<?> property, final Object old_valu
      *  As thread C finally continues, it finds pending_display_and_group empty.
      *  --> Showing A, then C, skipping B.
      */
-    private void updatePendingDisplay(final JobMonitor monitor) {
-        Platform.runLater(() -> {
-            try {
-                final DisplayAndGroup handle = pending_display_and_group.getAndSet(null);
-                if (handle == null) {
-                    // System.out.println("Nothing to handle");
-                    return;
-                }
-                if (inner == null) {
-                    // System.out.println("Aborted: " + handle);
-                    return;
-                }
+    private synchronized void updatePendingDisplay(final JobMonitor monitor)
+    {
+        try
+        {
+            final DisplayAndGroup handle = pending_display_and_group.getAndSet(null);
+            if (handle == null)
+            {
+                // System.out.println("Nothing to handle");
+                return;
+            }
+            if (inner == null)
+            {
+                // System.out.println("Aborted: " + handle);
+                return;
+            }
 
-                monitor.beginTask("Load " + handle);
-                try {   // Load new model (potentially slow)
-                    final DisplayModel new_model = loadDisplayModel(model_widget, handle);
+            monitor.beginTask("Load " + handle);
+            try
+            {   // Load new model (potentially slow)
+                final DisplayModel new_model = loadDisplayModel(model_widget, handle);
 
-                    // Stop (old) runtime
-                    // EmbeddedWidgetRuntime tracks this property to start/stop the embedded model's runtime
-                    model_widget.runtimePropEmbeddedModel().setValue(null);
+                // Stop (old) runtime
+                // EmbeddedWidgetRuntime tracks this property to start/stop the embedded model's runtime
+                model_widget.runtimePropEmbeddedModel().setValue(null);
 
-                    // Atomically update the 'active' model
-                    final DisplayModel old_model = active_content_model.getAndSet(new_model);
-                    new_model.propBackgroundColor().addUntypedPropertyListener(backgroundChangedListener);
+                // Atomically update the 'active' model
+                final DisplayModel old_model = active_content_model.getAndSet(new_model);
+                new_model.propBackgroundColor().addUntypedPropertyListener(backgroundChangedListener);
 
-                    if (old_model != null) {   // Dispose old model
-                        final Future<Object> completion = toolkit.submit(() ->
-                        {
-                            toolkit.disposeRepresentation(old_model);
-                            return null;
-                        });
-                        checkCompletion(model_widget, completion, "timeout disposing old representation");
-                    }
-                    // Represent new model on UI thread
-                    toolkit.onRepresentationStarted();
+                if (old_model != null)
+                {   // Dispose old model
                     final Future<Object> completion = toolkit.submit(() ->
                     {
-                        representContent(new_model);
+                        toolkit.disposeRepresentation(old_model);
                         return null;
                     });
-                    checkCompletion(model_widget, completion, "timeout representing new content");
-                    // Allow EmbeddedWidgetRuntime to start the new runtime
-                    model_widget.runtimePropEmbeddedModel().setValue(new_model);
-                } catch (Exception ex) {
-                    logger.log(Level.WARNING, "Failed to handle embedded display " + handle, ex);
+                    checkCompletion(model_widget, completion, "timeout disposing old representation");
                 }
-            } finally {
-                toolkit.onRepresentationFinished();
+                // Represent new model on UI thread
+                toolkit.onRepresentationStarted();
+                final Future<Object> completion = toolkit.submit(() ->
+                {
+                    representContent(new_model);
+                    return null;
+                });
+                checkCompletion(model_widget, completion, "timeout representing new content");
+
+                // Allow EmbeddedWidgetRuntime to start the new runtime
+                model_widget.runtimePropEmbeddedModel().setValue(new_model);
             }
-        });
+            catch (Exception ex)
+            {
+                logger.log(Level.WARNING, "Failed to handle embedded display " + handle, ex);
+            }
+        }
+        finally
+        {
+            toolkit.onRepresentationFinished();
+        }
     }
 
     /** @param content_model Model to represent */
@@ -463,28 +471,26 @@ else if (inner.getHeight() != 0.0 && inner.getWidth() != 0.0)
     }
 
     @Override
-    public void dispose() {
-        Platform.runLater(() -> {
-            // When the file name is changed, updatePendingDisplay()
-            // will atomically update the active_content_model,
-            // represent the new model, and then set runtimePropEmbeddedModel.
-            //
-            // Fetching the embedded model from active_content_model
-            // could dispose a representation that hasn't been represented, yet.
-            // Fetching the embedded model from runtimePropEmbeddedModel
-            // could fail to dispose what's just now being represented.
-            //
-            // --> Very unlikely to happen because runtime has been stopped,
-            //     so nothing is changing the file name right now.
-
-            final DisplayModel em = active_content_model.getAndSet(null);
-            model_widget.runtimePropEmbeddedModel().setValue(null);
-
-            if (inner != null && em != null)
-                toolkit.disposeRepresentation(em);
-            inner = null;
-
-            super.dispose();
-        });
+    public void dispose()
+    {
+        // When the file name is changed, updatePendingDisplay()
+        // will atomically update the active_content_model,
+        // represent the new model, and then set runtimePropEmbeddedModel.
+        //
+        // Fetching the embedded model from active_content_model
+        // could dispose a representation that hasn't been represented, yet.
+        // Fetching the embedded model from runtimePropEmbeddedModel
+        // could fail to dispose what's just now being represented.
+        //
+        // --> Very unlikely to happen because runtime has been stopped,
+        //     so nothing is changing the file name right now.
+        final DisplayModel em = active_content_model.getAndSet(null);
+        model_widget.runtimePropEmbeddedModel().setValue(null);
+
+        if (inner != null  &&  em != null)
+            toolkit.disposeRepresentation(em);
+        inner = null;
+
+        super.dispose();
     }
 }

From 27cc9d27859aca6bb3b27a3b7cee142e7e8e56aa Mon Sep 17 00:00:00 2001
From: Abraham Wolk <abraham.wolk@ess.eu>
Date: Thu, 4 Jul 2024 11:16:58 +0200
Subject: [PATCH 58/59] CSSTUDIO-2472 Run dispose() on the UI-thread.

---
 .../EmbeddedDisplayRepresentation.java        | 41 ++++++++++---------
 1 file changed, 22 insertions(+), 19 deletions(-)

diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
index e6b032542d..dde8845324 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
@@ -15,6 +15,7 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.logging.Level;
 
+import javafx.application.Platform;
 import org.csstudio.display.builder.model.DirtyFlag;
 import org.csstudio.display.builder.model.DisplayModel;
 import org.csstudio.display.builder.model.UntypedWidgetPropertyListener;
@@ -473,24 +474,26 @@ else if (inner.getHeight() != 0.0 && inner.getWidth() != 0.0)
     @Override
     public void dispose()
     {
-        // When the file name is changed, updatePendingDisplay()
-        // will atomically update the active_content_model,
-        // represent the new model, and then set runtimePropEmbeddedModel.
-        //
-        // Fetching the embedded model from active_content_model
-        // could dispose a representation that hasn't been represented, yet.
-        // Fetching the embedded model from runtimePropEmbeddedModel
-        // could fail to dispose what's just now being represented.
-        //
-        // --> Very unlikely to happen because runtime has been stopped,
-        //     so nothing is changing the file name right now.
-        final DisplayModel em = active_content_model.getAndSet(null);
-        model_widget.runtimePropEmbeddedModel().setValue(null);
-
-        if (inner != null  &&  em != null)
-            toolkit.disposeRepresentation(em);
-        inner = null;
-
-        super.dispose();
+        Platform.runLater(() -> {
+            // When the file name is changed, updatePendingDisplay()
+            // will atomically update the active_content_model,
+            // represent the new model, and then set runtimePropEmbeddedModel.
+            //
+            // Fetching the embedded model from active_content_model
+            // could dispose a representation that hasn't been represented, yet.
+            // Fetching the embedded model from runtimePropEmbeddedModel
+            // could fail to dispose what's just now being represented.
+            //
+            // --> Very unlikely to happen because runtime has been stopped,
+            //     so nothing is changing the file name right now.
+            final DisplayModel em = active_content_model.getAndSet(null);
+            model_widget.runtimePropEmbeddedModel().setValue(null);
+
+            if (inner != null  &&  em != null)
+                toolkit.disposeRepresentation(em);
+            inner = null;
+
+            super.dispose();
+        });
     }
 }

From a0423e6189133e3601879969f38c6f8699ac1fd0 Mon Sep 17 00:00:00 2001
From: Abraham Wolk <abraham.wolk@ess.eu>
Date: Mon, 8 Jul 2024 14:39:07 +0200
Subject: [PATCH 59/59] CSSTUDIO-2460 Add a finalizer that deletes the
 temporary file containing an attachment when the corresponding
 'fileAttachment' is garbage collected.

---
 .../logbook/olog/ui/SingleLogEntryDisplayController.java | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SingleLogEntryDisplayController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SingleLogEntryDisplayController.java
index 2ef0956433..ba6960d68f 100644
--- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SingleLogEntryDisplayController.java
+++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SingleLogEntryDisplayController.java
@@ -188,7 +188,14 @@ private void fetchAttachments() {
             Collection<Attachment> attachments = logEntry.getAttachments().stream()
                     .filter((attachment) -> attachment.getName() != null && !attachment.getName().isEmpty())
                     .map((attachment) -> {
-                        OlogAttachment fileAttachment = new OlogAttachment();
+                        OlogAttachment fileAttachment = new OlogAttachment() {
+                            @Override
+                            protected void finalize() {
+                                if (getFile() != null && getFile().exists()) {
+                                    getFile().delete();
+                                }
+                            }
+                        };
                         fileAttachment.setContentType(attachment.getContentType());
                         fileAttachment.setThumbnail(false);
                         fileAttachment.setFileName(attachment.getName());